# =============================================================================
# Práctica 9: Juego de 21 (Black Jack) con interfaz remota
# Raspberry Pi Pico W - Modo Estación
# Sistemas Embebidos 2026-B
# =============================================================================
import network
import socket
import machine
import time
import random
# ======================= CONFIGURACIÓN DE RED (ESTACIÓN) =====================
# Credenciales de la red Wi-Fi a la que se conectará la Pico W
SSID = "Wokwi-GUEST" # Cambiar por el nombre de tu red
PASSWORD = "" # Cambiar por la contraseña de tu red
# ======================= CONFIGURACIÓN DE PINES ==========================
# --- Pantalla LCD 16x2 por I2C ---
# Se usa el bus I2C 0 con SDA en GP0 y SCL en GP1
i2c = machine.I2C(0, sda=machine.Pin(0), scl=machine.Pin(1), freq=100000)
# Escanear dispositivos I2C para encontrar la dirección del LCD
time.sleep_ms(500) # Esperar a que el bus I2C se estabilice
devices = i2c.scan()
print("Dispositivos I2C encontrados:", [hex(d) for d in devices])
if not devices:
print("ERROR: No se encontro ningun dispositivo I2C.")
print("Revisa las conexiones SDA(GP0) y SCL(GP1) al LCD.")
# --- Botones (con pull-down interno) ---
# Jugador 1: pedir carta (GP2), detenerse (GP3)
# Jugador 2: pedir carta (GP4), detenerse (GP5)
btn_j1_carta = machine.Pin(2, machine.Pin.IN, machine.Pin.PULL_DOWN)
btn_j1_stop = machine.Pin(3, machine.Pin.IN, machine.Pin.PULL_DOWN)
btn_j2_carta = machine.Pin(4, machine.Pin.IN, machine.Pin.PULL_DOWN)
btn_j2_stop = machine.Pin(5, machine.Pin.IN, machine.Pin.PULL_DOWN)
# ======================= DRIVER LCD I2C (PCF8574) =========================
# Usar la dirección detectada o 0x27 por defecto
LCD_ADDR = devices[0] if devices else 0x27
print("Usando LCD en direccion:", hex(LCD_ADDR))
LCD_COLS = 16
LCD_ROWS = 2
# Bits de control del expansor PCF8574 hacia el LCD
ENABLE = 0x04
BACKLIGHT = 0x08
def lcd_write_byte(data):
"""Envía un byte al expansor PCF8574 por I2C."""
i2c.writeto(LCD_ADDR, bytes([data]))
def lcd_toggle_enable(data):
"""Genera un pulso en el pin Enable del LCD para latchar datos."""
time.sleep_us(500)
lcd_write_byte(data | ENABLE)
time.sleep_us(500)
lcd_write_byte(data & ~ENABLE)
time.sleep_us(500)
def lcd_write_nibble(nibble):
"""
Envía un solo nibble (4 bits altos) al LCD.
Se usa SOLO durante la secuencia de inicialización, cuando
el LCD aún está en modo 8 bits y espera medio byte.
"""
data = nibble | BACKLIGHT
lcd_write_byte(data)
lcd_toggle_enable(data)
def lcd_send(data, mode=0):
"""
Envía un byte completo al LCD en modo 4 bits.
Divide el byte en dos nibbles (alto y bajo) y los envía por separado.
mode=0 -> comando, mode=1 -> dato (carácter).
"""
high = (data & 0xF0) | mode | BACKLIGHT
low = ((data << 4) & 0xF0) | mode | BACKLIGHT
lcd_write_byte(high)
lcd_toggle_enable(high)
lcd_write_byte(low)
lcd_toggle_enable(low)
def lcd_init():
"""
Secuencia de inicialización del LCD HD44780 en modo 4 bits.
Primero se envían nibbles sueltos (el LCD arranca en modo 8 bits),
luego se cambia a modo 4 bits y se configuran parámetros.
"""
time.sleep_ms(50)
# Paso 1: Enviar 0x3 como nibble suelto, 3 veces (reset a 8 bits)
lcd_write_nibble(0x30)
time.sleep_ms(5)
lcd_write_nibble(0x30)
time.sleep_ms(5)
lcd_write_nibble(0x30)
time.sleep_ms(5)
# Paso 2: Enviar 0x2 como nibble para cambiar a modo 4 bits
lcd_write_nibble(0x20)
time.sleep_ms(5)
# Paso 3: Ahora en modo 4 bits, configurar parámetros (bytes completos)
lcd_send(0x28) # 2 líneas, fuente 5x8
time.sleep_ms(1)
lcd_send(0x0C) # Display ON, cursor OFF
time.sleep_ms(1)
lcd_send(0x06) # Auto-incremento del cursor
time.sleep_ms(1)
lcd_send(0x01) # Limpiar pantalla
time.sleep_ms(5)
def lcd_clear():
"""Limpia la pantalla LCD."""
lcd_send(0x01)
time.sleep_ms(2)
def lcd_set_cursor(col, row):
"""Posiciona el cursor en la columna y fila indicadas."""
addr = col + (0x40 if row == 1 else 0x00)
lcd_send(0x80 | addr)
def lcd_print(text):
"""Escribe una cadena de texto en la posición actual del cursor."""
for char in text:
lcd_send(ord(char), 1)
def lcd_show(line1, line2=""):
"""Muestra dos líneas de texto en el LCD (utilidad rápida)."""
lcd_clear()
lcd_set_cursor(0, 0)
lcd_print(line1[:LCD_COLS])
lcd_set_cursor(0, 1)
lcd_print(line2[:LCD_COLS])
# ======================= VARIABLES DEL JUEGO ==============================
# Puntuación acumulada de cada jugador
score_j1 = 0
score_j2 = 0
# Última carta generada para mostrar en LCD
last_card_j1 = 0
last_card_j2 = 0
# Flags que indican si el jugador decidió detenerse
stopped_j1 = False
stopped_j2 = False
# Estado del juego: "Listo para iniciar", "Juego Activo",
# "Gana el jugador 1", "Gana el jugador 2", "La casa gana"
game_status = "Listo para iniciar"
def reset_game():
"""Reinicia todas las variables del juego a su estado inicial."""
global score_j1, score_j2, last_card_j1, last_card_j2
global stopped_j1, stopped_j2, game_status
score_j1 = 0
score_j2 = 0
last_card_j1 = 0
last_card_j2 = 0
stopped_j1 = False
stopped_j2 = False
game_status = "Listo para iniciar"
lcd_show(" BLACKJACK 21 ", "Listo p/ iniciar")
def determine_winner():
"""
Determina el ganador cuando ambos jugadores se detienen.
Reglas:
- Pierde quien rebase 21.
- Gana quien esté más cerca de 21 sin pasarse.
- La casa gana si hay empate o si ambos se pasan.
"""
global game_status
j1_bust = score_j1 > 21
j2_bust = score_j2 > 21
if j1_bust and j2_bust:
game_status = "La casa gana"
elif j1_bust:
game_status = "Gana el jugador 2"
elif j2_bust:
game_status = "Gana el jugador 1"
elif score_j1 > score_j2:
game_status = "Gana el jugador 1"
elif score_j2 > score_j1:
game_status = "Gana el jugador 2"
else:
# Empate: la casa gana
game_status = "La casa gana"
# Mostrar resultado en LCD
lcd_show(game_status, "J1:{} J2:{}".format(score_j1, score_j2))
def update_lcd_game():
"""Actualiza el LCD con la información del juego activo."""
line1 = "J1:{:<4} J2:{:<4}".format(score_j1, score_j2)
# Mostrar última carta generada o estado de detenido
parts = []
if stopped_j1:
parts.append("J1:STOP")
elif last_card_j1 > 0:
parts.append("J1:+{}".format(last_card_j1))
if stopped_j2:
parts.append("J2:STOP")
elif last_card_j2 > 0:
parts.append("J2:+{}".format(last_card_j2))
line2 = " ".join(parts) if parts else "Juego Activo"
lcd_show(line1, line2[:LCD_COLS])
# ======================= MANEJO DE BOTONES ================================
def check_buttons():
"""
Lee el estado de los 4 botones y ejecuta la acción correspondiente.
Solo actúa si el juego no ha terminado.
"""
global score_j1, score_j2, last_card_j1, last_card_j2
global stopped_j1, stopped_j2, game_status
# Si el juego ya terminó, no procesar botones
if game_status not in ("Listo para iniciar", "Juego Activo"):
return
changed = False
# --- Jugador 1: pedir carta ---
if btn_j1_carta.value() == 1 and not stopped_j1:
if game_status == "Listo para iniciar":
game_status = "Juego Activo"
card = random.randint(1, 10)
score_j1 += card
last_card_j1 = card
changed = True
time.sleep_ms(250) # Antirrebote
# --- Jugador 1: detenerse ---
if btn_j1_stop.value() == 1 and not stopped_j1:
if game_status == "Listo para iniciar":
game_status = "Juego Activo"
stopped_j1 = True
changed = True
time.sleep_ms(250)
# --- Jugador 2: pedir carta ---
if btn_j2_carta.value() == 1 and not stopped_j2:
if game_status == "Listo para iniciar":
game_status = "Juego Activo"
card = random.randint(1, 10)
score_j2 += card
last_card_j2 = card
changed = True
time.sleep_ms(250)
# --- Jugador 2: detenerse ---
if btn_j2_stop.value() == 1 and not stopped_j2:
if game_status == "Listo para iniciar":
game_status = "Juego Activo"
stopped_j2 = True
changed = True
time.sleep_ms(250)
# Si ambos jugadores se detuvieron, determinar ganador
if stopped_j1 and stopped_j2:
determine_winner()
elif changed:
update_lcd_game()
# ======================= PÁGINA WEB =======================================
def build_html():
"""
Genera el HTML de la página Web del juego.
Incluye puntuaciones, estado y enlace para reiniciar.
La página se auto-actualiza cada 2 segundos.
"""
# Color del estado según resultado
if "Gana" in game_status:
color = "#27ae60"
elif "casa" in game_status:
color = "#e74c3c"
elif "Activo" in game_status:
color = "#f39c12"
else:
color = "#3498db"
html = """<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="refresh" content="2">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>BlackJack 21 - Pico W</title>
<style>
body {{
font-family: Arial, sans-serif;
background: #1a1a2e;
color: #eee;
text-align: center;
margin: 0; padding: 20px;
}}
h1 {{
color: #e94560;
font-size: 2em;
margin-bottom: 5px;
}}
.card-container {{
display: flex;
justify-content: center;
gap: 30px;
margin: 25px 0;
}}
.player-box {{
background: #16213e;
border-radius: 15px;
padding: 25px 35px;
min-width: 160px;
box-shadow: 0 4px 15px rgba(0,0,0,0.3);
}}
.player-box h2 {{
margin: 0 0 10px 0;
color: #e94560;
}}
.score {{
font-size: 3em;
font-weight: bold;
color: #fff;
}}
.status {{
font-size: 1.4em;
margin: 20px auto;
padding: 12px 30px;
border-radius: 10px;
display: inline-block;
background: {color};
font-weight: bold;
}}
a.reset {{
display: inline-block;
margin-top: 20px;
padding: 12px 35px;
background: #e94560;
color: #fff;
text-decoration: none;
border-radius: 8px;
font-size: 1.1em;
font-weight: bold;
}}
a.reset:hover {{
background: #c0392b;
}}
</style>
</head>
<body>
<h1>🃏 BlackJack 21</h1>
<p>Raspberry Pi Pico W — Modo Estación</p>
<div class="card-container">
<div class="player-box">
<h2>Jugador 1</h2>
<div class="score">{s1}</div>
</div>
<div class="player-box">
<h2>Jugador 2</h2>
<div class="score">{s2}</div>
</div>
</div>
<div class="status">{status}</div>
<br>
<a class="reset" href="/reset">↻ Reiniciar Juego</a>
</body>
</html>"""
return html.format(s1=score_j1, s2=score_j2, status=game_status, color=color)
# ======================= CONEXIÓN WI-FI ===================================
def connect_wifi():
"""Conecta la Pico W a la red Wi-Fi en modo Estación (STA)."""
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(SSID, PASSWORD)
lcd_show("Conectando a", "WiFi...")
print("Conectando a WiFi:", SSID)
# Esperar conexión con timeout de 20 segundos
timeout = 20
while not wlan.isconnected() and timeout > 0:
time.sleep(1)
timeout -= 1
if wlan.isconnected():
ip = wlan.ifconfig()[0]
print("Conectado! IP:", ip)
lcd_show("IP:", ip)
time.sleep(2)
return ip
else:
lcd_show("Error WiFi", "Reinicie")
print("Error al conectar WiFi")
return None
# ======================= SERVIDOR WEB =====================================
def start_server(ip):
"""
Inicia el servidor HTTP en el puerto 80.
Atiende peticiones GET para la página principal y el reinicio.
"""
addr = socket.getaddrinfo(ip, 80)[0][-1]
s = socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(addr)
s.listen(1)
s.setblocking(False) # No bloqueante para poder leer botones
print("Servidor HTTP en http://{}:80".format(ip))
return s
def handle_client(server_socket):
"""
Verifica si hay una conexión entrante y la atiende.
Si la URL es /reset, reinicia el juego.
"""
try:
cl, addr = server_socket.accept()
except OSError:
# No hay conexiones pendientes (modo no bloqueante)
return
try:
request = cl.recv(1024).decode("utf-8")
# Detectar si se solicitó reiniciar el juego
if "GET /reset" in request:
reset_game()
print("Juego reiniciado desde la Web")
# Enviar la página HTML como respuesta
response = build_html()
cl.send("HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n")
cl.send(response)
except Exception as e:
print("Error manejando cliente:", e)
finally:
cl.close()
# ======================= PROGRAMA PRINCIPAL ================================
def main():
"""Función principal: inicializa hardware, conecta WiFi y ejecuta el bucle."""
# Inicializar LCD
lcd_init()
lcd_show(" BLACKJACK 21 ", " Iniciando... ")
time.sleep(1)
# Conectar a WiFi en modo estación
ip = connect_wifi()
if ip is None:
return # No se pudo conectar
# Mostrar mensaje de bienvenida / listo para jugar
reset_game()
# Iniciar servidor Web
server = start_server(ip)
# Bucle principal: revisar botones y atender peticiones Web
while True:
check_buttons() # Leer botones físicos
handle_client(server) # Atender peticiones HTTP
time.sleep_ms(50) # Pequeña pausa para estabilidad
# Ejecutar el programa
main()