# A Pong game for a Raspberry Pi Pico with a 128x64 SSD1306 OLED
# Now with a custom bitmap ball!
import machine
import ssd1306
import framebuf # Make sure framebuf is imported
import time
import random
# --- HARDWARE SETUP ---
SCREEN_WIDTH = 128
SCREEN_HEIGHT = 64
i2c = machine.I2C(0, sda=machine.Pin(16), scl=machine.Pin(17))
display = ssd1306.SSD1306_I2C(SCREEN_WIDTH, SCREEN_HEIGHT, i2c)
pot1 = machine.ADC(26)
pot2 = machine.ADC(27)
# --- GAME ASSETS & CONFIGURATION ---
# (NEW) Custom ball bitmap data and dimensions
image_EviSmile1_bits = bytearray(b'0\x03\x00`\x01\x80\xe0\x01\xc0\xf3\xf3\xc0\xff\xff\xc0\xff\xff\xc0\x7f\xff\x80\x7f\xff\x80\x7f\xff\x80\xef\xfd\xc0\xe7\xf9\xc0\xe3\xf1\xc0\xe1\xe1\xc0\xf1\xe3\xc0\xff\xff\xc0\x7f\xff\x80{\xf7\x80=/\x00\x1e\x1e\x00\x0f\xfc\x00\x03\xf0\x00')
BALL_WIDTH = 18
BALL_HEIGHT = 21
# (NEW) Create the FrameBuffer for the ball image ONCE before the loop
fb_ball = framebuf.FrameBuffer(image_EviSmile1_bits, BALL_WIDTH, BALL_HEIGHT, framebuf.MONO_HLSB)
# Paddles
PADDLE_WIDTH = 3
PADDLE_HEIGHT = 12
BALL_SPEED_X = 1.5
BALL_SPEED_Y = 1.5
# --- GAME STATE VARIABLES ---
score1 = 0
score2 = 0
paddle1_y = float(SCREEN_HEIGHT / 2 - PADDLE_HEIGHT / 2)
paddle2_y = float(SCREEN_HEIGHT / 2 - PADDLE_HEIGHT / 2)
# (CHANGED) Ball position is initialized using the new dimensions
ball_x = float(SCREEN_WIDTH / 2 - BALL_WIDTH / 2)
ball_y = float(SCREEN_HEIGHT / 2 - BALL_HEIGHT / 2)
ball_dx = float(random.choice([-BALL_SPEED_X, BALL_SPEED_X]))
ball_dy = float(random.choice([-BALL_SPEED_Y, BALL_SPEED_Y]))
# --- GAME FUNCTIONS ---
def reset_ball():
global ball_x, ball_y, ball_dx
# (CHANGED) Reset using new dimensions
ball_x = SCREEN_WIDTH / 2 - BALL_WIDTH / 2
ball_y = SCREEN_HEIGHT / 2 - BALL_HEIGHT / 2
ball_dx = -ball_dx
display.show()
time.sleep(0.5)
# --- MAIN GAME LOOP ---
while True:
# 1. HANDLE INPUT
pot1_value = pot1.read_u16()
pot2_value = pot2.read_u16()
paddle1_y = (pot1_value / 65535) * (SCREEN_HEIGHT - PADDLE_HEIGHT)
paddle2_y = (pot2_value / 65535) * (SCREEN_HEIGHT - PADDLE_HEIGHT)
# 2. UPDATE GAME STATE
ball_x += ball_dx
ball_y += ball_dy
# (CHANGED) Ball collision logic now uses BALL_HEIGHT
if ball_y <= 0 or ball_y >= SCREEN_HEIGHT - BALL_HEIGHT:
ball_dy = -ball_dy
# (CHANGED) Paddle collision logic updated for the bitmap's dimensions
# Left paddle
if ball_dx < 0 and ball_x < PADDLE_WIDTH and (paddle1_y < ball_y + BALL_HEIGHT and ball_y < paddle1_y + PADDLE_HEIGHT):
ball_dx = -ball_dx
# Right paddle
if ball_dx > 0 and ball_x + BALL_WIDTH > SCREEN_WIDTH - PADDLE_WIDTH and (paddle2_y < ball_y + BALL_HEIGHT and ball_y < paddle2_y + PADDLE_HEIGHT):
ball_dx = -ball_dx
# (CHANGED) Scoring logic now waits for the whole bitmap to go off-screen
if ball_x <= -BALL_WIDTH:
score2 += 1
reset_ball()
elif ball_x >= SCREEN_WIDTH:
score1 += 1
reset_ball()
# 3. DRAW EVERYTHING
display.fill(0)
# Draw field and scores
for i in range(0, SCREEN_HEIGHT, 4):
display.pixel(SCREEN_WIDTH // 2, i, 1)
display.text(str(score1), SCREEN_WIDTH // 2 - 24, 5)
display.text(str(score2), SCREEN_WIDTH // 2 + 16, 5)
# Draw paddles
display.fill_rect(0, int(paddle1_y), PADDLE_WIDTH, PADDLE_HEIGHT, 1)
display.fill_rect(SCREEN_WIDTH - PADDLE_WIDTH, int(paddle2_y), PADDLE_WIDTH, PADDLE_HEIGHT, 1)
# (THE BIG CHANGE!) Draw the ball bitmap instead of a rectangle
display.blit(fb_ball, int(ball_x), int(ball_y))
display.show()
time.sleep_ms(10)