1. Resumen
BYTE WARS es un juego 2D en el que el jugador no controla la nave con el teclado: escribe una estrategia en BWL (Byte Wars Language), el compilador la traduce a bytecode y una máquina virtual ejecuta cada instrucción para mover, disparar y activar el escudo.
El núcleo completo está escrito en C99 y se compila con Emscripten a WebAssembly, por lo que corre directamente en el navegador. El mismo núcleo se compila también a un ejecutable nativo de consola para pruebas y demostración.
código BWL │ ▼ lexer → parser → AST → análisis semántico → bytecode │ ▼ máquina virtual (PC, pila, variables, saltos) │ ▼ juego 2D (naves, balas, escudo, salud, puntuación)
2. Equipo
3. Proceso
El trabajo se organizó en fases con entregables verificables. Cada fase terminó con compilación y pruebas antes de pasar a la siguiente.
| Fase | Actividad | Resultado |
|---|---|---|
| 0 | Análisis del portal existente, copia de seguridad y commit inicial | Línea base protegida; Temas 11 y 4 conservados en legacy/ |
| 1 | Especificación de BWL, lexer y parser | Tokens, AST, errores léxicos y sintácticos con línea/columna |
| 2 | Análisis semántico, tabla de símbolos y generación de bytecode | Variables, builtins, validaciones, desensamblador |
| 3 | VM de pila, juego determinista y CLI nativo | Ejecución paso a paso, bucles, WAIT, colisiones y oleadas |
| 4 | Puente WebAssembly con Emscripten | bytewars.wasm de 87 KB y bytewars.js |
| 5 | Interfaz web: editor, paneles, canvas y controles | Tablero de cuatro paneles y análisis del compilador |
| 6 | Documentación, capturas y resultados | Esta página, README y documentos de equipo/proceso/resultados |
| 7 | QA final, responsive, accesibilidad y Docker | Completada: Chromium headless, móvil 390 px, Docker build y contenedor verificados |
Decisiones técnicas
- Núcleo en C99: es el requisito del tema y permite el mismo código en WASM y en nativo.
- Ejecución en el cliente: el navegador compila y ejecuta; el servidor no ejecuta código del usuario.
- VM de pila: las operaciones y comparaciones son sencillas de explicar y de visualizar.
- Arenas estáticas y límites: evitan fugas de memoria y hacen el comportamiento predecible.
- PRNG xorshift con semilla fija: las partidas y las pruebas son reproducibles.
- Sin assets externos: naves, balas y fondo se dibujan con Canvas 2D.
- Juego a pantalla completa (PC y móvil): al ejecutar, el juego se abre sobre la página con HUD, PC/ticks y controles, y una cuenta regresiva 3 · 2 · 1 antes de empezar. En móvil, un botón flotante permite reabrirlo sin hacer scroll.
- Tema claro/oscuro: variables CSS, detección de
prefers-color-scheme, botón sol/luna y persistencia. El canvas del juego se mantiene oscuro para conservar el contraste.
Dificultades y soluciones
- Las constantes decimales no caben en un operando entero: se añadió un campo
double numa la instrucciónPUSH. - Un bucle infinito podía congelar la página: la VM tiene un presupuesto de 200 instrucciones por tick y cede el turno.
- La VM y el juego no debían quedar acoplados: la VM recibe un
BwHostcon callbacks, lo que permite probarla con un host falso. - El portal existente no debía romperse: los Temas 11 y 4 se conservaron y se ocultaron del menú principal para la entrega del Tema 6.
4. Resultados
La aplicación permite escribir, compilar, inspeccionar y ejecutar un programa completo. El tablero muestra los cuatro paneles conectados en tiempo real.
Qué funciona
- Compilación con errores de léxico, sintáctico y semántico, con línea y columna.
- Bytecode visible, instrucción actual resaltada y desensamblador con direcciones.
- VM con PC, pila, variables reservadas y de usuario, ticks y estado.
- Modo tiempo real y modo paso a paso.
- Movimiento, disparo, escudo, espera, bucles, condicionales y builtins.
- Juego con salud, puntuación, oleadas, enemigos que disparan y victoria/derrota.
- Protección contra bucles infinitos y errores de ejecución.
- Descarga de
bytewars.exeybytewars_cli.








