Testing with the bot
The bot script (pruebas/bot.json): steps, conditions, signals, the router, several scripts, and what greengames probar requires before it calls a match passed.
DescribesFormat 1, API 1 For those who write code
On this page
A game is not tested until someone plays it from start to finish. In Green Games that someone can be a bot: it drives a player with the same inputs as a person (a direction and three buttons) and follows a bot script that you write. The bot script is the list of steps for playing the whole match to the end.
The bot script lives in pruebas/bot.json and travels inside the .ggx. Three things use it:
greengames probar, which runs the match with no screen and says whether the game can be played and finished;- the autopilot of the game player (the demos and the screenshots);
- the bots that accompany the player when they choose to play with bots.
The examples on this page come from real files: step names, ids and comments are in Spanish, as the format requires.
A real bot script#
The one from the template of greengames crear (packages/toolkit/plantilla/pruebas/bot.json): it goes through the six coins of the yard, climbing onto the platform by the wedge.
{
"$schema": "../../../packages/sdk/esquemas/bot.schema.json",
"formato": 1,
"tiempoMaximo": 70,
"esperado": { "resultado": "victoria" },
"pasos": [
{ "ir": [8, 0, -6] },
{ "ir": [7, 0, -2.6] },
{ "ir": [7, 2, 6], "saltar": "no" },
{ "ir": [3.3, 2, 7] },
{ "ir": [-2, 0, 8] },
{ "ir": [-8, 0, 4] },
{ "ir": [-6, 0, -4] },
{ "ir": [0, 0, 0] }
]
}The second step takes the bot to the foot of the wedge and the third takes it up without jumping. The bot goes in a straight line: every change of direction is a step.
Steps#
| Step | What it does |
|---|---|
{ "ir": destino } | It walks in a straight line to the destination. See The ir step. |
{ "esperar": segundos } | It stays still for that long. |
{ "esperarPieza": id, ... } | It waits until the piece meets all the given conditions: cerca + radio, con (it has the tag), sin (it does not have it), giroY: [desde, hasta]. tiempoMax: 30 s. |
{ "pulsar": accion } | It presses saltar, accion or accion2 once (about 3 frames). |
{ "mirar": destino } | It turns the avatar to look at a point or at "pieza:id". Useful in first person, before pulsar, so that jugador.apunta() hits the target. |
{ "elegir": "menu:opcion" } | It waits for the menu with that id to open and picks that option. |
{ "esperarHasta": condicion } | It waits, still, until a condition is met. tiempoMax: 30 s. |
{ "mantener": accion, "segundos": n } | It holds a button down for n seconds, or "hasta": condicion. |
{ "repetir": [pasos], ... } | It repeats the steps: "veces": n, "hasta": condicion (checked before each round) or "mientras": condicion. With none of them, until the match ends. |
{ "si": condicion, "entonces": [pasos], "sino": [pasos] } | It picks a branch. sino is optional. |
{ "esperarFin": true } | It stays still until the match ends by itself (up to tiempoMaximo). |
A bot script accepts up to 500 steps.
The ir step#
The destination can be:
| Destination | Example | Where it goes |
|---|---|---|
| A point | [0, 0, 9] | To those coordinates. The y is the height of the feet. |
| A piece | "pieza:movil-1" | To its top face. If the piece moves, it follows it. |
| A zone | "zona:meta" | To the center of the zone. |
| A spawn point | "aparicion:control-1" | To that spawn point. |
Options:
| Option | Default | What it does |
|---|---|---|
saltar | "auto" | "auto": it jumps at an edge, a step or when stuck. "no": never. "si": it jumps as soon as it can. |
tolerancia | 0.6 | Horizontal distance at which the destination counts as reached. |
tiempoMax | 15 s | Seconds before the bot is declared stuck on this step. |
control | false | It marks the step as a return point. See Behaviors worth knowing. |
ruta | false | true: instead of going in a straight line, it looks for a path. See The router. |
hasta | A condition: the step also ends when it is met ("follow piece X until it has tag Y"). | |
siTeletransportan | "control" | What to do if the game teleports the player during the step. "control": go back to the nearest control step. "siguiente": count the step as finished. "seguir": keep going to the destination. |
Ladders. In a tile map, if the destination of an ir is higher or lower and the bot is touching a ladder, it uses it. You take it there in two steps: first to the foot of the ladder and then to the point above, for example { "ir": [28.5, 0, 0] } and { "ir": [28.5, 5, 0] }.
Waiting for the right moment#
Dangers with a rhythm (platforms, barriers, lights) are not crossed by walking without looking. The bot script waits until a piece is where it suits. Three stretches of the bot script of Circuito Luz Verde (games/circuito-luz-verde/pruebas/bot.json):
{ "esperarPieza": "movil-1", "cerca": [0, -0.3, 32.7], "radio": 0.4 },
{ "ir": "pieza:movil-1" },
{ "esperarPieza": "movil-1", "cerca": [0, -0.3, 38.3], "radio": 0.4 },
{ "ir": [0, 0, 41.9] },It waits until the moving platform is at its near end, gets on, waits until it reaches the other end and gets off.
{ "esperarPieza": "brazo", "giroY": [115, 150] },
{ "ir": [3, 4, 61.5] },It waits until the arm of the sweeper is at an angle that leaves the way clear.
{ "esperarPieza": "semaforo-corredor", "con": "recien-verde" },
{ "ir": [0, 4, 84.2] },It waits until the traffic light has the tag recien-verde. The game's script sets that tag just for this, for 0.3 seconds each time the light turns green. That is the general rule: when the bot needs to know something that cannot be seen in the position of a piece, the script tells it with a tag or with a signal.
Conditions#
A condition is an object. It is met if all its keys are met.
| Condition | It is met when |
|---|---|
{ "pieza": "puerta" } | The piece exists. With "existe": false, when it does not exist. |
{ "pieza": "puerta", "con": "abierta" } | The piece has that tag. Also sin, cerca + radio and giroY. |
{ "pieza": "torre", "dato": { "clave": "vida", "menor": 20 } } | A value of pieza.datos meets es, mayor or menor. |
{ "senal": "ronda", "es": 3 } | The script left that signal with that value. Also mayor and menor. |
{ "senal": "puerta-abierta" } | The signal exists and is not false, 0 or ''. |
{ "jugador": { "enZona": "meta" } } | The bot's player meets that. Also enSuelo, congelado, sobre (piece id), cerca + radio, puntosMin, puntosMax. |
{ "escena": "sotano" } | That is the loaded scene. |
{ "menu": "inicio" } | There is an open menu with that id. |
{ "tiempo": 30 } | At least that many seconds of the match have passed. |
{ "todas": [c1, c2] }, { "alguna": [c1, c2] }, { "no": c } | Combinations: all, any, not. |
Signals#
juego.senalar(nombre, valor) is the way for the bot script to know which moment the game is in without spending tags. The script leaves the signal and the bot waits for it (esperarHasta), repeats until it sees it (repetir ... hasta) or decides with it (si).
This example is tested with greengames probar on a copy of patio-de-monedas, the example game made with the editor. Four lines are added to the script, outside the rules block:
// Fuera del bloque de reglas: una señal para el guion del bot cuando alguien ya tiene las cinco monedas.
juego.cadaCuadro(() => {
if (jugadores.todos().some((jugador) => jugador.puntos >= 5)) juego.senalar('todas-las-monedas');
});and the bot script collects the five coins with the router, waits for the signal and only then goes to the goal:
{
"formato": 1,
"tiempoMaximo": 110,
"esperado": { "resultado": "victoria", "puntajeMinimo": 5 },
"pasos": [
{ "ir": "pieza:moneda", "ruta": true },
{ "ir": "pieza:moneda-2", "ruta": true },
{ "ir": "pieza:moneda-4", "ruta": true },
{ "ir": "pieza:moneda-3", "ruta": true },
{ "ir": "pieza:moneda-5", "ruta": true },
{ "esperarHasta": { "senal": "todas-las-monedas" }, "tiempoMax": 5 },
{ "ir": "zona:meta", "ruta": true },
{ "esperarFin": true }
]
}With that bot script, the six runs (three difficulties, two seeds) end in victory with 5 points.
The router#
With "ruta": true, the bot looks for a path over a half-meter grid, measuring the floor with rays. It goes around walls and gaps, climbs steps of up to 0.45 and goes down drops of up to 1.2. It recalculates every two seconds and when it gets stuck, so it follows a destination that moves.
It is good for single-level settings: yards, mazes, arenas. Its limits:
- It does not understand ceilings or stacked floors. It measures the floor from above: under a bridge it sees the bridge.
- It needs corridors at least 1.5 wide.
- It does not plan jumps. For jumps and platforms you still write
irsteps by hand, one per stretch. That also holds in thelateralview: in a 2D platformer level the bot crosses gaps and climbs onto each platform with oneirper stretch. - It does not know which pieces are dangerous. It goes around what is in the way, not what takes points away. In the example above, the order of the coins is chosen so that no stretch crosses the lava slab in the center; with another order, the bot steps on it, the game sends it back to the start and the test fails.
Several bot scripts#
Besides pruebas/bot.json (the main one) there can be pruebas/bot-<nombre>.json. The typical use is to test that you can lose. The game Semáforo ships pruebas/bot-derrota.json, which starts like this:
{
"$schema": "../../../packages/sdk/esquemas/bot.schema.json",
"formato": 1,
"tiempoMaximo": 420,
"esperado": {
"resultado": "derrota"
},greengames probar runs the main one and the additional ones that declare esperado. With --guion <nombre> you choose which. They all go inside the .ggx.
For a game with several players, greengames probar <slug> --jugadores 4 puts four players in the room, all following the bot script.
Fields of the bot script#
| Field | Default | What it does |
|---|---|---|
formato | Always 1. | |
pasos | The list of steps. Required. | |
esperado | How the match must end: resultado (victoria, derrota or fin), puntajeMinimo, puntajeMaximo. | |
esperadoPorDificultad | What is expected on one difficulty, which replaces esperado on it: { "dificil": { "resultado": "derrota" } }. | |
semillas | [1, 2] | Which seeds to test with: a list, or a number N (seeds 1 to N). |
aprobarCon | all | How many runs of each difficulty must pass. |
tiempoMaximo | 180 | Seconds of match before the run counts as failed. |
porDificultad | Different steps for one difficulty, which replace pasos on it. |
For a game with a lot of randomness, where not even a good bot script always wins, say so in the bot script. Copa de Bloques (games/copa-de-bloques/pruebas/bot.json) tests eight seeds and asks for four to pass, with a minimum score:
{
"$schema": "../../../packages/sdk/esquemas/bot.schema.json",
"formato": 1,
"tiempoMaximo": 900,
"semillas": 8,
"aprobarCon": 4,
"esperado": {
"resultado": "victoria",
"puntajeMinimo": 1000
},What greengames probar requires#
greengames probar packages the game into a .ggx and opens it again before running: what gets tested is what gets published. Then it plays the match with no screen on each difficulty and with each seed (by default, 1 and 2). A run passes if:
- the match reaches
juego.terminarwithintiempoMaximo; - the result and the score match
esperado; - no script event throws errors;
- no limit is exceeded (script time, changes per frame, sounds);
- the bot does not get stuck. If it gets stuck, the report says on which step and at which coordinates it stopped.
In addition, once per game, the first run is repeated with the same seed and must give exactly the same result. If it does not, the test fails with "el juego no es reproducible" (the game is not reproducible): see Determinism.
The whole test passes when, on each difficulty, all the runs pass, or as many as aprobarCon asks for, and none had game errors or exceeded limits. Those two are never forgiven, whatever aprobarCon asks for.
The real output of a passed test and of a failed one is in The greengames tool.
Behaviors worth knowing#
- The bot goes in a straight line except with
ruta: true. - It works the same in every view. It decides in world coordinates. In
lateral, its destinations must be on the plane. - Coins do not block it. If the destination of an
iris a piece that disappears when touched, the step counts as done. - A low wall does not stop it. A wall less than 3 units tall does not stop an avatar that jumps (2.6 of jump plus 0.45 of step). If you do not want it to jump,
"saltar": "no". - Control points.
control: truemarks anirstep as a return point: if the game makes the player respawn, the bot resumes from the control step nearest to where it ended up, within the same stretch of the bot script. After a scene change it does not go back: it continues with the current step. - Frozen does not count. While the game has the player frozen, the time of an
irstep does not run. - When it runs out of steps, it waits 10 seconds. If the bot uses up its steps, or gets stuck, the test waits 10 seconds of match and cuts off. If your game ends by itself later (on time), the last step must be
{ "esperarFin": true }. pulsaris short. It lasts about 3 frames; to hold a button down usemantener.- Filler bots, the ones that accompany the player, follow the main bot script, with random pauses.
- The bot's screen. For the script, a bot's screen is 1280 x 720 and is not a touch screen.
If the game ships no bot script#
greengames probar does not run without pruebas/bot.json: it says it is missing and ends with an error. The SDK has guionBasico(paquete), which builds a minimal bot script for a game that has none: it looks in the initial scene for a zone with the id or tag meta, final or llegada and goes to it with routing. It is a starting point, not a test of the whole game. The editor uses it when testing and exporting a hand-written game that has no bot script.
Next#
- The greengames tool: the options of
probarand how to read its output. - Case study: the complete bot script of Circuito Luz Verde, step by step.
- The test bot in the editor: the same test, without writing the file.