'''# RUNNER_EMG_JUMP_2BOTONES_WOKWI.py
# Runner en eje X: obstáculos estáticos; salto con EMG (umbral adaptativo)
# Botones:
# - BTN_MODE (33): Cambia dificultad en el menú
# - BTN_START (25): Inicia juego | mantener >800 ms en juego = finalizar (Game Over)
from machine import Pin, I2C, PWM, ADC
import framebuf, time, urandom
# ====== SSD1306 (driver reducido) ======
class SSD1306:
def __init__(self, width, height, i2c, addr=0x3C):
self.width = width; self.height = height
self.i2c = i2c; self.addr = addr
self.buffer = bytearray(self.height * self.width // 8)
self.fb = framebuf.FrameBuffer(self.buffer, self.width, self.height, framebuf.MONO_VLSB)
self.init_display()
def write_cmd(self, cmd):
try: self.i2c.writeto(self.addr, bytearray([0x80, cmd]))
except: pass
def init_display(self):
for cmd in (0xAE,0x20,0x00,0x40,0xA1,0xA8,0x3F,0xD3,0x00,0xD5,0x80,0xD9,0xF1,0xDA,0x12,0xDB,0x40,0x8D,0x14,0xAF):
self.write_cmd(cmd)
self.fill(0); self.show()
def show(self):
try:
self.write_cmd(0x21); self.write_cmd(0); self.write_cmd(self.width-1)
self.write_cmd(0x22); self.write_cmd(0); self.write_cmd(self.height//8 - 1)
self.i2c.writeto(self.addr, b'\x40' + self.buffer)
except: pass
def fill(self, c): self.fb.fill(c)
def text(self, s, x, y): self.fb.text(s, x, y)
def pixel(self, x, y, c): self.fb.pixel(x, y, c)
def rect(self, x, y, w, h, c): self.fb.rect(x, y, w, h, c)
def fill_rect(self, x, y, w, h, c): self.fb.fill_rect(x, y, w, h, c)
def blit(self, fbuf, x, y): self.fb.blit(fbuf, x, y)
class SSD1306_I2C(SSD1306):
pass
# -------------------------
# HARDWARE
# -------------------------
i2c = I2C(0, scl=Pin(22), sda=Pin(21))
oled = SSD1306_I2C(128, 64, i2c)
BTN_START = Pin(25, Pin.IN, Pin.PULL_UP) # iniciar / finalizar (long)
BTN_MODE = Pin(33, Pin.IN, Pin.PULL_UP) # cambiar dificultad en menú
buzzer = PWM(Pin(26)); buzzer.duty_u16(0)
# EMG ENTRADA (ADC)
emg = ADC(Pin(34))
if hasattr(emg, "atten"):
emg.atten(ADC.ATTN_11DB) # 0-3.3V
# Nota: este código asume señal tipo "envelope" 0-3.3V. Si es cruda, ver explicación al final.
# -------------------------
# SPRITE JUGADOR (8x8)
# -------------------------
PLAYER = bytearray([
0b00011000,
0b00111100,
0b01111110,
0b11111111,
0b01111110,
0b00111100,
0b00100100,
0b01000010
])
fb_player = framebuf.FrameBuffer(PLAYER, 8, 8, framebuf.MONO_HLSB)
# -------------------------
# ESTADOS
# -------------------------
STATE_MENU = 0
STATE_GAME = 1
STATE_OVER = 2
# -------------------------
# VARIABLES GLOBALES
# -------------------------
state = STATE_MENU
difficulty = 0 # 0=Principiante, 1=Medio, 2=Difícil
GROUND_Y = 56
WORLD_W = 128
PLAYER_W, PLAYER_H = 8, 8
# Física del jugador
player_x = 5
player_y = GROUND_Y - PLAYER_H
vx = 2.0
vy = 0.0
GRAVITY = 0.5
JUMP_V = -6.5
on_ground = True
# Obstáculos (estáticos)
obstacles = [] # {'id', 'x', 'w', 'h'}
score = 0
laps = 0
avoided_this_lap = set()
# Botones (edge + long-press)
_last_mode = 1; _last_start = 1
_last_mode_time = 0; _last_start_time = 0
DEBOUNCE_MS = 70
LONG_MS = 800
# -------- EMG procesado / calibración --------
def read_emg_u16():
try: return emg.read_u16()
except AttributeError: return emg.read() * 16 # 0..4095 -> ~0..65535
# Filtro EMA (exponential moving average) de la envolvente
emg_ema = None
EMA_ALPHA = 0.2 # 0.1-0.3: más alto = responde más rápido
# Basal y ruido (calibración)
emg_baseline = 0
emg_noise = 0
TH_K = 5.0 # Umbral = basal + TH_K*ruido (sube/baja sensibilidad)
REFRACT_MS = 350 # Refractario: evita múltiples saltos
_last_jump_time = 0
def calibrate_emg(duration_ms=2000):
"""Mide EMG en reposo para obtener basal y ruido."""
global emg_baseline, emg_noise, emg_ema
oled.fill(0); oled.text("Calibrando EMG", 8, 24); oled.text("Mantener REPOSO", 2, 40); oled.show()
t0 = time.ticks_ms()
vals = []
emg_ema = None
while time.ticks_diff(time.ticks_ms(), t0) < duration_ms:
v = read_emg_u16()
if emg_ema is None:
emg_ema = v
else:
emg_ema = int((1-EMA_ALPHA)*emg_ema + EMA_ALPHA*v)
vals.append(emg_ema)
time.sleep_ms(5)
# estadísticas simples
if not vals:
emg_baseline, emg_noise = 2000, 300
else:
# media y MAD (desviación absoluta mediana como estimador robusto)
vals.sort()
median = vals[len(vals)//2]
mad = sorted([abs(x - median) for x in vals])[len(vals)//2]
emg_baseline = median
emg_noise = max(100, mad) # evita 0
oled.fill(0); oled.text("Listo!", 44, 28); oled.show(); time.sleep_ms(400)
def emg_jump_detector():
"""Devuelve True si la señal EMG supera el umbral adaptativo."""
global emg_ema, _last_jump_time
v = read_emg_u16()
if emg_ema is None:
emg_ema = v
else:
emg_ema = int((1-EMA_ALPHA)*emg_ema + EMA_ALPHA*v)
th = int(emg_baseline + TH_K * emg_noise)
now = time.ticks_ms()
if emg_ema > th and time.ticks_diff(now, _last_jump_time) > REFRACT_MS:
_last_jump_time = now
return True
return False
# -------------------------
# Utilidades varias
# -------------------------
def beep(freq=1000, duration=100, volume=30000):
try:
buzzer.freq(freq); buzzer.duty_u16(volume)
time.sleep_ms(duration); buzzer.duty_u16(0)
except: pass
def btn_event(pin, is_mode):
"""Devuelve 'short', 'long' o None."""
global _last_mode, _last_start, _last_mode_time, _last_start_time
now = time.ticks_ms()
last_state = _last_mode if is_mode else _last_start
last_time = _last_mode_time if is_mode else _last_start_time
s = pin.value()
if last_state == 1 and s == 0 and time.ticks_diff(now, last_time) > DEBOUNCE_MS:
t0 = now
while pin.value() == 0:
if time.ticks_diff(time.ticks_ms(), t0) >= LONG_MS:
while pin.value() == 0: time.sleep_ms(10)
if is_mode: _last_mode_time = time.ticks_ms(); _last_mode = 1
else: _last_start_time = time.ticks_ms(); _last_start = 1
return "long"
time.sleep_ms(10)
if is_mode: _last_mode_time = time.ticks_ms(); _last_mode = 1
else: _last_start_time = time.ticks_ms(); _last_start = 1
return "short"
if is_mode: _last_mode = s
else: _last_start = s
return None
def mode_event(): return btn_event(BTN_MODE, True)
def start_event(): return btn_event(BTN_START, False)
# -------------------------
# Obstáculos y juego
# -------------------------
def generate_obstacles():
global obstacles, avoided_this_lap
obstacles = []; avoided_this_lap = set()
if difficulty == 0:
count, (hmin,hmax), min_gap = 3, (8,10), 24
elif difficulty == 1:
count, (hmin,hmax), min_gap = 5, (8,14), 20
else:
count, (hmin,hmax), min_gap = 7, (10,18), 16
xs = []; attempts = 0
while len(xs) < count and attempts < 200:
attempts += 1
x = urandom.randint(24, WORLD_W - 12)
if all(abs(x - xi) >= min_gap for xi in xs): xs.append(x)
xs.sort()
for i, x in enumerate(xs):
h = urandom.randint(hmin, hmax)
obstacles.append({'id': i, 'x': x, 'w': 8, 'h': h})
def reset_player():
global player_x, player_y, vx, vy, on_ground
player_x = 5; player_y = GROUND_Y - PLAYER_H
vy = 0.0; on_ground = True
vx = 1.8 if difficulty == 0 else (2.4 if difficulty == 1 else 3.2)
def draw_world():
oled.fill_rect(0, GROUND_Y + 1, 128, 2, 1)
for o in obstacles:
oled.fill_rect(o['x'], GROUND_Y - o['h'], o['w'], o['h'], 1)
def draw_player():
oled.blit(fb_player, int(player_x), int(player_y))
def physics_step():
global player_x, player_y, vy, on_ground
player_x += vx
if not on_ground:
vy += GRAVITY
player_y += vy
if player_y >= GROUND_Y - PLAYER_H:
player_y = GROUND_Y - PLAYER_H
vy = 0.0; on_ground = True
def try_jump():
global vy, on_ground
if on_ground:
vy = JUMP_V; on_ground = False
beep(1400, 60, 16000)
def check_collisions():
px = int(player_x); py = int(player_y)
for o in obstacles:
ox, oy, ow, oh = o['x'], GROUND_Y - o['h'], o['w'], o['h']
if (px < ox + ow and px + PLAYER_W > ox and
py < oy + oh and py + PLAYER_H > oy):
return True
return False
def update_scoring():
global score, laps, avoided_this_lap
for o in obstacles:
if o['id'] not in avoided_this_lap and player_x > (o['x'] + o['w']):
avoided_this_lap.add(o['id']); score += 1
if player_x > (WORLD_W - PLAYER_W):
laps += 1; score += 5
beep(2200, 120, 22000)
generate_obstacles(); reset_player()
# -------------------------
# Ciclos de estado
# -------------------------
def enter_menu(): global state; state = STATE_MENU
def start_game():
global state, score, laps, emg_ema, _last_jump_time
score = 0; laps = 0
generate_obstacles(); reset_player()
emg_ema = None; _last_jump_time = 0
state = STATE_GAME; beep(1200, 120)
def game_over(): global state; state = STATE_OVER; beep(300, 300, 28000)
# -------------------------
# Inicio
# -------------------------
calibrate_emg(2000)
enter_menu()
# -------------------------
# LOOP PRINCIPAL
# -------------------------
while True:
oled.fill(0)
# ===== MENÚ =====
if state == STATE_MENU:
oled.text("RUNNER EMG STATIC", 0, 0)
oled.text("> Principiante" + (" <" if difficulty == 0 else ""), 6, 18)
oled.text("> Medio" + (" <" if difficulty == 1 else ""), 6, 28)
oled.text("> Dificil" + (" <" if difficulty == 2 else ""), 6, 38)
oled.text("MODE=cambiar", 2, 52)
oled.text("START=jugar", 72, 52)
if mode_event() == "short":
difficulty = (difficulty + 1) % 3; beep(900, 60)
if start_event() == "short":
start_game()
# ===== JUEGO =====
elif state == STATE_GAME:
# Salto por EMG (umbral adaptativo)
if emg_jump_detector():
try_jump()
# Finalizar manualmente con START (mantener)
evs = start_event()
if evs == "long":
game_over()
physics_step()
draw_world(); draw_player()
oled.text("S:{}".format(score), 0, 0)
oled.text("L:{}".format(laps), 64, 0)
if check_collisions():
game_over()
update_scoring()
# ===== GAME OVER =====
elif state == STATE_OVER:
oled.text("GAME OVER", 28, 16)
oled.text("Score: {}".format(score), 20, 32)
oled.text("START=Menu", 22, 50)
if start_event() == "short":
enter_menu()
oled.show()
time.sleep_ms(20)'''
'''from machine import Pin, ADC, I2C
import framebuf, time, random
# ====== SSD1306 (driver reducido) ======
class SSD1306:
def __init__(self, width, height, i2c, addr=0x3C):
self.width = width; self.height = height
self.i2c = i2c; self.addr = addr
self.buffer = bytearray(self.height * self.width // 8)
self.fb = framebuf.FrameBuffer(self.buffer, self.width, self.height, framebuf.MONO_VLSB)
self.init_display()
def write_cmd(self, cmd):
try: self.i2c.writeto(self.addr, bytearray([0x80, cmd]))
except: pass
def init_display(self):
for cmd in (0xAE,0x20,0x00,0x40,0xA1,0xA8,0x3F,0xD3,0x00,0xD5,0x80,0xD9,0xF1,0xDA,0x12,0xDB,0x40,0x8D,0x14,0xAF):
self.write_cmd(cmd)
self.fill(0); self.show()
def show(self):
try:
self.write_cmd(0x21); self.write_cmd(0); self.write_cmd(self.width-1)
self.write_cmd(0x22); self.write_cmd(0); self.write_cmd(self.height//8 - 1)
self.i2c.writeto(self.addr, b'\x40' + self.buffer)
except: pass
def fill(self, c): self.fb.fill(c)
def text(self, s, x, y): self.fb.text(s, x, y)
def pixel(self, x, y, c): self.fb.pixel(x, y, c)
def rect(self, x, y, w, h, c): self.fb.rect(x, y, w, h, c)
def fill_rect(self, x, y, w, h, c): self.fb.fill_rect(x, y, w, h, c)
def blit(self, fbuf, x, y): self.fb.blit(fbuf, x, y)
class SSD1306_I2C(SSD1306):
pass
# ============================
# HARDWARE
# ============================
btn_start = Pin(25, Pin.IN, Pin.PULL_UP)
btn_up = Pin(33, Pin.IN, Pin.PULL_UP)
btn_down = Pin(26, Pin.IN, Pin.PULL_UP)
pot = ADC(Pin(34))
pot.width(ADC.WIDTH_10BIT)
pot.atten(ADC.ATTN_11DB)
i2c = I2C(0, scl=Pin(22), sda=Pin(21))
oled = SSD1306_I2C(128, 64, i2c)
# ============================
# ESTADOS
# ============================
STATE_MENU = 0
STATE_GAME = 1
STATE_OVER = 2
state = STATE_MENU
difficulty = 0 # 0 fácil, 1 medio, 2 difícil
# ============================
# VARIABLES DE JUEGO
# ============================
player_y = 48
player_vel = 0
gravity = 2
jump_force = -20
obstacles = []
score = 0
last_jump = 0
# ============================
# BOTONES
# ============================
def pressed(btn):
if not btn.value(): # LOW = pulsado
time.sleep_ms(120)
while not btn.value():
pass
return True
return False
# ============================
# OBSTÁCULOS
# ============================
def generate_obstacles():
global obstacles
obstacles = [{"x": 128 + i * 60, "h": random.randint(10, 24)} for i in range(3)]
# ============================
# REINICIAR PLAYER
# ============================
def reset_player():
global player_y, player_vel
player_y = 48
player_vel = 0
# ============================
# SALTO POR POTENCIÓMETRO (EMG)
# ============================
def emg_jump():
global player_vel, last_jump
val = pot.read()
threshold = 650 # ajustable
now = time.ticks_ms()
if val > threshold and time.ticks_diff(now, last_jump) > 700:
player_vel = jump_force
last_jump = now
# ============================
# INICIAR JUEGO + PREPARACIÓN
# ============================
def start_game():
global state, score, last_jump
score = 0
reset_player()
generate_obstacles()
last_jump = 0
state = STATE_GAME
prep_time = 3000 if difficulty == 0 else (2000 if difficulty == 1 else 1000)
t_end = time.ticks_add(time.ticks_ms(), prep_time)
while time.ticks_diff(t_end, time.ticks_ms()) > 0:
rem = time.ticks_diff(t_end, time.ticks_ms()) // 1000
oled.fill(0)
oled.text("Prepárate...", 20, 16)
oled.text("Empieza en:", 20, 32)
oled.text(str(rem + 1), 60, 48)
oled.show()
time.sleep_ms(40)
# ============================
# DIBUJO
# ============================
def draw_game():
oled.fill(0)
oled.fill_rect(10, player_y, 10, 10, 1)
for ob in obstacles:
oled.fill_rect(ob["x"], 64 - ob["h"], 8, ob["h"], 1)
oled.text("Score: {}".format(score), 2, 2)
oled.show()
# ============================
# LÓGICA DE JUEGO
# ============================
def update_game():
global player_y, player_vel, score, state
emg_jump()
player_vel += gravity
player_y += player_vel
if player_y > 48:
player_y = 48
player_vel = 0
speed = 3 + difficulty
for ob in obstacles:
ob["x"] -= speed
if ob["x"] == 10:
score += 1
if ob["x"] < -10:
ob["x"] = random.randint(128, 200)
ob["h"] = random.randint(10, 24)
if 10 < ob["x"] < 20:
if player_y + 10 > 64 - ob["h"]:
state = STATE_OVER
# ============================
# LOOP PRINCIPAL
# ============================
while True:
# ---------- MENÚ ----------
if state == STATE_MENU:
oled.fill(0)
oled.text("DINO EMG", 34, 6)
oled.text("Dificultad:", 18, 22)
if difficulty == 0:
oled.text("> Fácil", 18, 36)
elif difficulty == 1:
oled.text("> Medio", 18, 36)
else:
oled.text("> Difícil", 18, 36)
oled.text("START para jugar", 6, 54)
oled.show()
if pressed(btn_up):
difficulty = min(2, difficulty + 1)
if pressed(btn_down):
difficulty = max(0, difficulty - 1)
if pressed(btn_start):
start_game()
# ---------- JUEGO ----------
elif state == STATE_GAME:
update_game()
draw_game()
time.sleep_ms(25)
# --------- GAME OVER -------
elif state == STATE_OVER:
oled.fill(0)
oled.text("GAME OVER", 28, 16)
oled.text("Score: {}".format(score), 20, 32)
oled.text("START = Reiniciar", 4, 50)
oled.show()
if pressed(btn_start):
start_game()'''
'''# RUNNER_EMG_JUMP_WOKWI_FINAL.py
# Sin buzzer - sensor EMG funcional en cada reinicio
# Dificultad con botones - salto por EMG - tiempo de preparación
from machine import Pin, I2C, ADC
import framebuf, time, urandom
# ===== OLED SSD1306 =====
class SSD1306:
def __init__(self, width, height, i2c, addr=0x3C):
self.width = width; self.height = height
self.i2c = i2c; self.addr = addr
self.buffer = bytearray(self.height * self.width // 8)
self.fb = framebuf.FrameBuffer(self.buffer, self.width, self.height, framebuf.MONO_VLSB)
self.init_display()
def write_cmd(self, cmd):
try:
self.i2c.writeto(self.addr, bytearray([0x80, cmd]))
except:
pass
def init_display(self):
cmds = [
0xAE,0x20,0x00,0x40,0xA1,0xA8,0x3F,0xD3,0x00,0xD5,0x80,
0xD9,0xF1,0xDA,0x12,0xDB,0x40,0x8D,0x14,0xAF
]
for cmd in cmds:
self.write_cmd(cmd)
self.fill(0); self.show()
def show(self):
try:
self.write_cmd(0x21); self.write_cmd(0); self.write_cmd(self.width-1)
self.write_cmd(0x22); self.write_cmd(0); self.write_cmd(self.height//8 - 1)
self.i2c.writeto(self.addr, b'\x40' + self.buffer)
except:
pass
def fill(self, c): self.fb.fill(c)
def text(self, s, x, y): self.fb.text(s, x, y)
def pixel(self, x, y, c): self.fb.pixel(x, y, c)
def rect(self, x, y, w, h, c): self.fb.rect(x, y, w, h, c)
def fill_rect(self, x, y, w, h, c): self.fb.fill_rect(x, y, w, h, c)
def blit(self, fbuf, x, y): self.fb.blit(fbuf, x, y)
# ===== HARDWARE =====
i2c = I2C(0, scl=Pin(22), sda=Pin(21))
oled = SSD1306(128, 64, i2c)
# Botones (PULL-UP -> LOW = presionado)
btn_start = Pin(25, Pin.IN, Pin.PULL_UP)
btn_up = Pin(26, Pin.IN, Pin.PULL_UP)
btn_down = Pin(33, Pin.IN, Pin.PULL_UP)
# EMG (ADC)
emg = ADC(Pin(34))
emg.atten(ADC.ATTN_11DB)
# ===== SPRITE =====
PLAYER = bytearray([
0b00011000,
0b00111100,
0b01111110,
0b11111111,
0b01111110,
0b00111100,
0b00100100,
0b01000010
])
fb_player = framebuf.FrameBuffer(PLAYER, 8, 8, framebuf.MONO_HLSB)
# ===== VARIABLES =====
difficulty = 0
state = 0 # 0=MENU, 1=GAME, 2=OVER
GROUND_Y = 56
PLAYER_W = 8; PLAYER_H = 8
WORLD_W = 128
player_x = 5
player_y = GROUND_Y - PLAYER_H
vx = 2.0
vy = 0
GRAVITY = 0.5
JUMP_V = -6.5
on_ground = True
obstacles = []
score = 0
laps = 0
avoided = set()
# === EMG variables ===
emg_ema = None
emg_baseline = 0
emg_noise = 0
EMA_ALPHA = 0.2
TH_K = 5
REFRACT_MS = 350
_last_jump = 0
# ===== FUNCIONES =====
def pressed(btn):
if not btn.value(): # LOW = presionado
time.sleep_ms(120)
while not btn.value():
pass
return True
return False
def read_emg():
return emg.read()
def calibrate_emg(t=1500):
global emg_baseline, emg_noise, emg_ema
oled.fill(0); oled.text("Calibrando EMG", 10, 24); oled.show()
vals = []
emg_ema = None
t0 = time.ticks_ms()
while time.ticks_diff(time.ticks_ms(), t0) < t:
v = read_emg()
if emg_ema is None:
emg_ema = v
else:
emg_ema = int((1-EMA_ALPHA)*emg_ema + EMA_ALPHA*v)
vals.append(emg_ema)
time.sleep_ms(5)
vals.sort()
median = vals[len(vals)//2]
mad = sorted([abs(x - median) for x in vals])[len(vals)//2]
emg_baseline = median
emg_noise = max(20, mad)
oled.fill(0); oled.text("Listo!", 40, 28); oled.show()
time.sleep_ms(400)
def emg_jump():
global emg_ema, _last_jump
v = read_emg()
if emg_ema is None:
emg_ema = v
else:
emg_ema = int((1-EMA_ALPHA)*emg_ema + EMA_ALPHA*v)
th = int(emg_baseline + TH_K * emg_noise)
now = time.ticks_ms()
if emg_ema > th and time.ticks_diff(now, _last_jump) > REFRACT_MS:
_last_jump = now
return True
return False
def generate_obstacles():
global obstacles, avoided
avoided = set()
obstacles = []
if difficulty == 0:
count, (hmin,hmax), gap = 3, (8,10), 24
elif difficulty == 1:
count, (hmin,hmax), gap = 5, (8,14), 20
else:
count, (hmin,hmax), gap = 7, (10,18), 16
xs = []
while len(xs) < count:
x = urandom.randint(24, WORLD_W-12)
if all(abs(x-x2)>=gap for x2 in xs):
xs.append(x)
xs.sort()
for i,x in enumerate(xs):
obstacles.append({"id":i, "x":x, "w":8, "h":urandom.randint(hmin,hmax)})
def reset_player():
global player_x, player_y, vy, on_ground, vx
player_x = 5; player_y = GROUND_Y - PLAYER_H
on_ground = True; vy = 0
vx = 1.8 if difficulty==0 else (2.4 if difficulty==1 else 3.2)
def physics():
global player_x, player_y, vy, on_ground
player_x += vx
if not on_ground:
vy += GRAVITY
player_y += vy
if player_y >= GROUND_Y - PLAYER_H:
player_y = GROUND_Y - PLAYER_H
vy = 0; on_ground = True
def jump():
global vy, on_ground
if on_ground:
vy = JUMP_V
on_ground = False
def check_collision():
px, py = int(player_x), int(player_y)
for o in obstacles:
ox, oy = o["x"], GROUND_Y - o["h"]
if (px < ox+o["w"] and px+PLAYER_W > ox and
py < oy+o["h"] and py+PLAYER_H > oy):
return True
return False
def score_update():
global score, laps, avoided
for o in obstacles:
if o["id"] not in avoided and player_x > o["x"]+o["w"]:
avoided.add(o["id"])
score += 1
if player_x > WORLD_W - PLAYER_W:
laps += 1; score += 5
generate_obstacles(); reset_player()
# ===== INICIO =====
calibrate_emg()
state = 0 # MENU
# ===== LOOP PRINCIPAL =====
while True:
oled.fill(0)
# ===== MENU =====
if state == 0:
oled.text("RUNNER EMG", 20, 0)
oled.text("> Principiante" + (" <" if difficulty==0 else ""), 6, 18)
oled.text("> Medio" + (" <" if difficulty==1 else ""), 6, 28)
oled.text("> Dificil" + (" <" if difficulty==2 else ""), 6, 38)
oled.text("UP/DOWN elegir", 4, 52)
if pressed(btn_up):
difficulty = (difficulty+1) % 3
if pressed(btn_down):
difficulty = (difficulty-1) % 3
if pressed(btn_start):
# Reinicio EMG
calibrate_emg()
generate_obstacles()
reset_player()
score = 0; laps = 0
# Tiempo preparación
prep = 3000 if difficulty==0 else (2000 if difficulty==1 else 1000)
t_end = time.ticks_add(time.ticks_ms(), prep)
while time.ticks_diff(t_end, time.ticks_ms()) > 0:
oled.fill(0)
oled.text("Prepárate...", 20, 20)
r = time.ticks_diff(t_end, time.ticks_ms())//1000 + 1
oled.text("Empieza en:", 20, 40)
oled.text(str(r), 60, 56)
oled.show()
time.sleep_ms(50)
state = 1 # GAME
# ===== GAME =====
elif state == 1:
if emg_jump():
jump()
physics()
oled.fill_rect(0,GROUND_Y+1,128,2,1)
for o in obstacles:
oled.fill_rect(o["x"],GROUND_Y-o["h"],o["w"],o["h"],1)
oled.blit(fb_player, int(player_x), int(player_y))
oled.text("S:{}".format(score),0,0)
oled.text("L:{}".format(laps),64,0)
if check_collision():
state = 2
score_update()
# ===== GAME OVER =====
elif state == 2:
oled.text("GAME OVER", 28, 16)
oled.text("Score: {}".format(score), 20, 32)
oled.text("START=Reintentar", 4, 52)
if pressed(btn_start):
calibrate_emg()
generate_obstacles()
reset_player()
score = 0; laps = 0
state = 1
oled.show()
time.sleep_ms(20) #ESTE HA SIDO EL MEJOR'''
# RUNNER_EMG_JUMP_WOKWI_FINAL.py
# Sin buzzer - sensor EMG funcional en cada reinicio
# Dificultad con botones - salto por EMG - tiempo de preparación
from machine import Pin, I2C, ADC
import framebuf, time, urandom
# ===== OLED SSD1306 =====
class SSD1306:
def __init__(self, width, height, i2c, addr=0x3C):
self.width = width; self.height = height
self.i2c = i2c; self.addr = addr
self.buffer = bytearray(self.height * self.width // 8)
self.fb = framebuf.FrameBuffer(self.buffer, self.width, self.height, framebuf.MONO_VLSB)
self.init_display()
def write_cmd(self, cmd):
try:
self.i2c.writeto(self.addr, bytearray([0x80, cmd]))
except:
pass
def init_display(self):
cmds = [
0xAE,0x20,0x00,0x40,0xA1,0xA8,0x3F,0xD3,0x00,0xD5,0x80,
0xD9,0xF1,0xDA,0x12,0xDB,0x40,0x8D,0x14,0xAF
]
for cmd in cmds:
self.write_cmd(cmd)
self.fill(0); self.show()
def show(self):
try:
self.write_cmd(0x21); self.write_cmd(0); self.write_cmd(self.width-1)
self.write_cmd(0x22); self.write_cmd(0); self.write_cmd(self.height//8 - 1)
self.i2c.writeto(self.addr, b'\x40' + self.buffer)
except:
pass
def fill(self, c): self.fb.fill(c)
def text(self, s, x, y): self.fb.text(s, x, y)
def pixel(self, x, y, c): self.fb.pixel(x, y, c)
def rect(self, x, y, w, h, c): self.fb.rect(x, y, w, h, c)
def fill_rect(self, x, y, w, h, c): self.fb.fill_rect(x, y, w, h, c)
def blit(self, fbuf, x, y): self.fb.blit(fbuf, x, y)
# ===== HARDWARE =====
i2c = I2C(0, scl=Pin(22), sda=Pin(21))
oled = SSD1306(128, 64, i2c)
# Botones (PULL-UP -> LOW = presionado)
btn_start = Pin(25, Pin.IN, Pin.PULL_UP)
btn_up = Pin(26, Pin.IN, Pin.PULL_UP)
btn_down = Pin(33, Pin.IN, Pin.PULL_UP)
# EMG (ADC)
emg = ADC(Pin(34))
emg.atten(ADC.ATTN_11DB)
# ===== SPRITE =====
PLAYER = bytearray([
0b00011000,
0b00111100,
0b01111110,
0b11111111,
0b01111110,
0b00111100,
0b00100100,
0b01000010
])
fb_player = framebuf.FrameBuffer(PLAYER, 8, 8, framebuf.MONO_HLSB)
# ===== VARIABLES =====
difficulty = 0
state = 0 # 0=MENU, 1=GAME, 2=OVER
GROUND_Y = 56
PLAYER_W = 8; PLAYER_H = 8
WORLD_W = 128
player_x = 5
player_y = GROUND_Y - PLAYER_H
vx = 2.0
vy = 0
GRAVITY = 0.5
JUMP_V = -6.5
on_ground = True
obstacles = []
score = 0
laps = 0
avoided = set()
# === EMG variables ===
emg_ema = None
emg_baseline = 0
emg_noise = 0
EMA_ALPHA = 0.2
TH_K = 5
REFRACT_MS = 350
_last_jump = 0
# ===== FUNCIONES =====
def pressed(btn):
if not btn.value(): # LOW = presionado
time.sleep_ms(120)
while not btn.value():
pass
return True
return False
def read_emg():
return emg.read()
def calibrate_emg(t=1500):
global emg_baseline, emg_noise, emg_ema
oled.fill(0); oled.text("Calibrando EMG", 10, 24); oled.show()
vals = []
emg_ema = None
t0 = time.ticks_ms()
while time.ticks_diff(time.ticks_ms(), t0) < t:
v = read_emg()
if emg_ema is None:
emg_ema = v
else:
emg_ema = int((1-EMA_ALPHA)*emg_ema + EMA_ALPHA*v)
vals.append(emg_ema)
time.sleep_ms(5)
vals.sort()
median = vals[len(vals)//2]
mad = sorted([abs(x - median) for x in vals])[len(vals)//2]
emg_baseline = median
emg_noise = max(20, mad)
oled.fill(0); oled.text("Listo!", 40, 28); oled.show()
time.sleep_ms(400)
def emg_jump():
global emg_ema, _last_jump
v = read_emg()
if emg_ema is None:
emg_ema = v
else:
emg_ema = int((1-EMA_ALPHA)*emg_ema + EMA_ALPHA*v)
th = int(emg_baseline + TH_K * emg_noise)
now = time.ticks_ms()
if emg_ema > th and time.ticks_diff(now, _last_jump) > REFRACT_MS:
_last_jump = now
return True
return False
# -------------------------
# Obstáculos y juego
# -------------------------
def generate_obstacles():
global obstacles, avoided_this_lap
obstacles = []; avoided_this_lap = set()
if difficulty == 0:
count, (hmin,hmax), min_gap = 3, (8,10), 24
elif difficulty == 1:
count, (hmin,hmax), min_gap = 5, (8,14), 20
else:
count, (hmin,hmax), min_gap = 7, (10,18), 16
xs = []; attempts = 0
while len(xs) < count and attempts < 200:
attempts += 1
x = urandom.randint(24, WORLD_W - 12)
if all(abs(x - xi) >= min_gap for xi in xs): xs.append(x)
xs.sort()
for i, x in enumerate(xs):
h = urandom.randint(hmin, hmax)
obstacles.append({'id': i, 'x': x, 'w': 8, 'h': h})
# ======== FUNCIÓN MODIFICADA: VELOCIDAD REAL POR DIFICULTAD ========
def reset_player():
global player_x, player_y, vy, on_ground, vx
player_x = 5
player_y = GROUND_Y - PLAYER_H
on_ground = True
vy = 0
if difficulty == 0:
vx = 1.8
elif difficulty == 1:
vx = 2.8
else:
vx = 3.8
def physics():
global player_x, player_y, vy, on_ground
player_x += vx
if not on_ground:
vy += GRAVITY
player_y += vy
if player_y >= GROUND_Y - PLAYER_H:
player_y = GROUND_Y - PLAYER_H
vy = 0; on_ground = True
def jump():
global vy, on_ground
if on_ground:
vy = JUMP_V
on_ground = False
def check_collision():
px, py = int(player_x), int(player_y)
for o in obstacles:
ox, oy = o["x"], GROUND_Y - o["h"]
if (px < ox+o["w"] and px+PLAYER_W > ox and
py < oy+o["h"] and py+PLAYER_H > oy):
return True
return False
def score_update():
global score, laps, avoided
for o in obstacles:
if o["id"] not in avoided and player_x > o["x"]+o["w"]:
avoided.add(o["id"])
score += 1
if player_x > WORLD_W - PLAYER_W:
laps += 1; score += 5
generate_obstacles(); reset_player()
# ===== INICIO =====
calibrate_emg()
state = 0 # MENU
# ===== LOOP PRINCIPAL =====
while True:
oled.fill(0)
# ===== MENU =====
if state == 0:
oled.text("RUNNER EMG", 20, 0)
oled.text("> Principiante" + (" <" if difficulty==0 else ""), 6, 18)
oled.text("> Medio" + (" <" if difficulty==1 else ""), 6, 28)
oled.text("> Dificil" + (" <" if difficulty==2 else ""), 6, 38)
oled.text("UP/DOWN elegir", 4, 52)
if pressed(btn_up):
difficulty = (difficulty+1) % 3
if pressed(btn_down):
difficulty = (difficulty-1) % 3
if pressed(btn_start):
calibrate_emg()
generate_obstacles()
reset_player()
score = 0; laps = 0
prep = 3000 if difficulty==0 else (2000 if difficulty==1 else 1000)
t_end = time.ticks_add(time.ticks_ms(), prep)
while time.ticks_diff(t_end, time.ticks_ms()) > 0:
oled.fill(0)
oled.text("Preparate...", 20, 20)
r = time.ticks_diff(t_end, time.ticks_ms())//1000 + 1
oled.text("Empieza en:", 20, 40)
oled.text(str(r), 60, 56)
oled.show()
time.sleep_ms(50)
state = 1 # GAME
# ===== GAME =====
elif state == 1:
if emg_jump():
jump()
physics()
oled.fill_rect(0,GROUND_Y+1,128,2,1)
for o in obstacles:
oled.fill_rect(o["x"],GROUND_Y-o["h"],o["w"],o["h"],1)
oled.blit(fb_player, int(player_x), int(player_y))
oled.text("S:{}".format(score),0,0)
oled.text("L:{}".format(laps),64,0)
if check_collision():
state = 2
score_update()
# ===== GAME OVER =====
elif state == 2:
oled.text("GAME OVER", 28, 16)
oled.text("Score: {}".format(score), 20, 32)
oled.text("START=Reintentar", 4, 52)
if pressed(btn_start):
calibrate_emg()
generate_obstacles()
reset_player()
score = 0; laps = 0
state = 1
oled.show()
time.sleep_ms(20)