Files

655 lines
21 KiB
Lua
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- 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