"""
PingPong.py - A simple ping-pong game for the Raspberry Pi Pico
Author: Arijit Sengupta
"""
from machine import Pin, I2C
import time
from Buzzer import *
from Displays import *
from LightStrip import *
from Button import *
from Log import *
class Player:
"""
Represents a player in the ping-pong game
"""
def __init__(self, name, button_pin, handler):
"""
Initialize the player with a name and button
"""
self.name = name
self.button = Button(button_pin, name, handler=handler)
self.score = 0
def increment_score(self):
"""
Increment the player's score
"""
self.score += 1
class Ball:
"""
Represents the ball in the ping-pong game
"""
def __init__(self, lightstrip, start_position=0, start_direction=0):
"""
Initialize the ball with the lightstrip and initial position and direction
"""
self.lightstrip = lightstrip
self.position = start_position
self.direction = start_direction # 0 for stopped, 1 for right, -1 for left
self.color = RED # default to player 1 color
def move(self):
"""
Move the ball one step in the current direction,
but only if it doesn't result in going out of bounds
"""
if self.direction != 0:
new_position = self.position + self.direction
# Check for out-of-bounds
if 0 <= new_position <= 7:
self.lightstrip.setPixel(self.position, BLACK, show=False)
self.position = new_position
self.lightstrip.setPixel(self.position, self.color, show=False)
self.lightstrip.show()
def set_color(self, color):
"""
Set the color of the ball
"""
self.color = color
self.lightstrip.setPixel(self.position, self.color)
def reset(self, position, direction, color):
"""
Reset the ball to a specific position, direction and color
"""
self.lightstrip.off()
self.position = position
self.direction = direction
self.color = color
self.lightstrip.setPixel(self.position, self.color)
class Game:
"""
Represents the ping-pong game logic
"""
def __init__(self, player1, player2, ball, buzzer, display):
"""
Initialize the game with players, ball, buzzer, and display
"""
self.player1 = player1
self.player2 = player2
self.ball = ball
self.buzzer = buzzer
self.display = display
self.game_over = False
self.last_hit_by = 1 # which player last hit the ball
self.serving = 1 # which player is serving
def check_for_hit(self):
"""
Check if the ball has reached an end and should be considered hit or miss
"""
if self.ball.position == 0 or self.ball.position == 7:
self.buzzer.beep(tones['C5'], 50) # Ball reaches end
def handle_player_action(self, player):
"""
Handle a player's button press
"""
if self.game_over:
return
if player == self.player1:
if self.ball.position == 0 and self.ball.direction <= 0 and self.serving == 1: # Serve
self.ball.direction = 1
self.ball.set_color(BLUE)
self.last_hit_by = 1
self.serving = 2 # switch serve to the other player
self.display.clear(1)
self.buzzer.beep(MI, 50)
elif self.ball.position == 0 and self.ball.direction == 1: # Miss
self.buzzer.beep(tones['G5'], 100)
self.player2.increment_score()
self.ball.direction = -1 # Reverse for miss
self.ball.set_color(RED)
self.last_hit_by = 2
self.serving = 2
if self.check_game_over():
return
# Actually move the ball for a miss
while self.ball.position > 0:
self.ball.move()
time.sleep(0.3) # Adjust speed of ball movement after miss if needed
time.sleep(1)
self.reset_ball()
elif self.ball.position != 0: # Foul
self.buzzer.beep(tones['G5'], 100)
self.player2.increment_score()
self.ball.direction = -1 # Move to player 2 for foul
self.ball.set_color(RED)
self.last_hit_by = 2
self.serving = 2
if self.check_game_over():
return
# Actually move the ball for a foul
while self.ball.position > 0:
self.ball.move()
time.sleep(0.3) # Adjust speed of ball movement after foul if needed
time.sleep(1)
self.reset_ball()
elif player == self.player2:
if self.ball.position == 7 and self.ball.direction >= 0 and self.serving == 2: # Serve
self.ball.direction = -1
self.ball.set_color(RED)
self.last_hit_by = 2
self.serving = 1 # switch serve to player 1
self.display.clear(1)
self.buzzer.beep(MI, 50)
elif self.ball.position == 7 and self.ball.direction == -1: # Miss
self.buzzer.beep(tones['G5'], 100)
self.player1.increment_score()
self.ball.direction = 1 # Reverse for miss
self.ball.set_color(BLUE)
self.last_hit_by = 1
self.serving = 1
if self.check_game_over():
return
# Actually move the ball for a miss
while self.ball.position < 7:
self.ball.move()
time.sleep(0.3) # Adjust speed of ball movement after miss if needed
time.sleep(1)
self.reset_ball()
elif self.ball.position != 7: # Foul
self.buzzer.beep(tones['G5'], 100)
self.player1.increment_score()
self.ball.direction = 1 # Move to player 1 for foul
self.ball.set_color(BLUE)
self.last_hit_by = 1
self.serving = 1
if self.check_game_over():
return
# Actually move the ball for a foul
while self.ball.position < 7:
self.ball.move()
time.sleep(0.3) # Adjust speed of ball movement after foul if needed
time.sleep(1)
self.reset_ball()
def reset_ball(self):
"""
Reset the ball to the starting position
"""
self.ball.reset(0 if self.serving == 1 else 7, 0, RED if self.serving == 1 else BLUE)
self.display.showText("Press to serve", 1, 0)
def check_game_over(self):
"""
Check if the game is over and display the winner
"""
if self.player1.score >= 10 or self.player2.score >= 10:
self.game_over = True
winner = self.player1.name if self.player1.score >= 10 else self.player2.name
self.display.clear()
self.display.showText(f"{winner} Wins!", 0, 0)
self.ball.lightstrip.off()
for _ in range(3):
self.buzzer.beep(DO2, 100)
time.sleep(0.2)
return True
return False
class GameController:
"""
Coordinates the game, handling hardware and game logic
"""
def __init__(self, button1_pin, button2_pin, neopixel_pin, buzzer_pin, lcd_sda, lcd_scl):
"""
Initialize the game controller
"""
Log.name = 'PingPong'
Log.level = ALL
self.display = LCDDisplay(sda=lcd_sda, scl=lcd_scl)
self.lightstrip = LightStrip(pin=neopixel_pin, numleds=8)
self.buzzer = PassiveBuzzer(pin=buzzer_pin)
self.player1 = Player("Player 1", button1_pin, self)
self.player2 = Player("Player 2", button2_pin, self)
self.ball = Ball(self.lightstrip)
self.game = Game(self.player1, self.player2, self.ball, self.buzzer, self.display)
# Display initial score
self.display_score()
self.game.reset_ball()
self.buzzer.beep(DO, 200)
def display_score(self):
"""
Display the current score on the LCD
"""
self.display.showNumbers(self.game.player1.score, self.game.player2.score, row=0, col=0)
def buttonPressed(self, button_name):
"""
Handle button presses, delegating to the game logic
"""
if button_name == "Player 1":
self.game.handle_player_action(self.game.player1)
elif button_name == "Player 2":
self.game.handle_player_action(self.game.player2)
self.display_score()
def buttonReleased(self, button_name):
"""
Handle button releases (currently not used)
"""
pass
def run(self):
"""
Main game loop
"""
try:
while not self.game.game_over:
self.ball.move()
self.game.check_for_hit()
time.sleep(0.3) # Adjust ball speed here
except Exception as e:
Log.e(f'Exception: {e}')
self.display.clear()
self.display.showText("Error", 0, 0)
self.display.showText(f'{e}', 1, 0)
self.lightstrip.off()
finally:
self.display.clear()
self.display.showText("Game Over", 0, 0)
self.lightstrip.off()
self.buzzer.stop()
# Initialize and run the game
controller = GameController(button1_pin=18, button2_pin=17, neopixel_pin=2, buzzer_pin=16, lcd_sda=0, lcd_scl=1)
controller.run()