Spielerisch Lernen - [Teil 2]

Program games yourself and manufacture game controllers yourself with little effort - Part 2

In the first part I showed you the idea of ​​making your own game controller and introduced the turtle race, in which you had to press your button as quickly as possible to get your turtle across the finish line first. Today I want to show you a game with analog input devices.

Pong is the first video game to be distributed worldwide. The first version from 1972 was developed by Atari for amusement arcades. With the spread of home computers in the 1980s, there were versions for them too. This game has never disappeared, there are new ideas for implementation mainly due to the spread of the Raspberry Pi and the new wave of enthusiasm for programming.

The game principle of Pong is similar to that of Table tennis: The ball moves from left to right and back on the screen. Each of the two players controls a club or racket with a rotary knob (Paddle) up and down to play the ball back. If this does not succeed, the opponent receives one point.

Hardware required

To move the rackets you need two game controllers, for which creative solutions are required, because the original paddles can no longer be bought. The mouse could be a solution if the second player is replaced by the unbeatable computer. In this blog we want to use self-made paddles with 10 kOhm potentiometers (see picture), but also mention the idea of Ultrasonic distance sensors in the Raspberry Pi Foundation blog .

You have surely recognized the basic material for the game controller. After you have eaten the chocolate and removed the surprise of the Ü-egg, you can drill holes in the yellow "eggs" at the top and bottom and install the potentiometers with three cables each.

Analog signals from the potentiometer in connection with Raspberry Pi means that we need an analog-to-digital converter (ADC) such as the MCP3004 or MCP3008. These are the moments when we Raspians envy the Arduino faction for the analog inputs. Fortunately, the MCP3004 is an easy-to-use component, the evaluation of which is supported by the Python module gpiozero.

Of course, we must first activate the SPI interface in the Raspberry Pi configuration. Alternatively, with the necessary changes to the program, a ADS1115 on the I2C bus. This is done using the Python module from Adafruit ADS1x15.

For the animated graphics we use the pygamezero (pgzrun) module, a new, slimmed-down variant of the very extensive and very powerful pygame module. An introduction to programming with pgzrun is provided by Mark Vanstone in The MagPi magazine, issue 77 where I saw and further developed his version of pong.

I don't want to hide the fact that I didn't manage it all by myself. The display of the score in the graphical user interface at the end of the game caused problems, so I sought help in the forum of the Raspberry Pi organization. And got it! Because of the moderation, the best forum I know, but in English.


The structure

Here is the circuit diagram with Raspberry Pi, MCP3008 and two potentiometers. Please note that the IC requires both a supply voltage and a reference voltage for the ADC. In our case both 3.3V and GND. DOUT is connected with MISO (pin21), DIN connected with MOSI (pin19), CLK with CLK (pin23) and CS with CE0 (pin24). The counting of the analog inputs starts at 0.

In our program example, I use the MCP3004, the variant with four analog inputs. Together with a Raspi breadboard cobbler, it fits on half a breadboard (see picture above).

I confine myself to the part I have added to the explanations. For the explanations about pygamezero I refer to the above-mentioned page link to The Magpi 77.

The software

With from gpiozero import MCP3004 we import the class gpiozero. MCP3004 and then instantiate two objects pot1=MCP3004(0) And pot2=MCP3004(1). As mentioned above, the counting of analog inputs starts at 0.

In addition to the BALLSPEED, which we initially set smaller than in the original code and then increase every 60 seconds, we define an end when the first player has scored 10 points and an automatic RESTART.

Here is the program code as a whole:

# pong game based on pygamezero
# Pong was the first video game with world-wide distribution, published in 1972Pong
# source The MagPi magazine issue 77, pages 32 - 35
# modified by Raspberry Pi course at JFS Bad Bramstedt, Germany
# with the decisive contribution by MarkyV at raspberrypi.org/forums
# also thanks to Lucy Hattersley, editor of The MagPi magazine
# BALLSPEED starts rather slow, but increases every 60 seconds
# Game over, when one player scores 10
# Restart: Press Space Bar

import pgzrun
import random
from gpiozero import MCP3004
import math
from time import time, sleep          # new

pot1 = MCP3004(0)
pot2 = MCP3004(1)

# Set up the colours
BLACK = (0  ,0  ,0  )
WHITE = (255,255,255)
p1Score = p2Score = 0
END = 10                           # new: end of game, when first player has scored END
RESTART = 15                   # restart after xx seconds                             
BALLSPEED = 3                 # changed from 5 to 3
p1Y = 300
p2Y = 300
TIME = time()

