-- Pepe el Pintor — port a ascii/Lua del joc original en Turbo Pascal -- (Sergi Valor Martínez, 1999). Base: PINTOR3.PAS. -- -- ITER 2: motor base + enemics + vides. -- - Fase única (la 3 = rectangle gran) -- - O P Q A per a moure, pinta al pas, gasta el pot -- - Recàrrega al tornar al pot (fidel al Pascal: sound(i*10)) -- - 3 enemics amb spawn diferit per llindar de blocs pintats -- - Moviment IA dels enemics fidel a moure_malos (random eix + perseguir) -- - 5 vides, animació de mort, game over, victòria -- ==================================================================== -- CONSTANTS -- ==================================================================== MAP_W, MAP_H = 40, 25 HUD_Y0 = 25 -- Glifs (codis CP437). Els redefinim amb setchar per a fidelitat. PEPE_PLE = 2 -- ☻ Pepe amb pintura PEPE_BUIT = 1 -- ☺ Pepe sense pintura PARED = 0xB1 -- ▒ paret FONS = 0xDB -- █ fons (sense pintar / pintat = distingit per `pintat`) POT_GLIF = 232 -- ◘ marca visual del pot MALO_GLIF = 88 -- 'X' enemic -- Constants del joc POT_MAX = 90 PEPE_INI_X = 37 -- cambra del pot (sortint 1 cel·la del marc a la dreta) PEPE_INI_Y = 11 TICS_MOVIMENT = 5 -- frames per tic de moviment (60/5 = 12 Hz) TICS_OMPLIR = 3 -- frames entre +1 de pot al recarregar NUM_MALOS = 3 VIDES_INI = 5 -- Llindars d'aparició dels enemics (proporcionals al PINTOR3 escalat als -- nostres ~829 blocs). Apareixen quan total_blocs baixa per sota d'aquests. BLOCS_SPAWN = { 750, 520, 370 } -- Animació de mort: 10000 Hz → 1000 Hz, restant 250 cada pas MORT_FREQ_INI = 10000 MORT_FREQ_END = 1000 MORT_FREQ_STEP = 250 MORT_FRAMES = 36 -- ~600 ms d'animació de mort -- Estats ESTAT_PLAYING = "playing" ESTAT_MURIGUENT = "muriguent" ESTAT_GAMEOVER = "gameover" ESTAT_VICTORIA = "victoria" -- ==================================================================== -- ESTAT GLOBAL -- ==================================================================== mapa = {} pepe = { x=PEPE_INI_X, y=PEPE_INI_Y, pinta=true } pot = POT_MAX total_blocs = 0 omplint = false omplir_i = 0 omplir_max = 0 malos = {} -- { {x, y, viu=bool, aparegut=bool}, ... } vides = VIDES_INI estat_joc = ESTAT_PLAYING estat_t = 0 -- contador de frames dins de l'estat actual -- Buffer d'edges d'input direccional (vore explicació a l'iter 1). input_buf = { up=false, down=false, left=false, right=false } function sample_input() if btnp(KEY_Q) then input_buf.up = true end if btnp(KEY_A) then input_buf.down = true end if btnp(KEY_O) then input_buf.left = true end if btnp(KEY_P) then input_buf.right = true end end function reset_input() input_buf.up, input_buf.down, input_buf.left, input_buf.right = false, false, false, false end -- ==================================================================== -- GLIFS CUSTOM (bitmaps CP437) -- ==================================================================== function definir_glifs() setchar(PEPE_PLE, 0x7E, 0xFF, 0xDB, 0xFF, 0xC3, 0xE7, 0xFF, 0x7E) setchar(PEPE_BUIT, 0x7E, 0x81, 0xA5, 0x81, 0xBD, 0x99, 0x81, 0x7E) setchar(PARED, 0x44, 0x11, 0x44, 0x11, 0x44, 0x11, 0x44, 0x11) setchar(FONS, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF) setchar(POT_GLIF, 0x00, 0x7E, 0x7E, 0x42, 0x66, 0x7E, 0x3C, 0x00) -- L'enemic 'X' del CP437 ja és coherent al ROM nadiu d'ascii, però el -- redefinim igualment per a controlar-ho. setchar(MALO_GLIF, 0x00, 0xC3, 0x66, 0x3C, 0x18, 0x3C, 0x66, 0xC3) end -- ==================================================================== -- MAPA -- ==================================================================== function tipo_a(x, y) if x < 0 or x >= MAP_W or y < 0 or y >= MAP_H then return PARED end return mapa[x][y].tipo end -- Una cel·la és "pintada" si Pepe o un malo hi han passat. Els malos només -- es poden moure per cel·les pintades (fidel al Pascal: a_v(destí) == bg). function pintat_a(x, y) if x < 0 or x >= MAP_W or y < 0 or y >= MAP_H then return false end return mapa[x][y].pintat end function hi_ha_malo(x, y) for i = 1, NUM_MALOS do local m = malos[i] if m.viu and m.x == x and m.y == y then return true end end return false end function carregar_mapa(num) filein("maps/"..tostr(num)..".map", 0, MAP_W*MAP_H) total_blocs = 0 for x = 0, MAP_W-1 do mapa[x] = {} for y = 0, MAP_H-1 do local b = peek(x*MAP_H + y) mapa[x][y] = { tipo=b, pintat=false } if b == FONS then total_blocs = total_blocs + 1 end end end -- La cel·la del pot no es pinta: ja "pintada" per a la condició de victòria mapa[PEPE_INI_X][PEPE_INI_Y].pintat = true total_blocs = total_blocs - 1 end function pintar_mapa() for x = 0, MAP_W-1 do for y = 0, MAP_H-1 do local c = mapa[x][y] if c.tipo == PARED then color(COLOR_RED, COLOR_BLACK) print(chr(PARED), x, y) elseif c.tipo == FONS and c.pintat then color(COLOR_BROWN, COLOR_BLACK) print(chr(FONS), x, y) end end end end function pintar_pot() if not (pepe.x == PEPE_INI_X and pepe.y == PEPE_INI_Y) then color(COLOR_YELLOW, COLOR_BROWN) print(chr(POT_GLIF), PEPE_INI_X, PEPE_INI_Y) end end function pintar_pepe() local glif = pepe.pinta and PEPE_PLE or PEPE_BUIT local fons_color if pepe.x == PEPE_INI_X and pepe.y == PEPE_INI_Y then fons_color = COLOR_BROWN -- damunt del pot elseif mapa[pepe.x][pepe.y].pintat then fons_color = COLOR_BROWN else fons_color = COLOR_BLACK end color(COLOR_YELLOW, fons_color) print(chr(glif), pepe.x, pepe.y) end function pintar_malos() for i = 1, NUM_MALOS do local m = malos[i] if m.viu then -- Color enemic: cian clar (107 = 0x6B → bg 6 marró + fg 11 cian) color(COLOR_LIGHT_CYAN, COLOR_BROWN) print(chr(MALO_GLIF), m.x, m.y) end end end -- HUD: pintura, blocs restants, vides (com a ☻), controls function pintar_hud() color(COLOR_LIGHT_GRAY, COLOR_BLUE) local blank = " " for i = HUD_Y0, 29 do print(blank, 0, i) end color(COLOR_WHITE, COLOR_BLUE) print("PINTURA "..string.format("%02d", pot).."/"..tostr(POT_MAX), 1, 26) color(COLOR_YELLOW, COLOR_BLUE) print("BLOCS "..string.format("%04d", total_blocs), 17, 26) color(COLOR_LIGHT_RED, COLOR_BLUE) for i = 1, vides do print(chr(PEPE_PLE), 32 + i, 26) end color(COLOR_LIGHT_CYAN, COLOR_BLUE) print("O P Q A: moure", 1, 28) end -- ==================================================================== -- LÒGICA DEL PEPE -- ==================================================================== function intent_moviment(dx, dy) local nx, ny = pepe.x + dx, pepe.y + dy local t = tipo_a(nx, ny) if t == PARED then return end if t ~= FONS then return end -- Detecció de mort: si la cel·la destí té un malo (fidel a "a_v=bicho") if hi_ha_malo(nx, ny) then mort_pepe() return end pepe.x, pepe.y = nx, ny local c = mapa[nx][ny] -- El pot no es pinta if nx == PEPE_INI_X and ny == PEPE_INI_Y then sound(2000, 3) return end if not c.pintat then if pot > 0 then c.pintat = true pot = pot - 1 total_blocs = total_blocs - 1 sound(5000, 3) pepe.pinta = (pot > 0) else sound(1000, 3) -- so més greu quan no hi ha pintura (fidel: nota=1000) end else sound(5000, 3) end end function tic_pepe() if omplint then return end if input_buf.up or btn(KEY_Q) then intent_moviment( 0, -1) elseif input_buf.down or btn(KEY_A) then intent_moviment( 0, 1) elseif input_buf.left or btn(KEY_O) then intent_moviment(-1, 0) elseif input_buf.right or btn(KEY_P) then intent_moviment( 1, 0) end if pepe.x == PEPE_INI_X and pepe.y == PEPE_INI_Y and pot < POT_MAX then omplint = true omplir_i = 0 omplir_max = POT_MAX - pot end end -- Fidel al Pascal: sound(i*10) on i és el comptador del bucle function tic_omplir() if not omplint then return end sound(omplir_i * 10, 2) pot = pot + 1 omplir_i = omplir_i + 1 if omplir_i > omplir_max then if pot > POT_MAX then pot = POT_MAX end omplint = false nosound() end pepe.pinta = true end -- ==================================================================== -- ENEMICS — moure_malos del PINTOR3.PAS -- ==================================================================== function init_malos() malos = {} for i = 1, NUM_MALOS do malos[i] = { x=0, y=0, viu=false, aparegut=false } end end -- naiximent: busca totes les cel·les pintades (= chr 219 en el Pascal) i -- en tria una aleatòria. Reservem el pot perquè no naixi el malo damunt. function naiximent() local llocs = {} for x = 0, MAP_W-1 do for y = 0, MAP_H-1 do if mapa[x][y].pintat and not (x == PEPE_INI_X and y == PEPE_INI_Y) and not (x == pepe.x and y == pepe.y) then llocs[#llocs+1] = { x=x, y=y } end end end if #llocs == 0 then return PEPE_INI_X, PEPE_INI_Y end -- fallback (impossible si jugues) local pick = llocs[rnd(#llocs) + 1] return pick.x, pick.y end -- Jingle "música malos" del Pascal (sound creixent i decreixent en bucle) function jingle_malo() play("l0o3cdefgab>cl0o3cdefgab>c") end -- Check spawn: si total_blocs ha baixat per sota del llindar i el malo no -- ha aparegut encara, el fem aparèixer. function check_spawn() for i = 1, NUM_MALOS do local m = malos[i] if not m.aparegut and total_blocs <= BLOCS_SPAWN[i] then m.x, m.y = naiximent() m.viu = true m.aparegut = true jingle_malo() end end end -- Decisió de direcció del malo (fidel a moure_malos del Pascal): -- random(2): 0 = perseguir en eix X, 1 = perseguir en eix Y. La direcció és: -- meneja=0 esquerra, 1 dreta, 2 dalt, 3 baix. -- Retorna una de les 4 direccions (o nil si no s'aplica cap). function decidir_direccio(m) local meneja = nil if rnd(2) == 0 then if pepe.x - m.x > 0 then meneja = 1 -- pepe a la dreta del malo elseif pepe.x - m.x < 0 then meneja = 0 -- pepe a l'esquerra end else if pepe.y - m.y > 0 then meneja = 3 -- pepe avall elseif pepe.y - m.y < 0 then meneja = 2 -- pepe amunt end end return meneja end -- Intenta moure un malo en la direcció `meneja`. Fidel al Pascal: -- requereix que la cel·la destí (a) no siga paret, (b) no tinga un altre -- malo, i (c) siga "bg" (= cel·la pintada). Aquesta tercera condició limita -- els malos a la zona ja pintada pel Pepe. function moure_malo(m, meneja) if meneja == nil then return end local dx, dy = 0, 0 if meneja == 0 then dx = -1 elseif meneja == 1 then dx = 1 elseif meneja == 2 then dy = -1 elseif meneja == 3 then dy = 1 end local nx, ny = m.x + dx, m.y + dy if tipo_a(nx, ny) == PARED then return end if hi_ha_malo(nx, ny) then return end if not pintat_a(nx, ny) then return end m.x, m.y = nx, ny end function tic_malos() for i = 1, NUM_MALOS do local m = malos[i] if m.viu then moure_malo(m, decidir_direccio(m)) end end end function check_mort_per_malo() for i = 1, NUM_MALOS do local m = malos[i] if m.viu and m.x == pepe.x and m.y == pepe.y then mort_pepe() return end end end -- ==================================================================== -- MORT + ESTATS DE TRANSICIÓ -- ==================================================================== function set_estat(nou) estat_joc = nou estat_t = 0 end function mort_pepe() vides = vides - 1 set_estat(ESTAT_MURIGUENT) end function reset_partida() pot = POT_MAX total_blocs = 0 vides = VIDES_INI omplint = false pepe.x, pepe.y = PEPE_INI_X, PEPE_INI_Y pepe.pinta = true init_malos() carregar_mapa(3) reset_input() set_estat(ESTAT_PLAYING) end -- Respawn després de mort (sense reset complet, sols Pepe a casa i pot ple) function respawn_pepe() pepe.x, pepe.y = PEPE_INI_X, PEPE_INI_Y pepe.pinta = true pot = POT_MAX omplint = false reset_input() end -- ==================================================================== -- LÒGICA PER ESTAT -- ==================================================================== function update_playing() sample_input() if omplint and (cnt() % TICS_OMPLIR) == 0 then tic_omplir() end if not omplint and (cnt() % TICS_MOVIMENT) == 0 then tic_pepe() check_mort_per_malo() if estat_joc ~= ESTAT_PLAYING then reset_input(); return end tic_malos() check_mort_per_malo() check_spawn() reset_input() if total_blocs <= 0 then set_estat(ESTAT_VICTORIA) play("l3o4ceg>c") return end end cls() pintar_mapa() pintar_pot() pintar_malos() pintar_pepe() pintar_hud() end function update_muriguent() -- Animació de mort: so descendent + parpadeig del Pepe local pas = flr(estat_t / 3) local freq = MORT_FREQ_INI - pas * MORT_FREQ_STEP if freq < MORT_FREQ_END then freq = MORT_FREQ_END end sound(freq, 2) cls() pintar_mapa() pintar_pot() pintar_malos() if (estat_t // 4) % 2 == 0 then pintar_pepe() end pintar_hud() if estat_t >= MORT_FRAMES then nosound() if vides <= 0 then set_estat(ESTAT_GAMEOVER) else respawn_pepe() set_estat(ESTAT_PLAYING) end end end function update_gameover() cls() pintar_mapa() pintar_pot() pintar_malos() pintar_hud() color(COLOR_WHITE, COLOR_RED) print(" A D E U ", 15, 11) if estat_t > 120 and btnp(KEY_SPACE) then reset_partida() end end function update_victoria() cls() pintar_mapa() pintar_pot() pintar_pepe() pintar_hud() if (estat_t // 10) % 2 == 0 then color(COLOR_GREEN, COLOR_BLACK) print("ENHORABONA!", 14, 11) end if estat_t > 180 and btnp(KEY_SPACE) then reset_partida() end end -- ==================================================================== -- BUCLE PRINCIPAL -- ==================================================================== function init() mode(1) border(COLOR_BLUE) definir_glifs() init_malos() carregar_mapa(3) cls() end function update() estat_t = estat_t + 1 if estat_joc == ESTAT_PLAYING then update_playing() elseif estat_joc == ESTAT_MURIGUENT then update_muriguent() elseif estat_joc == ESTAT_GAMEOVER then update_gameover() elseif estat_joc == ESTAT_VICTORIA then update_victoria() end end