This project is most suitable for Raspberry Pi beginners and Python beginners-intermediates.
Getting startedOnce you have acquired the necessary equipment, there are a few steps before you can get into writing the game; including downloading the necessary software.
I did so by following this tutorial, which involves installing:
- Pico’s custom build of MicroPython (.uf2 file)
- Thonny – software used to program your pico
Once you have completed the set-up, you are ready to get coding! Firstly, you need to install the picounicorn and utime libraries.
#Import libraries
import picounicorn
import utime
picounicorn.init()
Step 2 - Outline VariablesThe next step is to outline some of the key variables. Including the Pico unicorn width and height, the starting ball co-ordinates/direction, scoring for each player, player colours, ball colours and the starting paddle Y co-ordinates for each player (onlistAB/onlistXY). Next you need to create listW containing all of the x values in the height and listH containing all of the y values in the width; these are used later to iterate through each LED on the matrix.
#Define setup variables
w = picounicorn.get_width()#16
h = picounicorn.get_height()#7
startballx=7
startbally=3
startdirH=1
startdirV=0
startscoreXY=0
startscoreAB=0
onlistAB=[2,3,4]
onlistXY=[2,3,4]
#Create lists of pixels for the height/width of the whole display/ball area
listW=[]#makes a list 0-15
for i in range(w):
listW.append(i)
listH=[]#makes a list 0-6
for i in range(h):
listH.append(i)
listWball=[]#makes a list 1-14
for i in range(1,15):
listWball.append(i)
listHball=[]#makes a list 0-6
for i in range(0,7):
listHball.append(i)
# Define colours
playerXYcolours=(0,114,178)
playerABcolours=(255,70,160)
ballcolours=playerABcolours
To display letters for the title display and numbers for the scoring, you need to create constants for each character. I used strings and arrays to do this; using an "X" to represent an illuminated LED and whitespace to symbolise an "off" LED.
pongtitle=[["RRRRRRRRRRRRRRRR"],
[" R R R R"],
[" R R R R R R R R"],
[" R R R R R R"],
[" RRR R R R RRR R"],
[" RRR R R R R"],
["RRRRRRRRRRRRRRRR"]]
BLANKSECTION= [" " for i in range(h)]
NUMZERO=[" ",
"XXX",
"X X",
"X X",
"X X",
"XXX",
" "]
NUMONE=[" ",
" X ",
"XX ",
" X ",
" X ",
"XXX",
" "]
NUMTWO=[" ",
"XXX",
" X",
"XXX",
"X ",
"XXX",
" "]
NUMTHREE=[" ",
"XXX",
" X",
"XXX",
" X",
"XXX",
" "]
NUMFOUR=[" ",
"X X",
"X X",
"XXX",
" X",
" X",
" "]
NUMFIVE=[" ",
"XXX",
"X ",
"XXX",
" X",
"XXX",
" "]
DASH=[" ",
" ",
" ",
" XX ",
" ",
" ",
" "]
LETTERW=[" ",
"X X",
"X X",
"X X",
"X X X",
"XXXXX",
" "]
LETTERI=[" ",
"XXX",
" X ",
" X ",
" X ",
"XXX",
" "]
LETTERN=[" ",
"X X",
"XX X",
"X XX",
"X X",
"X X",
" "]
PUNCEXCALM=[" ",
"X ",
"X ",
"X ",
" ",
"X ",
" "]
Step 3 - Create DictionariesThese are used to easily access the required letters/numbers.
#Create dictionaries to store scoring numbers/text
scoredict={0:NUMZERO,1:NUMONE,2:NUMTWO,3:NUMTHREE,4:NUMFOUR,5:NUMFIVE,"dash":DASH}
textdict={"W":LETTERW,"I":LETTERI,"N":LETTERN,"!":PUNCEXCALM}
Step 4 - Display FunctionsUsed to set all pixels to black (R, G, B=0, 0, 0).
#Function to clear display by setting pixels to black
def cleardisplay():
for x in range(w):
for y in range(h):
picounicorn.set_pixel(x, y, 0, 0, 0)
Used to display the scoring and "WIN!" message.
#Function to update display with letters/numbers
def updatedisplay(displaymap):
x=0
y=0
for row in displaymap:
for line in row:
for char in line:
if x < w and y <h:
if char == "X":
r, g, b = 255,255,255
elif char == "R":
r, g, b = [int(c * 255) for c in hsv_to_rgb(x / w, y / h, 1.0)]
elif char == "D":
r, g, b = 200,200,40
elif char == "A":
r, g, b = playerABcolours
elif char == "Y":
r, g, b = playerXYcolours
else:
r,g,b=0,0,0
picounicorn.set_pixel(x, y, r, g, b)
x+=1
x=0
y+=1
return displaymap
Step 5 - Generate Score/Text FunctionsUsed to generate the display by combining the appropriate letter/number and the "BLANKSECTION" array defined previously. I used.replace() to assign different characters to the letter/number arrays; this changes the LED colour during the display function.
#Function to generate an 2D array to represent the current score in the format: PlayerABscore - PlayerXYscore
def generatescore(scoreAB,scoreXY):
scoreABpix = [item.replace("X","A") for item in scoredict[scoreAB]]
scoreXYpix = [item.replace("X","Y") for item in scoredict[scoreXY]]
dashpix = [item.replace("X","D") for item in scoredict["dash"]]
fulldisplay = [["{}{}{}{}{}".format(BLANKSECTION[i],scoreABpix[i],dashpix[i],scoreXYpix[i],BLANKSECTION[i])] for i in range(h)]
return fulldisplay
#Function to generate a 2D array of specified text e.g. "WIN!"
def generatemessage(winningcolour):
text1=[item.replace("X",winningcolour) for item in textdict["W"]]
text2=[item.replace("X",winningcolour) for item in textdict["I"]]
text3=[item.replace("X",winningcolour) for item in textdict["N"]]
text4=[item.replace("X",winningcolour) for item in textdict["!"]]
fulldisplay = [["{} {} {} {} ".format(text1[i],text2[i],text3[i],text4[i])] for i in range(h)]
return fulldisplay
Step 6 - "Scroll" Screen FunctionThis function takes the display map, passes it into the "scroll map" array and uses slicing to move the first array element (line[0]) to the end of the array; creating a "scrolling" screen effect.
#Function to make the "display map" seem as though it is "scrolling" (loop)
def scrolldisplay(displaymap):
scrollmap=[]
rowcount=0
for row in displaymap:
scrollmap.append([])
for line in row:
scrollmap[rowcount]= [line[1:]+line[0]]
rowcount+=1
return scrollmap
Step 7 - Display Title ScreenDisplays the title screen for 2 seconds.
#Display the title screen for 2 seconds
updatedisplay(pongtitle)
utime.sleep(2)
Step 8 - Display The BallThis function takes in the current and previous ball co-ordinates; used to create the ball and following ball trail. It also requires the ball colour, which is then changed based on which player last "hit" the ball.
The ball function is then invoked prior to the main game loop.
#Function to create the ball and ball trail(using previous ball co-ords)
def ball(currentballx,currentbally,prevballx,prevbally,prevball2x,prevball2y,ballcolours):
for x in listWball:
for y in listHball:
if x == currentballx:
if y == currentbally:
r,g,b=ballcolours
picounicorn.set_pixel(x, y, r, g, b)
elif x == prevballx:
if y == prevbally:
r,g,b=[round(element * 0.3) for element in ballcolours]
picounicorn.set_pixel(x, y, r, g, b)
elif x == prevball2x:
if y == prevball2y:
r,g,b=[round(element * 0.2) for element in ballcolours]
picounicorn.set_pixel(x, y, r, g, b)
else:
r,g,b=0,0,0
picounicorn.set_pixel(x, y, r, g, b)
return currentballx,currentbally,prevballx,prevbally,prevball2x,prevball2y
#Initial invocation of "ball" function
ball(startballx,startbally,startballx,startbally,startballx,startbally,ballcolours)
ballx=startballx
bally=startbally
dirH=startdirH
dirV=startdirV
scoreAB=startscoreAB
scoreXY=startscoreXY
Step 9 - Player Paddle FunctionsUsed to light up PlayerAB and PlayerXY paddles; where PlayerAB paddle is in the first column (x=0) and PlayerXY paddle is in the furtherest right column (X=16).
#Function to create playerAB paddle (in the first column)
def lightcontrolAB(onlistAB):
for x in range(1):
for y in listH:
if y in onlistAB:
r,g,b=playerABcolours
else:
r,g,b=0,0,0
picounicorn.set_pixel(x, y, r, g, b)
#Function to create playerXY paddle (in the last column)
def lightcontrolXY(onlistXY):
for x in range(15,16):
for y in listH:
if y in onlistXY:
r,g,b=playerXYcolours
else:
r,g,b=0,0,0
picounicorn.set_pixel(x, y, r, g, b)
Step 10 - Ball Position FunctionThis function plays a large part in the game; controlling the ball position, direction of travel and scoring.
#Function to determine the ball colour & direction (based on it's position)and update scoring
def ballposition(currentballx,currentbally,currentdirH,currentdirV,scoreAB,scoreXY,ballcolours,startdirH):
if currentballx == listWball[0]:#if ball is in the furthest left column
ballcolours=playerABcolours
if currentbally in onlistAB:
currentdirH=1
if currentbally == onlistAB[0]:
currentdirV=-1
if currentbally == onlistAB[2]:
currentdirV=1
else:
scoreXY+=1
startdirH=1
currentballx=startballx
currentbally=startbally
currentdirH=startdirH
currentdirV=startdirV
cleardisplay()
updatedisplay(generatescore(scoreAB,scoreXY))
utime.sleep(1)
elif currentballx == listWball[-1]:#if ball is in the furthest right column
ballcolours=playerXYcolours
if currentbally in onlistXY:
currentdirH= -1
if currentbally == onlistXY[0]:
currentdirV=-1
if currentbally == onlistXY[2]:
currentdirV=1
else:
scoreAB+=1
startdirH=- 1
currentballx=startballx
currentbally=startbally
currentdirH=startdirH
currentdirV=startdirV
cleardisplay()
updatedisplay(generatescore(scoreAB,scoreXY))
utime.sleep(1)
elif currentballx in listWball:
if currentbally == listHball[0]:
currentdirV=1
elif currentbally == listHball[-1]:
currentdirV=-1
prevball2x=currentballx-currentdirH
prevball2y=currentbally-currentdirV
prevballx=currentballx
prevbally=currentbally
currentballx=currentballx+currentdirH
currentbally=currentbally+currentdirV
ball(currentballx,currentbally,prevballx,prevbally,prevball2x,prevball2y,ballcolours)
return currentballx,currentbally,currentdirH,currentdirV,scoreAB,scoreXY,ballcolours,startdirH
Step 11 - Main Game LoopThis "While true" loop runs the game. It passes all of the necessary parameters to the ball position function, including co-ordinates, direction and colours. The loop checks if a button is pressed and moves the paddles appropriately by updating the paddle list (e.g. "onlistAB") and then invokes the "light control" function.
Finally, to declare a winner, the scoring is checked and the game comes to an end when one player scores 5 points.
#Function which runs the game
while True:
ballx,bally,dirH,dirV,scoreAB,scoreXY,ballcolours,startdirH = ballposition(ballx,bally,dirH,dirV,scoreAB,scoreXY,ballcolours,startdirH)
if scoreAB >=5 or scoreXY >=5:
if scoreAB>=5:
winningcolour="A"
else:
winningcolour="Y"
cleardisplay()
currentdisplaymap=updatedisplay(generatemessage(winningcolour))
for i in range(w*3):
currentdisplaymap=updatedisplay(scrolldisplay(currentdisplaymap))
utime.sleep(0.1)
break
else:
if picounicorn.is_pressed(picounicorn.BUTTON_A):
if onlistAB[0]!=0:
onlistAB.insert(0,onlistAB[0]-1)
onlistAB=onlistAB[:-1]
elif picounicorn.is_pressed(picounicorn.BUTTON_B):
if onlistAB[-1]!=6:
onlistAB.insert(len(onlistAB),onlistAB[-1]+1)
onlistAB=onlistAB[1:]
if picounicorn.is_pressed(picounicorn.BUTTON_X):
if onlistXY[0]!=0:
onlistXY.insert(0,onlistXY[0]-1)
onlistXY=onlistXY[:-1]
elif picounicorn.is_pressed(picounicorn.BUTTON_Y):
if onlistXY[-1]!=6:
onlistXY.insert(len(onlistXY),onlistXY[-1]+1)
onlistXY=onlistXY[1:]
lightcontrolAB(onlistAB)
lightcontrolXY(onlistXY)
utime.sleep(0.1)
Conclusion - a great first Raspberry Pi project :)I highly recommend giving it a go!
Comments
Please log in or sign up to comment.