-- Pepe el Pintor — port a ascii/Lua del joc original en Turbo Pascal -- (Sergi Valor Martínez, 1999). Base: PINTOR3.PAS. -- -- ITER 3: multifase. 3 mapes (rect xicotet, creu+rombe, rect gran) amb -- transició "ENHORABONA!" entre fases. En acabar la fase 3, loop a la fase 1 -- mantenint el flux infinit del Pascal original. -- ==================================================================== -- 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 NETEJA_GLIF = 176 -- ░ char de neteja_pantalla del Pascal -- Fletxes (CP437) per al diagrama de tecles del HUD ARR_UP = 30 -- ▲ ARR_DOWN = 31 -- ▼ ARR_LEFT = 17 -- ◄ ARR_RIGHT = 16 -- ► -- 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 NUM_FASES = 3 -- Llindars d'aparició dels enemics expressats com a *fracció* del total -- inicial de la fase. PINTOR3 tenia 1000/700/500 blocs sobre ~1320 → 76% / -- 53% / 38%. Apareixen quan total_blocs baixa per sota. SPAWN_FRAC = { 0.76, 0.53, 0.38 } -- 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_AVIS = "avis" -- pantalla inicial "AVÍS — Aquest joc encara..." ESTAT_PLAYING = "playing" ESTAT_MURIGUENT = "muriguent" ESTAT_GAMEOVER = "gameover" ESTAT_INTERFASE = "interfase" -- "ENHORABONA!" entre fases (fidel al Pascal) INTERFASE_FRAMES = 240 -- ~4 segons (l'original feia delay(5000) = 5s) NETEJA_FRAMES = 60 -- duració del sweep de neteja al final del interfase -- ==================================================================== -- 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 fase = 1 -- 1..NUM_FASES, loop al final total_inicial = 0 -- valor de total_blocs al començar la fase (per als llindars) truco = false -- invulnerabilitat (mode debug, l'original ho tenia comentat amb tecla I) 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) setchar(MALO_GLIF, 0x00, 0xC3, 0x66, 0x3C, 0x18, 0x3C, 0x66, 0xC3) -- Char de la neteja_pantalla del Pascal (chr 176 — ░ tinta clara) setchar(NETEJA_GLIF, 0x11, 0x44, 0x11, 0x44, 0x11, 0x44, 0x11, 0x44) -- Fletxes triangulars per al diagrama del HUD setchar(ARR_UP, 0x00, 0x18, 0x3C, 0x7E, 0xFF, 0x00, 0x00, 0x00) setchar(ARR_DOWN, 0x00, 0x00, 0x00, 0xFF, 0x7E, 0x3C, 0x18, 0x00) setchar(ARR_LEFT, 0x00, 0x10, 0x30, 0x70, 0xF0, 0x70, 0x30, 0x10) setchar(ARR_RIGHT, 0x00, 0x08, 0x0C, 0x0E, 0x0F, 0x0E, 0x0C, 0x08) 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 total_inicial = total_blocs -- per a calcular llindars d'spawn 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, fase, vides + diagrama de tecles al lateral dret -- (fidel a l'original que dibuixava les fletxes Q/A/O/P amb chars 30,31,16,17) function pintar_hud() color(COLOR_LIGHT_GRAY, COLOR_BLUE) local blank = " " for i = HUD_Y0, 29 do print(blank, 0, i) end -- Columna esquerra: dades del joc color(COLOR_WHITE, COLOR_BLUE) print("PINTURA "..string.format("%02d", pot).."/"..tostr(POT_MAX), 1, 25) color(COLOR_YELLOW, COLOR_BLUE) print("BLOCS "..string.format("%04d", total_blocs), 1, 26) color(COLOR_LIGHT_GREEN, COLOR_BLUE) print("FASE "..tostr(fase), 1, 27) color(COLOR_LIGHT_RED, COLOR_BLUE) for i = 1, vides do print(chr(PEPE_PLE), 9 + i, 27) end -- Diagrama de tecles (fidel al PINTOR original) -- Q -- ▲ -- O ◄ ► P -- ▼ -- A color(COLOR_LIGHT_CYAN, COLOR_BLUE) print("Q", 30, 25) print(chr(ARR_UP), 30, 26) print("O", 28, 27) print(chr(ARR_LEFT), 29, 27) print(chr(ARR_RIGHT), 31, 27) print("P", 32, 27) print(chr(ARR_DOWN), 30, 28) print("A", 30, 29) if truco then color(COLOR_WHITE, COLOR_RED) print("INVULN", 1, 29) end 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 -- "Música malos" del Pascal: el bucle for de 6 vegades × 41 sounds de 0 a -- 8000 Hz creixent ràpidament. Aproximem amb una pujada cromàtica que -- recorre 4-5 octaves en una passada (un sol play, asincron a l'engine). function jingle_malo() play("l0o2cdefgab>cdefgab>cdefgab>cdefgab>cdefg") end -- Check spawn: si total_blocs ha baixat per sota del llindar proporcional al -- total inicial de la fase, i el malo encara no ha aparegut, el fem aparèixer. function check_spawn() for i = 1, NUM_MALOS do local m = malos[i] local llindar = flr(total_inicial * SPAWN_FRAC[i]) if not m.aparegut and total_blocs <= llindar 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() if truco then return end -- invulnerabilitat de debug vides = vides - 1 set_estat(ESTAT_MURIGUENT) end function reset_partida() pot = POT_MAX total_blocs = 0 vides = VIDES_INI fase = 1 omplint = false pepe.x, pepe.y = PEPE_INI_X, PEPE_INI_Y pepe.pinta = true init_malos() carregar_mapa(fase) reset_input() set_estat(ESTAT_PLAYING) end -- Carrega una fase concreta (reinicia vides i pot, ja siga per la transició -- normal o per l'atall de debug). Si n > NUM_FASES, loop a 1. function anar_a_fase(n) if n > NUM_FASES then n = 1 end if n < 1 then n = NUM_FASES end fase = n vides = VIDES_INI pot = POT_MAX omplint = false pepe.x, pepe.y = PEPE_INI_X, PEPE_INI_Y pepe.pinta = true init_malos() carregar_mapa(fase) reset_input() set_estat(ESTAT_PLAYING) end -- Avança a la fase següent (fidel al PINTOR3: nfase++ i si > 3, loop a 1). function avancar_fase() anar_a_fase(fase + 1) 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() -- Atalls de debug: 1/2/3 = salta a fase, I = toggle invulnerable if btnp(KEY_1) then anar_a_fase(1); return elseif btnp(KEY_2) then anar_a_fase(2); return elseif btnp(KEY_3) then anar_a_fase(3); return end if btnp(KEY_I) then truco = not truco end 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_INTERFASE) 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 -- Transició entre fases: ENHORABONA! parpadejant, sweep de neteja per -- columnes (fidel al `neteja_pantalla` del Pascal) i salt a la fase següent. function update_interfase() cls() pintar_mapa() pintar_pot() pintar_pepe() pintar_hud() -- ENHORABONA! parpadejant durant la primera part if estat_t < INTERFASE_FRAMES - NETEJA_FRAMES and (estat_t // 12) % 2 == 0 then color(COLOR_LIGHT_GREEN, COLOR_BLACK) print("ENHORABONA!", 14, 11) end -- Sweep de neteja (últims NETEJA_FRAMES frames). Omplim columnes -- progressivament amb chr(176) = ░ negre sobre negre. if estat_t >= INTERFASE_FRAMES - NETEJA_FRAMES then local progr = estat_t - (INTERFASE_FRAMES - NETEJA_FRAMES) local cols = flr(progr * MAP_W / NETEJA_FRAMES) if cols > MAP_W then cols = MAP_W end color(COLOR_DARK_GRAY, COLOR_BLACK) for x = 0, cols - 1 do for y = 0, MAP_H - 1 do print(chr(NETEJA_GLIF), x, y) end end end if estat_t >= INTERFASE_FRAMES then avancar_fase() end end -- Pantalla AVÍS inicial (fidel a beta_version del PINTOR2/PEPEDIEG). -- Mostra una caixa amb el text, després va apuntant "Prem una tecla per a -- continuar" cap amunt fins una posició final, i espera una tecla. function update_avis() cls() color(COLOR_WHITE, COLOR_BLACK) print("- AVIS -", 16, 3) color(COLOR_WHITE, COLOR_BLACK) print("+------------------------------+", 3, 6) print("| |", 3, 7) print("| Port a la fantasy console |", 3, 8) print("| ascii del joc original en |", 3, 9) print("| Turbo Pascal de 1999, fet |", 3, 10) print("| per Sergi Valor Martinez. |", 3, 11) print("| |", 3, 12) print("| Que sapigues que les tres |", 3, 13) print("| fases son en bucle infinit. |", 3, 14) print("| |", 3, 15) print("+------------------------------+", 3, 16) -- Animació del missatge pujant des de fila 24 fins fila 19 (5 fil) local fila_final = 19 local pas = estat_t // 8 local fila = 24 - pas if fila < fila_final then fila = fila_final end if (estat_t // 6) % 2 == 0 then color(COLOR_LIGHT_CYAN, COLOR_BLACK) print("- Prem una tecla per a continuar -", 3, fila) end -- Quan el missatge ja ha arribat a la posició final, accepta tecles if fila == fila_final then if btnp(KEY_SPACE) or btnp(KEY_RETURN) then anar_a_fase(1) return end for k = KEY_A, KEY_Z do if btnp(k) then anar_a_fase(1); return end end end end -- ==================================================================== -- BUCLE PRINCIPAL -- ==================================================================== function init() wintitle("© 1999 Pepe el pintor — JailDesigner") mode(1) border(COLOR_BLUE) definir_glifs() init_malos() fase = 1 carregar_mapa(fase) set_estat(ESTAT_AVIS) cls() end function update() if btnp(KEY_ESCAPE) then os.exit(0) end estat_t = estat_t + 1 if estat_joc == ESTAT_AVIS then update_avis() elseif 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_INTERFASE then update_interfase() end end