

في هذا الدرس سنتعلم كيفية بناء لعبة العداء اللا نهائى باستخدام لوحة راسبيرى باى بيكو 2W، فى هذه اللعبة يقابل اللاعب عوائق قادمة فى طريقه وعليه القفز فوق الحواجز القادمة فى الوقت المثالى عن طريق الضغط على مفتاح ضغاط متصل باللوحة.
قم بتوصيل الأسلاك بين لوحة راسبيرى باى بيكو 2W والشاشة الكريستالية والمفتاح الضغاط كما فى الصورة التالية:

التوصيلات من لوحة راسبيرى باى بيكو 2W :
• نقوم بتوصيل منفذ ال VBUS بلوحة راسبيرى باى بيكو2W ← المنافذ الموجبة بلوحة التجارب
• منفذ ال GND بلوحة راسبيرى باى بيكو2W ←المنافذ السالبة بلوحة التجارب
ثانيا : التوصيلات من المفتاح :
• الطرف الاول من المفتاح ← المنافذ السالبة بلوحة التجارب
• الطرف الثانى من المفتاح ← منفذ رقم 2 بلوحة راسبيرى باى بيكو 2W
ثالثا : التوصيلات من الشاشة الكريستالية:
• منفذ ال VCC للشاشة الكريستالية ← المنافذ الموجبة بلوحة التجارب
• منفذ ال GND للشاشة الكريستالية ← المنافذ السالبة بلوحة التجارب
• منفذ SDA للشاشة الكريستالية ← منفذ رقم 0 بلوحة راسبيرى باى بيكو 2W
• منفذ SCL للشاشة الكريستالية ← منفذ رقم 1 بلوحة راسبيرى باى بيكو 2W
وظيفة النص البرمجي التالي هي التحكم في قفز العداء عن طريق الضغط على المفتاح الضغاط المتصل بلوحة راسبيرى باي بيكو.
'''
Voltaat Learn (http://learn.voltaat.com)
Link to the full tutorial:
Tutorial: Building an endless runner game using a Raspberry Pi Pico board.
This code is for building an endless runner game using a Raspberry Pi Pico board
Note: You can use this sketch with any Raspberry Pi Pico.
'''
import machine
import random
import time
from machine import I2C, Pin
from lcd_api import LcdApi
from pico_i2c_lcd import I2cLcd
# Game settings
GAME_SPEED = 1
PIN_BUTTON = 2
# Sprite definitions
SPRITE_RUN1 = 1
SPRITE_RUN2 = 2
SPRITE_JUMP = 3
SPRITE_JUMP_UPPER = 4
SPRITE_JUMP_LOWER = 5
SPRITE_TERRAIN_EMPTY = ' '
SPRITE_TERRAIN_SOLID = 6
SPRITE_TERRAIN_SOLID_RIGHT = 7
SPRITE_TERRAIN_SOLID_LEFT = 8
HERO_HORIZONTAL_POSITION = 1
TERRAIN_WIDTH = 16
TERRAIN_EMPTY = 0
TERRAIN_LOWER_BLOCK = 1
TERRAIN_UPPER_BLOCK = 2
# Hero positions (simplified full-body jump)
HERO_POSITION_OFF = 0
HERO_POSITION_RUN_LOWER_1 = 1
HERO_POSITION_RUN_LOWER_2 = 2
HERO_POSITION_JUMP_LOWER = 3 # Full-body jump at bottom
HERO_POSITION_JUMP_MID = 4 # Full-body jump in middle
HERO_POSITION_JUMP_UPPER = 5 # Full-body jump at top
HERO_POSITION_RUN_UPPER_1 = 6 # Running at top
HERO_POSITION_RUN_UPPER_2 = 7 # Running at top
# I2C and LCD setup
I2C_ADDR = 0x27
I2C_NUM_ROWS = 2
I2C_NUM_COLS = 16
# Global variables
terrain_upper = [SPRITE_TERRAIN_EMPTY] * TERRAIN_WIDTH
terrain_lower = [SPRITE_TERRAIN_EMPTY] * TERRAIN_WIDTH
button_pushed = False
lcd = None
def init_lcd():
global lcd
try:
addresses = [0x27, 0x3F, 0x20, 0x38]
i2c = None
lcd = None
for addr in addresses:
try:
i2c = I2C(0, sda=Pin(0), scl=Pin(1), freq=100000)
devices = i2c.scan()
print("Detected I2C devices:", [hex(device) for device in devices])
if addr in devices:
lcd = I2cLcd(i2c, addr, I2C_NUM_ROWS, I2C_NUM_COLS)
print(f"LCD found at address: {hex(addr)}")
break
except Exception as e:
print(f"Error at address {hex(addr)}: {e}")
continue
if lcd is None:
print("LCD not found, using simulation mode.")
lcd = DummyLcd()
return lcd
except Exception as e:
print(f"LCD initialization error: {e}")
return DummyLcd()
class DummyLcd:
def __init__(self):
self.rows = I2C_NUM_ROWS
self.cols = I2C_NUM_COLS
print("Dummy LCD initialized")
def clear(self):
print(" " * 40)
def move_to(self, x, y):
pass
def putstr(self, text):
print(text)
def custom_char(self, location, charmap):
print(f"Custom character created at location {location}")
def backlight_on(self):
pass
def backlight_off(self):
pass
def button_handler(pin):
global button_pushed
button_pushed = True
def initialize_graphics():
global lcd
# Custom character graphics (full-body hero jump)
graphics = [
# Run position 1 - running man
[0x0E, 0x0E, 0x04, 0x0E, 0x15, 0x04, 0x0A, 0x11],
# Run position 2 - running man
[0x0E, 0x0E, 0x04, 0x0E, 0x15, 0x04, 0x0A, 0x0A],
# Jump full-body - hero jumping (entire body)
[0x0E, 0x0E, 0x15, 0x0E, 0x04, 0x04, 0x0A, 0x11],
# Jump full mid - hero mid-air
[0x0E, 0x0E, 0x15, 0x0E, 0x04, 0x04, 0x0A, 0x11],
# Jump full upper - hero at top
[0x0E, 0x0E, 0x15, 0x0E, 0x04, 0x04, 0x0A, 0x11],
# Ground - solid block
[0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F],
# Ground right - solid block
[0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F],
# Ground left - solid block
[0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F],
]
try:
for i in range(min(8, len(graphics))):
lcd.custom_char(i + 1, graphics[i])
print("Graphics initialized successfully")
except Exception as e:
print(f"Error creating custom characters: {e}")
for i in range(TERRAIN_WIDTH):
terrain_upper[i] = SPRITE_TERRAIN_EMPTY
terrain_lower[i] = SPRITE_TERRAIN_EMPTY
def advance_terrain(terrain, new_terrain):
for i in range(TERRAIN_WIDTH):
current = terrain[i]
next_val = new_terrain if i == TERRAIN_WIDTH - 1 else terrain[i + 1]
if current == SPRITE_TERRAIN_EMPTY:
terrain[i] = SPRITE_TERRAIN_SOLID if next_val == SPRITE_TERRAIN_SOLID else SPRITE_TERRAIN_EMPTY
elif current == SPRITE_TERRAIN_SOLID:
terrain[i] = SPRITE_TERRAIN_EMPTY if next_val == SPRITE_TERRAIN_EMPTY else SPRITE_TERRAIN_SOLID
elif current in [SPRITE_TERRAIN_SOLID_RIGHT, SPRITE_TERRAIN_SOLID_LEFT]:
terrain[i] = SPRITE_TERRAIN_SOLID
def draw_hero(position, terrain_upper, terrain_lower, score):
collide = False
upper_save = terrain_upper[HERO_HORIZONTAL_POSITION]
lower_save = terrain_lower[HERO_HORIZONTAL_POSITION]
upper = SPRITE_TERRAIN_EMPTY
lower = SPRITE_TERRAIN_EMPTY
# Hero states with full-body jump
if position == HERO_POSITION_OFF:
upper = lower = SPRITE_TERRAIN_EMPTY
elif position == HERO_POSITION_RUN_LOWER_1:
upper = SPRITE_TERRAIN_EMPTY
lower = SPRITE_RUN1
elif position == HERO_POSITION_RUN_LOWER_2:
upper = SPRITE_TERRAIN_EMPTY
lower = SPRITE_RUN2
elif position == HERO_POSITION_JUMP_LOWER:
upper = SPRITE_TERRAIN_EMPTY
lower = SPRITE_JUMP
elif position in [HERO_POSITION_JUMP_MID, HERO_POSITION_JUMP_UPPER]:
upper = SPRITE_JUMP
lower = SPRITE_TERRAIN_EMPTY
elif position == HERO_POSITION_RUN_UPPER_1:
upper = SPRITE_RUN1
lower = SPRITE_TERRAIN_EMPTY
elif position == HERO_POSITION_RUN_UPPER_2:
upper = SPRITE_RUN2
lower = SPRITE_TERRAIN_EMPTY
if upper != ' ':
terrain_upper[HERO_HORIZONTAL_POSITION] = upper
collide = False if upper_save == SPRITE_TERRAIN_EMPTY else True
if lower != ' ':
terrain_lower[HERO_HORIZONTAL_POSITION] = lower
collide = collide or (False if lower_save == SPRITE_TERRAIN_EMPTY else True)
digits = 5 if score > 9999 else 4 if score > 999 else 3 if score > 99 else 2 if score > 9 else 1
upper_line = "".join(chr(c) if isinstance(c, int) and 1 <= c <= 8 else str(c) for c in terrain_upper[:TERRAIN_WIDTH])
lower_line = "".join(chr(c) if isinstance(c, int) and 1 <= c <= 8 else str(c) for c in terrain_lower[:TERRAIN_WIDTH])
score_str = str(score)
if len(upper_line) + len(score_str) <= I2C_NUM_COLS:
upper_display = upper_line + " " * (I2C_NUM_COLS - len(upper_line) - len(score_str)) + score_str
else:
upper_display = upper_line[:I2C_NUM_COLS - len(score_str)] + score_str
lcd.clear()
lcd.move_to(0, 0)
lcd.putstr(upper_display[:I2C_NUM_COLS])
lcd.move_to(0, 1)
lcd.putstr(lower_line[:I2C_NUM_COLS])
terrain_upper[HERO_HORIZONTAL_POSITION] = upper_save
terrain_lower[HERO_HORIZONTAL_POSITION] = lower_save
return collide
class TerrainGenerator:
def __init__(self):
self.last_block_type = TERRAIN_EMPTY
self.blocks_since_last_gap = 0
self.min_gap_interval = 4
self.max_blocks_before_gap = 3
self.difficulty_level = 0
self.distance_counter = 0
self.last_block_end = -10
def update_difficulty(self, distance):
self.distance_counter = distance
self.difficulty_level = min(3, distance // 50)
def generate_terrain_block(self):
blocks_since_last = self.blocks_since_last_gap
if blocks_since_last < self.min_gap_interval:
gap_needed = self.min_gap_interval - blocks_since_last
self.blocks_since_last_gap += gap_needed
self.last_block_type = TERRAIN_EMPTY
return TERRAIN_EMPTY, gap_needed
base_density = 0.8
density_bonus = self.difficulty_level * 0.05
block_density = min(0.9, base_density + density_bonus)
if self.blocks_since_last_gap >= self.min_gap_interval + self.max_blocks_before_gap:
gap_length = random.randint(4, 6)
self.blocks_since_last_gap = 0
self.last_block_type = TERRAIN_EMPTY
return TERRAIN_EMPTY, gap_length
if random.random() < block_density:
self.blocks_since_last_gap = 1
if self.last_block_type in [TERRAIN_UPPER_BLOCK, TERRAIN_LOWER_BLOCK]:
if random.random() < 0.7:
block_type = self.last_block_type
else:
block_type = random.choice([TERRAIN_LOWER_BLOCK, TERRAIN_UPPER_BLOCK])
else:
block_type = random.choice([TERRAIN_LOWER_BLOCK, TERRAIN_UPPER_BLOCK])
self.last_block_type = block_type
block_length = random.randint(2, 3)
return block_type, block_length
else:
gap_length = random.randint(1, 2)
self.blocks_since_last_gap += gap_length
self.last_block_type = TERRAIN_EMPTY
return TERRAIN_EMPTY, gap_length
def main():
global button_pushed, lcd
print("Starting the game...")
lcd = init_lcd()
button = Pin(PIN_BUTTON, Pin.IN, Pin.PULL_UP)
button.irq(trigger=Pin.IRQ_FALLING, handler=button_handler)
initialize_graphics()
hero_pos = HERO_POSITION_RUN_LOWER_1
new_terrain_type = TERRAIN_EMPTY
current_block_length = 4
playing = False
blink = False
distance = 0
jump_progress = 0
is_jumping = False
terrain_gen = TerrainGenerator()
print("Game ready. Press the button to start...")
while True:
if not playing:
if blink:
lcd.clear()
lcd.move_to(3, 0)
lcd.putstr("Start Game")
lcd.move_to(1, 1)
lcd.putstr("VOLTAAT LEARN")
time.sleep(0.5)
else:
lcd.clear()
time.sleep(0.3)
blink = not blink
if button_pushed:
print("Game started...")
initialize_graphics()
hero_pos = HERO_POSITION_RUN_LOWER_1
playing = True
button_pushed = False
distance = 0
jump_progress = 0
is_jumping = False
current_block_length = 4
new_terrain_type = TERRAIN_EMPTY
terrain_gen = TerrainGenerator()
lcd.clear()
continue
terrain_gen.update_difficulty(distance)
advance_terrain(terrain_lower, SPRITE_TERRAIN_SOLID if new_terrain_type == TERRAIN_LOWER_BLOCK else SPRITE_TERRAIN_EMPTY)
advance_terrain(terrain_upper, SPRITE_TERRAIN_SOLID if new_terrain_type == TERRAIN_UPPER_BLOCK else SPRITE_TERRAIN_EMPTY)
current_block_length -= 1
if current_block_length <= 0:
new_terrain_type, current_block_length = terrain_gen.generate_terrain_block()
print(f"New block: {new_terrain_type}, length: {current_block_length}, distance: {distance}")
# Jump handling - full-body jump
if button_pushed and not is_jumping and hero_pos in [HERO_POSITION_RUN_LOWER_1, HERO_POSITION_RUN_LOWER_2, HERO_POSITION_RUN_UPPER_1, HERO_POSITION_RUN_UPPER_2]:
is_jumping = True
jump_progress = 0
button_pushed = False
if is_jumping:
jump_progress += 1
# Simple jump sequence
if jump_progress == 1:
hero_pos = HERO_POSITION_JUMP_LOWER
elif jump_progress == 2:
hero_pos = HERO_POSITION_JUMP_MID
elif jump_progress == 3:
hero_pos = HERO_POSITION_JUMP_UPPER
elif jump_progress == 4:
hero_pos = HERO_POSITION_JUMP_MID
elif jump_progress == 5:
hero_pos = HERO_POSITION_JUMP_LOWER
else:
is_jumping = False
jump_progress = 0
if terrain_lower[HERO_HORIZONTAL_POSITION] != SPRITE_TERRAIN_EMPTY:
hero_pos = HERO_POSITION_RUN_UPPER_1
else:
hero_pos = HERO_POSITION_RUN_LOWER_1
# Running animation when not jumping
if not is_jumping:
if hero_pos in [HERO_POSITION_RUN_LOWER_1, HERO_POSITION_RUN_UPPER_1]:
hero_pos += 1
elif hero_pos in [HERO_POSITION_RUN_LOWER_2, HERO_POSITION_RUN_UPPER_2]:
hero_pos -= 1
if draw_hero(hero_pos, terrain_upper, terrain_lower, distance >> 2):
print(f"Game over! Score: {distance >> 2}")
playing = False
for i in range(6):
lcd.clear()
lcd.move_to(4, 0)
lcd.putstr("Game Over")
lcd.move_to(3, 1)
lcd.putstr(f"Score: {distance >> 2}")
time.sleep(0.3)
lcd.clear()
time.sleep(0.2)
else:
distance += 1
time.sleep(0.2 * (1 / GAME_SPEED))
if __name__ == "__main__":
main()
بعد رفع الكود البرمجى قم بالضغط على المفتاح الضغاط ليقفز اللاعب على العوائق التى امامه, ويجب عليك الضغط على المفتاح فى الوقت المثالى حتى لا يصطدم اللاعب بالعوائق.