def draw():
    global screen, p1Score, p2Score
    screen.draw.text(str(p1Score) , center=(105, 40), color=WHITE, fontsize=60)
    screen.draw.text(str(p2Score) , center=(705, 40), color=WHITE, fontsize=60)
    if p2Score==END:
        screen.draw.text("Player2 wins!" , center=(400, 30), color=WHITE, fontsize=60)
    if p1Score==END:
        screen.draw.text("Player1 wins!", center=(400, 30), color=WHITE, fontsize=60)

def on_key_down(key):
    global p1Score, p2Score, BALLSPEED
    if (p2Score == END or p1Score == END) and key.name == "SPACE":
        p1Score = p2Score = 0
        BALLSPEED = 3
def update():
    if p2Score < END and p1Score < END:

def init():
    global ballX, ballY, ballDirX, ballDirY
    ballX = 400
    ballY = 300
    a = random.randint(10, 350)
    while (a > 80 and a < 100) or (a > 260 and a < 280):
        a = random.randint(10, 350)
    ballDirX = math.cos(math.radians(a))
    ballDirY = math.sin(math.radians(a))

def drawPaddles():
    global p1Y, p2Y
    p1rect = Rect((100, p1Y-30), (10, 60))
    p2rect = Rect((700, p2Y-30), (10, 60))
    screen.draw.filled_rect(p1rect, "red")
    screen.draw.filled_rect(p2rect, "red")

def updatePaddles():
    global p1Y, p2Y
    p1Y = (pot1.value * 540) +30
    p2Y = (pot2.value * 540) +30
    if keyboard.up:
        if p2Y > 30:
            p2Y -= 2
    if keyboard.down:
        if p2Y < 570:
            p2Y += 2
    if keyboard.w:
        if p1Y > 30:
            p1Y -= 2
    if keyboard.s:
        if p1Y < 570:
            p1Y += 2
def updateBall():
    global ballX, ballY, ballDirX, ballDirY, p1Score, p2Score, BALLSPEED, TIME
    ballX += ballDirX*BALLSPEED
    ballY += ballDirY*BALLSPEED
    ballRect = Rect((ballX-4,ballY-4),(8,8))
    p1rect = Rect((100, p1Y-30), (10, 60))
    p2rect = Rect((700, p2Y-30), (10, 60))
    if  time() - TIME > 60:             	#new
        BALLSPEED += 1                  	#new
        TIME = time()                   	#new
        print("BALLSPEED is now: ", BALLSPEED)

    if checkCollide(ballRect, p1rect) or checkCollide(ballRect, p2rect):
        ballDirX *= -1
    if ballY < 4 or ballY > 596:
        ballDirY *= -1
    if ballX < 0:
        p2Score += 1
    if ballX > 800:
        p1Score += 1

def checkCollide(r1,r2):
    return (
        r1.x < r2.x + r2.w and
        r1.y < r2.y + r2.h and
        r1.x + r1.w > r2.x and
        r1.y + r1.h > r2.y

def drawBall():
    screen.draw.filled_circle((ballX, ballY), 8, "white")

Have fun making and playing

Projekte für anfänger

1 comment

Bernd-Steffen Großmann

Bernd-Steffen Großmann

Hallo Herr Albrecht, zunächst einmal herzlichen Dank für den interessanten und unterhaltsamen Beitrag, der die Themen Mikrocontroller, Programmieren und Spielen bedient und sicher bei vielen interessierten Makern das Interesse an dem Raspi, dessen Programmierung und dem Selbstbau der nötigen Hardware weckt bzw. vertieft. Der Blog hat bestimmt einige Reichweite in der DIY-Welt. Mir ist auch bewusst, dass die Darstellung der Schaltung in dem Zusammenhang als „Freiflug“-Verdrahtung mit Steckbrett und Steckkabeln in Fritzing-Grafik üblich geworden ist. Ich finde es aber immer gut, wenn die Beiträge auch mit einem ordentlichen elektrischen Schaltbild versehen werden, wie jeder Elektroniker und Nachrichtentechnik-Student (mancher sogar in der Schule in Physik) gelernt hat. Das als kleine Kritik. Die größere muss ich an die Konsistenz des Beitrags anbringen. In der Schaltung und deren Beschreibung verwenden Sie den MCP3008, aber im Programm und dessen Erläuterung den MCP3004! Jemand, der sich mit Schaltungstechnik und Programmierung schon auskennt, wird es nicht schwer fallen, die Komponente gegen die jeweils andere zu ersetzen, aber diejenigen, die einfach nachbauen wollen, für die wird es eine Herausforderung, der sie wohl nicht immer gewachsen sind. Also: warum haben Sie sich nicht an ein und denselben AD-Wandler in Schaltung und Programmierung gehalten?

Leave a comment

All comments are moderated before being published