Creació de jocs amb PyBadge

Programació Dades pràctiques     Recursos CITCEA
Tutorial Exemples Projectes   Inici

Dots & boxes

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.

Pantalla del joc

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).

Gràfics         Gràfics         Gràfics         Gràfics         Gràfics         Gràfics         Gràfics         Gràfics

Pantalla         Pantalla

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()

 

 

 

 

 

 

 

 

 

 

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