En aquest joc hi ha una pantalla que té una matriu de punts i cal anar posant segments que uneixen dos punts amb l'objectiu d'anar tancant caixes quadrades. El joc fa servir set botons:
| Polsador | Funció |
| START | Inici de la partida |
| ← | Moviment a l'esquerra |
| ↑ | Moviment amunt |
| → | Moviment a la dreta |
| ↓ | Moviment avall |
| A | Confirmació Selecció del mode de dos jugadors |
| B | Rotació Selecció del mode d'un jugador |
El cursor es marca amb línia discontínua i els traços que posa cada jugador són de color blau o magenta, segons el cas. Una creu del color del jugador indica qui ha tancat aquella caixa.

Es fan servir vuit fitxers de gràfics, atès que els textos també es moostren en forma gràfica. Els fitxers són: tile0.bmp (taulell), tile1.bmp (elements del joc), dots.bmp (primera línia del títol), and.bmp (segona línia del títol), boxes.bmp (tercera línia del títol), blau_.bmp (guanya el blau), mage_.bmp (guanya el magenta) i empat_.bmp (empat).

El programa és el següent:
import board import random import analogio import ugame import stage import time import neopixel
# 1. CONFIGURACIÓ I INICIALITZACIÓ
# Aleatori
soroll = analogio.AnalogIn(board.A3)
random.seed(soroll.value + int(time.monotonic() * 1000))
# LEDs
leds = neopixel.NeoPixel(board.NEOPIXEL, 5, brightness=0.2)
# Bancs gràfics
banc_fons = stage.Bank.from_bmp16("tile0.bmp")
banc_lin = stage.Bank.from_bmp16("tile1.bmp")
banc_dots = stage.Bank.from_bmp16("dots.bmp")
banc_and = stage.Bank.from_bmp16("and.bmp")
banc_boxes = stage.Bank.from_bmp16("boxes.bmp")
banc_blau = stage.Bank.from_bmp16("blau_.bmp")
banc_mage = stage.Bank.from_bmp16("mage_.bmp")
banc_empat = stage.Bank.from_bmp16("empat_.bmp")
# Fons i Stage
fons = stage.Grid(banc_fons, 10, 8) # 160x128
joc = stage.Stage(ugame.display, 12)
# Diccionari de linies
li_disc = {1: (0, 1), 2: (2, 3)} # línies discontínues
li_solid = {1: (4, 5), 2: (6, 7)} # línies contínues
cas_tile = {1: 8, 2: 9} # creus al centre
TRANSP = 15 # tile transparent # Rotacions gir_h = 0 gir_v_cw = 1 # 90° horari gir_v_ccw = 3 # 90° antihorari
def gir(mode, r):
if mode == 0:
return gir_h
return gir_v_ccw if r == 1 else gir_v_cw
# Estat lògic del joc caselles = [[0, 0, 0], [0, 0, 0]] # 2x3 caixes arestes_h = [[0, 0, 0], [0, 0, 0], [0, 0, 0]] # 3x3 línies H arestes_v = [[0, 0, 0, 0], [0, 0, 0, 0]] # 2x4 línies V
# 2. FUNCIONS GRÀFIQUES (SPRITES)
# Posicions de línies i caselles
def pos_h(f, c):
y = 24 + 32 * f - 8
x0 = 24 + 32 * c
return (x0 + 8, y), (x0 + 24, y)
def pos_v(f, c):
x_base = 24 + 32 * c
y_base = 24 + 32 * f
if f == 0:
x = x_base - 1
y = y_base + 8
else:
x = x_base - 8
y = y_base - 1
return (x, y), (x, y + 16)
def pos_casella(fy, fx):
return 27 + 32 * fx + 8, 27 + 32 * fy + 8
# Sprites de línia i caselles
def crea_par(xy1, xy2):
s1 = stage.Sprite(banc_lin, TRANSP, xy1[0], xy1[1])
s2 = stage.Sprite(banc_lin, TRANSP, xy2[0], xy2[1])
return [s1, s2]
def crea_casella(xy):
return stage.Sprite(banc_lin, TRANSP, xy[0], xy[1])
# Base: línies confirmades
fitxes_h = [[crea_par(*pos_h(f, c)) for c in range(3)] for f in range(3)]
fitxes_v = [[crea_par(*pos_v(f, c)) for c in range(4)] for f in range(2)]
# Cursor: línies discontínues (previsualització)
overlay_h = [[crea_par(*pos_h(f, c)) for c in range(3)] for f in range(3)]
overlay_v = [[crea_par(*pos_v(f, c)) for c in range(4)] for f in range(2)]
# Castelles: creus al centre
casella_spr = [[crea_casella(pos_casella(fy, fx)) for fx in range(3)] for fy in range(2)]
# Llistes planes de sprites del joc
all_overlay = []
for fila in overlay_h + overlay_v:
for parella in fila:
all_overlay += parella
all_base = []
for fila in fitxes_h + fitxes_v:
for parella in fila:
all_base += parella
all_caselles = []
for fila in casella_spr:
all_caselles += fila
# Títol portada
dots_tiles = [
[TRANSP, 0, 1, 2, 3, 4, 5, 15, TRANSP, TRANSP],
[TRANSP, 6, 7, 8, 9, 10, 11, 12, TRANSP, TRANSP],
]
and_tiles = [
[TRANSP, TRANSP, 0, 1, TRANSP, TRANSP, TRANSP, TRANSP, TRANSP, TRANSP],
[TRANSP, TRANSP, 2, 3, TRANSP, TRANSP, TRANSP, TRANSP, TRANSP, TRANSP],
]
boxes_tiles = [
[TRANSP, 0, 1, 2, 3, 4, 5, 6, TRANSP, TRANSP],
[TRANSP, 8, 9, 10, 11, 12, 13, 14, TRANSP, TRANSP],
]
sprites_titol = []
def crea_titol():
global sprites_titol
sprites_titol = []
top_y = 16
# "dots"
base_y = top_y
for row in range(2):
y = base_y + row * 16
for col in range(10):
frame = dots_tiles[row][col]
if frame == TRANSP:
continue
x = 16 + col * 16
sprites_titol.append(stage.Sprite(banc_dots, frame, x, y))
# "and"
base_y = top_y + 2 * 16
for row in range(2):
y = base_y + row * 16
for col in range(10):
frame = and_tiles[row][col]
if frame == TRANSP:
continue
x = 38 + col * 16
sprites_titol.append(stage.Sprite(banc_and, frame, x, y))
# "boxes"
base_y = top_y + 4 * 16
for row in range(2):
y = base_y + row * 16
for col in range(10):
frame = boxes_tiles[row][col]
if frame == TRANSP:
continue
x = 8 + col * 16
sprites_titol.append(stage.Sprite(banc_boxes, frame, x, y))
# Taulell de joc (fons)
def dib_taulell():
tau = [
[5, 5, 5, 5, 5, 5, 5, 5, 5, 5],
[5, 4, 0, 4, 0, 4, 0, 4, 3, 5],
[5, 0, 0, 0, 0, 0, 0, 0, 3, 5],
[5, 4, 0, 4, 0, 4, 0, 4, 3, 5],
[5, 0, 0, 0, 0, 0, 0, 0, 3, 5],
[5, 4, 0, 4, 0, 4, 0, 4, 3, 5],
[5, 2, 2, 2, 2, 2, 2, 2, 1, 5],
[5, 5, 5, 5, 5, 5, 5, 5, 5, 5]
]
for i in range(8):
for j in range(10):
fons.tile(j, i, tau[i][j])
# Helpers de pintat
def amaga(par):
par[0].set_frame(TRANSP, gir_h)
par[1].set_frame(TRANSP, gir_h)
def amaga_caselles():
for fy in range(2):
for fx in range(3):
casella_spr[fy][fx].set_frame(TRANSP, gir_h)
def pinta_previs(mode, f, c, jug):
tiles = li_disc[jug]
if mode == 0: # H
overlay_h[f][c][0].set_frame(tiles[0], gir_h)
overlay_h[f][c][1].set_frame(tiles[1], gir_h)
else: # V
g = gir(1, f)
if g == gir_v_cw:
overlay_v[f][c][0].set_frame(tiles[0], g)
overlay_v[f][c][1].set_frame(tiles[1], g)
else: # CCW ? swap
overlay_v[f][c][0].set_frame(tiles[1], g)
overlay_v[f][c][1].set_frame(tiles[0], g)
def pinta_solid(mode, f, c, jug):
tiles = li_solid[jug]
if mode == 0:
fitxes_h[f][c][0].set_frame(tiles[0], gir_h)
fitxes_h[f][c][1].set_frame(tiles[1], gir_h)
else:
g = gir(1, f)
if g == gir_v_cw:
fitxes_v[f][c][0].set_frame(tiles[0], g)
fitxes_v[f][c][1].set_frame(tiles[1], g)
else:
fitxes_v[f][c][0].set_frame(tiles[1], g)
fitxes_v[f][c][1].set_frame(tiles[0], g)
def pinta_casella(fy, fx, jug):
casella_spr[fy][fx].set_frame(cas_tile[jug], gir_h)
def refresc(jug, mode, cr):
# 0) neteja overlay
for f in range(3):
for c in range(3):
amaga(overlay_h[f][c])
for f in range(2):
for c in range(4):
amaga(overlay_v[f][c])
# 1) línies confirmades
for f in range(3):
for c in range(3):
if arestes_h[f][c] != 0:
pinta_solid(0, f, c, arestes_h[f][c])
else:
amaga(fitxes_h[f][c])
for f in range(2):
for c in range(4):
if arestes_v[f][c] != 0:
pinta_solid(1, f, c, arestes_v[f][c])
else:
amaga(fitxes_v[f][c])
# 1b) creus de caselles
for fy in range(2):
for fx in range(3):
if caselles[fy][fx] in (1, 2):
pinta_casella(fy, fx, caselles[fy][fx])
else:
casella_spr[fy][fx].set_frame(TRANSP, gir_h)
# 2) previsualització
f_sel, c_sel = cr
pinta_previs(mode, f_sel, c_sel, jug)
# 3) redibuix complet
joc.render_block()
# 3. LÒGICA DEL JOC
def linia_lliure(mode, f, c):
return (arestes_h[f][c] == 0) if mode == 0 else (arestes_v[f][c] == 0)
def posa_linia(mode, f, c, jug):
pinta_solid(mode, f, c, jug)
if mode == 0:
arestes_h[f][c] = jug
else:
arestes_v[f][c] = jug
def caixa_completa(fy, fx):
if not (0 <= fy < 2 and 0 <= fx < 3):
return False
top = arestes_h[fy][fx] != 0
bottom = arestes_h[fy+1][fx] != 0
left = arestes_v[fy][fx] != 0
right = arestes_v[fy][fx+1] != 0
return top and bottom and left and right
def tanca_caixes(mode, f, c, jug):
t = 0
if mode == 0:
if f-1 >= 0 and caselles[f-1][c] == 0 and caixa_completa(f-1, c):
caselles[f-1][c] = jug
pinta_casella(f-1, c, jug)
t += 1
if f < 2 and caselles[f][c] == 0 and caixa_completa(f, c):
caselles[f][c] = jug
pinta_casella(f, c, jug)
t += 1
else:
if c-1 >= 0 and caselles[f][c-1] == 0 and caixa_completa(f, c-1):
caselles[f][c-1] = jug
pinta_casella(f, c-1, jug)
t += 1
if c < 3 and caselles[f][c] == 0 and caixa_completa(f, c):
caselles[f][c] = jug
pinta_casella(f, c, jug)
t += 1
return t
def totes_arestes():
for f in range(3):
for c in range(3):
if arestes_h[f][c] == 0:
return False
for f in range(2):
for c in range(4):
if arestes_v[f][c] == 0:
return False
return True
def resultat_partida():
j1 = j2 = 0
for fy in range(2):
for fx in range(3):
if caselles[fy][fx] == 1:
j1 += 1
elif caselles[fy][fx] == 2:
j2 += 1
if j1 > j2:
return 1
if j2 > j1:
return 2
return 3
# Màquina
def ia_tria_linia():
lliures = []
for f in range(3):
for c in range(3):
if arestes_h[f][c] == 0:
lliures.append((0, f, c))
for f in range(2):
for c in range(4):
if arestes_v[f][c] == 0:
lliures.append((1, f, c))
if not lliures:
return None
return random.choice(lliures)
# Layouts
def set_layout_titol():
joc.layers = sprites_titol + [fons]
def set_layout_joc():
joc.layers = all_overlay + all_caselles + all_base + [fons]
# Texts del lobby
text_mode1 = stage.Text(20, 1)
text_mode1.move(16, 24)
text_mode1.text("Selecciona mode")
text_mode2 = stage.Text(20, 1)
text_mode2.move(16, 54)
text_mode2.text("A: 2 jugadors")
text_mode3 = stage.Text(20, 1)
text_mode3.move(16, 84)
text_mode3.text("B: 1 jugador")
# 4. PANTALLA FINAL
# Indicar guanyador
blau_tiles = [
[TRANSP, TRANSP, 0, 1, 2, 3, 4, 5, TRANSP, TRANSP],
[TRANSP, TRANSP, 6, 7, 8, 9, 10, 11, TRANSP, TRANSP],
]
mage_tiles = [
[TRANSP, TRANSP, 0, 1, 2, 3, 4, 5, TRANSP, TRANSP],
[TRANSP, TRANSP, 6, 7, 8, 9, 10, 11, TRANSP, TRANSP],
]
empat_tiles = [
[TRANSP, TRANSP, 0, 1, 2, 3, 4, 5, TRANSP, TRANSP],
[TRANSP, TRANSP, 6, 7, 8, 9, 10, 11, TRANSP, TRANSP],
]
sprites_final = []
def crea_final(resultat):
global sprites_final
sprites_final = []
banc = None
tiles = None
if resultat == 1: # Blau guanya
banc = banc_blau
tiles = blau_tiles
elif resultat == 2: # Magenta guanya
banc = banc_mage
tiles = mage_tiles
else: # Empat
banc = banc_empat
tiles = empat_tiles
top_y = 44
for row in range(2):
y = top_y + row * 16
for col in range(10):
frame = tiles[row][col]
if frame == TRANSP:
continue
x = col * 16 - 5
sprites_final.append(stage.Sprite(banc, frame, x, y))
def set_layout_final():
joc.layers = sprites_final + [fons]
def set_layout_lobby():
joc.layers = [text_mode1, text_mode2, text_mode3, fons]
# Pantalles
def mostra_pantalla_titol():
for i in range(8):
for j in range(10):
fons.tile(j, i, 0)
crea_titol()
set_layout_titol()
joc.render_block()
def mostra_lobby():
for i in range(8):
for j in range(10):
fons.tile(j, i, 0)
set_layout_lobby()
joc.render_block()
global abans
while True:
boto = ugame.buttons.get_pressed()
# A ? 2 jugadors
if not abans & ugame.K_O and boto & ugame.K_O:
return 2
# B ? 1 jugador
if not abans & ugame.K_X and boto & ugame.K_X:
return 1
abans = boto
joc.tick()
# Bucle principal estat = 0 # 0=títol, 2=joc, 3=final abans = 0 jugador = 1 mode = 0 # 0=H, 1=V coord = [0, 0] game_mode = 2 # 2 jugadors per defecte
def executa_final_joc():
# 1. Calculem qui ha guanyat (1=Blau, 2=Magenta, 3=Empat)
g = resultat_partida()
# 2. Encendre LED guanyador
if g == 1:
leds.fill((0, 0, 255)) # BLAU
elif g == 2:
leds.fill((255, 0, 255)) # MAGENTA
else:
leds.fill((255, 255, 255)) # BLANC per empat
# 3. Creem els sprites del títol corresponent
crea_final(g)
# 4. Canviem les capes perquè es vegin
joc.layers = sprites_final + all_caselles + all_base + [fons]
# 5. Forcem que es pinti ARA MATEIX a la pantalla
joc.render_block()
mostra_pantalla_titol()
while True:
if estat == 0:
boto = ugame.buttons.get_pressed()
if not abans & ugame.K_START and boto & ugame.K_START:
# Apaguem LEDs en començar
leds.fill((0, 0, 0))
# Lobby selecció mode
game_mode = mostra_lobby()
# Allibera A/B
while ugame.buttons.get_pressed() & (ugame.K_O | ugame.K_X):
joc.tick()
abans = 0
# Reset estat
for f in range(3):
for c in range(3):
arestes_h[f][c] = 0
for f in range(2):
for c in range(4):
arestes_v[f][c] = 0
for fy in range(2):
for fx in range(3):
caselles[fy][fx] = 0
amaga_caselles()
mode = 0
coord = [0, 0]
if game_mode == 1:
jugador = random.randint(1, 2)
else:
jugador = 1
dib_taulell()
set_layout_joc()
first_frame=True
estat = 2
abans = boto
joc.tick()
elif estat == 2:
if first_frame:
# 1. Actualitzem posicions de sprites
refresc(jugador, mode, coord)
# 2. Dibuixem el fons (ho fa refresc amb render_block)
# 3. FORCEM que els sprites es pintin
joc.tick()
# 4. Desactivem la bandera
first_frame = False
# 5. Saltem la resta del bucle per evitar doble lògica en aquest frame
continue
# Torn màquina
if game_mode == 1 and jugador == 2:
jugada = ia_tria_linia()
if jugada is not None:
mode_ia, f_ia, c_ia = jugada
mode = mode_ia
coord = [f_ia, c_ia]
time.sleep(0.2)
posa_linia(mode_ia, f_ia, c_ia, jugador)
t = tanca_caixes(mode_ia, f_ia, c_ia, jugador)
if t > 0:
if jugador == 1: leds.fill((0, 0, 255))
else: leds.fill((255, 0, 255))
joc.render_block()
time.sleep(0.005)
leds.fill((0, 0, 0))
else:
leds.fill((0, 0, 0))
jugador = 1
refresc(jugador, mode, coord)
if totes_arestes():
executa_final_joc()
estat = 3
joc.tick()
continue
# Torn humà
boto = ugame.buttons.get_pressed()
moved = False
if mode == 0:
if not abans & ugame.K_RIGHT and boto & ugame.K_RIGHT and coord[1] < 2:
coord[1] += 1; moved = True
if not abans & ugame.K_LEFT and boto & ugame.K_LEFT and coord[1] > 0:
coord[1] -= 1; moved = True
if not abans & ugame.K_DOWN and boto & ugame.K_DOWN and coord[0] < 2:
coord[0] += 1; moved = True
if not abans & ugame.K_UP and boto & ugame.K_UP and coord[0] > 0:
coord[0] -= 1; moved = True
else:
if not abans & ugame.K_RIGHT and boto & ugame.K_RIGHT and coord[1] < 3:
coord[1] += 1; moved = True
if not abans & ugame.K_LEFT and boto & ugame.K_LEFT and coord[1] > 0:
coord[1] -= 1; moved = True
if not abans & ugame.K_DOWN and boto & ugame.K_DOWN and coord[0] < 1:
coord[0] += 1; moved = True
if not abans & ugame.K_UP and boto & ugame.K_UP and coord[0] > 0:
coord[0] -= 1; moved = True
if moved:
refresc(jugador, mode, coord)
# B = canviar orientació
if not abans & ugame.K_X and boto & ugame.K_X:
mode = 1 - mode
if mode == 0:
if coord[0] > 2:
coord[0] = 2
if coord[1] > 2:
coord[1] = 2
else:
if coord[0] > 1:
coord[0] = 1
if coord[1] > 3:
coord[1] = 3
refresc(jugador, mode, coord)
# A = confirmar línia
if not abans & ugame.K_O and boto & ugame.K_O:
f, c = coord
if linia_lliure(mode, f, c):
posa_linia(mode, f, c, jugador)
t = tanca_caixes(mode, f, c, jugador)
if t > 0:
# Ha tancat caixa: encendre
if jugador == 1:
leds.fill((0, 0, 255))
else:
leds.fill((255, 0, 255))
refresc(jugador, mode, coord)
joc.render_block()
time.sleep(0.005)
leds.fill((0, 0, 0))
else:
# No ha tancat: apagar i canviar torn
leds.fill((0, 0, 0))
jugador = 2 if jugador == 1 else 1
refresc(jugador, mode, coord)
if totes_arestes():
executa_final_joc()
estat = 3
# START = tornar a la pantalla de títol
if not abans & ugame.K_START and boto & ugame.K_START:
estat = 0
mostra_pantalla_titol()
abans = boto
joc.tick()
else:
# Estat final: es manté el taulell fins prémer START
boto = ugame.buttons.get_pressed()
if not abans & ugame.K_START and boto & ugame.K_START:
estat = 0
mostra_pantalla_titol()
abans = boto
joc.tick()

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