En aquest joc, unes fletxes van baixant per la pantalla i cal capturar-les, prement el botó que correspon a cada una, quan estan damunt del rectangle fosc. El joc fa servir cinc botons:
| Polsador | Funció |
| START | Inici de la partida |
| ← | Captura les fletxes que apunten a l'esquerra |
| ↑ | Captura les fletxes que apunten amunt |
| → | Captura les fletxes que apunten a la dreta |
| ↓ | Captura les fletxes que apunten avall |

Es fan servir dos fitxers de gràfics: tile.bmp i tile2.bmp.
El programa és el següent:
import ugame import stage import random import time import neopixel import board
# --- VARIABLES DE ESTADO Y PUNTUACIÓN GLOBALES --- # Banderas para dibujar las pantallas de UI solo una vez (optimización y limpieza) pantalla_inici_dibujada = False pantalla_gameover_dibujada = False # Récord de la sesión record_sesion = 0 # --- CONSTANTES DE ESTADO DE JUEGO --- ESTADO_INICIO = 0 ESTADO_JUEGO = 1 ESTADO_GAMEOVER = 2 # Nuevo estado para forzar la limpieza de pantalla # Vidas vidas = 3 vidas_max = 3 # Variables de estado inicial estado_juego = ESTADO_INICIO
# --- CONFIGURACIÓN DE PANTALLA ---
# Cargar el banco de imágenes
banc = stage.Bank.from_bmp16("tile.bmp")
banc_vidas = stage.Bank.from_bmp16("tile2.bmp")
# Crear el fondo
fons = stage.Grid(banc, 10, 8)
# Crear los cuatro jugadores (flechas)
abajo = stage.Sprite(banc, 7, 32, 10)
arriba = stage.Sprite(banc, 6, 64, 10)
derecha = stage.Sprite(banc, 8, 96, 10)
izquierda = stage.Sprite(banc, 9, 128, 10)
# Vidas
marcador_vidas = stage.Grid(banc_vidas, vidas_max, 1) # Grid de 1 fila x 3 columnas
marcador_vidas.move(110, 3) # posición arriba a la izquierda
# El ancho del bloque sigue siendo 6 para evitar la acumulación de números.
texto = stage.Text(width=6, height=1)
# Texto para la pantalla de inicio (ocupa más espacio)
texto_inicio = stage.Text(width=20, height=10)
# Texto para la pantalla de Game Over
texto_gameover = stage.Text(width=20, height=9)
# Crear el juego
joc = stage.Stage(ugame.display, 12)
# --- FUNCIÓN DE INICIALIZACIÓN/REINICIO DEL JUEGO ---
def inicializar_juego():
global punts, esperando_abajo, esperando_arriba, esperando_derecha, esperando_izquierda
global temps_actual, temps_reaparicio_abajo, temps_reaparicio_arriba
global temps_reaparicio_derecha, temps_reaparicio_izquierda
global velocitat_abajo, velocitat_arriba, velocitat_derecha, velocitat_izquierda
global tiempo_ultimo_incremento, flechas_perdidas
global vidas
# 1. Resetear puntuación y contadores
punts = 0
flechas_perdidas = 0
tiempo_ultimo_incremento = time.monotonic()
# 2. Resetear estados y posiciones de flechas
esperando_abajo = True
esperando_arriba = True
esperando_derecha = True
esperando_izquierda = True
abajo.move(-32, -32)
arriba.move(-32, -32)
derecha.move(-32, -32)
izquierda.move(-32, -32)
# 3. Resetear velocidades a la base
velocitat_abajo = 4
velocitat_arriba = 4
velocitat_derecha = 4
velocitat_izquierda = 4
# 4. Resetear tiempos de reaparición (escalonados)
temps_actual = time.monotonic()
temps_reaparicio_abajo = temps_actual + random.randint(1, 3)
temps_reaparicio_arriba = temps_actual + random.randint(4, 6)
temps_reaparicio_derecha = temps_actual + random.randint(7, 9)
temps_reaparicio_izquierda = temps_actual + random.randint(10, 12)
#5. Resetear marcador de vidas
vidas = 3 # o el número de vidas que quieras al iniciar
actualizar_marcador() # actualiza los corazones al inicio
# --- CONFIGURACIÓN DEL MARCADOR --- # MARCADOR DE PUNTOS punts = 0 pos_x = 5 pos_y = 5
texto.move(pos_x, pos_y)
texto.text("{:<6}".format(punts))
# MARCADOR VIDAS
def actualizar_marcador():
for i in range(vidas_max):
if i < vidas:
marcador_vidas.tile(i, 0, 0) # corazón lleno
else:
marcador_vidas.tile(i, 0, 1) # corazón vacío
# --- CONFIGURACIÓN NEOPIXEL --- pixel_pin = board.D8 num_pixels = 1 pixels = neopixel.NeoPixel(pixel_pin, num_pixels, brightness=0.3, auto_write=False)
def encender_neopixel(color=(0, 255, 0), duracion=0.2):
pixels[0] = color
pixels.show()
pixels[0] = (0, 0, 0)
pixels.show()
# --- FUNCIONES AUXILIARES ---
def dib_taulell():
# Dibuja el fondo del tablero de juego (césped, carretera, etc.)
tau = [
[11, 11, 11, 11, 11, 11, 11, 11, 11, 11],
[11, 11, 11, 11, 11, 11, 11, 11, 11, 11],
[12, 12, 12, 12, 12, 12, 12, 12, 12, 12],
[12, 12, 12, 12, 12, 12, 12, 12, 12, 12],
[13, 13, 13, 13, 13, 13, 13, 13, 13, 13],
[13, 0, 4, 4, 4, 4, 4, 4, 1, 13],
[14, 3, 5, 5, 5, 5, 5, 5, 2, 14],
[14, 14, 14, 14, 14, 14, 14, 14, 14, 14]
]
for i in range(8):
for j in range(10):
fons.tile(j, i, tile=tau[i][j])
def dib_fons_inici():
# Dibuja un fondo simple y uniforme para la pantalla de inicio
# Usamos el tile 11 (parece cielo o un color liso)
for i in range(8):
for j in range(10):
fons.tile(j, i, tile=11)
def obtener_x_libre(flechas_activas):
# Lógica para obtener una posición X libre
posibles_x = [16, 32, 48, 64, 80, 96, 112, 128] # 8 columnas
x_ocupadas = [flecha.x for flecha in flechas_activas if flecha.x > 0] # Aseguramos que solo contamos flechas visibles
libres = [x for x in posibles_x if x not in x_ocupadas]
if not libres:
return random.choice(posibles_x)
return random.choice(libres)
# Hacemos el render_block inicial para dibujar el fondo
joc.render_block()
# --- BUCLE PRINCIPAL DEL JUEGO ---
while True:
if estado_juego == ESTADO_INICIO:
keys = ugame.buttons.get_pressed()
if not pantalla_inici_dibujada:
# Bloque de limpieza de texto completo para el objeto 20x10
# Se usa 10 líneas de 20 espacios (200 caracteres)
bloque_limpieza = (" " * 20 + "\n") * 9 + (" " * 20)
dib_fons_inici()
# --- LIMPIEZA DEFENSA ACTIVA ---
# Aseguramos que el texto del marcador esté fuera.
texto.text("")
texto.move(-32, -32)
# Limpieza agresiva del texto de Game Over para evitar artefactos
texto_gameover.text(bloque_limpieza)
texto_gameover.move(-32, -32)
# -------------------------------
texto_inicio.move(3, 3)
texto_inicio.text(
" \n\n"
" EL JUEGO DE \n"
" LAS FLECHAS\n\n\n"
" ¡Pulsa el boton\n"
" START para\n"
" empezar!"
)
joc.layers = [ texto_inicio, fons]
joc.render_block()
pantalla_inici_dibujada = True
joc.tick()
if keys & ugame.K_START:
pantalla_inici_dibujada = False # Resetear bandera de inicio
pantalla_gameover_dibujada = False # Resetear bandera de game over
# Es crucial mover el texto de inicio fuera de la pantalla antes de empezar
texto_inicio.text("")
texto_inicio.move(-32, -32)
inicializar_juego()
dib_taulell()
# --- Restaurar el marcador de puntos para la nueva partida ---
pos_x = 1
pos_y = 5
texto.move(pos_x, pos_y)
texto.text("{:<6}".format(punts))
# ------------------------------------------------------------
estado_juego = ESTADO_JUEGO
# Se establece el orden de las capas: Texto (puntuación) sobre las flechas, flechas sobre el fondo.
joc.layers = [texto,marcador_vidas, abajo, arriba, derecha, izquierda, fons]
joc.render_block()
# --- PANTALLA DE GAME OVER (ESTADO_GAMEOVER) ---
elif estado_juego == ESTADO_GAMEOVER:
keys = ugame.buttons.get_pressed()
# Aseguramos que todas las flechas (sprites) estén fuera
# de la vista, limpiando cualquier artefacto gráfico que pudieran causar.
abajo.move(-32, -32)
arriba.move(-32, -32)
derecha.move(-32, -32)
izquierda.move(-32, -32)
joc.render_sprites([]) # Borra cualquier sprite que haya quedado en el buffer
# Solo dibujamos la pantalla una vez
if not pantalla_gameover_dibujada:
# Bloque de limpieza de texto completo para el objeto 20x10
bloque_limpieza = (" " * 20 + "\n") * 8 + (" " * 20)
dib_fons_inici() # Dibuja fondo (configura los tiles)
# Asegurarse de que el récord esté actualizado antes de mostrarlo
if punts > record_sesion:
record_sesion = punts
# Limpiamos y movemos el marcador de puntos fuera de la pantalla
texto.text("")
texto.move(-32, -32)
# CRUCIAL: Limpiamos explícitamente el bloque de texto de Game Over
# Usamos el bloque_limpieza para sobrescribir todo el área antes de escribir el nuevo texto.
texto_gameover.text(bloque_limpieza)
texto_gameover.move(3, 20)
texto_gameover.text(
" GAME OVER\n\n"
" Puntos: " + str(punts) + "\n\n"
" Record: " + str(record_sesion) + "\n\n"
" Pulsa START para\n"
" reiniciar\n"
)
# Establecemos las capas y renderizamos UNA SOLA VEZ
joc.layers = [ texto_gameover, fons]
joc.render_block()
pantalla_gameover_dibujada = True # Solo se dibuja una vez
joc.tick()
if keys & ugame.K_START:
# Bloque de limpieza de texto completo para el objeto 20x10
bloque_limpieza = (" " * 20 + "\n") * 9 + (" " * 20)
# Limpiamos el texto_gameover y lo movemos fuera de la pantalla
# Usamos la limpieza agresiva al salir para que esté listo para la siguiente partida
texto_gameover.text(bloque_limpieza)
texto_gameover.move(-32, -32)
# Limpieza adicional: Forzamos un renderizado del fondo solo antes de ir a INICIO
joc.layers = [fons]
joc.render_block()
pantalla_gameover_dibujada = False # reinicia la bandera
pantalla_inici_dibujada = False
estado_juego = ESTADO_INICIO
# --- BUCLE PRINCIPAL DEL JUEGO (ESTADO_JUEGO) ---
elif estado_juego == ESTADO_JUEGO:
actualizar_marcador()
puntuacion_anterior = punts
temps_actual = time.monotonic() # Tiempo actual en segundos
# --- Lógica de Aceleración y Movimiento de Flechas ---
interval_incremento = 10
incremento_velocitat = 1
# Aumentar velocidades con el tiempo
if temps_actual - tiempo_ultimo_incremento > interval_incremento:
velocitat_abajo += incremento_velocitat
velocitat_arriba += incremento_velocitat
velocitat_derecha += incremento_velocitat
velocitat_izquierda += incremento_velocitat
tiempo_ultimo_incremento = temps_actual
# --- Obtener lista de flechas activas para la función de aparición ---
# Si la flecha no está "esperando", está activa en pantalla.
flechas_activas = []
if not esperando_abajo: flechas_activas.append(abajo)
if not esperando_arriba: flechas_activas.append(arriba)
if not esperando_derecha: flechas_activas.append(derecha)
if not esperando_izquierda: flechas_activas.append(izquierda)
# -- jugador "abajo" --
if not esperando_abajo:
abajo.move(abajo.x, abajo.y + velocitat_abajo)
if abajo.y > 138:
esperando_abajo = True
flechas_perdidas += 1
vidas -= 1
actualizar_marcador()
joc.render_block()
encender_neopixel((255, 0, 0)) # Rojo por flecha perdida
abajo.move(-32, -32)
temps_reaparicio_abajo = temps_actual + random.randint(1, 3)
if flechas_perdidas >= 3:
if punts > record_sesion: record_sesion = punts # Actualiza récord
estado_juego = ESTADO_GAMEOVER # Transición a Game Over
else:
if temps_actual >= temps_reaparicio_abajo:
esperando_abajo = False
# Aquí la lógica de obtener_x_libre es compleja, usamos solo las flechas activas para determinar la X libre.
x_reaparecer = obtener_x_libre(flechas_activas)
abajo.move(x_reaparecer, -16)
actualizar_marcador()
# -- jugador "arriba" --
if not esperando_arriba:
arriba.move(arriba.x, arriba.y + velocitat_arriba)
if arriba.y > 138:
esperando_arriba = True
flechas_perdidas += 1
vidas -= 1
actualizar_marcador()
joc.render_block()
encender_neopixel((255, 0, 0))
arriba.move(-32, -32)
temps_reaparicio_arriba = temps_actual + random.randint(1, 3)
if flechas_perdidas >= 3:
if punts > record_sesion: record_sesion = punts
estado_juego = ESTADO_GAMEOVER # Transición a Game Over
else:
if temps_actual >= temps_reaparicio_arriba:
esperando_arriba = False
x_reaparecer = obtener_x_libre(flechas_activas)
arriba.move(x_reaparecer, -16)
actualizar_marcador()
# -- jugador "derecha" --
if not esperando_derecha:
derecha.move(derecha.x, derecha.y + velocitat_derecha)
if derecha.y > 138:
esperando_derecha = True
flechas_perdidas += 1
vidas -= 1
actualizar_marcador()
joc.render_block()
encender_neopixel((255, 0, 0))
derecha.move(-32, -32)
temps_reaparicio_derecha = temps_actual + random.randint(1, 3)
if flechas_perdidas >= 3:
if punts > record_sesion: record_sesion = punts
estado_juego = ESTADO_GAMEOVER # Transición a Game Over
else:
if temps_actual >= temps_reaparicio_derecha:
esperando_derecha = False
x_reaparecer = obtener_x_libre(flechas_activas)
derecha.move(x_reaparecer, -16)
actualizar_marcador()
# -- jugador "izquierda" --
if not esperando_izquierda:
izquierda.move(izquierda.x, izquierda.y + velocitat_izquierda)
if izquierda.y > 138:
esperando_izquierda = True
flechas_perdidas += 1
vidas -= 1
actualizar_marcador()
joc.render_block()
encender_neopixel((255, 0, 0))
izquierda.move(-32, -32)
temps_reaparicio_izquierda = temps_actual + random.randint(1, 3)
if flechas_perdidas >= 3:
if punts > record_sesion: record_sesion = punts
estado_juego = ESTADO_GAMEOVER # Transición a Game Over
else:
if temps_actual >= temps_reaparicio_izquierda:
esperando_izquierda = False
x_reaparecer = obtener_x_libre(flechas_activas)
izquierda.move(x_reaparecer, -16)
actualizar_marcador()
# --- Zona d'impacte ---
zona_y_min = 80
zona_y_max = 112
fletxes_en_zona = {
"UP": arriba.y >= zona_y_min and arriba.y <= zona_y_max,
"DOWN": abajo.y >= zona_y_min and abajo.y <= zona_y_max,
"LEFT": izquierda.y >= zona_y_min and izquierda.y <= zona_y_max,
"RIGHT": derecha.y >= zona_y_min and derecha.y <= zona_y_max
}
# --- Lectura dels polsadors del PyBadge ---
keys = ugame.buttons.get_pressed()
# --- Detecció d'encerts ---
if keys & ugame.K_UP and fletxes_en_zona["UP"]:
# Lógica de acierto UP...
punts += 1
arriba.move(-32, -32)
esperando_arriba = True
temps_reaparicio_arriba = temps_actual + random.randint(1, 3)
encender_neopixel(color=(0, 255, 0))
if keys & ugame.K_DOWN and fletxes_en_zona["DOWN"]:
# Lógica de acierto DOWN...
punts += 1
abajo.move(-32, -32)
esperando_abajo = True
temps_reaparicio_abajo = temps_actual + random.randint(1, 3)
encender_neopixel(color=(0, 255, 0))
if keys & ugame.K_LEFT and fletxes_en_zona["LEFT"]:
# Lógica de acierto LEFT...
punts += 1
izquierda.move(-32, -32)
esperando_izquierda = True
temps_reaparicio_izquierda = temps_actual + random.randint(1, 3)
encender_neopixel(color=(0, 255, 0))
if keys & ugame.K_RIGHT and fletxes_en_zona["RIGHT"]:
# Lógica de acierto RIGHT...
punts += 1
derecha.move(-32, -32)
esperando_derecha = True
temps_reaparicio_derecha = temps_actual + random.randint(1, 3)
encender_neopixel(color=(0, 255, 0))
# ?? Actualizamos el marcador SOLO si el valor ha cambiado.
if punts != puntuacion_anterior:
# Usamos relleno de 6 espacios con justificación izquierda (<)
texto.text("{:<6}".format(punts))
# 2. Forzamos el redibujado de las capas estáticas (como el texto)
joc.render_block()
# Dibujar sprites activos
sprites_visibles = []
if not esperando_abajo:
sprites_visibles.append(abajo)
if not esperando_arriba:
sprites_visibles.append(arriba)
if not esperando_derecha:
sprites_visibles.append(derecha)
if not esperando_izquierda:
sprites_visibles.append(izquierda)
joc.render_sprites(sprites_visibles)
joc.tick()

Esta obra de Oriol Boix está licenciada bajo una licencia no importada Creative Commons Reconocimiento-NoComercial-SinObraDerivada 3.0.