Port inicial de Bombardero a ascii

This commit is contained in:
2026-05-18 12:58:39 +02:00
parent 2bbbf38c13
commit df4717005b
6 changed files with 1161 additions and 2 deletions
+8 -1
View File
@@ -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
+275
View File
@@ -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
```
+46 -1
View File
@@ -1,3 +1,48 @@
# bombardero_amstrad_ascii
Versió del joc "Bombardero" del manual de Amstrad per a ascii
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.
+70
View File
@@ -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
+762
View File
@@ -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
Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB