diff --git a/.gitignore b/.gitignore index c7459ee..d178a3a 100644 --- a/.gitignore +++ b/.gitignore @@ -46,7 +46,7 @@ $RECYCLE.BIN/ .LSOverride # Icon must end with two \r -Icon +Icon # Thumbnails ._* @@ -67,3 +67,6 @@ Network Trash Folder Temporary Items .apdisk +# Binaris del interpret ascii (no son del joc) +ascii +ascii.exe diff --git a/3d_monster_jail.lua b/3d_monster_jail.lua new file mode 100644 index 0000000..fdc2c91 --- /dev/null +++ b/3d_monster_jail.lua @@ -0,0 +1,358 @@ +laberinto = {} +primera_fila = 2 +ultima_fila = 28 +primera_columna = 1 +ultima_columna = 38 +jugador = { + x = primera_columna, + y = primera_fila, + velocitat = 0 +} +malos = {{ + x = 0, + y = 0, + velocitat = 0, + dx = -1, + dy = 0, + carrero_sense_sortida = 0 +}, { + x = 0, + y = 0, + velocitat = 0, + dx = 0, + dy = 0, + carrero_sense_sortida = 0 +}, { + x = 0, + y = 0, + velocitat = 0, + dx = 0, + dy = 0, + carrero_sense_sortida = 0 +}, { + x = 0, + y = 0, + velocitat = 0, + dx = 0, + dy = 0, + carrero_sense_sortida = 0 +}, { + x = 0, + y = 0, + velocitat = 0, + dx = 0, + dy = 0, + carrero_sense_sortida = 0 +}} +rellotge = 0 +num_malos = 1 + +function init() + mode(1) + color(10, 15, 15) + cls() + crea_laberinto() + pinta_laberinto() + crea_malos() + -- play("o4v5l1crcl4dcferl1crcl4dcgfr") + -- print("hola",0,0) +end + +function update() + rellotge = rellotge + 1 + mou_jugador() + mou_malos() + pinta_laberinto() + pinta_jugador() + pinta_malos() + print(malos[1].dx, 0, 0) + print(malos[1].dy, 4, 0) +end + +function crea_laberinto() + -- genera el laberinto + local longitud, inicio + for i = 0, 1199 do + -- plena tot de murs a partir de la primera_fila-1 + if i < ((primera_fila - 1) * 40) then + laberinto[i] = 0x20 + else + laberinto[i] = 0x8F + end + end + + -- posa cami en la primera i última fila + for i = primera_columna, ultima_columna do + laberinto[(40 * primera_fila) + i] = 0x20 + laberinto[(40 * ultima_fila) + i] = 0x20 + end + + -- posa cami en la primera i última columna + for i = primera_fila, ultima_fila do + laberinto[(40 * i) + primera_columna] = 0x20 + laberinto[(40 * i) + ultima_columna] = 0x20 + end + + -- crea filas aleatories + local fila = primera_fila + while fila < ultima_fila - 1 do + if rnd(10) % 10 > 4 then + -- 60% de probabilitat de pintar la fila sencera + for j = primera_columna, ultima_columna do + laberinto[(40 * fila) + j] = 0x20 + end + else + -- 40% de probabilitat de pintar sols un segment + longitud = max(15, rnd(ultima_columna - primera_columna) + 1) + inicio = rnd(ultima_columna - longitud) + primera_columna + for j = 1, longitud do + laberinto[(40 * fila) + j + inicio] = 0x20 + end + end + fila = fila + rnd(3) + 3 + end + + -- crea columnas aleatories + local columna = primera_columna + while columna < ultima_columna - 1 do + if rnd(10) % 10 > 4 then + -- 60% de probabilitat de pintar la columna sencera + for j = primera_fila, ultima_fila do + laberinto[(40 * j) + columna] = 0x20 + end + else + -- 40% de probabilitat de pintar sols un segment + longitud = max(5, rnd(ultima_fila - primera_fila) + 1) + inicio = rnd(ultima_fila - longitud) + primera_fila - 1 + for j = 1, longitud do + laberinto[(40 * (j + inicio)) + columna] = 0x20 + end + end + columna = columna + rnd(2) + 2 + end +end + +function pinta_laberinto() + -- pinta el laberinto en pantalla + for i = 0, 1199 do + ink(10) + print(chr(laberinto[i]), i % 40, flr(i / 40)) + -- crea el efecto 3D pintant un caracter per damunt + if laberinto[i] == 0x8F then + -- color(15,10) + -- print(chr(0xCF), i % 40, flr(i / 40)-1) + -- print(chr(0xCF), i % 40, flr(i / 40)-2) + end + end +end + +function pinta_jugador() + ink(0) + print(chr(0xF8), jugador.x, jugador.y) +end + +function mou_jugador() + -- mou el jugador a la nova ubicacio en funció de les tecles apretades + if jugador.velocitat == 0 then + if btn(KEY_UP) then + coloca_jugador(jugador.x, jugador.y - 1) + elseif btn(KEY_DOWN) then + coloca_jugador(jugador.x, jugador.y + 1) + end + if btn(KEY_LEFT) then + coloca_jugador(jugador.x - 1, jugador.y) + elseif btn(KEY_RIGHT) then + coloca_jugador(jugador.x + 1, jugador.y) + end + else + jugador.velocitat = jugador.velocitat - 1 + end +end + +function coloca_jugador(x, y) + -- coloca el jugador en el punt x, y en cas de que siga una casella válida + if laberinto[y * 40 + x] == 0x20 then + jugador.x = x + jugador.y = y + jugador.velocitat = 8 + end +end + +function pinta_malos() + -- pina els malos en pantalla + ink(4) + for i = 1, num_malos do + print(chr(0x07), malos[i].x, malos[i].y) + end +end + +function crea_malos() + -- inicialitza el vector de malos + for i = 1, num_malos do + malos[i].x = ultima_columna + malos[i].y = primera_fila + malos[i].dx = -1 + malos[i].dy = 0 + pensa_malo(i) + end +end + +function mou_malos() + -- mou tots els malos a la nova ubicacio en funció de la seua direcció + for i = 1, num_malos do + if malos[i].velocitat == 0 then + coloca_malo(i, malos[i].x + malos[i].dx, malos[i].y + malos[i].dy) + -- if num_eixides_mapa(malos[i].x, malos[i].y) > 2 then + -- Si hi ha cami dalt o baix i a la esquerra o dreta, es a dir, + -- si hi ha cami tant vertical com horitzontal al voltant del + -- malo, recalcula cap on toca anar + if cami_vertical(malos[i].x, malos[i].y) and cami_horitzontal(malos[i].x, malos[i].y) then + pensa_malo(i) + end + else + malos[i].velocitat = malos[i].velocitat - 1 + end + end +end + +function coloca_malo(num, x, y) + -- coloca el malo en el punt x, y en cas de que siga una casella válida + local exito + if laberinto[y * 40 + x] == 0x20 then + malos[num].x = x + malos[num].y = y + malos[num].velocitat = 16 + exito = 1 + else -- en aquest cas, havem arribat al final de un carrero sense sortida + exito = 0 + -- pensa_malo(num) + malos[i].carrero_sense_sortida = 1 + malos[num].dx = malos[num].dx * -1 + malos[num].dy = malos[num].dy * -1 + end + return exito +end + +function pensa_malo(num) + -- el malo pensara de la següent manera. Quan arriba a un encreuament ha de triar + -- una de les quatre direccions posibles valorant la idoneitat de cada una en funcio de la + -- distancia i de si ha de desfer cami. Despres començarà per la mes idonea per vore si te + -- cami en eixa direcció i passarà a la següent en cas contrari. També pot donar-se el cas + -- de que no vullga una de les opcions perque està eixint de un cami sense sortida + + -- primer guarda la direccio que tenia el malo per si la necesitem mes endavant + local ultim_dx = malos[num].dx + local ultim_dy = malos[num].dy + -- posa a cero totes les direccions + malos[num].dx = 0 + malos[num].dy = 0 + + -- esta funcio calcula la distancia entre dos punts + function calcula_distancia(x1, y1, x2, y2) + local dist + dist = abs(x2 - x1) + abs(y2 - y1) + return dist + end + + -- valora quin es el millor cami (N,S,E,O) + local N = calcula_distancia(jugador.x, jugador.y, malos[num].x, malos[num].y - 1) + local S = calcula_distancia(jugador.x, jugador.y, malos[num].x, malos[num].y + 1) + local E = calcula_distancia(jugador.x, jugador.y, malos[num].x - 1, malos[num].y) + local O = calcula_distancia(jugador.x, jugador.y, malos[num].x + 1, malos[num].y) + + local opcions = {N, S, E, O} + table.sort(opcions) + + local opcio = 1 + while (true) do + if opcio == 5 or fer(opcions[opcio]) == true then + break + end + opcio = opcio + 1 + end + + -- el malo calcula la direcció que ha de pendre per anar cap al jugador + -- sempre i quan hi haja cami + + if malos[num].x > jugador.x and es_cami(malos[num].x - 1, malos[num].y) then + malos[num].dx = -1 + end + + if malos[num].x < jugador.x and es_cami(malos[num].x + 1, malos[num].y) then + malos[num].dx = 1 + end + + if malos[num].y > jugador.y and es_cami(malos[num].x, malos[num].y - 1) then + malos[num].dy = -1 + end + + if malos[num].y < jugador.y and es_cami(malos[num].x, malos[num].y + 1) then + malos[num].dy = 1 + end + + -- el malo soles pot moures en un eix, per tant en cas de que intente moure's en els dos, + -- s'ha de decidir en quin es millor + + -- per altra banda, si tornem de un carreró sense sortida segur que arribem a un encreuament + -- amb sortides en els dos eixos + + -- per tant, si el malo arriba a un encreuament tria un dels dos camins i si ve d'un carreró + -- ha de obviar el cami d'on ve + + if malos[num].dx ~= 0 and malos[num].dy ~= 0 then + -- si la distancia en x es major que en y, mantenim el moviment en x + if abs(malos[num].x - jugador.x) > abs(malos[num].y - jugador.y) then + malos[num].dy = 0 + else + malos[num].dx = 0 + end + end + + -- si el malo ve de un carrero sense sortida + +end + +function num_eixides_mapa(x, y) + -- torna el numero de caselles amb cami que hi ha al voltant del punt x, y + local eixides = 0 + if laberinto[y * 40 + x + 1] == 0x20 then + eixides = eixides + 1 + end + if laberinto[y * 40 + x - 1] == 0x20 then + eixides = eixides + 1 + end + if laberinto[(y + 1) * 40 + x] == 0x20 then + eixides = eixides + 1 + end + if laberinto[(y - 1) * 40 + x] == 0x20 then + eixides = eixides + 1 + end + return eixides +end + +function es_cami(x, y) + -- torna vertader si en eixa coordenada hi ha cami + if laberinto[y * 40 + x] == 0x20 then + return true + else + return false + end +end + +function cami_vertical(x, y) + -- torna vertader si hi ha cami dalt o baix + if es_cami(x, y - 1) or es_cami(x, y + 1) then + return true + else + return false + end +end + +function cami_horitzontal(x, y) + -- torna vertader si hi ha cami dalt o baix + if es_cami(x + 1, y) or es_cami(x - 1, y) then + return true + else + return false + end +end 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/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