import utime
import urandom
from machine import Pin, I2C, freq
from ssd1306 import SSD1306_I2C
import framebuf
# ----- Hardware Setup -----
# In Wokwi, ensure SDA is Pin 6 and SCL is Pin 7
i2c = I2C(1, scl=Pin(7), sda=Pin(6), freq=400000)
oled = SSD1306_I2C(128, 64, i2c)
# Hardware Flip (180 degrees) - replaces the slow show_rotated function
oled.write_cmd(0xA1) # Segment remap
oled.write_cmd(0xC8) # COM scan direction
# Button Setup (Mapping to your original Pad logic)
# Connect buttons between the Pin and GND
btn_feed = Pin(2, Pin.IN, Pin.PULL_UP) # Pad 1
btn_status = Pin(1, Pin.IN, Pin.PULL_UP) # Pad 4
btn_play = Pin(26, Pin.IN, Pin.PULL_UP) # Pad 6
# --- Game Bitmaps ---
CAT_A = bytearray([
0,16,64,0,56,224,0,105,160,0,201,32,1,159,160,227,
255,224,243,255,224,54,255,232,119,127,240,110,111,120,111,239,
112,126,127,248,63,255,224,15,255,192,1,129,128,1,129,128
])
CAT_B = bytearray([
0,16,64,0,56,224,0,105,160,0,201,32,1,159,160,227,
255,224,243,255,224,54,255,232,119,127,240,110,111,120,111,239,
112,126,127,248,63,255,224,15,255,192,3,0,192,6,0,96
])
fb_a = framebuf.FrameBuffer(CAT_A, 24, 16, framebuf.MONO_HLSB)
fb_b = framebuf.FrameBuffer(CAT_B, 24, 16, framebuf.MONO_HLSB)
# --- Stats & State ---
STATE_PET, STATE_GAME = 0, 1
mode = STATE_PET
hunger_lvl, boredom_lvl = 100, 100
last_tick = utime.ticks_ms()
# --- Physics Constants ---
GRAVITY = 4
JUMP_VEL = -18
GROUND_Y = 40
cat_y, cat_v, obs_x, game_score = GROUND_Y, 0, 128, 0
def draw_full_face(expression="neutral", blink=False):
oled.fill(0)
size = 14
if expression == "critical":
for i in range(2):
oled.line(20+i, 25, 20+size+i, 25+size, 1)
oled.line(20+i, 25+size, 20+size+i, 25, 1)
oled.line(94+i, 25, 94+size+i, 25+size, 1)
oled.line(94+i, 25+size, 94+size+i, 25, 1)
elif expression == "sad":
for i in range(3):
oled.line(20, 38+i, 20+size, 32+i, 1)
oled.line(94, 32+i, 94+size, 38+i, 1)
elif blink:
oled.fill_rect(20, 32, size, 2, 1)
oled.fill_rect(94, 32, size, 2, 1)
else:
oled.fill_rect(20, 25, size, size, 1)
oled.fill_rect(94, 25, size, size, 1)
m_y = 33 if expression == "happy" else 44 if expression in ["sad", "critical"] else 38
oled.fill_rect(57, m_y-13, size, size, 1)
oled.fill_rect(44, m_y, size, size, 1)
oled.fill_rect(70, m_y, size, size, 1)
while True:
now = utime.ticks_ms()
# Check Button States (Pressed = 0 because of PULL_UP)
feed_pressed = not btn_feed.value()
status_pressed = not btn_status.value()
play_pressed = not btn_play.value()
if mode == STATE_PET:
if utime.ticks_diff(now, last_tick) > 5000: # Sped up for testing
hunger_lvl, boredom_lvl = max(0, hunger_lvl - 2), max(0, boredom_lvl - 3)
last_tick = now
if feed_pressed:
hunger_lvl = min(100, hunger_lvl + 15)
utime.sleep_ms(200)
if play_pressed:
mode = STATE_GAME
cat_y, cat_v, obs_x, game_score = GROUND_Y, 0, 128, 0
utime.sleep_ms(300)
mood = "critical" if (hunger_lvl <= 0 or boredom_lvl <= 0) else "sad" if (hunger_lvl < 30 or boredom_lvl < 30) else "happy"
draw_full_face(mood, blink=(now // 2000 % 3 == 0))
if status_pressed:
oled.fill_rect(10, 0, 108, 20, 0); oled.rect(10, 0, 108, 20, 1)
oled.text("H:{}% B:{}%".format(hunger_lvl, boredom_lvl), 18, 6)
elif mode == STATE_GAME:
oled.fill(0); oled.line(0, 56, 128, 56, 1)
if play_pressed and cat_y >= GROUND_Y:
cat_v = JUMP_VEL
cat_v += GRAVITY
cat_y += cat_v
if cat_y > GROUND_Y: cat_y = GROUND_Y; cat_v = 0
obs_x -= 8
if obs_x < -16: obs_x = 128; game_score += 1
active_fb = fb_a if (cat_y < GROUND_Y or (now // 100 % 2 == 0)) else fb_b
oled.blit(active_fb, 20, int(cat_y))
oled.fill_rect(obs_x, 48, 8, 8, 1)
oled.text("Win: {}/5".format(game_score), 35, 0)
# Collision Detection
if obs_x < 44 and obs_x > 10 and cat_y > 32:
utime.sleep_ms(500)
mode = STATE_PET
if game_score >= 5:
boredom_lvl = min(100, boredom_lvl + 30)
mode = STATE_PET
oled.show()
utime.sleep_ms(10)