# PingPongGame.py
# Implementation of a simple PingPong game on RPi Pico
import time, utime
from machine import Pin, I2C
from Buzzer import *
from Button import *
from LightStrip import *
from Displays import *
from Log import *
class PingPongGame:
"""
Implements the logic for a 2-player PingPong game on RPi Pico.
Uses Buzzer, Button, LightStrip, and LCDDisplay for hardware interaction.
"""
def __init__(self, buzzerPin=16, buttonPin1=3, buttonPin2=4, neoPixelPin=2, \
lcdSdaPin=0, lcdSclPin=1):
"""Initializes game hardware and parameters."""
Log.i("PingPongGame: constructor")
self.buzzer = PassiveBuzzer(buzzerPin)
self.button1 = Button(buttonPin1, "P1", handler=self)
self.button2 = Button(buttonPin2, "P2", handler=self)
self.lightStrip = LightStrip(pin=neoPixelPin, numleds=8)
self.display = LCDDisplay(sda=lcdSdaPin, scl=lcdSclPin)
self.resetGame()
def resetGame(self):
"""Resets the game to the initial state."""
Log.i("PingPongGame: resetGame")
self.scoreP1 = 0
self.scoreP2 = 0
self.servingPlayer = "P1"
self.ballPosition = 0
self.ballDirection = 1
self.gameOver = False
self.updateDisplay()
self.lightStrip.setPixel(self.ballPosition, RED)
def updateDisplay(self):
"""Updates the LCD with current game information."""
Log.i("PingPongGame: updateDisplay")
self.display.showText(f"P1:{self.scoreP1} P2:{self.scoreP2}", row=0)
if self.gameOver:
winner = "P1" if self.scoreP1 >= 10 else "P2"
self.display.showText(f"{winner} Wins!", row=1)
else:
server = "P1" if self.servingPlayer == "P1" else "P2"
self.display.showText(f"{server} to serve", row=1)
def buttonPressed(self, buttonName):
"""Handles button presses for serving and hitting."""
Log.i(f"PingPongGame: buttonPressed by {buttonName}")
if self.gameOver:
self.resetGame()
return
if self.servingPlayer == buttonName: # Serve
self.ballDirection = 1 if buttonName == "P1" else -1
self.servingPlayer = "P2" if buttonName == "P1" else "P1" # Switch server
self.updateDisplay()
self.moveBall() # Start automatic ball movement
elif not self.gameOver: # Hit attempt
# This block is where the main change is:
if buttonName == "P2" and self.ballDirection == 1: # P2 hitting
if self.ballPosition == 7: # Valid hit
self.buzzer.beep(tone=DO, duration=10)
self.ballDirection = -1 # Reverse direction
self.moveBall() # Continue automatic movement in the opposite direction
elif 0 <= self.ballPosition < 7: # Foul because it was hit before reaching 7
self.handleFoul("P2")
elif buttonName == "P1" and self.ballDirection == -1: # P1 hitting
if self.ballPosition == 0: # Valid hit
self.buzzer.beep(tone=DO, duration=10)
self.ballDirection = 1 # Reverse direction
self.moveBall() # Continue automatic movement
elif 0 < self.ballPosition <= 7: # Foul because it was hit before reaching 0
self.handleFoul("P1")
def handleFoul(self, player):
"""Handles foul logic for the given player."""
Log.i(f"PingPongGame: Foul by {player}")
self.buzzer.beep(tone=RE, duration=500)
if player == "P1":
self.scoreP2 += 1
self.servingPlayer = "P2"
self.ballPosition = 7 # Reset ball position
else: # player == "P2"
self.scoreP1 += 1
self.servingPlayer = "P1"
self.ballPosition = 0 # Reset ball position
self.updateDisplay()
self.lightStrip.setPixel(self.ballPosition, RED)
self.checkForGameOver()
def buttonReleased(self, buttonName):
pass # No action on button release
def moveBall(self):
"""Moves the ball automatically and checks for hits during movement."""
while 0 <= self.ballPosition <= 7:
self.lightStrip.setPixel(self.ballPosition, BLACK) # Clear current position
self.ballPosition += self.ballDirection
if 0 <= self.ballPosition <= 7: # Ball is still in bounds
self.lightStrip.setPixel(self.ballPosition, RED)
nextPos = self.ballPosition + self.ballDirection
if 0 <= nextPos <= 7:
self.lightStrip.setPixel(nextPos, (50, 0, 0)) #Show next position dimly
time.sleep_ms(200)
# Check for hits *during* ball movement. Important change here!
if self.ballDirection == 1 and self.button2.isPressed() and self.ballPosition == 7:
self.buzzer.beep(tone=DO, duration=10)
self.ballDirection = -1 #Reverse direction immediately
elif self.ballDirection == -1 and self.button1.isPressed() and self.ballPosition == 0:
self.buzzer.beep(tone=DO, duration=10)
self.ballDirection = 1
if not 0<=self.ballPosition<=7: # Check for out of bounds after the hit check
break
# Handle miss (out of bounds) - only if loop finishes without a return.
if self.ballPosition < 0:
self.scoreP2 += 1
self.servingPlayer = "P2"
self.ballPosition = 7 # Reset ball
elif self.ballPosition > 7:
self.scoreP1 += 1
self.servingPlayer = "P1"
self.ballPosition = 0 # Reset ball
self.buzzer.beep(tone=RE, duration=500)
self.updateDisplay()
self.lightStrip.setPixel(self.ballPosition, RED)
self.checkForGameOver()
def checkForGameOver(self):
"""Checks if the game is over (score >= 10)."""
if self.scoreP1 >= 10 or self.scoreP2 >= 10:
self.gameOver = True
self.updateDisplay()
self.buzzer.beep(tone=DO2, duration=1000) # Win sound
self.lightStrip.run(runtype=LightStrip.RAINBOW)
# Initialize and start the game
game = PingPongGame()