Files

632 lines
19 KiB
Lua

-- ============================================================
-- REBOTES — port a la fantasy console "ascii" del original
-- de Alexander Martin para Amstrad CPC (AMSOFT, 1984).
-- Sergi Valor, 2026.
-- ============================================================
-- ============================================================
-- CONFIGURACION (todo lo tuneable vive aqui)
-- ============================================================
-- Pantalla y layout
MODO = 1
ANCHO = 40
ALTO = 30
-- Zona de juego (donde se mueve la pelota)
PELOTA_X_MIN = 2
PELOTA_X_MAX = 37
TECHO_Y = 1
-- Filas de ladrillos (idéntico al original: filas 1, 2, 5, 6).
-- Estructura: { fila, color, puntos, glifo }
FILAS_LADRILLOS = nil -- se inicializa más abajo cuando ya hay paleta
-- Pala
PALA_Y = 27
PALA_W = 4
PALA_X_MIN = PELOTA_X_MIN
PALA_X_MAX = PELOTA_X_MAX - PALA_W + 1
PALA_X_INI = 18
PALA_STEP = 1 -- paso por movimiento (el original era 2)
-- Pelota
PELOTA_GLIFO = 231
PELOTA_Y_INI = 11 -- como el original
TICS_PELOTA = 5 -- frames entre movimientos de pelota (~12 Hz)
TICS_PALA = 2 -- auto-repeat del input de pala
-- Pelotas iniciales (vidas)
PELOTAS_INI = 5
-- Puntuaciones por fila de ladrillo (se asignan en FILAS_LADRILLOS abajo)
-- Duraciones de transición (ms, vía time())
MS_PERDIDA = 800
MS_PANTALLA = 600
MS_GAMEOVER_MIN = 800
-- ============================================================
-- PALETA (mapeo aproximado CPC firmware → CGA disponible)
-- ============================================================
COL_FONDO = COLOR_BLUE -- el original usa INK 0,1 = azul oscuro
COL_PELOTA = COLOR_WHITE
COL_PALA = COLOR_LIGHT_GRAY
COL_PARED = COLOR_LIGHT_GRAY
COL_TEXTO = COLOR_LIGHT_GRAY
COL_TITULO = COLOR_YELLOW
COL_PROMPT = COLOR_LIGHT_GREEN
COL_RECORD = COLOR_YELLOW
COL_GAMEOVER = COLOR_LIGHT_RED
COL_HUD_FG = COLOR_LIGHT_GRAY
COL_HUD_BG = COLOR_BLACK -- HUD sobre la misma franja del juego
-- Colores de las 3 "categorías" de ladrillos del original:
-- PEN 3 (naranja, fila 1, 20 pts), PEN 2 (cyan, fila 2, 10 pts), PEN 1 (claro, filas 5-6, 5 pts)
COL_LADR_TOP = COLOR_LIGHT_RED -- naranja ≈ rojo claro
COL_LADR_MID = COLOR_LIGHT_CYAN -- pastel cyan
COL_LADR_LOW = COLOR_WHITE -- pastel claro
-- ============================================================
-- GLIFOS (códigos que vamos a redefinir con setchar)
-- ============================================================
LADRILLO = 143
PALA_GLIFO = 131
PARED_GLIFO = 149 -- decorativa (cols 0 y 39)
-- PELOTA_GLIFO ya definido arriba (231)
-- ============================================================
-- TIPOS DE CELDA
-- ============================================================
T_VACIO = 0
T_LADRILLO = 1
-- ============================================================
-- ESTADOS DEL JUEGO
-- ============================================================
ESTADO_TITULO = "titulo"
ESTADO_INSTRUC = "instruc"
ESTADO_JUEGO = "juego"
ESTADO_PERDIDA = "perdida"
ESTADO_PANTALLA = "pantalla" -- pantalla limpiada: pausa breve y regenera
ESTADO_GAMEOVER = "gameover"
-- ============================================================
-- ESTADO GLOBAL
-- ============================================================
mapa = {}
pelota = { x=0, y=0, xa=1, ya=1, ultimo_tic=0 }
pala = { x=PALA_X_INI, ultimo_tic=0 }
pelotas = PELOTAS_INI
puntos = 0
maximo = 0
nom_record = "AAA"
ladrillos_vivos = 0
estado = ESTADO_TITULO
estado_t0_ms = 0
estado_t0_fr = 0
record_letra_idx = 1
hay_nuevo_record = false
-- ============================================================
-- UTILIDADES DE ESTADO
-- ============================================================
function set_estado(nuevo)
estado = nuevo
estado_t0_ms = time()
estado_t0_fr = cnt()
end
function t_estado_ms() return time() - estado_t0_ms end
function t_estado_fr() return cnt() - estado_t0_fr end
-- ============================================================
-- GLIFOS PERSONALIZADOS
-- ============================================================
function definir_glifos()
-- Ladrillo: macizo con junta horizontal (filas 2 y 5 vacías para sugerir mortero)
setchar(LADRILLO, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0x00, 0xFF)
-- Pala: superficie convexa, cuerpo macizo
setchar(PALA_GLIFO, 0x00, 0x3C, 0x7E, 0xFF, 0xFF, 0xFF, 0xFF, 0x00)
-- Pelota: esfera pequeña
setchar(PELOTA_GLIFO,0x00, 0x3C, 0x7E, 0x7E, 0x7E, 0x7E, 0x3C, 0x00)
-- Pared decorativa: trazo vertical central
setchar(PARED_GLIFO, 0x18, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x18)
end
-- ============================================================
-- INICIALIZACIÓN DEL MAPA
-- Cada celda guarda { tipo, color, puntos }. Las celdas vacías
-- tienen puntos=0 para simplificar el código de colisión.
-- ============================================================
function init_mapa()
for x = 0, ANCHO-1 do
mapa[x] = {}
for y = 0, ALTO-1 do
mapa[x][y] = { tipo=T_VACIO, color=COL_FONDO, puntos=0 }
end
end
end
-- Las filas de ladrillos del original. Se llama después de cargar la paleta.
function configurar_filas()
FILAS_LADRILLOS = {
{ fila=1, color=COL_LADR_TOP, puntos=20 },
{ fila=2, color=COL_LADR_MID, puntos=10 },
{ fila=5, color=COL_LADR_LOW, puntos=5 },
{ fila=6, color=COL_LADR_LOW, puntos=5 },
}
end
function generar_ladrillos()
init_mapa()
ladrillos_vivos = 0
for _, fl in ipairs(FILAS_LADRILLOS) do
for x = PELOTA_X_MIN, PELOTA_X_MAX do
mapa[x][fl.fila] = { tipo=T_LADRILLO, color=fl.color, puntos=fl.puntos }
ladrillos_vivos = ladrillos_vivos + 1
end
end
end
function tipo_en(x, y)
if x < 0 or x >= ANCHO or y < 0 or y >= ALTO then return T_VACIO end
return mapa[x][y].tipo
end
-- ============================================================
-- RENDER
-- ============================================================
function pintar_fondo()
color(COL_TEXTO, COL_FONDO)
cls()
end
function pintar_paredes()
color(COL_PARED, COL_FONDO)
for y = 0, PALA_Y do
print(chr(PARED_GLIFO), 0, y)
print(chr(PARED_GLIFO), ANCHO - 1, y)
end
end
function pintar_mapa()
for x = 0, ANCHO-1 do
for y = 0, ALTO-1 do
local c = mapa[x][y]
if c.tipo == T_LADRILLO then
color(c.color, COL_FONDO)
print(chr(LADRILLO), x, y)
end
end
end
end
function pintar_pala()
color(COL_PALA, COL_FONDO)
for i = 0, PALA_W - 1 do
print(chr(PALA_GLIFO), pala.x + i, PALA_Y)
end
end
function pintar_pelota()
color(COL_PELOTA, COL_FONDO)
print(chr(PELOTA_GLIFO), pelota.x, pelota.y)
end
function pintar_hud()
color(COL_HUD_FG, COL_HUD_BG)
local blank = ""
for i = 1, ANCHO do blank = blank .. " " end
print(blank, 0, ALTO - 1)
print("Maximo "..string.format("%04d", maximo), 1, ALTO - 1)
print("Puntos "..string.format("%04d", puntos), 15, ALTO - 1)
print("Pelotas "..tostr(pelotas), 29, ALTO - 1)
end
-- ============================================================
-- LÓGICA DE LA PALA
-- ============================================================
function tic_pala()
if (cnt() - pala.ultimo_tic) < TICS_PALA then return end
if btn(KEY_LEFT) or btn(KEY_O) then
pala.x = pala.x - PALA_STEP
if pala.x < PALA_X_MIN then pala.x = PALA_X_MIN end
pala.ultimo_tic = cnt()
elseif btn(KEY_RIGHT) or btn(KEY_P) then
pala.x = pala.x + PALA_STEP
if pala.x > PALA_X_MAX then pala.x = PALA_X_MAX end
pala.ultimo_tic = cnt()
end
end
function pelota_sobre_pala(px, py)
return py == PALA_Y and px >= pala.x and px < pala.x + PALA_W
end
-- ============================================================
-- LÓGICA DE LA PELOTA
-- ============================================================
function reset_pelota()
pelota.x = pala.x + flr(PALA_W / 2)
pelota.y = PELOTA_Y_INI
pelota.xa = (rnd(2) == 0) and 1 or -1
pelota.ya = 1
pelota.ultimo_tic = cnt()
end
function destruir_ladrillo(x, y)
local m = mapa[x][y]
puntos = puntos + m.puntos
mapa[x][y] = { tipo=T_VACIO, color=COL_FONDO, puntos=0 }
ladrillos_vivos = ladrillos_vivos - 1
sfx_ladrillo()
end
function tic_pelota()
local nx = pelota.x + pelota.xa
local ny = pelota.y + pelota.ya
-- Rebote en paredes laterales
if nx < PELOTA_X_MIN then
nx = PELOTA_X_MIN
pelota.xa = -pelota.xa
sfx_pared()
elseif nx > PELOTA_X_MAX then
nx = PELOTA_X_MAX
pelota.xa = -pelota.xa
sfx_pared()
end
-- Rebote en techo
if ny < TECHO_Y then
ny = TECHO_Y
pelota.ya = -pelota.ya
sfx_pared()
end
-- Rebote en pala
if pelota_sobre_pala(nx, ny) then
pelota.ya = -pelota.ya
ny = PALA_Y - 1
sfx_pala()
-- Efecto angular: si la pelota venía de fuera de la pala, invierte xa
if pelota.x < pala.x or pelota.x >= pala.x + PALA_W then
pelota.xa = -pelota.xa
-- Re-ajustar nx con el xa nuevo, sin pasar de paredes
nx = pelota.x + pelota.xa
if nx < PELOTA_X_MIN then nx = PELOTA_X_MIN end
if nx > PELOTA_X_MAX then nx = PELOTA_X_MAX end
end
end
-- Colisión con ladrillo en (nx, ny)
if tipo_en(nx, ny) == T_LADRILLO then
destruir_ladrillo(nx, ny)
pelota.ya = -pelota.ya
-- Mantener nx, ny pero anular el movimiento vertical "dentro" del ladrillo:
-- la pelota rebota antes de entrar, así que ny se reposiciona una fila atrás.
ny = pelota.y
end
-- Pelota perdida: ha caído por debajo de la línea de la pala
if ny > PALA_Y then
pelota.x = nx
pelota.y = ny
pelota_perdida()
return
end
pelota.x = nx
pelota.y = ny
end
function pelota_perdida()
pelotas = pelotas - 1
sfx_perdida()
if pelotas <= 0 then
if puntos > maximo then
maximo = puntos
nom_record = "AAA"
record_letra_idx = 1
hay_nuevo_record = true
else
hay_nuevo_record = false
end
sfx_gameover()
set_estado(ESTADO_GAMEOVER)
else
set_estado(ESTADO_PERDIDA)
end
end
-- ============================================================
-- TRANSICIONES Y PARTIDA
-- ============================================================
function nueva_partida()
puntos = 0
pelotas = PELOTAS_INI
pala.x = PALA_X_INI
generar_ladrillos()
reset_pelota()
end
function siguiente_pantalla()
generar_ladrillos()
pala.x = PALA_X_INI
reset_pelota()
end
-- ============================================================
-- RECORDS (igual patrón que bombardero: 5 bytes score + 3 bytes nombre)
-- ============================================================
function cargar_record()
local f = io.open("records", "rb")
if not f then return end
local data = f:read(8)
f:close()
if not data or #data < 8 then return end
local b = { string.byte(data, 1, 8) }
maximo = b[1]*10000 + b[2]*1000 + b[3]*100 + b[4]*10 + b[5]
if b[6] >= 32 and b[6] < 127
and b[7] >= 32 and b[7] < 127
and b[8] >= 32 and b[8] < 127 then
nom_record = string.char(b[6], b[7], b[8])
end
end
function guardar_record()
local f = io.open("records", "wb")
if not f then return end
local p = maximo
local d5 = flr(p / 10000); p = p - d5*10000
local d4 = flr(p / 1000); p = p - d4*1000
local d3 = flr(p / 100); p = p - d3*100
local d2 = flr(p / 10); p = p - d2*10
local d1 = p
f:write(string.char(d5, d4, d3, d2, d1,
string.byte(nom_record, 1) or 65,
string.byte(nom_record, 2) or 65,
string.byte(nom_record, 3) or 65))
f:close()
end
-- ============================================================
-- SFX
-- ============================================================
function sfx_pala() sound(800, 3) end
function sfx_pared() sound(400, 2) end
function sfx_ladrillo() sound(1600, 4) end
function sfx_perdida() play("l1o3cba") end
function sfx_select() sound(2000, 3) end
function sfx_gameover() play("l2o3bal1gfedco2c") end
function sfx_pantalla() play("l1o4ceg>c") end
-- ============================================================
-- ESTADOS — TÍTULO
-- ============================================================
function update_titulo()
pintar_fondo()
color(COL_TITULO, COL_FONDO)
print("R E B O T E S", 13, 4)
color(COL_TEXTO, COL_FONDO)
print("(Alexander Martin / AMSOFT 1984)", 4, 6)
print("port a ascii — Sergi Valor, 2026", 4, 8)
-- Filitas de ladrillos decorativas
color(COL_LADR_TOP, COL_FONDO)
for x = 14, 25 do print(chr(LADRILLO), x, 11) end
color(COL_LADR_MID, COL_FONDO)
for x = 14, 25 do print(chr(LADRILLO), x, 12) end
color(COL_LADR_LOW, COL_FONDO)
for x = 14, 25 do print(chr(LADRILLO), x, 13) end
color(COL_PALA, COL_FONDO)
for i = 0, PALA_W - 1 do print(chr(PALA_GLIFO), 18 + i, 16) end
color(COL_PELOTA, COL_FONDO)
print(chr(PELOTA_GLIFO), 19, 15)
color(COL_RECORD, COL_FONDO)
print("RECORD "..string.format("%04d", maximo).." "..nom_record, 12, 19)
if (cnt() // 30) % 2 == 0 then
color(COL_PROMPT, COL_FONDO)
print("Pulsa ESPACIO para jugar", 8, 22)
end
color(COL_TEXTO, COL_FONDO)
print("I = instrucciones", 11, 24)
if btnp(KEY_SPACE) then
sfx_select()
nueva_partida()
set_estado(ESTADO_JUEGO)
elseif btnp(KEY_I) then
sfx_select()
set_estado(ESTADO_INSTRUC)
end
end
-- ============================================================
-- ESTADOS — INSTRUCCIONES
-- ============================================================
function update_instruc()
pintar_fondo()
color(COL_TITULO, COL_FONDO)
print("- INSTRUCCIONES -", 11, 1)
color(COL_TEXTO, COL_FONDO)
print("Mueve la pala con las flechas", 5, 4)
print("izquierda y derecha (o O / P).", 5, 5)
print("Rompe los ladrillos con la", 5, 8)
print("pelota. Puntos por fila:", 5, 9)
color(COL_LADR_TOP, COL_FONDO)
print(chr(LADRILLO).." 20 puntos (fila superior)", 5, 11)
color(COL_LADR_MID, COL_FONDO)
print(chr(LADRILLO).." 10 puntos (segunda fila)", 5, 12)
color(COL_LADR_LOW, COL_FONDO)
print(chr(LADRILLO)..chr(LADRILLO).." 5 puntos (filas bajas)", 5, 13)
color(COL_TEXTO, COL_FONDO)
print("Tienes "..tostr(PELOTAS_INI).." pelotas.", 5, 16)
print("Limpia la pantalla y se", 5, 18)
print("regenera con otra tanda.", 5, 19)
if (cnt() // 30) % 2 == 0 then
color(COL_PROMPT, COL_FONDO)
print("Pulsa una tecla para volver", 6, 23)
end
if btnp(KEY_SPACE) or btnp(KEY_RETURN) or btnp(KEY_ESCAPE) then
sfx_select()
set_estado(ESTADO_TITULO)
end
end
-- ============================================================
-- ESTADOS — JUEGO
-- ============================================================
function update_juego()
tic_pala()
if (cnt() - pelota.ultimo_tic) >= TICS_PELOTA then
pelota.ultimo_tic = cnt()
tic_pelota()
if estado ~= ESTADO_JUEGO then return end
if ladrillos_vivos <= 0 then
sfx_pantalla()
set_estado(ESTADO_PANTALLA)
return
end
end
pintar_fondo()
pintar_paredes()
pintar_mapa()
pintar_pala()
pintar_pelota()
pintar_hud()
end
-- ============================================================
-- ESTADOS — PELOTA PERDIDA (pausa breve antes de lanzar la siguiente)
-- ============================================================
function update_perdida()
pintar_fondo()
pintar_paredes()
pintar_mapa()
pintar_pala()
pintar_hud()
color(COL_GAMEOVER, COL_FONDO)
print(" PELOTA PERDIDA ", 12, 14)
if t_estado_ms() >= MS_PERDIDA then
reset_pelota()
set_estado(ESTADO_JUEGO)
end
end
-- ============================================================
-- ESTADOS — PANTALLA LIMPIADA
-- ============================================================
function update_pantalla()
pintar_fondo()
pintar_paredes()
pintar_pala()
pintar_hud()
color(COL_PROMPT, COL_FONDO)
print(" PANTALLA LIMPIA! ", 11, 14)
if t_estado_ms() >= MS_PANTALLA then
siguiente_pantalla()
set_estado(ESTADO_JUEGO)
end
end
-- ============================================================
-- ESTADOS — GAME OVER (+ entrada de nombre si récord)
-- ============================================================
function update_gameover()
pintar_fondo()
pintar_paredes()
pintar_mapa()
pintar_pala()
pintar_hud()
color(COL_GAMEOVER, COL_FONDO)
print(" G A M E O V E R ", 10, 12)
color(COL_TEXTO, COL_FONDO)
print("Puntos: "..string.format("%04d", puntos), 14, 15)
if hay_nuevo_record then
color(COL_RECORD, COL_FONDO)
print("NUEVO RECORD!", 13, 17)
print("Nombre: "..nom_record, 14, 19)
if (cnt() // 30) % 2 == 0 then
color(COL_PROMPT, COL_FONDO)
print("Pulsa A..Z (3 letras)", 9, 21)
end
if t_estado_ms() >= MS_GAMEOVER_MIN then
for sc = KEY_A, KEY_Z do
if btnp(sc) then
local lt = string.char(65 + sc - KEY_A)
nom_record = string.sub(nom_record, 1, record_letra_idx-1)
..lt..
string.sub(nom_record, record_letra_idx+1)
record_letra_idx = record_letra_idx + 1
sfx_select()
if record_letra_idx > 3 then
guardar_record()
hay_nuevo_record = false
set_estado(ESTADO_TITULO)
end
return
end
end
end
else
if (cnt() // 30) % 2 == 0 then
color(COL_PROMPT, COL_FONDO)
print("Pulsa ESPACIO para volver", 8, 20)
end
if t_estado_ms() >= MS_GAMEOVER_MIN and btnp(KEY_SPACE) then
set_estado(ESTADO_TITULO)
end
end
end
-- ============================================================
-- BUCLE PRINCIPAL
-- ============================================================
function init()
wintitle("© 1984 Rebotes — Alexander Martin")
mode(MODO)
border(COL_FONDO)
color(COL_TEXTO, COL_FONDO)
definir_glifos()
configurar_filas()
init_mapa()
cargar_record()
set_estado(ESTADO_TITULO)
cls()
end
function update()
if btnp(KEY_ESCAPE) then os.exit(0) end
if estado == ESTADO_TITULO then update_titulo()
elseif estado == ESTADO_INSTRUC then update_instruc()
elseif estado == ESTADO_JUEGO then update_juego()
elseif estado == ESTADO_PERDIDA then update_perdida()
elseif estado == ESTADO_PANTALLA then update_pantalla()
elseif estado == ESTADO_GAMEOVER then update_gameover()
end
end