diff --git a/.gitignore b/.gitignore index 7014d68..726df0d 100644 --- a/.gitignore +++ b/.gitignore @@ -31,7 +31,7 @@ $RECYCLE.BIN/ .LSOverride # Icon must end with two \r -Icon +Icon # Thumbnails ._* @@ -67,3 +67,10 @@ Temporary Items # .nfs files are created when an open file is removed but is still being accessed .nfs* +# ---> Bombardero +# Binari de l'intèrpret ascii (es compila a part) +/ascii +# Fitxer de rècord generat pel joc +/records + + diff --git a/ASCII_API.md b/ASCII_API.md new file mode 100644 index 0000000..657a413 --- /dev/null +++ b/ASCII_API.md @@ -0,0 +1,275 @@ +# ASCII — Referencia del intérprete Lua + +Documento extraído del código fuente en `c:/mingw/gitea/ascii/` (principalmente `ascii.cpp`, `lua.cpp`, `play.cpp`, `ascii.h`). Sirve como guía para portar Pepe Runner desde Turbo Pascal a Lua. + +> Versión analizada del intérprete: v0.6.1 aprox (según el mensaje del boot ROM en `lua.cpp`). + +--- + +## 1. Modelo de ejecución + +Cada juego/programa es **un solo fichero `.lua`** que define dos funciones globales: + +```lua +function init() + -- se llama una sola vez al arrancar +end + +function update() + -- se llama cada frame (~60 FPS, vsync) +end +``` + +- El intérprete se invoca como `ascii.exe nombre_juego.lua`. Si no se pasa argumento, intenta cargar `game.lua`. +- También se puede **arrastrar y soltar** un `.lua` sobre la ventana para cargarlo. +- **F5** reinicia el juego (re-llama a `init()` y vuelve a empezar el bucle). +- **ESC** pausa la ejecución y abre una consola de depuración (`> ` prompt). Los comandos `run` y `cont` la cierran. Comandos prefijados con `?` evalúan e imprimen (ej.: `?1+1`). +- Se usa la versión estándar de Lua que está vendorizada en `ascii/lua/` (con `luaL_openlibs`), así que están disponibles `string`, `math`, `table`, etc. + +--- + +## 2. Modos de pantalla — `mode(n)` + +| Modo | Resolución carácter | Resolución pixel | Notas | +|------|--------------------:|-----------------:|-------| +| 0 | 80 × 30 | 640 × 240 | Color único global (no por-carácter) — usado para depuración / texto. `current_color` aplica a toda la pantalla. | +| 1 | 40 × 30 | 320 × 240 | **Modo por defecto.** Color por carácter. | +| 2 | 20 × 15 | 160 × 120 | Mitad de resolución. Cómodo para tiles grandes (ej.: sokoban). | +| 3 | 32 × 24 | 256 × 192 | Estilo "ZX Spectrum" (con bordes anchos). | + +Cada carácter es **8×8 píxeles**. Los gráficos son texto coloreado, no píxeles libres — el "lienzo" es una matriz de celdas (carácter + atributo de color). + +--- + +## 3. Paleta de colores (16, CGA/EGA) + +Constantes Lua predefinidas (`lua.cpp` líneas 610-625): + +| Código | Constante | Aprox. | +|-------:|------------------------|---------------| +| 0 | `COLOR_BLACK` | #000000 | +| 1 | `COLOR_BLUE` | #0000AA | +| 2 | `COLOR_GREEN` | #00AA00 | +| 3 | `COLOR_CYAN` | #00AAAA | +| 4 | `COLOR_RED` | #AA0000 | +| 5 | `COLOR_MAGENTA` | #AA00AA | +| 6 | `COLOR_BROWN` | #AA5500 | +| 7 | `COLOR_LIGHT_GRAY` | #AAAAAA | +| 8 | `COLOR_DARK_GRAY` | #555555 | +| 9 | `COLOR_LIGHT_BLUE` | #5555FF | +| 10 | `COLOR_LIGHT_GREEN` | #55FF55 | +| 11 | `COLOR_LIGHT_CYAN` | #55FFFF | +| 12 | `COLOR_LIGHT_RED` | #FF5555 | +| 13 | `COLOR_LIGHT_MAGENTA` | #FF55FF | +| 14 | `COLOR_YELLOW` | #FFFF55 | +| 15 | `COLOR_WHITE` | #FFFFFF | + +El atributo de color de una celda es 1 byte: nibble bajo = INK (tinta), nibble alto = PAPER (fondo). + +--- + +## 4. API — Funciones expuestas a Lua + +### Pantalla y color + +| Función | Descripción | +|---------|-------------| +| `mode(n)` | Cambia modo de pantalla (0-3) y hace cls. | +| `cls([chr=32])` | Limpia con el carácter dado (32 = espacio). En modo ≠0 además rellena el color attr. | +| `ink(c)` | Color de tinta (0-15). | +| `paper(c)` | Color de fondo (0-15). | +| `border(c)` | Color del borde de la ventana. | +| `color(ink, paper, [border])` | Combina los tres. | +| `locate(x, y)` | Posiciona cursor en celda (x, y). | +| `print(str, [x, y])` | Imprime `str` (sin salto de línea). Si se dan x,y, primero hace `locate`. | +| `crlf()` | CR + LF (mueve cursor a inicio de siguiente línea). | + +### Entrada + +| Función | Descripción | +|---------|-------------| +| `btn(k)` | `true` si la tecla `k` está pulsada *en este frame* (estado SDL_GetKeyboardState). | +| `btnp(k)` | `true` solo en el frame en que la tecla se pulsa (edge). | +| `mousex()` / `mousey()` | Posición del ratón en **coordenadas de carácter** (ya escalado al modo). | +| `mousewheel()` | Delta de la rueda en este frame. | +| `mousebutton(i)` | `true` si el botón `i` está pulsado (1=izq, 2=medio, 3=der; usa `SDL_BUTTON(i)`). | + +> **Nota**: `whichbtn()` está declarado en `ascii.h` y existe en C++, pero **no está expuesto a Lua** (no aparece en los `lua_setglobal` de `lua.cpp`). Para detectar qué tecla se ha pulsado en un frame hay que iterar con `btnp()` sobre las constantes `KEY_*`. + +> **Bug de `tostr` con negativos**: la implementación de `tostr()` en `lua.cpp` (función `intToStr`) hace `(x % 10) + '0'` que con `x = -1` produce `-1 + 48 = 47`, o sea `'/'`. Por tanto `tostr(-1)` devuelve `"/"`, `tostr(-2)` devuelve `"."`, etc. Si vas a imprimir un número que puede ser negativo, usa `string.format("%d", n)` (que sí maneja signo) o clampa con `max(0, n)` antes de pasar a `tostr`. + +Códigos de tecla — todos definidos como globales `KEY_*` en Lua. Lista completa (de `lua.cpp` 502-608): + +``` +KEY_A..KEY_Z = 4..29 +KEY_1..KEY_0 = 30..39 (1=30, 2=31, ..., 9=38, 0=39) +KEY_RETURN=40 KEY_ESCAPE=41 KEY_BACKSPACE=42 KEY_TAB=43 KEY_SPACE=44 +KEY_MINUS=45 KEY_EQUALS=46 KEY_LEFTBRACKET=47 KEY_RIGHTBRACKET=48 +KEY_BACKSLASH=49 KEY_NONUSHASH=50 KEY_SEMICOLON=51 KEY_APOSTROPHE=52 +KEY_GRAVE=53 KEY_COMMA=54 KEY_PERIOD=55 KEY_SLASH=56 KEY_CAPSLOCK=57 +KEY_F1..KEY_F12 = 58..69 +KEY_PRINTSCREEN=70 KEY_SCROLLLOCK=71 KEY_PAUSE=72 +KEY_INSERT=73 KEY_HOME=74 KEY_PAGEUP=75 KEY_DELETE=76 KEY_END=77 KEY_PAGEDOWN=78 +KEY_RIGHT=79 KEY_LEFT=80 KEY_DOWN=81 KEY_UP=82 +KEY_NUMLOCKCLEAR=83 KEY_KP_DIVIDE=84 KEY_KP_MULTIPLY=85 KEY_KP_MINUS=86 KEY_KP_PLUS=87 KEY_KP_ENTER=88 +KEY_KP_1..KEY_KP_0 = 89..98 KEY_KP_PERIOD=99 +KEY_NONUSBACKSLASH=100 KEY_APPLICATION=101 +KEY_LCTRL=224 KEY_LSHIFT=225 KEY_LALT=226 KEY_LGUI=227 +KEY_RCTRL=228 KEY_RSHIFT=229 KEY_RALT=230 KEY_RGUI=231 +``` + +(Son los SDL2 scancodes.) + +### Matemáticas + +`abs(x)`, `ceil(x)`, `flr(x)`, `sgn(x)`, `sin(x)`, `cos(x)`, `atan2(dx, dy)`, `sqrt(x)`, `max(a,b)`, `min(a,b)`, `mid(a,b,c)` (devuelve el del medio, equivalente a `clamp`). + +`rnd(n)` devuelve un entero en `[0, n-1]` (`rand()%n`). `srand(seed)` siembra el RNG. + +### Strings + +| Función | Descripción | +|---------|-------------| +| `tostr(v)` | Convierte valor a string. Soporta nil, function, table (formato `{k=v,...}`), number, boolean, string. | +| `strlen(s)` | Longitud en bytes. | +| `ascii(s, i)` | Código del byte en índice `i` (0-based). | +| `chr(n)` | String de un solo carácter cuyo código es `n`. | +| `substr(s, start, length)` | Subcadena. | + +> Nota: Lua estándar también está disponible, así que `string.format`, `string.sub`, etc., funcionan. Pero los demos usan estas helpers. + +### Memoria + +| Función | Descripción | +|---------|-------------| +| `peek(addr)` | Lee 1 byte de la VRAM/memoria (0..0x1FFF). | +| `poke(addr, val)` | Escribe 1 byte. | +| `memcpy(dst, src, size)` | Copia bytes en la memoria del fantasy console. | +| `setchar(idx, b0..b7)` | Define los 8 bytes del carácter `idx` en el char-ROM (sobrescribe la fuente). | + +**Mapa de memoria** (8 KB total, `mem[8192]`): + +- `0x0000` (0): char_screen (matriz de códigos de carácter por celda) +- Tras char_screen viene color_screen (offset = `screen_width * screen_height`) +- `0x0A00` (2560 = `MEM_CHAR_OFFSET`): char-ROM (definición de glifos, 8 bytes por carácter, 256 chars = 2048 bytes) +- `0x1200` (4608 = `MEM_BOOT_OFFSET`): zona de boot/recursos de ROM + +Para los modos 1 y 2 los color_screen offsets son 1200 y 300 respectivamente; en modo 3 es 768; en modo 0 no hay color_screen por celda (color global). + +### Audio + +**Sonido simple:** +- `sound(freq, len)` — onda cuadrada a `freq` Hz durante `len` (en algo similar a centésimas de segundo; `audio_len = len*44.1`). +- `nosound()` — silencio inmediato. + +**Mini-lenguaje MML — `play(str)`:** + +Sintaxis tipo BASIC `PLAY` / MML. Tokens (case-sensitive, minúsculas): + +| Token | Significado | +|-------|-------------| +| `c d e f g a b` | Nota. Acepta sufijo `#` o `+` (sostenido) o `-` (bemol). Luego dígito 0-9 para duración. | +| `r` | Silencio. Acepta dígito de duración. | +| `o<0-7>` | Octava absoluta. | +| `>` `<` | Sube / baja octava. | +| `l<0-9>` | Longitud por defecto para notas sin duración. | +| `v<0-9>` | Volumen (se traduce a `(d-0)<<4`). | +| `t<0-9>` | Tempo. | + +Duraciones: índice 0-9 → tabla `{313,625,938,1250,1875,2500,3750,5000,7500,10000}` (de "redonda" a "trentaidosava", aproximadamente). + +Ejemplo (de `breakout.lua`): + +```lua +play("l0o3bagfedc") -- escala descendente como sonido de game-over +play("o5l0c") -- pitido agudo (rebote) +``` + +### Ficheros y portapapeles + +| Función | Descripción | +|---------|-------------| +| `load([filename])` | Reinicia y carga otro `.lua` (o el mismo si filename=nil). | +| `fileout(name, addr, size)` | Vuelca `size` bytes de memoria a un binario. | +| `filein(name, addr, size)` | Carga un binario a memoria. | +| `toclipboard(str)` | Copia al portapapeles del SO. | +| `fromclipboard()` | Lee del portapapeles (máx 1023 chars). | + +### Utilidades de tiempo / frame + +| Función | Descripción | +|---------|-------------| +| `time()` | Milisegundos desde inicio (`SDL_GetTicks()`). | +| `cnt()` | Contador de frames desde el último `rst()`. | +| `rst()` | Resetea el contador de frames a 0. | +| `log(str)` | Imprime en la consola de debug (no en pantalla). | + +--- + +## 5. Caracteres especiales útiles + +Los demos usan códigos > 127 que corresponden a glifos definidos en `rom.c` (el char-ROM por defecto). Algunos vistos: + +- `\003` (3) — un bloque relleno (usado en pong para los compases) +- `\016` (16) — cubo de caja (en sokoban) +- `\127` (127) — pared en sokoban (redefinido con `setchar(127, ...)`) +- `\143`, `\154`, `\150`, `\156`, `\149` — esquinas y trazos de marcos +- `\248`, `\250`, `\251` — sprite animado de "OK" en sokoban +- `\233` — pelota en breakout +- `\131` — pala en breakout +- `\001` — cubo de tetromino (redefinido con `setchar(1, 0xff,0x81,...)`) + +Para usarlos siempre se puede hacer `setchar(idx, b0..b7)` con la bitmap deseada y luego imprimirlo con `print(chr(idx), x, y)`. + +--- + +## 6. Patrón típico de un juego + +```lua +function init() + mode(1) + cls() + -- estado inicial + player = {x=10, y=15} + score = 0 +end + +function update() + -- input + if btnp(KEY_LEFT) then player.x = player.x - 1 end + if btnp(KEY_RIGHT) then player.x = player.x + 1 end + + -- lógica + -- ... + + -- render (no hay vsync explícito; el bucle ya hace flip al final) + cls() + color(COLOR_WHITE, COLOR_BLACK) + print("\248", player.x, player.y) + print("SCORE: "..tostr(score), 0, 0) +end +``` + +**Cosas a recordar:** + +- No se "pintan" píxeles; se imprime un código de carácter en una celda y se le asocia un atributo de color (ink + paper). Para gráficos personalizados, redefinir glifos con `setchar`. +- El bucle de render lo hace el motor C++ después de `update()` — no hay que llamar a ningún `flip`. +- El `state machine` típico se hace asignando `update = otra_funcion` (ver `demos/sokoban.lua`). +- Las coordenadas de pantalla son **enteras y por celda**, no por píxel. (0,0) es esquina superior izquierda. +- Para depurar: `log("mensaje")` o pulsar ESC y usar la consola con `?variable`. + +--- + +## 7. Cómo compilar el intérprete + +Desde `c:/mingw/gitea/ascii/`: + +```sh +make windows +``` + +Requiere MinGW (g++) y SDL2 para Windows. Produce `ascii.exe`. Para correr Pepe Runner: + +```sh +ascii.exe pepe_runner.lua +``` diff --git a/README.md b/README.md index c38de39..7ece213 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,48 @@ # bombardero_amstrad_ascii -Versió del joc "Bombardero" del manual de Amstrad per a ascii \ No newline at end of file +Port a la fantasy console **ascii** del juego *Bombardero* publicado por +AMSOFT en 1984 (Dave Town), originalmente escrito en BASIC de Amstrad CPC. + +## El juego + +Pilotas un avión sobre una ciudad desierta y tienes que bombardear los +edificios para poder aterrizar. El avión vuela de izquierda a derecha y, al +llegar al borde, vuelve a salir por la izquierda una fila más bajo. Si chocas +con un edificio: muerte. Si consigues aterrizar: subes de nivel y velocidad. + +Solo puedes tener una bomba en el aire a la vez. + +## Controles + +- **ESPACIO** — lanzar bomba (durante la partida) / continuar (en menús) +- **0..5** — elegir nivel al empezar (0 = AS, 5 = principiante) +- **0..9** — elegir velocidad al empezar (0 = lenta, 9 = rápida) +- **I** — instrucciones desde el título +- **A..Z** — introducir nombre al batir el récord + +## Cómo ejecutar + +Necesitas el intérprete de la fantasy console ascii. Desde el directorio del +juego: + +```sh +ascii bombardero.lua +``` + +El récord se guarda en un fichero `records` junto al juego. + +## Ficheros + +- `bombardero.lua` — el juego. +- `bombardero.bas` — listado BASIC original del Amstrad CPC, como referencia. +- `ASCII_API.md` — referencia del intérprete Lua de ascii. +- `chuleta_font_ascii.png` — tabla de glifos del charset de ascii. + +## Agradecimientos + +- A **JailDoctor** por crear la fantasy console ascii y mantener su charset y + paleta tan cómodos para portar clásicos. +- A **Dave Town** y **AMSOFT** (1984) por el juego original. +- A la **AUA — Amstrad Users Association** por conservar el código fuente del + original y hacerlo accesible para esta versión. +- A **Claude** (Anthropic) por la ayuda en el port a Lua/ascii. diff --git a/bombardero.bas b/bombardero.bas new file mode 100644 index 0000000..3afe8a6 --- /dev/null +++ b/bombardero.bas @@ -0,0 +1,70 @@ +10 'BOMBARDERO por DAVE TOWN +20 'copyright (c) AMSOFT 1984 +30 ' +40 MODE 1:CLS:INK 0,0:BORDER 0:INK 1,18:INK 2,6:INK 3,4:INK 5,15:INK 6,2:INK 7,24:INK 8,8:INK 9,26:INK 10,10:INK 11,20:INK 12,12:INK 13,16:INK 14,14:INK 15,21 +50 SYMBOL AFTER 240:SYMBOL 241,&40,&60,&70,&7F,&7F,&EF,&7,&0:SYMBOL 242,&0,&32,&7A,&FE,&FA,&F2,&E0,&0 +60 puntos=0:maxi=0:avi$=CHR$(241)+CHR$(242):x=2:y=2:cae=0:a=2:b=2 +70 GOSUB 480 +80 CLS +90 PEN 2:LOCATE 1,15:INPUT"Elija nivel: 0 (AS) a 5 (PRINCIPIANTE) ",nivel +100 IF nivel<0 OR nivel>5 THEN GOTO 90 +110 nivel=nivel+10 +120 LOCATE 1,15:PRINT CHR$(18);:LOCATE 1,15:INPUT"Elija velocidad: 0 (MAX) a 100(MIN) ",vel +130 IF vel>100 OR vel<0 GOTO 120 +140 ' +150 'Edificios +160 ' +170 MODE 0:FOR base=5 TO 15:FOR altura=21 TO INT(RND(1)*8+nivel) STEP-1:LOCATE base,altura:PEN base-2:PRINT CHR$(143)+CHR$(8)+CHR$(11)+CHR$(244);:NEXT:NEXT +180 PLOT 0,20,4:DRAW 640,20,4 +190 LOCATE 1,25:PEN 2:PRINT"PUNTOS";puntos;:LOCATE 13,25:PRINT"MAX";maxi; +200 ' +210 'Juego +220 ' +230 LOCATE x-1,y:PRINT" "; +240 PEN 1:LOCATE x,y:PRINT avi$;:PEN 2 +250 IF y=21 AND x=15 THEN GOTO 290:ELSE GOTO 340 +260 ' +270 'Aterriza +280 ' +290 FOR c=0 TO 1000:NEXT +300 puntos=puntos+100-(nivel*2):nivel=nivel-1:x=2:y=2:a=2:b=2:cae=0 +310 IF nivel<10 THEN nivel=10:vel=vel-20 +320 IF vel<0 THEN vel=0 +330 GOTO 150 +340 FOR c=0 TO vel:NEXT +350 x=x+1 +360 IF x=18 THEN LOCATE x-1,y:PRINT CHR$(18);:x=2:y=y+1:LOCATE x,y:PEN 1:PRINT avi$;:PEN 2 +370 a$=INKEY$:IF a$=" " AND cae=0 THEN cae=1:b=y+2:a=x +380 IF y=21 THEN cae=0 +390 IF cae=1 THEN LOCATE a,b:PRINT CHR$(252);:LOCATE a,b-1:PRINT" ";:b=b+1:IF b>21 THEN LOCATE a,b:PRINT" ";:LOCATE a,b-1:PRINT" ";:LOCATE a,b-1:PRINT" ":a=0:b=0:cae=0:SOUND 3,4000,10,12,0,0,10 +400 ga=(a-0.5)*32:gb=400-(b*16):bomba=TEST(ga,gb) +410 IF bomba>0 THEN GOTO 670 +420 gx=((x+1.5)*32):gy=408-(y*16):choque=TEST(gx,gy) +430 IF choque>0 THEN GOTO 570 +440 GOTO 230 +450 ' +460 'Instrucciones +470 ' +480 LOCATE 1,2:PEN 1:PRINT"Usted esta pilotando un avion sobre una ciudad desierta y tiene que pasar sobre los edificios para aterrizar y repostar.Su avion se mueve de izquierda a derecha.";:PRINT +490 PRINT:PRINT"Al llegar a la derecha, el avion vuelve a salir por la izquierda, pero MAS BAJO.Dispone de un numero limitado de bombas y puede hacerlas caer sobre los edifi-cios pulsando la BARRA ESPACIADORA.";:PRINT +500 PRINT:PRINT"Cada vez que aterriza, sube la altura de los edificios y la velocidad.";:PRINT:PRINT:PRINT"UNA VEZ DISPARADA UNA BOMBA,YA NO PUEDE DISPARAR OTRA MIENTRAS NO HAYA EXPLOSIO-NADO LA PRIMERA!!!": +510 PEN 2:LOCATE 1,24:PRINT:PRINT"Pulse una tecla para empezar."; +520 a$=INKEY$:IF a$="" GOTO 520 +530 RETURN +540 ' +550 'Colision +560 ' +570 LOCATE x-1,y:PRINT CHR$(32)+CHR$(32)+CHR$(32)+CHR$(253)+CHR$(8)+CHR$(238)+CHR$(8); +580 FOR t=1 TO 10:SOUND 7,4000,5,15,0,0,5:PEN t:PRINT CHR$(253)+CHR$(8)+CHR$(238)+CHR$(8)+CHR$(32)+CHR$(8);:FOR tm=0 TO 50:NEXT:NEXT:PEN 2 +590 CLS:LOCATE 1,5:PRINT"Ha conseguido";puntos;"puntos." +600 IF puntos>maxi THEN maxi=puntos:LOCATE 1,8:PRINT"BATIO EL RECORD!!"; +610 puntos=0:LOCATE 1,12:PRINT"Pulse V para volver a empezar"; +620 a$=INKEY$:IF a$="v" OR a$="V" GOTO 630 ELSE GOTO 620 +630 PEN 1:MODE 1:x=2:y=2:a=2:b=2:GOTO 90 +640 ' +650 'Edificio bombardeado +660 ' +670 LOCATE a,b-1:PRINT" "+CHR$(8);:PEN 4:FOR tr=1 TO INT(RND(1)*3)+1:puntos=puntos+ 5:SOUND 3,4000,10,12,0,0,10:LOCATE a,b:FOR t=0 TO 4:PRINT CHR$(253)+CHR$(8)+CHR$(32)+CHR$(8);:NEXT:b=b+1 +680 IF b=24 THEN b=b-1 +690 NEXT +700 LOCATE 7,25:PEN 2:PRINT puntos;:cae=0:a=x:b=y:PEN 4:GOTO 230 diff --git a/bombardero.lua b/bombardero.lua new file mode 100644 index 0000000..a367bbd --- /dev/null +++ b/bombardero.lua @@ -0,0 +1,762 @@ +-- ============================================================ +-- 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() + 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 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 diff --git a/chuleta_font_ascii.png b/chuleta_font_ascii.png new file mode 100644 index 0000000..5356fa5 Binary files /dev/null and b/chuleta_font_ascii.png differ