765 lines
26 KiB
Lua
765 lines
26 KiB
Lua
-- ============================================================
|
|
-- BOMBARDERO — port a la fantasy console "ascii" del original
|
|
-- de Dave Town para Amstrad CPC (AMSOFT, 1984).
|
|
-- Sergi Valor, 2026.
|
|
-- ============================================================
|
|
|
|
-- ============================================================
|
|
-- CONFIGURACION (todo lo tuneable vive aqui)
|
|
-- ============================================================
|
|
|
|
-- Pantalla y layout
|
|
MODO = 1 -- mode(1) = 40x30, color por carácter
|
|
ANCHO = 40
|
|
ALTO = 30
|
|
|
|
AVION_Y_MIN = 1 -- fila más alta a la que puede llegar el avión
|
|
AVION_Y_MAX = 21 -- última fila antes de aterrizar (~ fila 21 del original)
|
|
EDIF_BASE_Y = 22 -- los edificios apoyan aquí (justo encima del suelo)
|
|
SUELO_Y = 23 -- línea visible del suelo
|
|
HUD_Y0 = 25 -- banda HUD (filas 25..27)
|
|
|
|
-- Avión (2 chars de ancho)
|
|
AVION_W = 2
|
|
AVION_X_MIN = 0
|
|
AVION_X_MAX = ANCHO - AVION_W -- 38: a partir de aquí el avión "envuelve"
|
|
AVION_X_INI = 0
|
|
AVION_Y_INI = AVION_Y_MIN + 1 -- empieza en fila 2 (como el original)
|
|
|
|
-- Edificios
|
|
EDIF_X_INI = 4
|
|
EDIF_X_FIN = 37
|
|
EDIF_X_STEP = 3 -- separación entre edificios (con avión de 2 chars deja 1 hueco)
|
|
EDIF_RANGO = 7 -- variabilidad aleatoria del tejado
|
|
-- Tejado más bajo (= edificio más alto) por nivel del jugador.
|
|
-- Nivel 0 (AS) → tejados desde fila 9 (edificios altos, casi tocando el avión).
|
|
-- Nivel 5 (principiante) → tejados desde fila 16 (edificios bajitos).
|
|
EDIF_TEJADO_BASE = 9
|
|
EDIF_TEJADO_NIVEL = 1 -- cada punto de nivel sube los tejados esta cantidad
|
|
|
|
-- Niveles y velocidad (escala "intuitiva": 0 = fácil/lento, máx = duro/rápido)
|
|
NIVEL_MIN = 0
|
|
NIVEL_MAX = 5
|
|
VEL_MIN = 0
|
|
VEL_MAX = 9
|
|
TICS_AVION_BASE = 11 -- frames entre paso de avión a vel = 0 (≈ 5 mov/s)
|
|
TICS_AVION_MIN = 2 -- a vel = 9 (≈ 30 mov/s)
|
|
TICS_BOMBA = 4 -- frames entre paso de bomba (cae más rápido que el avión a vel baja)
|
|
|
|
-- Profundidad de perforación de la bomba: en cada impacto destruye un número
|
|
-- aleatorio en [BOMBA_PERF_MIN, BOMBA_PERF_MAX] de bloques de la columna y se
|
|
-- consume (fiel al "FOR tr=1 TO INT(RND*3)+1" del original).
|
|
BOMBA_PERF_MIN = 1
|
|
BOMBA_PERF_MAX = 3
|
|
|
|
-- Subida de dificultad al aterrizar (el original bajaba `vel-20` y `nivel-1`)
|
|
SUBIDA_VEL_POR_ATERRIZAJE = 1
|
|
SUBIDA_NIVEL_POR_ATERRIZAJE = 1 -- nivel sube → edificios más altos (más difícil)
|
|
|
|
-- Puntuaciones
|
|
PUNTOS_BLOQUE = 5
|
|
PUNTOS_ATERRIZAJE = 100
|
|
PUNTOS_ATERRIZ_PEN = 10 -- restar por punto de nivel (más alto = menos puntos)
|
|
|
|
-- Vidas (el original no tenía, se acababa al primer choque)
|
|
VIDAS_INI = 1
|
|
|
|
-- Duraciones de transición (en ms, leídas con time())
|
|
MS_ATERRIZAJE = 1200 -- pausa al aterrizar antes de generar siguiente pantalla
|
|
MS_CHOQUE = 1500 -- animación de explosión del avión
|
|
MS_GAMEOVER_MIN = 800 -- antes de aceptar input en game over
|
|
|
|
-- ============================================================
|
|
-- PALETA (mapeo aproximado CPC firmware → CGA disponible)
|
|
-- ============================================================
|
|
COL_FONDO = COLOR_BLACK
|
|
COL_AVION = COLOR_LIGHT_CYAN
|
|
COL_BOMBA = COLOR_LIGHT_RED
|
|
COL_SUELO = COLOR_RED
|
|
COL_TEXTO = COLOR_LIGHT_GRAY
|
|
COL_TITULO = COLOR_YELLOW
|
|
COL_PROMPT = COLOR_LIGHT_GREEN
|
|
COL_HUD_FG = COLOR_LIGHT_GRAY
|
|
COL_HUD_BG = COLOR_BLUE
|
|
COL_RECORD = COLOR_YELLOW
|
|
COL_GAMEOVER = COLOR_LIGHT_RED
|
|
|
|
-- Colores cíclicos para edificios (el original iba con PEN base-2: cada columna
|
|
-- distinto). Se aplica módulo sobre el índice de edificio.
|
|
COL_EDIFICIOS = {
|
|
COLOR_BROWN,
|
|
COLOR_LIGHT_GREEN,
|
|
COLOR_LIGHT_BLUE,
|
|
COLOR_LIGHT_MAGENTA,
|
|
COLOR_CYAN,
|
|
COLOR_YELLOW,
|
|
COLOR_LIGHT_RED,
|
|
COLOR_GREEN,
|
|
COLOR_MAGENTA,
|
|
}
|
|
|
|
-- Ciclo de colores para la animación de explosión (mismo patrón "FOR t=1 TO 10:PEN t" del original)
|
|
COL_EXPLOSION = {
|
|
COLOR_YELLOW, COLOR_LIGHT_RED, COLOR_RED, COLOR_LIGHT_MAGENTA,
|
|
COLOR_WHITE, COLOR_YELLOW, COLOR_LIGHT_RED, COLOR_RED,
|
|
COLOR_LIGHT_MAGENTA, COLOR_WHITE,
|
|
}
|
|
|
|
-- ============================================================
|
|
-- GLIFOS (códigos que vamos a redefinir con setchar)
|
|
-- ============================================================
|
|
AVION_L = 241 -- mitad izquierda del avión (bitmap idéntico al SYMBOL del original)
|
|
AVION_R = 242 -- mitad derecha
|
|
BLOQUE = 143 -- ladrillo (el original usaba este código del char-ROM CPC)
|
|
BLOQUE2 = 244 -- variante (el original alternaba con chr(8) para superponer)
|
|
BOMBA = 252 -- bomba
|
|
EXPLO_A = 253 -- frame A de la explosión
|
|
EXPLO_B = 238 -- frame B de la explosión
|
|
|
|
-- ============================================================
|
|
-- ESTADOS (máquina de estados global)
|
|
-- ============================================================
|
|
ESTADO_TITULO = "titulo"
|
|
ESTADO_INSTRUC = "instruc"
|
|
ESTADO_NIVEL = "nivel" -- pidiendo elección de nivel (0-5)
|
|
ESTADO_VELOCIDAD = "velocidad" -- pidiendo elección de velocidad (0-9)
|
|
ESTADO_JUEGO = "juego"
|
|
ESTADO_ATERRIZA = "aterriza"
|
|
ESTADO_CHOQUE = "choque"
|
|
ESTADO_GAMEOVER = "gameover"
|
|
|
|
-- ============================================================
|
|
-- TIPOS DE CELDA DEL MAPA
|
|
-- ============================================================
|
|
T_VACIO = 0
|
|
T_EDIFICIO = 1
|
|
|
|
-- ============================================================
|
|
-- ESTADO GLOBAL DEL JUEGO
|
|
-- ============================================================
|
|
mapa = {} -- mapa[x][y] = { tipo=, color= }
|
|
avion = { x=0, y=0 }
|
|
bomba = { activa=false, fase="cayendo", x=0, y=0, restantes=0, ultimo_tic=0 }
|
|
nivel = 0 -- el que ha elegido el jugador (0..NIVEL_MAX)
|
|
nivel_act = 0 -- el que sube al aterrizar (puede crecer por encima)
|
|
vel = 0 -- velocidad (0..VEL_MAX)
|
|
puntos = 0
|
|
maxi = 0
|
|
nom_record = "AAA"
|
|
vidas = VIDAS_INI
|
|
|
|
ultimo_tic_avion = 0 -- contador de frames para temporizar al avión
|
|
|
|
estado = ESTADO_TITULO
|
|
estado_t0_ms = 0 -- time() cuando se entró al estado actual
|
|
estado_t0_fr = 0 -- cnt() cuando se entró al estado actual
|
|
|
|
-- Para la pantalla "elige nombre" tras batir récord
|
|
record_letra_idx = 1
|
|
hay_nuevo_record = false
|
|
|
|
-- ============================================================
|
|
-- UTILIDADES
|
|
-- ============================================================
|
|
function set_estado(nuevo)
|
|
estado = nuevo
|
|
estado_t0_ms = time()
|
|
estado_t0_fr = cnt()
|
|
end
|
|
|
|
function tiempo_en_estado_ms() return time() - estado_t0_ms end
|
|
function tiempo_en_estado_fr() return cnt() - estado_t0_fr end
|
|
|
|
function tics_por_paso_avion()
|
|
-- vel 0 → TICS_AVION_BASE, vel VEL_MAX → TICS_AVION_MIN
|
|
local span = TICS_AVION_BASE - TICS_AVION_MIN
|
|
local t = TICS_AVION_BASE - flr(vel * span / VEL_MAX)
|
|
if t < TICS_AVION_MIN then t = TICS_AVION_MIN end
|
|
return t
|
|
end
|
|
|
|
-- ============================================================
|
|
-- GLIFOS PERSONALIZADOS
|
|
-- ============================================================
|
|
function definir_glifos()
|
|
-- Avión: bitmaps idénticos a los SYMBOL del bombardero.bas original
|
|
setchar(AVION_L, 0x40, 0x60, 0x70, 0x7F, 0x7F, 0xEF, 0x07, 0x00)
|
|
setchar(AVION_R, 0x00, 0x32, 0x7A, 0xFE, 0xFA, 0xF2, 0xE0, 0x00)
|
|
|
|
-- Ladrillo macizo (con un patrón de mortero entre filas)
|
|
setchar(BLOQUE, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0x00, 0xFF)
|
|
|
|
-- Variante de ladrillo con junta desplazada (alterna con BLOQUE para textura)
|
|
setchar(BLOQUE2, 0xFF, 0xEF, 0x00, 0xFB, 0xFF, 0xEF, 0x00, 0xFB)
|
|
|
|
-- Bomba: óvalo con aleta
|
|
setchar(BOMBA, 0x18, 0x3C, 0x7E, 0x7E, 0x7E, 0x3C, 0x18, 0x42)
|
|
|
|
-- Explosión frame A: estrella radial
|
|
setchar(EXPLO_A, 0x99, 0x5A, 0x3C, 0xE7, 0xE7, 0x3C, 0x5A, 0x99)
|
|
|
|
-- Explosión frame B: chispazo más denso
|
|
setchar(EXPLO_B, 0x42, 0xA5, 0xDB, 0x7E, 0x7E, 0xDB, 0xA5, 0x42)
|
|
end
|
|
|
|
-- ============================================================
|
|
-- MAPA Y EDIFICIOS
|
|
-- ============================================================
|
|
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, glifo=BLOQUE }
|
|
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
|
|
|
|
-- Genera la skyline en base al nivel actual de la partida (nivel_act).
|
|
-- A más nivel, edificios más bajos (= más fácil): mismo comportamiento que el
|
|
-- bucle FOR base=5 TO 15 / FOR altura=21 TO INT(RND*8+nivel) del original.
|
|
function generar_edificios()
|
|
init_mapa()
|
|
local idx_color = 0
|
|
local tejado_min = EDIF_TEJADO_BASE + nivel_act * EDIF_TEJADO_NIVEL
|
|
|
|
for x = EDIF_X_INI, EDIF_X_FIN, EDIF_X_STEP do
|
|
local tejado = rnd(EDIF_RANGO) + tejado_min
|
|
if tejado > EDIF_BASE_Y then
|
|
-- Si el "tejado calculado" cae por debajo de la base, no hay edificio
|
|
-- (mismo "bug-feature" del original que dejaba huecos en niveles fáciles).
|
|
else
|
|
local col = COL_EDIFICIOS[(idx_color % #COL_EDIFICIOS) + 1]
|
|
for y = tejado, EDIF_BASE_Y do
|
|
-- Alternar BLOQUE / BLOQUE2 para variedad de textura
|
|
local glifo = ((y - tejado) % 2 == 0) and BLOQUE or BLOQUE2
|
|
mapa[x][y] = { tipo=T_EDIFICIO, color=col, glifo=glifo }
|
|
end
|
|
end
|
|
idx_color = idx_color + 1
|
|
end
|
|
end
|
|
|
|
-- ============================================================
|
|
-- RENDER
|
|
-- ============================================================
|
|
function pintar_fondo()
|
|
color(COL_TEXTO, COL_FONDO)
|
|
cls()
|
|
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_EDIFICIO then
|
|
color(c.color, COL_FONDO)
|
|
print(chr(c.glifo), x, y)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function pintar_suelo()
|
|
color(COL_SUELO, COL_FONDO)
|
|
-- Una línea horizontal usando el char 154 del CPC (─ trazo medio).
|
|
-- Si no queda bien visualmente, cambiar por chr(140) o un BLOQUE bajo.
|
|
local linea = ""
|
|
for i = 1, ANCHO do linea = linea .. chr(154) end
|
|
print(linea, 0, SUELO_Y)
|
|
end
|
|
|
|
function pintar_avion()
|
|
color(COL_AVION, COL_FONDO)
|
|
print(chr(AVION_L), avion.x, avion.y)
|
|
print(chr(AVION_R), avion.x+1, avion.y)
|
|
end
|
|
|
|
function pintar_bomba()
|
|
if not bomba.activa then return end
|
|
color(COL_BOMBA, COL_FONDO)
|
|
print(chr(BOMBA), bomba.x, bomba.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, HUD_Y0)
|
|
print(blank, 0, HUD_Y0 + 1)
|
|
print(blank, 0, HUD_Y0 + 2)
|
|
|
|
color(COL_HUD_FG, COL_HUD_BG)
|
|
print("PUNTOS "..string.format("%05d", puntos), 1, HUD_Y0)
|
|
print("MAX "..string.format("%05d", maxi).." "..nom_record, 22, HUD_Y0)
|
|
print("NIVEL "..tostr(nivel_act).." VEL "..tostr(vel), 1, HUD_Y0 + 1)
|
|
print("ESPACIO=bomba", 22, HUD_Y0 + 1)
|
|
end
|
|
|
|
-- ============================================================
|
|
-- LÓGICA DEL AVIÓN
|
|
-- ============================================================
|
|
function reset_avion()
|
|
avion.x = AVION_X_INI
|
|
avion.y = AVION_Y_INI
|
|
ultimo_tic_avion = cnt()
|
|
end
|
|
|
|
-- Devuelve true si el avión choca con un edificio en su posición actual
|
|
function avion_choca()
|
|
return tipo_en(avion.x, avion.y) == T_EDIFICIO
|
|
or tipo_en(avion.x+1, avion.y) == T_EDIFICIO
|
|
end
|
|
|
|
function avanza_avion()
|
|
avion.x = avion.x + 1
|
|
if avion.x > AVION_X_MAX then
|
|
-- Llegó al final de la pasada: si estaba en la última fila, aterriza
|
|
if avion.y >= AVION_Y_MAX then
|
|
iniciar_aterrizaje()
|
|
return
|
|
end
|
|
avion.x = AVION_X_MIN
|
|
avion.y = avion.y + 1
|
|
end
|
|
|
|
if avion_choca() then
|
|
iniciar_choque()
|
|
end
|
|
end
|
|
|
|
-- ============================================================
|
|
-- LÓGICA DE LA BOMBA
|
|
-- ============================================================
|
|
function lanzar_bomba()
|
|
if bomba.activa then return end
|
|
-- Bomba cae desde la cola del avión (mitad derecha), 1 fila por debajo
|
|
bomba.activa = true
|
|
bomba.fase = "cayendo"
|
|
bomba.x = avion.x + 1
|
|
bomba.y = avion.y + 1
|
|
bomba.restantes = 0
|
|
bomba.ultimo_tic = cnt()
|
|
sfx_lanzar()
|
|
end
|
|
|
|
function avanza_bomba()
|
|
if not bomba.activa then return end
|
|
|
|
if bomba.fase == "cayendo" then
|
|
if tipo_en(bomba.x, bomba.y + 1) == T_EDIFICIO then
|
|
-- Impacto: arrancar la fase de perforación (1..3 bloques)
|
|
bomba.fase = "explotando"
|
|
local span = BOMBA_PERF_MAX - BOMBA_PERF_MIN + 1
|
|
bomba.restantes = rnd(span) + BOMBA_PERF_MIN
|
|
else
|
|
bomba.y = bomba.y + 1
|
|
if bomba.y >= SUELO_Y then
|
|
bomba.activa = false
|
|
sfx_suelo()
|
|
end
|
|
end
|
|
return
|
|
end
|
|
|
|
-- fase == "explotando": destruye el bloque que tiene debajo, baja y resta
|
|
local ty = bomba.y + 1
|
|
if ty < SUELO_Y and tipo_en(bomba.x, ty) == T_EDIFICIO then
|
|
mapa[bomba.x][ty] = { tipo=T_VACIO, color=COL_FONDO, glifo=BLOQUE }
|
|
puntos = puntos + PUNTOS_BLOQUE
|
|
sfx_explo()
|
|
end
|
|
bomba.y = bomba.y + 1
|
|
bomba.restantes = bomba.restantes - 1
|
|
if bomba.restantes <= 0 or bomba.y >= SUELO_Y then
|
|
bomba.activa = false
|
|
end
|
|
end
|
|
|
|
-- ============================================================
|
|
-- TRANSICIONES
|
|
-- ============================================================
|
|
function iniciar_aterrizaje()
|
|
sfx_aterrizar()
|
|
puntos = puntos + max(0, PUNTOS_ATERRIZAJE - PUNTOS_ATERRIZ_PEN * nivel_act)
|
|
set_estado(ESTADO_ATERRIZA)
|
|
end
|
|
|
|
function iniciar_choque()
|
|
sfx_choque()
|
|
set_estado(ESTADO_CHOQUE)
|
|
end
|
|
|
|
function siguiente_pantalla()
|
|
nivel_act = nivel_act + SUBIDA_NIVEL_POR_ATERRIZAJE
|
|
vel = vel + SUBIDA_VEL_POR_ATERRIZAJE
|
|
if vel > VEL_MAX then vel = VEL_MAX end
|
|
bomba.activa = false
|
|
reset_avion()
|
|
generar_edificios()
|
|
end
|
|
|
|
function nueva_partida()
|
|
nivel_act = nivel
|
|
puntos = 0
|
|
vidas = VIDAS_INI
|
|
bomba.activa = false
|
|
reset_avion()
|
|
generar_edificios()
|
|
end
|
|
|
|
-- ============================================================
|
|
-- RECORDS (persistencia en fichero "records", igual que pepe_runner)
|
|
-- Formato: 5 bytes (centenas-millares..unidades) + 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) }
|
|
maxi = 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 = maxi
|
|
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_lanzar() play("l0o4cdefg") end
|
|
function sfx_explo() sound(4000, 8) end
|
|
function sfx_suelo() sound(800, 10) end
|
|
function sfx_aterrizar() play("l1o4ceg>c") end
|
|
function sfx_choque() play("l1o3bal0gfedco2c") end
|
|
function sfx_select() sound(2000, 3) end
|
|
function sfx_gameover() play("l2o3bal1gfedco2c") end
|
|
|
|
-- ============================================================
|
|
-- ESTADOS — TÍTULO
|
|
-- ============================================================
|
|
function update_titulo()
|
|
pintar_fondo()
|
|
|
|
color(COL_TITULO, COL_FONDO)
|
|
print("B O M B A R D E R O", 10, 4)
|
|
color(COL_TEXTO, COL_FONDO)
|
|
print("(Dave Town / AMSOFT 1984)", 7, 6)
|
|
print("port a ascii — Sergi Valor, 2026", 3, 8)
|
|
|
|
-- Avión decorativo de muestra
|
|
color(COL_AVION, COL_FONDO)
|
|
print(chr(AVION_L), 18, 11)
|
|
print(chr(AVION_R), 19, 11)
|
|
|
|
color(COL_RECORD, COL_FONDO)
|
|
print("RECORD "..string.format("%05d", maxi).." "..nom_record, 11, 14)
|
|
|
|
if (cnt() // 30) % 2 == 0 then
|
|
color(COL_PROMPT, COL_FONDO)
|
|
print("Pulsa ESPACIO para jugar", 8, 18)
|
|
end
|
|
color(COL_TEXTO, COL_FONDO)
|
|
print("I = instrucciones", 11, 20)
|
|
|
|
if btnp(KEY_SPACE) then
|
|
sfx_select()
|
|
set_estado(ESTADO_NIVEL)
|
|
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("Pilotas un avion sobre una ciudad", 3, 4)
|
|
print("desierta. Debes bombardear los", 3, 5)
|
|
print("edificios para poder aterrizar.", 3, 6)
|
|
|
|
print("El avion vuela de izquierda a", 3, 8)
|
|
print("derecha y, al llegar al borde,", 3, 9)
|
|
print("vuelve a salir por la izquierda", 3, 10)
|
|
print("una fila MAS BAJO.", 3, 11)
|
|
|
|
print("Si chocas con un edificio: muerte.", 3, 13)
|
|
print("Si aterrizas: subes de nivel y", 3, 14)
|
|
print("velocidad.", 3, 15)
|
|
|
|
print("Solo una bomba a la vez!", 3, 17)
|
|
|
|
color(COL_PROMPT, COL_FONDO)
|
|
print("ESPACIO = lanzar bomba", 3, 20)
|
|
|
|
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 — SELECCIÓN DE NIVEL
|
|
-- ============================================================
|
|
function update_nivel()
|
|
pintar_fondo()
|
|
color(COL_TITULO, COL_FONDO)
|
|
print("ELIGE NIVEL", 14, 8)
|
|
color(COL_TEXTO, COL_FONDO)
|
|
print("0 = AS (edificios altos, dificil)", 3, 11)
|
|
print("5 = PRINCIPIANTE (edif. bajos)", 3, 12)
|
|
|
|
if (cnt() // 30) % 2 == 0 then
|
|
color(COL_PROMPT, COL_FONDO)
|
|
print("Pulsa 0..5", 14, 16)
|
|
end
|
|
|
|
-- KEY_1..KEY_9 = 30..38 y KEY_0 = 39 (no contiguos como dígitos, así que va a mano)
|
|
if btnp(KEY_0) then nivel = 0; arrancar_partida_nivel(); return end
|
|
if btnp(KEY_1) then nivel = 1; arrancar_partida_nivel(); return end
|
|
if btnp(KEY_2) then nivel = 2; arrancar_partida_nivel(); return end
|
|
if btnp(KEY_3) then nivel = 3; arrancar_partida_nivel(); return end
|
|
if btnp(KEY_4) then nivel = 4; arrancar_partida_nivel(); return end
|
|
if btnp(KEY_5) then nivel = 5; arrancar_partida_nivel(); return end
|
|
end
|
|
|
|
function arrancar_partida_nivel()
|
|
sfx_select()
|
|
set_estado(ESTADO_VELOCIDAD)
|
|
end
|
|
|
|
-- ============================================================
|
|
-- ESTADOS — SELECCIÓN DE VELOCIDAD
|
|
-- ============================================================
|
|
function update_velocidad()
|
|
pintar_fondo()
|
|
color(COL_TITULO, COL_FONDO)
|
|
print("ELIGE VELOCIDAD", 12, 8)
|
|
color(COL_TEXTO, COL_FONDO)
|
|
print("0 = LENTA 9 = RAPIDA", 9, 11)
|
|
print("Nivel elegido: "..tostr(nivel), 12, 13)
|
|
|
|
if (cnt() // 30) % 2 == 0 then
|
|
color(COL_PROMPT, COL_FONDO)
|
|
print("Pulsa 0..9", 14, 16)
|
|
end
|
|
|
|
if btnp(KEY_0) then vel = 0; arrancar_juego(); return end
|
|
if btnp(KEY_1) then vel = 1; arrancar_juego(); return end
|
|
if btnp(KEY_2) then vel = 2; arrancar_juego(); return end
|
|
if btnp(KEY_3) then vel = 3; arrancar_juego(); return end
|
|
if btnp(KEY_4) then vel = 4; arrancar_juego(); return end
|
|
if btnp(KEY_5) then vel = 5; arrancar_juego(); return end
|
|
if btnp(KEY_6) then vel = 6; arrancar_juego(); return end
|
|
if btnp(KEY_7) then vel = 7; arrancar_juego(); return end
|
|
if btnp(KEY_8) then vel = 8; arrancar_juego(); return end
|
|
if btnp(KEY_9) then vel = 9; arrancar_juego(); return end
|
|
end
|
|
|
|
function arrancar_juego()
|
|
sfx_select()
|
|
nueva_partida()
|
|
set_estado(ESTADO_JUEGO)
|
|
end
|
|
|
|
-- ============================================================
|
|
-- ESTADOS — JUEGO
|
|
-- ============================================================
|
|
function update_juego()
|
|
-- Input: bomba
|
|
if btnp(KEY_SPACE) then lanzar_bomba() end
|
|
|
|
-- Tic del avión (según velocidad)
|
|
if cnt() - ultimo_tic_avion >= tics_por_paso_avion() then
|
|
ultimo_tic_avion = cnt()
|
|
avanza_avion()
|
|
if estado ~= ESTADO_JUEGO then return end -- aterrizó o chocó
|
|
end
|
|
|
|
-- Tic de la bomba (ritmo propio, independiente)
|
|
if bomba.activa and (cnt() - bomba.ultimo_tic) >= TICS_BOMBA then
|
|
bomba.ultimo_tic = cnt()
|
|
avanza_bomba()
|
|
end
|
|
|
|
-- Render
|
|
pintar_fondo()
|
|
pintar_mapa()
|
|
pintar_suelo()
|
|
pintar_bomba()
|
|
pintar_avion()
|
|
pintar_hud()
|
|
end
|
|
|
|
-- ============================================================
|
|
-- ESTADOS — ATERRIZAJE (transición con pausa de ms)
|
|
-- ============================================================
|
|
function update_aterriza()
|
|
pintar_fondo()
|
|
pintar_mapa()
|
|
pintar_suelo()
|
|
pintar_avion()
|
|
pintar_hud()
|
|
|
|
color(COL_PROMPT, COL_FONDO)
|
|
print(" ATERRIZAJE ! ", 13, 10)
|
|
|
|
if tiempo_en_estado_ms() >= MS_ATERRIZAJE then
|
|
siguiente_pantalla()
|
|
set_estado(ESTADO_JUEGO)
|
|
end
|
|
end
|
|
|
|
-- ============================================================
|
|
-- ESTADOS — CHOQUE (animación de explosión sobre el avión)
|
|
-- ============================================================
|
|
function update_choque()
|
|
pintar_fondo()
|
|
pintar_mapa()
|
|
pintar_suelo()
|
|
pintar_hud()
|
|
|
|
-- Pintar el avión con explosión parpadeante en su posición
|
|
local fr = tiempo_en_estado_fr()
|
|
local idx = (fr // 4) % #COL_EXPLOSION + 1
|
|
color(COL_EXPLOSION[idx], COL_FONDO)
|
|
local glifo = (fr // 2) % 2 == 0 and EXPLO_A or EXPLO_B
|
|
print(chr(glifo), avion.x, avion.y)
|
|
print(chr(glifo), avion.x+1, avion.y)
|
|
|
|
if tiempo_en_estado_ms() >= MS_CHOQUE then
|
|
vidas = vidas - 1
|
|
if vidas <= 0 then
|
|
-- Game over: ¿récord?
|
|
if puntos > maxi then
|
|
maxi = 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
|
|
reset_avion()
|
|
set_estado(ESTADO_JUEGO)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- ============================================================
|
|
-- ESTADOS — GAME OVER
|
|
-- ============================================================
|
|
function update_gameover()
|
|
pintar_fondo()
|
|
pintar_mapa()
|
|
pintar_suelo()
|
|
pintar_hud()
|
|
|
|
color(COL_GAMEOVER, COL_FONDO)
|
|
print(" G A M E O V E R ", 10, 10)
|
|
|
|
color(COL_TEXTO, COL_FONDO)
|
|
print("Puntos: "..string.format("%05d", puntos), 14, 13)
|
|
|
|
if hay_nuevo_record then
|
|
color(COL_RECORD, COL_FONDO)
|
|
print("NUEVO RECORD!", 13, 15)
|
|
print("Nombre: "..nom_record, 14, 17)
|
|
if (cnt() // 30) % 2 == 0 then
|
|
color(COL_PROMPT, COL_FONDO)
|
|
print("Pulsa A..Z (3 letras)", 9, 19)
|
|
end
|
|
|
|
if tiempo_en_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, 18)
|
|
end
|
|
if tiempo_en_estado_ms() >= MS_GAMEOVER_MIN and btnp(KEY_SPACE) then
|
|
set_estado(ESTADO_TITULO)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- ============================================================
|
|
-- BUCLE PRINCIPAL
|
|
-- ============================================================
|
|
function init()
|
|
wintitle("© 1984 Bombardero — Dave Town")
|
|
mode(MODO)
|
|
border(COL_FONDO)
|
|
color(COL_TEXTO, COL_FONDO)
|
|
definir_glifos()
|
|
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_NIVEL then update_nivel()
|
|
elseif estado == ESTADO_VELOCIDAD then update_velocidad()
|
|
elseif estado == ESTADO_JUEGO then update_juego()
|
|
elseif estado == ESTADO_ATERRIZA then update_aterriza()
|
|
elseif estado == ESTADO_CHOQUE then update_choque()
|
|
elseif estado == ESTADO_GAMEOVER then update_gameover()
|
|
end
|
|
end
|