import machine
import utime
import urandom
# Pins
PIN_BUTTON = 2
LCD_RS = 11
LCD_EN = 9
LCD_D4 = 6
LCD_D5 = 5
LCD_D6 = 4
LCD_D7 = 3
PIN_LEFT = 13
PIN_RIGHT = 14
# Sprite definitions
SPRITE_RUN1 = 1
SPRITE_RUN2 = 2
SPRITE_JUMP = 3
SPRITE_JUMP_UPPER = '.'
SPRITE_JUMP_LOWER = 4
SPRITE_TERRAIN_EMPTY = ' '
SPRITE_TERRAIN_SOLID = 5
SPRITE_TERRAIN_SOLID_RIGHT = 6
SPRITE_TERRAIN_SOLID_LEFT = 7
TERRAIN_WIDTH = 16
TERRAIN_EMPTY = 0
TERRAIN_LOWER_BLOCK = 1
TERRAIN_UPPER_BLOCK = 2
HERO_POSITION_OFF = 0
HERO_POSITION_RUN_LOWER_1 = 1
HERO_POSITION_RUN_LOWER_2 = 2
HERO_POSITION_JUMP_1 = 3 # 1-8 Jump airtime
HERO_POSITION_JUMP_2 = 4
HERO_POSITION_JUMP_3 = 5
HERO_POSITION_JUMP_4 = 6
HERO_POSITION_JUMP_5 = 7
HERO_POSITION_JUMP_6 = 8
HERO_POSITION_JUMP_7 = 9
HERO_POSITION_JUMP_8 = 10
HERO_POSITION_RUN_UPPER_1 = 11
HERO_POSITION_RUN_UPPER_2 = 12
# Simple LCD driver
class LCD:
def __init__(self, rs, en, d4, d5, d6, d7, cols=16, rows=2):
self.rs = machine.Pin(rs, machine.Pin.OUT)
self.en = machine.Pin(en, machine.Pin.OUT)
self.d4 = machine.Pin(d4, machine.Pin.OUT)
self.d5 = machine.Pin(d5, machine.Pin.OUT)
self.d6 = machine.Pin(d6, machine.Pin.OUT)
self.d7 = machine.Pin(d7, machine.Pin.OUT)
self.cols = cols
self.rows = rows
self.init_lcd()
def init_lcd(self):
utime.sleep_ms(50)
self.cmd(0x33)
utime.sleep_ms(5)
self.cmd(0x32)
utime.sleep_ms(5)
self.cmd(0x28)
self.cmd(0x0C)
self.cmd(0x06)
self.cmd(0x01)
utime.sleep_ms(2)
def cmd(self, value):
self.rs.value(0)
self.write_byte(value)
def write_byte(self, value):
# High nibble
self.d4.value((value >> 4) & 1)
self.d5.value((value >> 5) & 1)
self.d6.value((value >> 6) & 1)
self.d7.value((value >> 7) & 1)
self.pulse_enable()
# Low nibble
self.d4.value(value & 1)
self.d5.value((value >> 1) & 1)
self.d6.value((value >> 2) & 1)
self.d7.value((value >> 3) & 1)
self.pulse_enable()
def pulse_enable(self):
self.en.value(0)
utime.sleep_us(1)
self.en.value(1)
utime.sleep_us(1)
self.en.value(0)
utime.sleep_us(100)
def write_char(self, char):
self.rs.value(1)
self.write_byte(ord(char))
def set_cursor(self, col, row):
addr = col + (0x40 if row == 1 else 0x00)
self.cmd(0x80 | addr)
def print(self, text):
for char in text:
self.write_char(char)
def clear(self):
self.cmd(0x01)
utime.sleep_ms(2)
def create_char(self, location, charmap):
location &= 0x07
self.cmd(0x40 | (location << 3))
self.rs.value(1)
for line in charmap:
self.write_byte(line)
self.rs.value(0)
# Initialize LCD
lcd = LCD(LCD_RS, LCD_EN, LCD_D4, LCD_D5, LCD_D6, LCD_D7)
terrain_upper = [SPRITE_TERRAIN_EMPTY] * (TERRAIN_WIDTH + 1)
terrain_lower = [SPRITE_TERRAIN_EMPTY] * (TERRAIN_WIDTH + 1)
button_pushed = False
# Button Push
def button_handler(pin):
global button_pushed
button_pushed = True
# Sprite mapping
def sprite_to_char(sprite):
if sprite == SPRITE_TERRAIN_EMPTY:
return ' '
elif sprite == SPRITE_RUN1:
return '\x01'
elif sprite == SPRITE_RUN2:
return '\x02'
elif sprite == SPRITE_JUMP:
return '\x03'
elif sprite == SPRITE_JUMP_LOWER:
return '\x04'
elif sprite == SPRITE_TERRAIN_SOLID:
return '\x05'
elif sprite == SPRITE_TERRAIN_SOLID_RIGHT:
return '\x06'
elif sprite == SPRITE_TERRAIN_SOLID_LEFT:
return '\x07'
elif sprite == SPRITE_JUMP_UPPER:
return '.'
else:
return ' '
def initialize_graphics():
graphics = [
[0b01100,0b01100,0b00000,0b01110,0b11100,0b01100,0b11010,0b10011], # Run1
[0b01100,0b01100,0b00000,0b01100,0b01100,0b01100,0b01100,0b01110], # Run2
[0b01100,0b01100,0b00000,0b11110,0b01101,0b11111,0b10000,0b00000], # Jump
[0b11110,0b01101,0b11111,0b10000,0b00000,0b00000,0b00000,0b00000], # Jump lower
[0b11111,0b11111,0b11111,0b11111,0b11111,0b11111,0b11111,0b11111], # Ground
[0b00011,0b00011,0b00011,0b00011,0b00011,0b00011,0b00011,0b00011], # Ground right
[0b11000,0b11000,0b11000,0b11000,0b11000,0b11000,0b11000,0b11000], # Ground left
]
for i in range(7):
lcd.create_char(i+1, graphics[i])
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_RIGHT if next_val == SPRITE_TERRAIN_SOLID else SPRITE_TERRAIN_EMPTY
elif current == SPRITE_TERRAIN_SOLID:
terrain[i] = SPRITE_TERRAIN_SOLID_LEFT if next_val == SPRITE_TERRAIN_EMPTY else SPRITE_TERRAIN_SOLID
elif current == SPRITE_TERRAIN_SOLID_RIGHT:
terrain[i] = SPRITE_TERRAIN_SOLID
elif current == SPRITE_TERRAIN_SOLID_LEFT:
terrain[i] = SPRITE_TERRAIN_EMPTY
def draw_hero(position, hero_x, terrain_upper, terrain_lower, score):
collide = False
upper_save = terrain_upper[hero_x]
lower_save = terrain_lower[hero_x]
if position == HERO_POSITION_OFF:
upper, lower = SPRITE_TERRAIN_EMPTY, SPRITE_TERRAIN_EMPTY
elif position in [HERO_POSITION_RUN_LOWER_1, HERO_POSITION_RUN_LOWER_2]:
upper, lower = SPRITE_TERRAIN_EMPTY, (SPRITE_RUN1 if position == HERO_POSITION_RUN_LOWER_1 else SPRITE_RUN2)
elif position in [HERO_POSITION_JUMP_1, HERO_POSITION_JUMP_8]:
upper, lower = SPRITE_TERRAIN_EMPTY, SPRITE_JUMP
elif position in [HERO_POSITION_JUMP_2, HERO_POSITION_JUMP_7]:
upper, lower = SPRITE_JUMP_UPPER, SPRITE_JUMP_LOWER
elif position in [HERO_POSITION_JUMP_3,HERO_POSITION_JUMP_4,HERO_POSITION_JUMP_5,HERO_POSITION_JUMP_6]:
upper, lower = SPRITE_JUMP, SPRITE_TERRAIN_EMPTY
elif position == HERO_POSITION_RUN_UPPER_1:
upper, lower = SPRITE_RUN1, SPRITE_TERRAIN_EMPTY
elif position == HERO_POSITION_RUN_UPPER_2:
upper, lower = SPRITE_RUN2, SPRITE_TERRAIN_EMPTY
else:
upper, lower = SPRITE_TERRAIN_EMPTY, SPRITE_TERRAIN_EMPTY
if upper != ' ':
terrain_upper[hero_x] = upper
collide = upper_save != SPRITE_TERRAIN_EMPTY
if lower != ' ':
terrain_lower[hero_x] = lower
collide = collide or (lower_save != SPRITE_TERRAIN_EMPTY)
terrain_upper_str = ''.join([sprite_to_char(x) for x in terrain_upper[:TERRAIN_WIDTH]])
terrain_lower_str = ''.join([sprite_to_char(x) for x in terrain_lower[:TERRAIN_WIDTH]])
lcd.set_cursor(0,0)
lcd.print(terrain_upper_str)
score_str = str(score)
lcd.set_cursor(TERRAIN_WIDTH-len(score_str),0)
lcd.print(score_str)
lcd.set_cursor(0,1)
lcd.print(terrain_lower_str)
terrain_upper[hero_x] = upper_save
terrain_lower[hero_x] = lower_save
return collide
def main():
global button_pushed
button = machine.Pin(PIN_BUTTON, machine.Pin.IN, machine.Pin.PULL_UP)
button.irq(trigger=machine.Pin.IRQ_FALLING, handler=button_handler)
left_button = machine.Pin(PIN_LEFT, machine.Pin.IN, machine.Pin.PULL_UP)
right_button = machine.Pin(PIN_RIGHT, machine.Pin.IN, machine.Pin.PULL_UP)
initialize_graphics()
hero_pos = HERO_POSITION_RUN_LOWER_1
hero_x = 1
new_terrain_type, new_terrain_duration = TERRAIN_EMPTY, 1
playing, blink, distance = False, False, 0
while True:
if not playing:
draw_hero(HERO_POSITION_OFF if blink else hero_pos, hero_x, terrain_upper, terrain_lower, distance>>3)
if blink:
lcd.set_cursor(0,0)
lcd.print("Press Start")
utime.sleep_ms(250)
blink = not blink
if button_pushed:
initialize_graphics()
hero_pos, hero_x, playing, button_pushed, distance = HERO_POSITION_RUN_LOWER_1, 1, True, False, 0
continue
# Terrain scroll
lower_terrain = SPRITE_TERRAIN_SOLID if new_terrain_type == TERRAIN_LOWER_BLOCK else SPRITE_TERRAIN_EMPTY
upper_terrain = SPRITE_TERRAIN_SOLID if new_terrain_type == TERRAIN_UPPER_BLOCK else SPRITE_TERRAIN_EMPTY
advance_terrain(terrain_lower, lower_terrain)
advance_terrain(terrain_upper, upper_terrain)
new_terrain_duration -= 1
if new_terrain_duration == 0:
if new_terrain_type == TERRAIN_EMPTY:
new_terrain_type = TERRAIN_UPPER_BLOCK if urandom.getrandbits(2) == 0 else TERRAIN_LOWER_BLOCK
new_terrain_duration = 2 + urandom.getrandbits(4)
else:
new_terrain_type, new_terrain_duration = TERRAIN_EMPTY, 10 + urandom.getrandbits(4)
# Jump
if button_pushed:
if hero_pos <= HERO_POSITION_RUN_LOWER_2:
hero_pos = HERO_POSITION_JUMP_1
button_pushed = False
# Move left/right
if left_button.value() == 0:
hero_x = max(0, hero_x - 1)
if right_button.value() == 0:
hero_x = min(TERRAIN_WIDTH-1, hero_x + 1)
# Collision + hero state
if draw_hero(hero_pos, hero_x, terrain_upper, terrain_lower, distance>>3):
playing = False
else:
if hero_pos in [HERO_POSITION_RUN_LOWER_2, HERO_POSITION_JUMP_8]:
hero_pos = HERO_POSITION_RUN_LOWER_1
elif (hero_pos in [HERO_POSITION_JUMP_3, HERO_POSITION_JUMP_4, HERO_POSITION_JUMP_5]
and terrain_lower[hero_x] != SPRITE_TERRAIN_EMPTY):
hero_pos = HERO_POSITION_RUN_UPPER_1
elif (hero_pos >= HERO_POSITION_RUN_UPPER_1 and
terrain_lower[hero_x] == SPRITE_TERRAIN_EMPTY):
hero_pos = HERO_POSITION_JUMP_5
elif hero_pos == HERO_POSITION_RUN_UPPER_2:
hero_pos = HERO_POSITION_RUN_UPPER_1
else:
hero_pos += 1
distance += 1
delay = max(10, 80 - (distance // 20))
utime.sleep_ms(delay)
if __name__ == "__main__":
main()