Métricas medidas
| Métrica | Resultado |
|---|---|
| Tiempo de compilación del ejemplo (21 instrucciones) | ≈ 0.2 ms en el navegador |
| Serialización de un frame de juego | ≈ 0.03 ms |
Tamaño de bytewars.wasm | 87 KB |
Tamaño de bytewars_cli / bytewars.exe | 69 KB / 98 KB |
| Pruebas unitarias en C | 16 grupos, todos pasando |
| Límite de instrucciones por tick | 200, con cesión de turno |
Limitaciones conocidas
- Un nivel de tres oleadas; el selector de nivel ajusta la dificultad, no añade mapas nuevos.
- Sin sonido ni multijugador.
bytewars.exees un runner de consola, no una ventana gráfica nativa.- La VM es didáctica; no emula hardware real.
5. Lenguaje BWL
BWL es un lenguaje imperativo pequeño, con bloques, expresiones aritméticas y lógicas, variables y llamadas a funciones integradas.
// Estrategia básica
START
SET speed 3
REPEAT 5 {
MOVE RIGHT
SHOOT
WAIT 10
}
WHILE enemy_ahead() {
IF health < 30 {
SHIELD
} ELSE {
SHOOT
MOVE UP
}
}
END
Gramática
programa := sentencia*
sentencia := 'START' | 'END'
| 'SET' IDENT expresion
| 'MOVE' ('UP'|'DOWN'|'LEFT'|'RIGHT')
| 'SHOOT' | 'SHIELD'
| 'WAIT' expresion
| 'REPEAT' expresion bloque
| 'WHILE' expresion bloque
| 'IF' expresion bloque ('ELSE' bloque)?
bloque := '{' sentencia* '}'
expresion := or
or := and ('||' and)*
and := igualdad ('&&' igualdad)*
igualdad := comparacion (('=='|'!=') comparacion)*
comparacion := suma (('<'|'>'|'<='|'>=') suma)*
suma := multiplicacion (('+'|'-') multiplicacion)*
multiplicacion := unario (('*'|'/'|'%') unario)*
unario := ('-'|'!') unario | primario
primario := NUMERO | IDENT | IDENT '(' argumentos? ')'
| '(' expresion ')' | 'TRUE' | 'FALSE'
Variables reservadas
| Nombre | Tipo | Significado |
|---|---|---|
health | 0–100 | Salud de la nave |
score | número | Puntuación |
enemies_left | número | Enemigos vivos |
level | número | Nivel actual |
shield | 0/1 | Escudo activo |
ticks | número | Ticks de juego |
Las variables del usuario se crean con SET y deben existir antes de leerse. Los nombres reservados son de solo lectura.
Funciones integradas
| Función | Argumentos | Devuelve |
|---|---|---|
enemy_ahead() | 0 | 1 si hay un enemigo delante |
enemy_left() | 0 | 1 si hay un enemigo a la izquierda |
enemy_right() | 0 | 1 si hay un enemigo a la derecha |
random(n) | 1 | Entero pseudoaleatorio entre 0 y n-1 |
Límites
- Fuente: 16 KB; tokens: 2048; nodos del AST: 2048; instrucciones: 2048; variables: 64; pila: 256.
- Profundidad de bloques/expresiones: 64.
WAIT: 0–600 ticks.REPEAT: hasta 10 000 iteraciones por entrada.- 200 instrucciones de VM por tick de juego para evitar bloqueos.
6. Compilador
El compilador está en bytewars/src/ y se organiza en fases separadas.
| Archivo | Responsabilidad |
|---|---|
lexer.c | Convierte el texto en tokens con línea y columna; ignora comentarios //. |
parser.c | Descenso recursivo; construye el AST y reporta errores sintácticos. |
sema.c | Tabla de símbolos, variables reservadas, builtins, aridad y división entre cero literal. |
codegen.c | AST → bytecode, parcheo de saltos y desensamblador. |
compiler.c | Orquesta las fases y conserva los diagnósticos. |
Ejemplo de AST
Programa
START
SET speed
NUM 3
REPEAT
NUM 5
BLOQUE
MOVE RIGHT
SHOOT
WAIT
NUM 10
WHILE
BINARIO >
VAR health
NUM 0
BLOQUE
IF
CALL enemy_ahead (0 args)
BLOQUE
SHOOT
ELSE
BLOQUE
WAIT
NUM 3
END
Errores por fase
| Fase | Ejemplo |
|---|---|
| Léxico | SET x @ → «Carácter no reconocido» |
| Sintáctico | Falta } → «Se esperaba '}'…» |
| Semántico | SET x y → «Variable no definida 'y'»; SET health 5 → variable reservada |
| Ejecución | División entre cero dinámica, pila vacía, salto inválido |
7. Bytecode e ISA
Cada instrucción tiene un opcode, un operando entero, la línea del fuente y, para PUSH, una constante decimal.
typedef struct {
uint8_t op;
int32_t arg; // variable, dirección o builtin
int16_t line; // línea del fuente
double num; // constante de PUSH
} BwInstr;
| Opcode | Operando | Pila | Descripción |
|---|---|---|---|
PUSH | número | +1 | Empuja una constante |
LOAD / STORE | variable | +1 / −1 | Lee o escribe una variable |
ADD SUB MUL DIV MOD | — | −1 | Aritmética |
NEG NOT | — | 0 | Negación aritmética o lógica |
LT GT LE GE EQ NE | — | −1 | Comparaciones (1 o 0) |
AND OR | — | −1 | Lógica (evaluación no cortocircuitada) |
JMP JZ JNZ | dirección | 0 / −1 | Saltos |
CALL | builtin | variable | Llama a una función integrada |
MOVE_UP/DOWN/LEFT/RIGHT | — | 0 | Mueve la nave |
SHOOT SHIELD | — | 0 | Acciones de combate |
WAIT | — | −1 | Espera n ticks |
REPEAT_START / REPEAT_END | dirección | −1 / 0 | Bucle contado con pila de bucles |
HALT | — | 0 | Detiene la VM |
Desensamblado del ejemplo
0000 PUSH 3 0001 STORE speed 0002 PUSH 5 0003 REPEAT_START 0009 0004 MOVE_RIGHT 0005 SHOOT 0006 PUSH 10 0007 WAIT 0008 REPEAT_END 0009 LOAD health 0010 PUSH 0 0011 GT 0012 JZ 0020 0013 CALL enemy_ahead 0014 JZ 0017 0015 SHOOT 0016 JMP 0019 0017 PUSH 3 0018 WAIT 0019 JMP 0009 0020 HALT
8. Máquina virtual
La VM es de pila y mantiene todo el estado visible en la interfaz.
typedef struct {
BwInstr code[2048]; int code_len;
BwVar vars[64]; int var_count;
double stack[256]; int sp;
int pc;
BwLoopFrame loops[32]; int loop_sp;
int wait_ticks, halt_ticks, status;
long steps, ticks;
int budget_hits, has_error;
BwDiag error;
BwHost host;
} BwVM;
- Ejecución: cada tick de juego ejecuta hasta 200 instrucciones o hasta
WAIT/HALT. - Espera:
WAIT ndeja la VM esperando n ticks. - Bucles:
REPEAT_STARTguarda el contador en una pila de bucles;REPEAT_ENDlo decrementa. - Saltos:
JZ/JNZvalidan que la dirección exista. - Errores: pila vacía, división entre cero, variable inválida, salto inválido, opcode desconocido, presupuesto excedido.
- Reinicio: al llegar a
HALT/END, el programa se reinicia tras 30 ticks para seguir controlando la nave.
BwHost con callbacks para mover, disparar, activar el escudo y consultar builtins. Eso permite probarla con un host falso.9. Juego
Reglas
- Área lógica de 480 × 640.
- La nave se mueve en la parte inferior; los límites se respetan.
- Disparo con recarga; escudo temporal con recarga.
- Enemigos exploradores, cazadores y tanques.
- Salud 100; daño por impactos y por enemigos que cruzan la línea.
- Victoria al limpiar tres oleadas; derrota si la salud llega a 0.
Determinismo
Todo el azar usa un PRNG xorshift32 con semilla fija. El mismo programa, nivel y semilla producen el mismo resultado, lo que permite pruebas automáticas de colisiones, victoria y derrota sin depender del tiempo real.
El selector de nivel 1–3 ajusta velocidad, tipos de enemigo y frecuencia de disparo.
10. Arquitectura y tecnologías
Navegador
tema6.html + bytewars.js
├── editor, paneles, canvas
└── ByteWarsModule (WebAssembly)
├── lexer.c → parser.c → sema.c → codegen.c
├── vm.c
└── game.c → json.c
Servidor Node
├── sirve public/
├── /builds/ (proyecto anterior, intacto)
└── scripts/build-bytewars.sh → public/wasm
Nativo
├── bytewars_cli (Linux)
└── bytewars.exe (Windows, consola)
11. Cómo ejecutar
Requisitos: Node 18+, gcc, Emscripten y, opcionalmente, mingw-w64 para el .exe.
# 1. Construir el núcleo (WASM + binarios nativos) npm run build:bytewars # 2. Arrancar el portal npm start # http://localhost:3000 # 3. Pruebas npm test # 4. Solo el núcleo C make -C bytewars test make -C bytewars cli
Si falta el WASM, npm start intenta construirlo automáticamente. La página debe servirse por HTTP, no abrirse con file://, porque el navegador bloquea la carga del módulo WebAssembly.
Docker
docker compose up -d --build
La imagen instala Node, gcc, mingw-w64 y Emscripten, y compila el WASM durante el build. El contenedor escucha en el puerto 3000 y se publica con Traefik.
12. Pruebas
El proyecto incluye pruebas unitarias en C y pruebas de integración con el CLI.
== Pruebas de BYTE WARS == lexer parser errores por fase codegen vm aritmética vm if/else vm repeat/while vm error de ejecución vm wait vm acciones y builtins vm límite por tick juego límites juego disparo/escudo juego determinista programa de ejemplo json Todas las pruebas pasaron.
| Nivel | Qué comprueba |
|---|---|
| Lexer | Palabras clave, identificadores, números, comentarios y errores. |
| Parser | Bloques, precedencia, IF/ELSE, REPEAT/WHILE y errores. |
| Semántica | Variables no definidas, reservadas, builtins, aridad y división literal. |
| Codegen | Instrucciones, saltos parcheados, desensamblador y límites. |
| VM | Aritmética, ramas, bucles, WAIT, builtins, errores y presupuesto. |
| Juego | Límites, disparo, escudo, determinismo y colisiones. |
| Integración | Los cuatro ejemplos compilan; el ejemplo con errores falla en sintáctico; el frame contiene VM y juego. |
| Navegador | Carga del WASM, compilación, ejecución, pausa, paso a paso y variables en vivo. |
13. Descargas
Runner nativo
Ejecuta programas BWL desde la consola con el mismo núcleo C. Si se abre con doble clic, corre una demo incluida; desde la consola: bytewars.exe programa.bwl.
El código fuente completo está en bytewars/. Los Temas 11 y 4 se conservan en legacy/ y en sus páginas originales, fuera del menú principal.
14. Conclusiones
BYTE WARS demuestra que un compilador y una máquina virtual escritos en C pueden ser el centro de un juego real y ejecutarse en el navegador sin instalar nada. El jugador ve el ciclo completo: escribe una estrategia, observa los tokens y el AST, revisa el bytecode, sigue la ejecución de la VM y comprueba el efecto en la nave.
El proyecto separa claramente las responsabilidades: el compilador no dibuja, la VM no conoce el juego y la interfaz solo consume snapshots JSON. Esa separación permitió probar cada parte por separado y mantener el juego determinista.
Referencias
- Notas de la materia de Compiladores: análisis léxico, sintáctico, semántico y generación de código.
- Documentación de Emscripten:
emcc,MODULARIZEyEXPORTED_FUNCTIONS. - Especificación de WebAssembly y Canvas 2D del W3C.
- Documentación de Node.js para el servidor HTTP.
