Games
Laboratory. The game engine exists to press on the language; it carries no stability promise and may change in any release (
SUPPORT_TIERS.md).
A q:application type="game" is a game: scenes, prefabs, a character, and what happens when things touch — in tags. It compiles to a Godot 4.4.1 project: game.json holds everything declarative, one GDScript per scene holds the logic you wrote (q:set, q:if, q:loop, q:function and the action tags, compiled), and addons/quantum/ is the runtime, the same in every game. There is no script of your own anywhere: text where statements go is a compile error.
quantum run game.q # writes projects/<id>/godot/, which Godot opens, runs and exports
quantum run game.q --check # ...and opens it headless: any script error fails the build
quantum run game.q --watch # plays it; each save builds again and the game goes on where it was
python scripts/godot.py install # the pinned Godot, into ~/.cache/quantum/godotThe same inputs give the same game: all logic runs on the fixed physics tick, the only random source is seeded per scene, and quantum.runtime.godot_replay.replay() runs a build for N ticks under an input tape and returns its state — which is how the games below are tested in CI (tests/godot/).
from quantum.runtime.godot_replay import replay, tape_from_holds
state = replay('projects/hopper/godot', ticks=120,
tape=tape_from_holds([('right', 0, 120), ('jump', 52, 72)]))
state['level-1']['game']['score'] # 110: a coin and a stompThe shape of a game
<q:application id="hopper" type="game">
<q:set name="lives" value="3" type="number" /> <!-- game state: kept across scenes -->
<q:set name="best" value="0" type="number" saved="true" /> <!-- ...and between runs -->
<qg:tileset name="kenney" src="assets/kenney/tilemap_packed.png" tile="18" />
<qg:spritesheet name="chars" src="assets/kenney/tilemap-characters_packed.png" tile="24" />
<qg:sound name="coin" src="assets/kenney/audio/coin.ogg" />
<qg:prefab name="Coin" tag="coin" sheet="kenney" frame="151" hitbox="12x12" />
<qg:prefab name="Walker" tag="enemy" sheet="chars" frame="18" hitbox="18x18" ai="patrol" speed="30" />
<qg:scene name="level-1" width="256" height="224" seed="7">
<q:set name="coins" value="0" type="number" /> <!-- scene state: reset on entry -->
<qg:tilemap tileset="kenney" collision="true">
0,0,0,0
23,23,23,23
</qg:tilemap>
<qg:character id="player" controller="platformer" sheet="chars" x="40" y="160" hitbox="18x22">
<qg:on-collision with="coin">
<qg:destroy target="other" /> <qg:play sound="coin" />
<q:set name="coins" value="{coins + 1}" />
</qg:on-collision>
<qg:on-collision with="enemy" side="top"> <qg:destroy /> <qg:bounce /> </qg:on-collision>
</qg:character>
<qg:instance prefab="Coin" x="120" y="160" />
<qg:camera follow="player" />
<qg:hud><qg:counter bind="coins" label="COINS" /></qg:hud>
</qg:scene>
</q:application>Expressions are the language's ({coins + 1}, {me.state == 'big'}, {'key-1' not in taken}), compiled to GDScript with the same meaning: / is a float division, % follows the divisor's sign, len works on a string, an array or a dictionary. Every name must be declared (a q:set of the scene or the game, a parameter, a loop variable, or me/other in a handler): a typo is a compile error, not a null in Godot.
In a handler, me is the character or prefab the handler belongs to and other what it touched; me.state, me.health, other.name read them.
What a game is made of
Declared once, in q:application.
qg:tileset
A sheet of equal tiles; tilemaps and sprites name it. With shape="hex" its tiles are hexes with their points up, every other row of a map half a hex to the right (a strategy map); with shape="iso", diamonds, every other row half a diamond on. The rows of a qg:tilemap are the rows seen; a walker's neighbours, its way and the cell at a point follow the shape.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
name | a name | required | |
src | text | required | the image, relative to the .q or a folder above it |
tile | tile | required | tile size in pixels: 18 (square), or WxH for hexes and diamonds (28x32) |
shape | square / hex / iso | square | square tiles, hexes or diamonds |
Goes inside: q:application.
qg:spritesheet
A sheet of equal frames for characters and items. With slice=, it is a frame that stretches to a box (a dialogue's box=): its corners stay as drawn, its edges and middle stretch.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
name | a name | required | |
src | text | required | |
tile | tile | required | frame size in pixels: 24, or 8x32 for frames that are not square |
slice | text | how wide its edges are, in pixels, that do not stretch: 16, or left, top, right, bottom (16, 8, 16, 8); its first frame is the frame |
Goes inside: q:application.
qg:input
The keys of an action, instead of the defaults (arrows/WASD to move, space/Z/X to jump). A second player has no defaults: every action it uses is declared with player="2".
| Attribute | Type | Default | Meaning |
|---|---|---|---|
action | a name | required | left, right, up, down, jump, select, cancel — or a name of the game's own (buy, pause): qg:on-input reads it |
keys | text | required | comma-separated: Godot key names (Space, Left, A, Enter...) and joypad names — JoyA JoyB JoyX JoyY JoyL JoyR JoyL2 JoyR2 JoyStart JoySelect, JoyUp JoyDown JoyLeft JoyRight (the pad), JoyLeftStickUp/Down/Left/Right, JoyRightStickUp/Down/Left/Right; MouseLeft, MouseRight, MouseMiddle. Player n reads joypad n-1 |
player | integer | 1 | whose keys: the character with the same player= |
Goes inside: q:application.
qg:multiplayer
The game is played by several people, each on their own machine, in lockstep: every peer runs the whole game and a tick runs when every player's input for it has arrived. One hosts (--q-host=PORT, player 1), the others join (--q-join=HOST:PORT, players 2.. in order, or the seat --q-seat=N asks for). The players' characters are told apart by player=; nobody declares keys for players 2... A player who leaves can come back: the others wait, and the next to join takes the seat, catching up the game so far (not under rollback=, where the game ends).
| Attribute | Type | Default | Meaning |
|---|---|---|---|
players | integer | required | how many, 2 or more: the game starts by itself as the last of them joins |
min-players | integer | fewer may play: once this many are there, the host may start the game (qg:start-now, or the lobby's Start now) with those who are; net_players() then says how many play | |
delay | integer | 3 | ticks between a press and its effect, everywhere: hides the round trip |
check-every | integer | 60 | ticks between comparisons of the whole state across peers; a difference is a desync, reported and fatal (0: never) |
rollback | integer | 0 | rollback instead of waiting: up to this many ticks run ahead on a guess of the others' input and are run again when it was wrong (0: plain lockstep). The scenes the networked game reaches may only hold fighters, timers, menus, the HUD, pictures and a camera — nothing that goes through physics |
start | a name | the scene the networked game starts in, on every peer, once all the players are there (the first scene when not given); the scenes before it — a title, a lobby — run on each machine alone | |
transport | enet / websocket | enet | enet (desktop to desktop), or websocket (a browser build can join a desktop host) |
Goes inside: q:application.
qg:sound
A sound the game can play (qg:play).
| Attribute | Type | Default | Meaning |
|---|---|---|---|
name | a name | required | |
src | text | required | an .ogg or .wav |
loop | true / false | false | plays until qg:stop (music) |
bus | a name | the bus it is heard on (music, effects...): how loud a bus is, qg:volume and a menu's qg:slider set, kept between runs |
Goes inside: q:application.
qg:particles
A particle effect the game can show: a preset and a few overrides. qg:emit plays it once where a thing is; qg:emitter keeps it going in a scene. Only seen: the game's state never depends on it.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
name | a name | required | |
preset | puff / sparks / smoke / dust / fire / rain / snow / confetti / stars | required | puff, sparks, smoke, dust, confetti and stars play once; fire, rain and snow keep going |
color | #rrggbb | the preset's own when not given (confetti is every colour) | |
amount | integer | how many particles | |
lifetime | number | seconds each one lives | |
speed | number | pixels a second, about | |
size | number | pixels, about |
Goes inside: q:application.
qg:item
A kind of thing the players carry: keys, gems, potions. How many they hold is the game's state, kept across scenes and in a qg:save; held('key') reads it. A prefab with gives= is picked up as one; a solid prefab with opens-with= opens for one; qg:give and qg:take add and spend them; qg:inventory shows them.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
name | a name | required | |
label | text | what a player reads (else its name), in the HUD | |
sheet | a name | a qg:spritesheet or qg:tileset: its picture in qg:inventory | |
frame | integer | 0 | |
max | integer | 0 | the most they can hold (0: no limit) |
start | integer | 0 | how many they hold when the game starts |
sound | a name | a qg:sound, played as one is picked up | |
particles | a name | a qg:particles, burst where one is picked up |
Goes inside: q:application.
qg:prefab
A kind of thing the scene places with qg:instance. With ai= it moves.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
name | a name | required | |
tag | a name | what collisions see it as (with="coin") | |
sheet | a name | required | a qg:spritesheet or qg:tileset |
frame | integer | 0 | |
hitbox | WxH pixels | required | |
ai | patrol / wander / chase / pathfind / fly / sway / shuttle / path / turret / behavior | patrol: walks under gravity, turns at walls (and at edges with turns-at); wander: top-down, changes direction now and then (from the scene seed); chase: top-down, goes for the character within sight=, straight at it; pathfind: as chase, but around the walls, by the shortest way over the tilemap's free cells; fly: straight along heading=; sway: side to side across the scene; shuttle: a solid that goes dx=,dy= and back every period= ticks, carrying what stands on it; path: follows the qg:path it was spawned on (qg:spawn at="path"), then stands at its end; turret: stands, and fires fire-prefab at the nearest targets= within range= every fire-every; behavior: top-down, made of the parts its qg:behavior lists (qg:seek, qg:flee...) | |
range | number | 100.0 | turret: pixels |
targets | a name | turret: the tag it shoots at | |
attack | shoot / area | shoot | turret: shoot spawns fire-prefab headed at the target; area damages every target in range by damage= |
damage | integer | 1 | turret attack="area": the damage |
dx | number | 0.0 | shuttle: how far it goes, pixels |
dy | number | 0.0 | shuttle: how far it goes, pixels |
period | integer | 240 | shuttle: ticks for there and back |
one-way | true / false | false | solid: can be jumped through from below and stood on |
shape | text | solid: the solid part as x,y points from its centre, separated by semicolons (-96,-21; 96,-21; 96,6; -96,6), instead of the hitbox | |
scale | number | 1.0 | the picture's size, times its frame (the hitbox stays as given) |
walls | pass / stop / bounce | pass | fly: pass goes through the tiles; stop is gone on touching one; bounce turns off it, as a ball off a wall (the way across it reverses) |
trail | #rrggbb | a line it leaves behind it as it moves, fading (a shot's streak, a ship's wake): only seen | |
pushable | true / false | false | solid: a character controller="grid" or "pointer" that steps into it pushes it a cell on, if that cell is free (no wall, no thing): a crate |
extends | a name | another prefab it is made from: it has all of it (attributes, animations, states, handlers) but what it says itself; a qg:animation or qg:state of the same name, a handler with the same with= and side=, a qg:behavior, replace the other's | |
spacing | number | 0.0 | things of its tag keep at least this many pixels apart as they move: a crowd, not a pile (0: they may overlap) |
sight | number | 80.0 | chase, pathfind, and the parts of a behavior: pixels |
heading | text | down | fly: up, down, left, right, or a direction as x,y (-1,0.5) |
accel | number | 0.0 | fly: pixels per second added to its speed every second |
lifetime | integer | 0 | fly: gone after this many ticks (0: never); any fly is gone off-screen |
particles | a name | a qg:particles kept going where it is, as it moves (a fire, an engine's flame, a torch carried): only seen | |
visible | an expression | shown only while this is true, read every tick (me is it): blinking while hurt, hidden in the dark; it still collides | |
flip-v | true / false | false | its picture upside down |
layer | integer | 0 | drawn over things of a lower layer and under those of a higher one, whatever y-sort says |
health | integer | 1 | hits it takes (qg:damage); at 0 its qg:on-death runs and it is gone |
fire-prefab | a name | what it shoots, placed below it (or above, when heading is up) | |
fire-every | integer | 0 | ticks between shots (0: never) |
fire-sound | a name | ||
speed | text | 30 | pixels per second, for ai=; or a range, 150..250, drawn from the scene seed as each instance is placed |
rotate | true / false | false | fly: the sprite turns to face the heading |
direction | left / right | left | where it walks first |
turns-at | wall / edge | wall | edge: also turns before falling off |
gravity | number | 900.0 | |
solid | true / false | false | characters stand on it and bump it (a block) |
gives | a name | a qg:item: a character that touches it picks it up (amount= of them) and it is gone; a named instance stays gone when the scene is entered again | |
amount | integer | 1 | gives=: how many |
opens-with | a name | solid: a qg:item. A character that bumps it holding one spends it and it opens (it is gone; a named one stays gone); without one, its qg:on-locked runs | |
keeps | true / false | false | opens-with=: the item is not spent (a key for every door) |
Goes inside: q:application.
qg:seek
An enemy's part: it goes for the nearest target it notices, around the walls by the shortest way over the tilemap's free cells (way="straight": straight at it).
| Attribute | Type | Default | Meaning |
|---|---|---|---|
target | text | a character's id or a thing's tag; none: any character | |
sight | number | pixels it notices a target within (else the prefab's sight=) | |
through-walls | true / false | false | it notices a target behind a solid tile (else only one it sees) |
way | around / straight | around | |
speed | number | pixels per second (else the prefab's speed=) | |
when | an expression | only while this is true (me is the enemy) |
Goes inside: qg:behavior.
qg:flee
An enemy's part: it runs from the nearest one it notices, straight away from it.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
from | text | a character's id or a thing's tag; none: any character | |
sight | number | pixels (else the prefab's sight=) | |
through-walls | true / false | false | |
speed | number | ||
when | an expression |
Goes inside: qg:behavior.
qg:keep-distance
An enemy's part: it keeps between min= and max= pixels from the nearest one it notices: away when closer, towards it when farther, and still in between.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
from | text | a character's id or a thing's tag; none: any character | |
min | number | required | |
max | number | required | |
sight | number | pixels (else the prefab's sight=) | |
through-walls | true / false | false | |
speed | number | ||
when | an expression |
Goes inside: qg:behavior.
qg:patrol
An enemy's part: to and fro along a line, turning at walls (and after distance= pixels).
| Attribute | Type | Default | Meaning |
|---|---|---|---|
heading | left / right / up / down | where it goes first (else the prefab's direction=) | |
distance | number | 0.0 | pixels before it turns (0: only at walls) |
speed | number | ||
when | an expression |
Goes inside: qg:behavior.
qg:wander
An enemy's part: a new heading every 30 to 90 ticks (from the scene seed), and at once on bumping a wall.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
speed | number | ||
when | an expression |
Goes inside: qg:behavior.
qg:stand
An enemy's part: it stands still; the parts after it wait.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
when | an expression |
Goes inside: qg:behavior.
qg:shoot
An enemy's part: it fires prefab= at the nearest target within range=, headed at it, at most once every every= ticks. It shoots whatever part moves it.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
prefab | a name | required | what it fires: a prefab with ai="fly" |
at | text | a character's id or a thing's tag; none: any character | |
every | integer | 60 | ticks between shots |
range | number | 100.0 | pixels |
through-walls | true / false | false | it fires at a target behind a solid tile (else only one it sees) |
sound | a name | ||
when | an expression |
Goes inside: qg:behavior.
qg:leap
An enemy's part, with qg:gravity: it jumps, from the floor, when it bumps a wall as it walks (a step to climb) or while its when= holds; at most once every every= ticks.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
height | number | 40.0 | pixels |
at-wall | true / false | true | it jumps on bumping a wall as it walks |
every | integer | 30 | ticks between two leaps at least |
sound | a name | ||
when | an expression | it jumps while this is true too (me is the enemy) |
Goes inside: qg:behavior.
qg:animation
Frames of the sheet, cycled. A character plays "idle", "walk" and "jump" by what it does (a platformer "fall" while it comes down, when it has one; a topdown one "walk-up" and "walk-down" when it has them, "walk-up" upside down for down, and while it swings "attack", or "attack-up" and "attack-down" by where it faces); a prefab plays "walk".
| Attribute | Type | Default | Meaning |
|---|---|---|---|
name | a name | required | |
frames | text | required | comma-separated frame numbers |
fps | number | 8.0 | |
loop | true / false | true | false: it plays once, stays on its last frame, and its qg:on-animation-end runs (an attack, a fall, a death) |
Goes inside: qg:character, qg:prefab, qg:state.
qg:state
A form of a character or a prefab (small, big; calm, angry): hitbox, frame, animations, and for a prefab its speed and fire-every. me.state reads it; qg:become changes it.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
name | a name | required | |
hitbox | WxH pixels | a character needs one; a prefab keeps its own | |
frame | integer | its picture in this state (its own frame when not given) | |
speed | number | prefab: overrides its speed | |
fire-every | integer | prefab: overrides its fire-every | |
fire-prefab | a name | prefab: overrides its fire-prefab | |
range | number | turret: overrides its range | |
initial | true / false | false | the state it starts in (else the first one) |
Goes inside: qg:character, qg:prefab.
qg:move
A fighter's attack, on an action: an animation with one active frame, in which a box of reach= at at= (in front, in the facing direction) is tested against the opponent once. A hit takes damage=, stuns for stun= ticks and pushes push= pixels; blocked (the opponent holding away), it takes no damage and half the rest.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
name | a name | required | |
action | a name | required | a default action or one a qg:input declares |
frames | text | required | comma-separated frame numbers |
fps | number | 12.0 | |
active | integer | 1 | which frame (0-based) hits |
reach | WxH pixels | required | the hit box, WxH |
at | text | 0,0 | the hit box's centre from the body's, dx,dy; dx is forward |
damage | integer | 5 | |
stun | integer | 12 | |
push | number | 40.0 |
Goes inside: qg:character.
qg:scene
A screen of the game. The first one is where the game starts.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
name | a name | required | |
width | integer | 256 | |
height | integer | 224 | |
background | #rrggbb | #000000 | |
seed | an expression | 0 | the random seed, a number or an expression of the game's state read as the scene is entered (a world's number); the same seed gives the same game, so a scene entered again and again draws the same each time unless its seed is read from the state (seed="{round}") |
save | true / false | false | entering the scene saves the game to continue here (qg:save), as a room of an adventure does |
y-sort | true / false | false | characters and things are drawn by how low they stand, the lower in front: depth seen from above (a layer= still comes first) |
ambient | #rrggbb | a dark scene: the light where no qg:light reaches (#000000 is black, #ffffff as drawn); its solid tiles cast the shadows of the lights with shadows="true" | |
world | true / false | false | a scene of the world (qg:connect): entering it comes into the world, and it may hold qg:mirror, qg:on-event and qg:on-refused; a qg:camera there may follow="me", the player's own entity |
Goes inside: q:application.
What a scene holds
The level, the character, the things placed in it, and what the screen shows.
qg:tilemap
The level: CSV rows of tile numbers (0 is empty, n is tile n-1 of the tileset), or a Tiled map (src=) whose tile layers draw it and whose object layers place prefabs by class. Several in a scene are layers of one map, drawn in order (a floor, a path, the walls), on one tileset. A Tiled map may use several tilesets (each layer draws from one, which is the qg:tileset of the game with the same image) and flipped or turned tiles; an object's state property is the qg:state it starts in; objects of class zone (a tag property), exit (to and at properties) and path (a polyline) are the scene's qg:zone, qg:exit and qg:path; the map's own properties are read with tiled('name').
| Attribute | Type | Default | Meaning |
|---|---|---|---|
tileset | a name | required | |
collision | true / false | false | every tile is solid (a Tiled layer says so with a collision property) |
src | text | a .tmx, relative to the .q or a folder above it |
Holds the CSV rows.
Goes inside: qg:scene.
qg:character
A body the player moves: a platformer, or a walker on a world map.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
id | a name | required | |
controller | platformer / map / topdown / ship / fighter / grid / pointer / behavior | required | grid: steps from cell to cell of the tilemap; the tiles of a layer with collision, and the things, stop it, and stepping into a thing runs its qg:on-collision with the thing's tag; me.col and me.row are its cell. pointer: as grid, and a click (its player's qg:cursor) walks it to that cell by the shortest way around the walls and things. behavior: made of the parts its qg:behavior lists |
step-frames | integer | 15 | grid: ticks to walk one cell (a bump takes as long) |
diagonal | true / false | false | grid: two directions held at once step diagonally |
player | integer | 1 | whose keys move it (qg:input player=); 1 has the defaults |
health | integer | 100 | fighter: hits it takes; at 0 it is KO and qg:on-ko runs |
facing | left / right | right | fighter: where it looks at first |
bounds | scene / none | kept inside the scene: a ship unless none, a topdown character when scene | |
axis | both / vertical / horizontal | both | ship: which way it can move |
fire-action | a name | ship, platformer: the action that shoots (jump, or one a qg:input declares) | |
fire-prefab | a name | ship: what it shoots, placed above it; platformer: in front of it, a fly prefab heading the way it faces | |
fire-every | integer | 10 | ship: ticks between shots while the action is held; platformer: the fewest ticks between two presses that shoot |
fire-sound | a name | ||
at | an expression | map: the qg:map-node it starts on (a name, or an expression) | |
speed | number | 60.0 | map, topdown: pixels per second |
attack-action | jump | topdown: the action that swings in front | |
attack-reach | number | 16.0 | topdown: how far the swing reaches, pixels |
attack-frames | integer | 12 | topdown: how many ticks the swing lasts |
attack-sound | a name | a qg:sound, played on the swing | |
sheet | a name | required | |
frame | integer | 0 | |
x | number | required | |
y | number | required | |
hitbox | WxH pixels | required | |
run-speed | number | 90.0 | pixels per second |
jump-height | number | 64.0 | pixels, with the button held |
variable-jump | true / false | true | releasing the button cuts the jump |
coyote-frames | integer | 6 | ticks after leaving a ledge in which a jump still works |
buffer-frames | integer | 0 | platformer: a jump pressed up to this many ticks before landing jumps as it lands (0: only a press on the floor) |
gravity | number | 900.0 | pixels per second squared |
max-fall | number | 300.0 | terminal velocity, pixels per second |
jump-sound | a name | a qg:sound, played on take-off | |
jump-speed | number | platformer: the take-off speed, pixels per second, instead of jump-height | |
accel | number | 0.0 | platformer: pixels per second squared towards the run speed (0: at once) |
air-jumps | integer | 0 | platformer: jumps it may take in the air before landing again |
air-jump-boost | number | 1.0 | platformer: an air jump multiplies the speed across by this |
jump-cut | number | 0.0 | platformer: releasing the button while rising multiplies the rise by this, once (0: the extra gravity of variable-jump instead) |
scale | number | 1.0 | the picture's size, times its frame (the hitbox stays as given) |
if | an expression | placed only when this is true as the scene is built: a second player's character only in a networked game, net_status() != 'offline' | |
particles | a name | a qg:particles kept going where it is, as it moves (a fire, an engine's flame, a torch carried): only seen | |
visible | an expression | shown only while this is true, read every tick (me is it): blinking while hurt, hidden in the dark; it still collides | |
flip-v | true / false | false | its picture upside down |
layer | integer | 0 | drawn over things of a lower layer and under those of a higher one, whatever y-sort says |
Goes inside: qg:scene.
qg:behavior
What a character controller="behavior" is made of: parts, each a small rule. A platformer is qg:gravity, qg:walk and qg:jump; a top-down walker is qg:walk axis="both"; a flapping bird is qg:gravity and a qg:jump with air=; a ship that comes back round the screen is qg:walk axis="both" and qg:wrap. Each tick, whatever their order: walk and jump read the keys, gravity pulls, the body moves, then keep-inside, wrap and face. me.vx, me.vy and me.on_floor read it; qg:velocity sets its speed from the game's rules (a dash, a knock-back), so what no part covers is written in the game. In a qg:prefab ai="behavior", an enemy's parts instead: what it moves by is the first of qg:seek, qg:flee, qg:keep-distance, qg:patrol, qg:wander and qg:stand, in their order, whose when= holds and that has something to do (a seek with nothing in sight has not); it stands when none has. Its qg:shoot parts fire besides. me.doing reads the part that moved it ("" for none). With qg:gravity the enemy walks on floors: the parts move it left and right only, a seek goes straight, the prefab's turns-at="edge" keeps it from walking off a ledge, and qg:leap jumps; me.on_floor reads whether it stands.
Goes inside: qg:character, qg:prefab.
qg:gravity
A part: it falls, and stands on floors (without it, the body floats: top-down). A character's or an enemy's.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
strength | number | 900.0 | pixels per second squared |
max-fall | number | 300.0 | the speed it falls at most, pixels per second |
Goes inside: qg:behavior.
qg:walk
A part: left and right (and up and down, with axis="both") set its speed.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
speed | number | 90.0 | pixels per second |
accel | number | 0.0 | pixels per second squared towards the speed (0: at once) |
axis | x / both | x | x: across only (with gravity); both: in eight directions |
Goes inside: qg:behavior.
qg:jump
A part: a press on the floor leaps; with air=, also in the air. It needs qg:gravity.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
action | a name | jump | a default action, or one a qg:input declares |
height | number | 64.0 | pixels, with the button held |
speed | number | the take-off speed, pixels per second, instead of height= | |
coyote | integer | 6 | ticks after leaving a ledge in which a jump still works |
buffer | integer | 0 | a press up to this many ticks before landing jumps as it lands (0: only a press on the floor) |
air | integer | 0 | jumps it may take in the air before landing again |
variable | true / false | true | letting go while rising cuts the leap |
cut | number | 0.0 | letting go multiplies the rise by this, once (0: three times the gravity while it rises instead) |
sound | a name | a qg:sound, played on take-off |
Goes inside: qg:behavior.
qg:keep-inside
A part: the scene's edges stop it.
Goes inside: qg:behavior.
qg:wrap
A part: past an edge of the scene, it comes back on the other side.
Goes inside: qg:behavior.
qg:face
A part: its picture turns left or right the way it goes.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
by | velocity / input | velocity | velocity: the way it moves; input: the way it is pushed (it faces while it walks into a wall) |
Goes inside: qg:behavior.
qg:instance
A prefab placed in the scene.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
prefab | a name | required | |
x | number | required | |
y | number | required | |
name | a name | the node name (other.name in a handler); the prefab name otherwise | |
if | an expression | placed only when this is true as the scene is built |
Goes inside: qg:scene.
qg:emitter
A qg:particles effect that keeps going in the scene: rain across its width, a fire on a spot.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
particles | a name | required | |
x | number | required | |
y | number | required | |
width | number | 0.0 | rain and snow: the band they fall across, pixels (0: a point) |
Goes inside: qg:scene.
qg:spawner
Places count instances of a prefab, one every so many ticks, from a tick on.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
prefab | text | required | a prefab name, or several comma-separated: one is drawn from the seed for each instance |
from | integer | 0 | the tick of the first one |
every | integer | 60 | |
count | integer | 1 | 0: no end |
x | text | random | a number, or random across the scene width (from the seed) |
y | number | -12.0 | |
along | edges | edges: instead of x,y, a random point on the scene's border | |
heading | inward | along: a flying prefab heads into the scene... | |
spread | number | 0.0 | ...turned by up to this many degrees either way, from the seed |
Goes inside: qg:scene.
qg:zone
An invisible rectangle with a tag: what touches it runs its qg:on-collision with= that tag.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
name | a name | required | |
tag | a name | required | |
x | number | required | |
y | number | required | |
width | number | required | |
height | number | required |
Goes inside: qg:scene.
qg:sprite
A picture in the scene, with no behaviour: a backdrop, a divider, a sign.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
sheet | a name | required | |
frame | an expression | 0 | a number, or an expression the picture follows tick by tick: {1 if hurt > 0 else 0} |
x | number | required | |
y | number | required |
Goes inside: qg:scene.
qg:path
A route through the scene, straight from point to point, that ai="path" prefabs follow (qg:spawn at="path" path=).
| Attribute | Type | Default | Meaning |
|---|---|---|---|
name | a name | required | |
points | text | required | x,y pairs separated by semicolons: 0,100; 200,100; 200,300 |
Goes inside: qg:scene.
qg:cursor
A player's pointer in the scene: the mouse, moved also by that player's left/right/up/down by step= pixels. cursor.x, cursor.y (and, with grid=, cursor.col, cursor.row, snapped) in qg:on-select; with sheet= it is drawn. Under qg:multiplayer every player's cursor travels with their input.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
id | a name | its name in expressions, as a character's: aim.x, aim.y (with grid=, aim.col, aim.row), and a target of qg:beam | |
player | integer | 1 | |
step | number | 16.0 | pixels per tick while a direction is held |
grid | integer | cell size: the cursor snaps to cell centres | |
cells | true / false | false | it snaps to the middles of the cells of the scene's qg:tilemap, whatever their shape (a hex map's); cursor.col, cursor.row are the cell |
sheet | a name | ||
frame | integer | 0 |
Goes inside: qg:scene.
qg:exit
A rectangle that leads to another scene; the character arrives at the exit named at= there.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
name | a name | required | |
x | number | required | |
y | number | required | |
width | number | required | |
height | number | required | |
to | a name | required | |
at | a name | required |
Goes inside: qg:scene.
qg:map-node
A place on a world map. With scene=, pressing jump there enters that scene.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
name | a name | required | |
x | number | required | |
y | number | required | |
sheet | a name | required | |
frame | integer | 0 | |
scene | a name |
Goes inside: qg:scene.
qg:map-path
A way between two map nodes, both directions.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
from | a name | required | |
to | a name | required | |
requires | a name | a scene name that must be in the game state cleared |
Goes inside: qg:scene.
qg:camera
What the screen shows: it follows a character, or the middle of several, in the game's ticks (a replay and every peer frame the same). Only seen: nothing in the game's state depends on it.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
follow | text | required | a character id, or several: "p1, p2" (their middle) |
bounds | tilemap / none | tilemap | |
zoom | number | 1.0 | 2: twice as close |
dead-zone | WxH pixels | WxH: the box about the middle of the screen in which the followed one moves before the camera does | |
lead | number | 0.0 | pixels it looks ahead, the way the followed one goes |
smooth | number | 0.0 | how fast it catches up, per second (0: at once) |
fit | true / false | false | several followed: it zooms out to keep them all on the screen, down to zoom-min, and back in as they come together |
zoom-min | number | 0.5 | fit: as far out as it goes |
player | integer | one view per player: this is that player's. On one machine the screen splits (side by side for two, quarters for three or four); over the network each machine shows its own player's. A camera that follows no one present has no view |
Goes inside: qg:scene.
qg:hud
Text and pictures over the game.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
position | top-left / top-center / top-right / center / bottom-left / bottom-center / bottom-right | top-left | |
x | integer | with y=, its top-left corner on the screen, in pixels (in place of position=): a panel of a screen laid out by hand | |
y | integer | ||
box | a name | a qg:spritesheet with slice=: what it shows is in a box drawn with it, stretched (a frame) | |
font | text | a .ttf, relative to the .q or a folder above it | |
size | integer | 8 | the font size of its items, unless an item says otherwise |
if | an expression | shown only while this is true |
Goes inside: qg:scene, qg:overlay.
qg:counter
A number from the scene state, or how many of an item the players hold, in the HUD.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
bind | a name | a q:set of the scene or the game (or item=) | |
item | a name | a qg:item: how many are held (or bind=) | |
label | text | `` | text before the number |
size | integer | font size | |
color | #rrggbb | its colour (#rrggbb) |
Goes inside: qg:hud.
qg:text
A string from the scene state, in the HUD.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
bind | a name | a q:set of the scene (or value=) | |
value | an expression | an expression, read every frame (or bind=) | |
size | integer | font size | |
color | text | its colour: #rrggbb, or an expression giving one, read every frame (red when a balance is below zero) |
Goes inside: qg:hud.
qg:bar
A bar in the HUD: a number against its maximum — a q:set, or a fighter's health as id.health (bind=), or any expression (value=: a world's {me().hp}).
| Attribute | Type | Default | Meaning |
|---|---|---|---|
bind | text | a q:set of the scene or the game, or a fighter's id and .health (p1.health) | |
value | an expression | instead of bind=: an expression read every tick | |
max | an expression | 100 | a number, or an expression read every tick |
width | integer | 100 | |
height | integer | 10 | |
color | #rrggbb | #e04040 |
Goes inside: qg:hud.
qg:inventory
In the HUD: the items held, each its picture (the qg:item's sheet= and frame=) and how many, in a row; an item held 0 times is not shown.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
size | integer | the font size of the numbers |
Goes inside: qg:hud.
qg:menu
A list of buttons (and text fields) over the scene. A player moves through it with up/down and chooses with select (Enter, the joypad's A: the button that has the focus), or points at a button with the mouse and clicks it (a click away from the buttons chooses nothing) — the pointer is that player's qg:cursor and the click its "click" action, so a click is replayed and travels in the lockstep like a key.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
player | integer | 1 | whose keys and pointer choose |
local | true / false | false | the menu of whoever plays at this machine (its seat, in a networked game); it holds only shared fields, whose texts reach the others |
if | an expression | shown, and choosable, only while this is true: paused(), a q:set... | |
position | top-left / top-center / top-right / center / bottom-center | center | |
x | integer | with y=, its top-left corner on the screen, in pixels (in place of position=) | |
y | integer | ||
source | an expression | a list: the menu holds one qg:button, and shows it once for each row of it, read every frame (a squad to choose from, the clubs, a shop's goods); the row is as= in its label=, if= and what it runs | |
as | a name | with source=: the name of the row | |
font | text | a .ttf, relative to the .q or a folder above it | |
size | integer | 16 | font size |
box | a name | a qg:spritesheet with slice=: the buttons are in a box drawn with it, stretched (a frame) | |
columns | integer | 1 | its items in a grid of so many columns: left and right move along a row |
rows | integer | 0 | how many rows show at once; the others scroll into view with the focus (0: all of them) |
Goes inside: qg:scene, qg:overlay.
qg:button
A button of a qg:menu. What it holds runs when it is chosen: actions and statements, like a handler.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
label | text | required | its text, or an expression ({...}) read every frame |
if | an expression | shown, and choosable, only while this is true |
Goes inside: qg:menu.
qg:field
A text box of a qg:menu, bound to a string state: what is typed is the state. Typing is local to the machine (an address) — the lockstep does not carry it. A shared field (shared="true") is a draft until Enter sends it: the text goes with the player's input for a tick, and on that tick, on every machine of a networked game, it becomes the bound state and what the field holds runs, with sender (the seat that sent it) and text — names, a short chat. While a field is typed in, the keys type and play nothing; Enter, Escape or a click away from the box ends the typing (the click goes on to what is under the pointer), and the key that ended it plays only once let go.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
bind | a name | required | a q:set of the scene or the game |
label | text | `` | text before the box |
max-length | integer | 64 | |
shared | true / false | false | sent to every machine with the inputs, on Enter |
secret | true / false | false | a password: the box shows dots, and so does what the play protocol sees of it. The bound state is the game's as any other: set it to "" once it is used (a qg:sign-in's qg:on-done) |
Goes inside: qg:menu.
qg:lobby
The usual networked-game menu, ready made: play here (local=), host, an address field, join, cancel, and a line that says where things are. It is a qg:menu and a qg:hud the compiler writes; the address is the scene's lobby_address.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
local | a name | a scene to play on this machine alone (a button for it when given) | |
local-label | text | Play here | |
port | integer | 7777 | |
address | text | 127.0.0.1:7777 | what the address field starts with, unless the web page's address has ?join=... |
font | text | ||
size | integer | 16 | |
options | a name | a scene of the game's options (how loud, the full screen, the keys): an Options button goes there |
Goes inside: qg:scene.
Handlers
Where the logic goes: actions and statements, with me and other (cursor and other in qg:on-select).
qg:on-collision
What happens when this character or prefab touches something. Holds actions and statements; me is the toucher, other what it touched. In a prefab, only the game state is in reach.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
with | a name | required | the tag of what it touches |
side | any / top / bottom / left / right | any | top: landing on it; bottom: hitting it from below; left: hitting it from its left (it is to the right); right: from its right. A side handler runs before the "any" one, which then does not |
cooldown | integer | 0 | ticks before this handler can fire again |
Goes inside: qg:character, qg:prefab.
qg:on-hit
What happens when this character's swing (attack-action) reaches something. Holds actions and statements.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
with | a name | required | the tag of what it hits |
Goes inside: qg:character.
qg:on-fall
What happens when this character falls below the tilemap. Holds actions and statements.
Goes inside: qg:character.
qg:on-tile
What happens while this character touches a tile of a kind (qg:tile kind=): stands on it, is in it, or bumps it. Holds actions and statements; me is the character. Without a cooldown, once as it comes to touch one; with one, again each time the cooldown runs out while it still does.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
kind | ladder / water / ice / hurt | required | |
cooldown | integer | 0 | ticks before it may run again while still touching |
Goes inside: qg:character.
qg:on-ko
What happens when this fighter's health reaches 0. Holds actions and statements; me is the loser, other the winner.
Goes inside: qg:character.
qg:on-input
What happens when the player presses an action in this scene, or lets it go (on="release"); or, with sequence=, when a player presses several in order in a short time — a special move: "down, forward, punch" holds on the tick punch is pressed, if down and then forward were pressed before it within within= ticks (other presses between them do not matter, and the presses it uses are used up). "forward" and "back" are right and left as that player's character faces. Holds actions and statements.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
action | a name | a default action, or one a qg:input declares; or sequence= | |
on | press / release | press | press: as it goes down; release: as it is let go |
sequence | text | actions in the order pressed, separated by commas; forward and back follow the way the player's character faces | |
within | integer | 30 | sequence: how many ticks from its first press to its last (60 a second) |
player | integer | 1 | sequence: whose presses |
Goes inside: qg:scene, qg:overlay.
qg:on-select
What happens when a player presses select with their qg:cursor somewhere (or another action: action= with a qg:input of the right mouse button, MouseRight, is a right click on a thing). Holds actions and statements; cursor is where (x, y, col, row, player), other the thing under it, or null. hovered() reads the thing under a cursor at any time.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
player | integer | only this player's cursor (any, when not given) | |
action | a name | select | the action that chooses: select (the left button, Enter), or one a qg:input declares (keys="MouseRight", "WheelUp", "WheelDown") |
Goes inside: qg:scene.
qg:on-damage
What happens when this prefab takes qg:damage and lives (me.health is what is left).
Goes inside: qg:prefab.
qg:on-death
What happens when this prefab's health reaches 0, before it goes. In a scene, with of=, when any thing of that tag dies there.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
of | a name | scene: the tag |
Goes inside: qg:prefab, qg:scene.
qg:on-locked
What happens when a character bumps this prefab (opens-with=) without the item. Holds actions and statements; me is the locked thing, other the character.
Goes inside: qg:prefab.
qg:on-animation-end
What happens when an animation of this character or prefab with loop="false" has played to its last frame. Holds actions and statements; me is whose animation it is.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
name | a name | required | the animation |
Goes inside: qg:character, qg:prefab.
Actions
What a handler can do, besides q:set, q:if, q:loop, q:call and q:return.
qg:destroy
Removes a thing from the scene.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
target | a name | other | other, me, or a q:set holding a thing (thing_at) |
Goes inside: a handler.
qg:bounce
Throws the character up, as after a stomp.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
target | me | me | |
height | number | 32.0 | pixels |
Goes inside: a handler.
qg:play
Plays a qg:sound. Directly in a scene: as the scene is entered. With volume=, pitch= or vary=, it plays in a voice of its own, so the same sound can overlap itself. With sounds=, one of several, drawn each time (footsteps, hits that do not all sound alike). Only heard: the game's state records the sound's name (sounds= as written), never how it was played nor which one.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
sound | a name | a qg:sound; or sounds= | |
sounds | text | several qg:sounds, separated by commas: one of them each time | |
volume | number | 0.0 | decibels added (-6: about half as loud) |
pitch | number | 1.0 | 2: an octave up, 0.5: an octave down |
vary | number | 0.0 | a random pitch between pitch - vary and pitch + vary, each time: twenty hits that do not sound the same |
at | a name | heard from where a thing is (me, other, a character id, or a parameter holding one): softer and to one side the farther it is from the middle of the screen, silent two screens away. A thing's own sounds (its fire-sound, a leap's or a shot's sound=) are heard where it is |
Goes inside: a handler, qg:scene.
qg:respawn
Puts a character back at its start or its last checkpoint, still; a thing back where it was placed, with its first heading and speed.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
target | a name | me | me, other, or a character of the scene by id |
Goes inside: a handler.
qg:become
Changes a character or a thing to one of its qg:states.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
target | a name | me | other, me, or a parameter, loop variable or q:set holding a thing |
state | a name | required |
Goes inside: a handler.
qg:spawn
Places a new prefab instance in the scene.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
prefab | a name | required | |
at | a name | other | whose position: other, me, a character id, or a parameter, loop variable or q:set holding a thing; cursor: where the qg:on-select cursor is; path: the start of path= |
path | a name | at="path": a qg:path of the scene, which an ai="path" prefab follows | |
x | an expression | a point of the scene instead of at= (with y=): an expression, in pixels | |
y | an expression | ||
dx | number | 0.0 | |
dy | number | 0.0 | offset in pixels |
heading | text | an ai="fly" prefab: the way it goes, as x,y, each an expression ({aim.x - me.x},{aim.y - me.y}: at the pointer), instead of its own |
Goes inside: a handler.
qg:swap
Replaces a thing with an instance of another prefab, in its place.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
target | a name | other | other, or a q:set holding a thing |
prefab | a name | required |
Goes inside: a handler.
qg:checkpoint
Makes the other thing's position where the character respawns.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
target | me | me | |
at | other | other |
Goes inside: a handler.
qg:goto-scene
Leaves this scene for another, at the end of the tick. Scene state is lost; game state stays.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
name | a name | required | |
fade | number | 0.0 | seconds the new scene takes to come out of black (0: a cut); only the picture fades, the game runs from its first tick as without it |
Goes inside: a handler.
qg:damage
Takes health from a thing; at 0 its qg:on-death runs and it goes.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
target | a name | other | other, me, or a parameter, loop variable or q:set holding a thing |
amount | an expression | 1 |
Goes inside: a handler.
qg:burst
A puff of particles where a thing is. Cosmetic.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
at | a name | me | other, me, or a parameter, loop variable or q:set holding a thing |
color | #rrggbb | #ffffff | |
count | integer | 12 |
Goes inside: a handler.
qg:shake
Shakes the scene a thing is in. Cosmetic.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
at | a name | me | other, me, or a parameter, loop variable or q:set holding a thing |
frames | integer | 10 | |
strength | number | 3.0 | pixels |
Goes inside: a handler.
qg:deflect
Changes where a flying thing (ai="fly") goes: flips one axis of its heading, or sets the heading to dx,dy (expressions; the length does not matter).
| Attribute | Type | Default | Meaning |
|---|---|---|---|
target | a name | other | other, me, or a parameter, loop variable or q:set holding a thing |
axis | x / y | the axis to flip | |
dx | an expression | ||
dy | an expression |
Goes inside: a handler.
qg:stop
Stops a qg:sound (a looping one, mostly). Directly in a scene: as the scene is entered.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
sound | a name | required |
Goes inside: a handler, qg:scene.
qg:put
Moves a thing or a character to a point, at once.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
target | a name | other | me, other, a character id, or a q:set holding a thing (thing_at) |
x | an expression | required | |
y | an expression | required |
Goes inside: a handler.
qg:host
Hosts the networked game (qg:multiplayer): this machine is player 1 and waits for the others; the game starts in the start= scene when they are all there. net_status() says where things are.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
port | integer | 7777 |
Goes inside: a handler.
qg:join
Joins a networked game at an address: host:port, or a quantum relay's room (wss://relay.example/pong/ROOM, where two browsers meet); this machine is the next player.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
address | an expression | required | an expression: a q:set a qg:field fills, or text |
Goes inside: a handler.
qg:leave
Leaves the networked game, or stops hosting or joining.
Goes inside: a handler.
qg:pause
Pauses the scene: its characters, things, timers and spawners stop where they are. Menus, the HUD, the camera and qg:on-input go on, so a key or a button can resume it. paused() says whether it is.
Goes inside: a handler.
qg:resume
Goes on with a paused scene.
Goes inside: a handler.
qg:say
Opens a qg:dialogue of the scene: its lines one at a time, select for the next. The scene is paused while it is open (talking() says so); after the last line, its qg:on-end runs.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
dialogue | a name | required |
Goes inside: a handler.
qg:save
Saves the game to continue it later: the game state (every q:set of q:application) and a scene to come back to. Kept between runs (in a browser, by the browser); a new qg:save replaces it. has_save() says whether there is one. A networked game is not saved.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
scene | a name | the scene to continue in (none: this one); it starts over, what was in it is not saved | |
slot | an expression | which of several saves (1, 2, 3...: a game each); the scenes with save="true" save into it from then on. None: the one save |
Goes inside: a handler.
qg:continue
Continues the saved game (qg:save): its game state back, and on to its scene. Nothing when there is no save.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
slot | an expression | which save (qg:save slot=); the scenes with save="true" save into it from then on. None: the one save |
Goes inside: a handler.
qg:next-language
Shows the game in its next language (qg:language), after its own: a button for it is <qg:button label="{tr('Language') + ': ' + language()}">. The choice is kept for the next run. Only what is shown changes.
Goes inside: a handler.
qg:tween
Eases a property of a thing from what it is to a value, over some seconds: a coin that rises, a door that slides, a pulse. It runs in the game's ticks, so a replay and every peer see the same; it stops while the scene is paused, and a new tween of the same property replaces it.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
target | a name | me | me, other, a character id, or a q:set holding a thing |
property | x / y / rotation / scale / scale-x / scale-y / alpha / tint | required | x, y: where it is (collisions see it); rotation (degrees), scale, scale-x and scale-y (across and up: a squash, a stretch), alpha, tint (a colour its picture is multiplied by: #ffffff is as drawn): its picture |
to | an expression | required | a number, or for tint a colour (#rrggbb) |
from | an expression | where it starts (what it is now when not given) | |
seconds | number | 0.5 | |
trans | linear / sine / quad / cubic / back / bounce / elastic | sine | the curve |
ease | in / out / in-out | out | which end of the curve is slow |
repeat | integer | 0 | times it plays again (-1: for ever) |
yoyo | true / false | false | each repeat goes back the other way |
Goes inside: a handler.
qg:velocity
Sets the speed of a character controller="behavior", in pixels per second: a dash, a knock-back, a wind, a spring. An axis not given keeps its speed. Its parts go on from there (gravity pulls it back down; walk sets the speed across again on the next tick, unless it has accel=).
| Attribute | Type | Default | Meaning |
|---|---|---|---|
target | a name | me | me, other, or a character id |
x | an expression | ||
y | an expression |
Goes inside: a handler.
qg:emit
Plays a qg:particles effect once where a thing is, or a text that floats up from it and fades (a damage number, +10). Only seen: the game's state never depends on it.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
particles | a name | the effect (or text=) | |
text | an expression | a text instead: "+10", or an expression ({str(amount)}) | |
color | #rrggbb | #ffffff | the text's colour |
size | integer | 8 | the text's size |
at | a name | me | me, other, a character id, or a q:set holding a thing |
dx | number | 0.0 | |
dy | number | 0.0 | offset in pixels |
Goes inside: a handler.
qg:set-tile
Puts a tile in a cell of the scene's tilemap, or empties it: a block that breaks, a door drawn as tiles. Its collision changes from the next tick, monsters that pathfind find their way again, and the cells changed are part of the game's state (a replay, every peer). tile_at(col, row) reads a cell.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
col | an expression | required | the column, from 0 at the left |
row | an expression | required | the row, from 0 at the top |
tile | an expression | 0 | as in the rows of qg:tilemap: 0 nothing, n tile n-1 of the tileset, -n the same flipped |
layer | text | a layer's name (a Tiled map's); the first with collision, else the first, when not given |
Goes inside: a handler.
qg:play-animation
Plays an animation of a thing or a character from its first frame. Its own animations (idle, walk...) wait until it ends; a looping one holds until the next qg:play-animation, and name="" gives it its own animations back.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
target | a name | me | me, other, a character id, or a q:set holding a thing |
name | text | required | an animation of the target (empty: back to its own) |
Goes inside: a handler.
qg:give
The players get some of an item (up to its max=).
| Attribute | Type | Default | Meaning |
|---|---|---|---|
item | a name | required | a qg:item |
amount | integer | 1 |
Goes inside: a handler.
qg:take
The players spend some of an item (not below 0): a potion drunk, a key used.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
item | a name | required | a qg:item |
amount | integer | 1 |
Goes inside: a handler.
qg:reset-items
A new game: every qg:item back to its start=, and the named things picked up or opened come back.
Goes inside: a handler.
qg:for-each
Runs what it holds once for every thing of a tag in the scene, in the scene's order, with var= naming the thing: freeze every enemy, break the bricks near a bomb. The things are those there as it starts; one destroyed meanwhile is skipped.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
tag | a name | required | the things' tag |
var | a name | it | the name of each thing in turn (a target=, an expression) |
Goes inside: a handler.
qg:time-scale
Slow motion, or fast: the whole game runs at this speed — its ticks come further apart (or closer), each the same tick as ever, so a replay and every peer see the same game. The music is not slowed. A new scene starts at 1.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
scale | number | required | 0.25: four times slower; 1: as usual; up to 4 |
seconds | number | 0.0 | seconds as the player sees them, then back to 1 (0: until another qg:time-scale) |
Goes inside: a handler.
qg:quit
Ends the game: the window closes. In a browser there is nothing to quit to and it does nothing; can_quit() says whether it would.
Goes inside: a handler.
qg:start-now
The host starts the networked game with the players who are there, once qg:multiplayer's min-players= are (before that, and on the other machines, it does nothing). The lobby has a Start now button that does this.
Goes inside: a handler.
qg:watch
Joins a networked game at an address with no seat, to watch it: the players' inputs reach this machine too, and it runs the same game, its keys doing nothing. One who comes after the game has begun catches up on it first. net_status() is then "watching" and net_player() 0. Not under rollback=.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
address | an expression | required | host:port, or a relay's wss:// room |
Goes inside: a handler.
qg:after
Runs what it holds later, so many ticks (or seconds) after the handler reached it: a power-up that runs out, a bomb's fuse, a pause before the next serve. It counts the game's ticks, so a replay and every peer see the same; it waits while the scene is paused, and goes with the scene. The names it reads (me, other, a loop variable, a parameter) are what they were as it started; a thing gone by then is null, and an action on it does nothing.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
ticks | an expression | how many ticks from now (60 a second) | |
seconds | an expression | or how many seconds, rounded to ticks | |
name | a name | one of this name still waiting is replaced: the count starts again (a second power-up before the first runs out) |
Goes inside: a handler.
qg:beam
A line between two things for a while, following them as they move: a laser, a zap, a rope, an aiming line. Only seen.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
from | a name | me | me, other, a character or cursor id, or a parameter, loop variable or q:set holding a thing |
to | a name | required | the same |
seconds | number | 0.1 | how long it shows |
color | #rrggbb | #ffffff | |
width | number | 2.0 | pixels |
Goes inside: a handler.
qg:camera-to
The camera goes to a thing or a point, stays a while, and comes back to what it follows: a cutscene, a boss's entrance, the gate a lever opened. The scene waits meanwhile (unless pause="false"). The scene needs a qg:camera (in a q:function of the game, a scene without one is not shown anything).
| Attribute | Type | Default | Meaning |
|---|---|---|---|
target | a name | me, other, a character or cursor id, or a parameter, loop variable or q:set holding a thing (or x= and y=) | |
x | an expression | ||
y | an expression | a point of the scene instead | |
zoom | number | how close it goes (the camera's own when not given) | |
seconds | number | 0.6 | to go there, and as long to come back |
hold | number | 1.0 | seconds it stays |
pause | true / false | true | the scene waits while the camera is away |
Goes inside: a handler.
qg:attach
A thing is carried by another: it goes where its carrier goes, dx,dy from it (a key over a head, a torch in a hand, a turret on a tank). While carried it touches nothing and nothing touches it, and it stands in no cell; carrying(me, 'key') finds it.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
target | a name | other | what is carried: other, me, a character id, or a parameter, loop variable or q:set holding a thing |
to | a name | me | its carrier, the same |
dx | number | 0.0 | |
dy | number | -12.0 | from the carrier's middle |
Goes inside: a handler.
qg:detach
A carried thing is put down where it is (qg:attach): it touches again.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
target | a name | what is put down (or from= and tag=) | |
from | a name | the carrier: me, other, a character id... | |
tag | a name | with from=: every thing of this tag it carries |
Goes inside: a handler.
qg:volume
How loud a bus of the game's sounds is, from 0 (silent) to 100; kept between runs. volume('music') reads it. Only heard.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
bus | a name | required | a bus of the game's sounds (qg:sound bus=) |
value | an expression | required | 0 to 100 |
Goes inside: a handler.
qg:fullscreen
The game fills the screen, or goes back to a window; kept between runs. fullscreen() reads it. In a browser, the page goes full screen.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
on | an expression | true or false; none: the other of what it is |
Goes inside: a handler.
qg:rumble
A player's joypad shakes (and a phone, in the hand): a hit felt. Felt only, like a shake: nothing in the game depends on it. Over the network only the machine of that player's seat feels it; on one machine, player N's is the Nth joypad connected.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
player | an expression | 1 | whose joypad |
strength | number | 0.5 | 0 to 1 |
seconds | number | 0.2 |
Goes inside: a handler.
qg:music
The music of a scene: a looping qg:sound that plays while the scene is on. Going to a scene with other music crossfades from one to the other; a scene with the same music lets it play on; a scene with none fades it out. In a handler, it changes the music there and then, crossfading over fade= (a boss's theme as the fight starts, the scene's own back as it ends). Only heard, like a sound.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
sound | a name | required | a qg:sound with loop="true" |
fade | number | 1.0 | seconds the crossfade takes |
volume | number | 0.0 | decibels (0: as recorded; -6: half as loud) |
Goes inside: qg:scene, a handler.
qg:travel
A thing travels to a place by the cheapest way over the scene's qg:tilemap (qg:tile cost=), round the things and characters on it and where other travels end, a cell at a time: a unit of a tactics game. Its qg:on-end runs as it arrives (me the traveller); with no way, at once, where it is. In the game's ticks, so a replay and every peer see the same; a new travel of the same thing replaces it.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
target | a name | me | me, other, a parameter, a loop variable or a q:set holding a thing |
to | an expression | required | a cursor (its cell), or a thing or character (up to it, beside it) |
budget | an expression | how much of the way's cost it may spend: it stops where the next step would cost more (all the way when not given) | |
step | integer | 10 | ticks a cell |
Goes inside: a handler.
qg:open-url
Opens a web address in the player's browser: a new tab when the game runs in a browser, the system's browser elsewhere (a changelog, the game's page, its credits). Only http:// and https:// addresses. When an agent plays it (the play protocol, a replay) it opens nothing: the "open-url" event says what it would have opened.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
href | text | required | the address, or an expression ({...}) that gives one |
Goes inside: a handler.
qg:open-overlay
Opens a qg:overlay over the scene, as the tick ends: the scene stands still under it. Nothing when one is open already.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
name | a name | required | the qg:overlay |
Goes inside: a handler.
qg:close-overlay
Closes the qg:overlay that is open, as the tick ends: the scene under it goes on where it was. A qg:goto-scene from the overlay closes it too, and leaves the scene under it for the one it names.
Goes inside: a handler.
qg:post
Sends data to a web address as JSON (an HTTP POST), and the game goes on: the answer comes later, and runs its qg:on-done, with reply (the JSON the server answered, null for none), or its qg:on-fail, with reason (no answer in 20 seconds, cannot reach the server, or the server's refusal: 429: Too many requests). A report to a beta's server, a score to a table of the best. The names the handler reads (me, a loop's variable, a parameter) are what they were as it was sent. The answer is heard by the scene or overlay that sent it, if it is still there; one a q:function of the application sent, by the scene shown then if that one is gone (every scene has the function). In a browser the server must let the game's site post to it (q:endpoint origins=). Not in a qg:multiplayer game: the answer would come to each peer at another tick.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
to | text | required | the address, http:// or https://, or an expression ({...}) that gives one |
data | an expression | required | what is sent: a dictionary ({ {'text': text} }), as JSON |
timeout | number | 20.0 | seconds to wait for the answer before qg:on-fail |
Goes inside: a handler.
qg:sign-in
Signs the player in to the site whose accounts the world's are (qg:connect, a world with accounts="site"): sends data (the account and its password, as the site asks) as qg:post does, and the site answers a ticket, {"ticket": "..."}, signed for the world. The game keeps the ticket, not its state: the next scene of the world entered comes in with it, as that account, to its hero where it was. Its qg:on-done hears reply, the site's answer without the ticket (its name for the player, say); its qg:on-fail reason: the site's refusal (401: Wrong password), or the site gave no ticket. A ticket lasts a minute and comes in once: sign in again to come back. Over https:// the password is the site's alone.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
to | text | required | the site's address that signs in, http:// or https://, or an expression ({...}) that gives one |
data | an expression | required | what is sent: a dictionary ({ {'account': a, 'password': p} }), as JSON |
timeout | number | 20.0 | seconds to wait for the answer before qg:on-fail |
Goes inside: a handler.
qg:send
Asks the world (qg:connect) to run one of its commands, as this player: its other attributes are the command's arguments, each a text as written (dir="north") or an expression ({other.id}), all the command's params and no other. The game goes on: the world runs it with its next tick, and what it did comes as the views change, or the refusal comes to qg:on-refused.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
command | a name | required | a q:command of the world |
Goes inside: a handler.
Other tags
qg:asset
A picture or a sound of the game as it is in a qg:language: the banner with its words in the language, a voice. The scenes that start while the language is shown draw the picture; the sound plays from the moment it is chosen.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
name | a name | required | a qg:spritesheet, qg:tileset or qg:sound of the game |
src | text | required | its file in this language, relative to the .q or a folder above it; a picture has the same frame size |
Goes inside: qg:language.
qg:choice
One answer to a qg:line: what the player reads, and what happens when it is chosen (then the next line). Holds actions and statements: a qg:say opens another dialogue, a q:set remembers the answer.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
label | text | required | what it says, or an expression ({...}) |
if | an expression | offered only when this is true as the line shows |
Goes inside: qg:line.
qg:column
A column of a qg:table: its heading and, for each row, what it shows.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
value | an expression | required | an expression of the row |
label | text | `` | its heading (a table whose columns have none has no heading row) |
width | integer | 8 | how many characters wide: a longer text is cut, a shorter one padded |
align | left / right | left | right: numbers |
color | text | #rrggbb, or an expression of the row giving one (red when negative) |
Goes inside: qg:table.
qg:connect
The game is a world's client: the world is a q:application type="gameserver" that quantum gameserver run serves (PLAN_GAME_SERVER.md), and holds the rules; the game draws what the world says each player sees (qg:mirror), asks it what the player does (qg:send) and shows what happens (qg:on-event). The compiler reads the world's file: a command, a view, an event or a field the world has not is an error here, and every kind of its entities needs a qg:prefab of that name to draw it. The game comes into the world as a scene with world="true" is entered, and stays in it from scene to scene. --q-world=ws://... on the command line says where the world is, --q-name= the player's name. A world of a site's accounts (accounts="site") lets in a player who signed in: the game has a qg:sign-in before its scenes of the world (--q-ticket= gives one on the command line, for a test). Not with qg:multiplayer.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
world | text | required | the world's file (its .server.q), next to the game |
local | text | ws://127.0.0.1:7700 | where the world is while the game is made: the address quantum gameserver run serves |
server | text | where the world is for whoever plays the game exported (wss://...); local= when not given | |
name | an expression | the name the player comes in with, in a world of guests: an expression of the game's state ({user}), read as the first scene of the world is entered; a guest's number when not given |
Goes inside: q:application.
qg:dialogue
Lines someone says, shown one at a time in a box at the bottom of the screen (qg:say opens it).
| Attribute | Type | Default | Meaning |
|---|---|---|---|
name | a name | required | |
font | text | a .ttf, relative to the .q or a folder above it | |
size | integer | 24 | font size |
speed | number | 0.0 | letters a second, typed in as each line shows (select shows it whole at once); 0: the whole line at once |
sheet | a name | a qg:spritesheet of faces: a line's portrait= is a frame of it | |
box | a name | a qg:spritesheet with slice=: the box is drawn with it, stretched (a frame) |
Goes inside: qg:scene, qg:overlay.
qg:health-bar
A bar over a character or a prefab, kept up every tick: value= against max= (a mirrored entity's {me.hp} and {me.max_hp}). Only seen.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
value | an expression | required | |
max | an expression | required | |
dx | number | 0.0 | |
dy | number | -10.0 | from its middle, in pixels |
width | integer | 14 | |
height | integer | 2 | |
color | #rrggbb | #40e040 |
Goes inside: qg:character, qg:prefab.
qg:icon
A picture in the HUD: a frame of a sheet (a heart before the hearts, a logo on a title).
| Attribute | Type | Default | Meaning |
|---|---|---|---|
sheet | a name | required | a qg:spritesheet or qg:tileset |
frame | an expression | 0 | a frame of it, or an expression read every frame |
scale | number | 1.0 | how large: 2 is twice its size |
Goes inside: qg:hud.
qg:label
Text in the world, read every tick: over a character or a prefab, following it (a name over a player, what an enemy has left), or at a place of the scene (a sign). Only seen.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
value | an expression | required | an expression; on a character or a prefab, me is it |
x | number | in a scene: where its middle is | |
y | number | ||
dx | number | 0.0 | on a character or a prefab: how far from its middle |
dy | number | -12.0 | |
size | integer | 8 | |
color | #rrggbb | #ffffff | |
if | an expression | placed only when this is true as the scene is built |
Goes inside: qg:scene, qg:character, qg:prefab.
qg:language
The game's texts in another language: each qg:phrase gives one text as written in the game, and what it is in this language. A text with no phrase stays as written. The language is ?lang= in a web page, --q-lang= on the command line, else the system's, when the game has it; tr(text) translates a text an expression puts together, and tr_n(one, other, n) a count's word (1 potion, 2 potions) by the language's plural rule. In pt and es, a qg:lobby's own texts come translated. A qg:asset gives a picture or a sound drawn or spoken in the language.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
lang | text | required | a language code: pt, es, zh, pt_BR... |
name | text | `` | its own name for it (Português) |
font | text | a .ttf with the letters of its alphabet that the game's fonts lack (Cyrillic, Greek, Japanese): every text draws them from it |
Goes inside: q:application.
qg:light
A light: a torch on a wall, a lantern a character carries, the glow of a gem. In a scene with ambient= it is what can be seen; elsewhere it brightens what it reaches. Only seen: the game's state never reads it.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
x | number | in a scene: where it is | |
y | number | ||
dx | number | 0.0 | on a character or a prefab: how far from its middle |
dy | number | 0.0 | |
radius | number | 64.0 | how far it reaches, pixels |
color | #rrggbb | #ffffff | |
energy | number | 1.0 | how bright: 1 as drawn, 2 twice |
flicker | number | 0.0 | 0 to 1: how much it flickers, as a flame does (the same in every replay) |
shadows | true / false | false | the solid tiles of a scene with ambient= stop it, and cast shadows |
Goes inside: qg:scene, qg:character, qg:prefab.
qg:line
One line of a qg:dialogue: who says it, and what. With qg:choice inside, the player chooses (up and down, select, or a click), and the choice's actions run before the next line.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
who | text | `` | the name over the text, or an expression ({...}) |
text | text | required | the line, or an expression ({...}) read as it is shown |
portrait | integer | a frame of the dialogue's sheet=, beside the text |
Goes inside: qg:dialogue.
qg:mirror
Draws a view of the world (a q:view of the world's file): each entity the player sees in it is an instance of the qg:prefab named as its kind, which comes when the entity comes into the view and goes when it leaves. Its me (a label's {me.name}) reads the entity's fields, and the scene's handlers see it as other (a click on it: {other.id}). A step to the next tile is walked in the time the world gives a step of its kind; a move farther, a jump. The tiles are the scene's qg:tilemap's (16 pixels without one). In a scene with world="true".
| Attribute | Type | Default | Meaning |
|---|---|---|---|
view | a name | required | a q:view of the world; world when it has none |
Goes inside: qg:scene.
qg:on-done
What happens when the server a qg:post (or qg:sign-in) sent to has answered yes (a 2xx status): reply is what it answered, read from its JSON. Holds actions and statements.
Goes inside: qg:post, qg:sign-in.
qg:on-end
What happens when a qg:dialogue has shown its last line, or when a qg:tween is done (the last of its repeats; me is the thing it moved, and another tween in it plays after: a sequence). Holds actions and statements.
Goes inside: qg:dialogue, qg:tween, qg:travel.
qg:on-event
What the scene does when the world says something happened where the player sees (a q:emit of the world): value is what the event carries, at where (x and y in pixels, col and row the tile; null if nowhere), and other the mirrored entity it is of, if one is drawn. In a scene with world="true".
| Attribute | Type | Default | Meaning |
|---|---|---|---|
name | a name | required | the event= of a q:emit of the world |
Goes inside: qg:scene.
qg:on-fail
What happens when a qg:post (or qg:sign-in) did not get through: reason says why. Holds actions and statements.
Goes inside: qg:post, qg:sign-in.
qg:on-refused
What the scene does when the world refuses a qg:send: reason is what it said (its q:require's else=: "A wall."), command which it was. In a scene with world="true".
| Attribute | Type | Default | Meaning |
|---|---|---|---|
command | a name | only that command's refusals (every one's when not given) |
Goes inside: qg:scene.
qg:on-screen-enter
What happens when this character or prefab comes onto the screen from outside it (one placed on the screen has not come onto it). In a scene, with of=, when any thing of that tag does.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
of | a name | scene: the tag | |
side | any / top / bottom / left / right | any | the edge it came in by |
Goes inside: qg:character, qg:prefab, qg:scene.
qg:on-screen-exit
What happens when this character or prefab leaves the screen: the camera's view, or the scene when it has no camera. In a scene, with of=, when any thing of that tag does (other is the thing). Holds actions and statements; it runs in the game's tick, the same on every peer.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
of | a name | scene: the tag | |
side | any / top / bottom / left / right | any | the edge it left by |
Goes inside: qg:character, qg:prefab, qg:scene.
qg:on-separate
What happens when this character or prefab stops touching something it touched: a plate stepped off, a door that closes behind. Holds actions and statements; me is the one that touched, other what it touched. Not when the other is destroyed: it left nothing.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
with | a name | required | the tag of what it stops touching |
Goes inside: qg:character, qg:prefab.
qg:overlay
A panel over whatever screen is shown, reached from every scene: a report of a beta, help, a quick menu. It holds what a screen holds (qg:hud, qg:menu, qg:dialogue, qg:on-input, qg:timer, qg:when, q:set and q:function). While it is open the scene under it stands still as it was, seen through its shade, and nothing of it runs; closed, the scene goes on where it was. qg:open-overlay opens it, or its action= in any scene; qg:close-overlay closes it, or its action= again. Its q:sets start afresh each time it opens; the game's state is the game's. scene_name() there is the scene under it, focused() the option its menu's cursor was on. An agent playing it (the play protocol) sees the overlay, and under: the scene it is over. Not in a qg:multiplayer game.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
name | a name | required | not the name of a scene |
action | a name | an action that opens it from any scene and closes it: a qg:input's key (F9), a qg:touch-button's [!] on every screen | |
shade | number | 0.6 | how much the scene under it is darkened: 0, not at all; 1, hidden |
background | #rrggbb | #000000 | the colour of the shade |
freeze | true / false | true | false: the scene under it goes on (a world's, which never stands still: its entities walk, its events are heard), but its keys are the overlay's |
Goes inside: q:application.
qg:parallax
A backdrop behind the level that the camera scrolls slower than the level: a sky, distant hills. Its picture is one frame of a sheet, placed on the screen, repeating across. Several are drawn in order.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
sheet | a name | required | |
frame | integer | 0 | |
y | number | 0.0 | where its top is on the screen |
scroll | number | 0.0 | how fast it scrolls across with the camera: 0 stays still, 1 moves with the level |
repeat | true / false | true | repeats across, so it never ends |
Goes inside: qg:scene.
qg:phrase
One text of the game, in a qg:language. The plural word of a count (tr_n('potion', 'potions', n)) takes the forms its language has: in Russian, 1 зелье, 2 зелья, 5 зелий.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
text | text | required | the text exactly as the game shows it (a button, a line, a HUD text) |
as | text | required | what it is in this language; for a plural word, the form of the counts the others are not (5 зелий) |
one | text | a plural word's form for one (1, 21, 31 in ru, uk, be; 1 elsewhere); when not given, the phrase of the word for one | |
few | text | its form for a few: 2 to 4 (22 to 24...) in ru, uk, be and pl; 2 to 4 in cs and sk | |
many | text | its form for many (5 to 20, 25...) in ru, uk, be and pl; as= when not given |
Goes inside: qg:language.
qg:rebind
A key of a qg:menu: it shows the action's key, and once chosen, the next key, mouse button or joypad button pressed becomes the action's (Escape keeps the old one). Kept between runs. Local to the machine, as a field is: the replay and the lockstep carry the actions, not the keys.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
action | a name | required | an action of the game (qg:input, or a default one) |
label | text | `` | text before the key; the action's name when not given |
player | integer | 1 | whose action |
if | an expression | shown, and choosable, only while this is true |
Goes inside: qg:menu.
qg:slider
A value of a qg:menu that left and right change while it has the focus: how loud a bus is (bus=, from 0 to 100, kept between runs), or a number of the scene or the game (bind=).
| Attribute | Type | Default | Meaning |
|---|---|---|---|
label | text | `` | text before it |
bus | a name | a bus of the game's sounds (qg:sound bus=), or bind= | |
bind | a name | a number q:set of the scene or the game, or bus= | |
min | number | 0.0 | |
max | number | 100.0 | |
step | number | 10.0 | how much a press of left or right changes it |
if | an expression | shown, and choosable, only while this is true |
Goes inside: qg:menu.
qg:steer
A part: left and right turn the body, up pushes it the way it points and down holds it back; it drifts, and slows by drag= on its own. Its picture turns with it. An Asteroids ship, a car seen from above, a horse. It needs no qg:walk.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
turn | number | 180.0 | degrees a second |
thrust | number | 200.0 | pixels per second, added every second up is held |
drag | number | 1.0 | the part of its speed it loses every second on its own (0: none, it drifts for ever) |
max | number | 160.0 | its top speed, pixels per second |
heading | number | 0.0 | where it points at first, degrees: 0 right, 90 down |
Goes inside: qg:behavior.
qg:table
Rows from the state in the HUD, a line each, in columns: a squad, a league table, a shop, the best scores. source= is read every frame; each qg:column is an expression of the row (as=). With a monospaced font the columns line up as on a text screen.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
source | an expression | required | a list: of records, numbers, texts |
as | a name | required | the name of the row in the columns' expressions |
rows | integer | 0 | at most so many rows shown, from the first (0: all of them) |
Goes inside: qg:hud.
qg:tile
One tile of a qg:tileset that is not a full solid square, for the tilemap layers with collision: a lower top, a thin ledge, a slope, a tile to jump through from below, or no collision at all. A flipped tile (a negative number in the tilemap) flips its shape too.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
frame | integer | required | which tile: its number in the tilemap is frame+1 |
shape | text | the solid part, as x,y points from the tile's top-left corner separated by semicolons (0,10; 64,10; 64,64; 0,64); none: not solid | |
one-way | true / false | false | stood on from above, jumped through from below |
kind | ladder / water / ice / hurt | ladder: passed through, climbed with up and down, let go of with jump; water: passed through, slow to sink in, a jump is a stroke, slower to walk; ice: slid on (a solid tile to stand on in a platformer, a floor in a top-down game); hurt: a character's qg:on-tile kind="hurt" runs while it touches one. In a Tiled tileset: a tile's class, or its kind property | |
frames | integer | 1 | an animated tile (water, lava, a torch): how many frames it shows in turn, itself and the ones after it in the sheet's row, which are then not tiles of their own. Only seen |
fps | number | 4.0 | frames a second, with frames= |
cost | integer | 1 | what a step onto it costs a walker finding its way (ai="pathfind", qg:seek, a pointer's click, qg:travel, cost()): a forest 2, a marsh 3. The way found is the cheapest |
Goes inside: qg:tileset.
qg:timer
Runs its handler after so many ticks, or every so many ticks, in this scene.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
after | integer | ticks from entering the scene, once | |
every | integer | ticks between runs, from entering the scene (or from=) | |
from | integer | 0 | every: the tick the count starts at |
count | integer | 0 | every: stop after this many runs (0: never) |
Goes inside: qg:scene, qg:overlay.
qg:touch-button
A button on a touch screen that presses one of the game's actions while it is touched, as a key would (a replay and the lockstep see the action). In the application, every scene has it.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
action | a name | required | an action of the game |
x | number | required | |
y | number | required | its middle, on the screen |
size | number | 32.0 | its width across, in pixels |
label | text | `` | a letter or a word on it |
player | integer | 1 | whose action |
show | touch / always | touch | touch: only on a device with a touch screen |
Goes inside: q:application, qg:scene.
qg:touch-stick
A stick on a touch screen: dragged from its middle, it presses left, right, up and down as far as it is pushed (strength()). In the application, every scene has it.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
x | number | required | |
y | number | required | its middle, on the screen |
size | number | 56.0 | its width across, in pixels |
player | integer | 1 | whose directions |
show | touch / always | touch | touch: only on a device with a touch screen |
Goes inside: q:application, qg:scene.
qg:when
Runs its handler when a condition turns true: a wave beaten (count('enemy') == 0), a room solved, a score reached. The condition is read at the start of every tick, from the scene's first, after the qg:afters due then (not while the scene is paused); the handler runs on the tick it is first true, and again only after it has been false. Holds actions and statements.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
condition | an expression | required | an expression of the scene's state |
Goes inside: qg:scene, qg:overlay.
Statements
Inside a handler, a q:function, or (for q:if, q:loop, q:call) directly in a scene, where they run as it is entered: q:set, q:if, q:loop, q:function, q:return, q:call (q:else and q:elseif inside a q:if). A q:set directly in a scene declares the scene's state; in q:application, the game's, kept across scenes — with saved="true", between runs. A q:function directly in a scene is the scene's; in q:application, every scene's (and the prefabs' handlers'), reading the game's state, its parameters and its own variables. A q:set directly in a scene whose value is computed (a function, the game's state, random()) is set as the scene is entered, after its seed, in the order written. <q:set name="team.points"> sets a field of a record in place (in the list that holds it too), and index= an item of a list or a key of a dictionary. An expression may call len(), str(), int(), float(), abs(), min(), max(), round(), floor(), ceil(), sqrt(), random(), count(), thing_at(), tile_at(), net_status(), net_players(), net_player(), url_param(), paused(), talking(), tr(), tr_n(), language(), hovered(), has_save(), volume(), fullscreen(), distance(), nearest(), sees(), cost(), held(), can_quit(), ticks(), sin(), cos(), atan2(), sort(), carrying(), pick(), chance(), join(), fit(), thousands(), repeat(), entries(), url_encode(), scene_name(), focused(), save_text(); ticks() is the ticks the scene has played (not while paused), sin() and cos() take degrees and atan2(y, x) gives them (0 to the right, 90 down), sort(), pick(), chance() and join() are the Core's, fit(v, width) cuts or pads a text to a column (fit(v, width, 'right') for numbers), thousands(n, '.') writes 1.500.000, repeat(text, n) a bar, entries(record) a record as a list of {key, value} (a hero's bag, for a menu's source=) and url_encode(text) puts a text into a web address (qg:open-url, a form's link with what to fill in); scene_name() and focused() say where the player is, the scene and the option its menu's cursor is on (from a qg:overlay, the scene under it), and save_text() is the game as a qg:save writes it, as text (a report carries it). pressing(), pressed_for(), strength() read an action of player 1, or of another (pressing('run', 2)): held now, the ticks it has been held (on the tick it is let go, how long it was), how far it is pressed (0 or 1 under qg:multiplayer, where only pressed or not travels). A field a thing does not have (hero.foo) is a compile error that lists the fields it has: col, doing, facing, health, name, on_ceiling, on_floor, on_wall, player, row, state, tag, vx, vy, x, y, as its controller has them. A world's client (qg:connect) also has me(), entity(), connected(): the player's own entity as the world sent it (its private fields too; {} until it is seen), an entity the player sees by its id (or null), and whether the world let the player in; the fields of the world's entities (me().purse, a mirrored prefab's me.name, a click's other.id) are read from the world's file, and one no entity of it has is a compile error too.
gd: attributes
Every tag above becomes a Godot node, and gd:name="value" sets a property of that node, as it is: the compiler forwards it, the runtime calls node.set() once as the scene is built, and nothing is reimplemented. The name must be a property of the node's class (inherited ones included), with a type the compiler can write: float, int, bool, String, Vector2 and Vector2i as x,y, Color as #rrggbb or #rrggbbaa. Anything else — a property the class does not have, a value of the wrong type, a gd: on a tag that becomes no node (qg:tileset, qg:spritesheet) — is a compile error with the line, and the error names the properties the class does have.
The table is Godot 4.4.1's own class reference (quantum/runtime/godot/godot_properties.json, generated by scripts/generate-godot-properties.py). Properties only: methods, signals, and what the runtime sets from the language's attributes (position, velocity, name, the collision layers) are not forwarded. When the same gd: attribute shows up in a second game, it is a qg: attribute waiting to be named.
| Node | Tags | Properties |
|---|---|---|
| ProgressBar | qg:bar | 67 |
| Camera2D | qg:camera | 57 |
| CharacterBody2D | qg:character, a thing prefab and its instances | 48 |
| Label | qg:counter, qg:text | 71 |
| Node2D | qg:cursor, qg:path, qg:scene | 31 |
| Area2D | qg:exit, qg:zone, an item prefab and its instances | 49 |
| CanvasLayer | qg:hud, qg:menu | 16 |
| Sprite2D | qg:map-node, qg:sprite | 41 |
| AudioStreamPlayer | qg:sound | 19 |
| Node | qg:spawner, qg:timer | 9 |
| TileMapLayer | qg:tilemap | 41 |
| StaticBody2D | a block prefab and its instances | 36 |
| AnimatableBody2D | a shuttle prefab and its instances | 37 |
<q:application id="smooth" type="game">
<qg:tileset name="k" src="assets/kenney/tilemap_packed.png" tile="18" />
<qg:spritesheet name="c" src="assets/kenney/tilemap-characters_packed.png" tile="24" />
<qg:prefab name="Coin" tag="coin" sheet="k" frame="151" hitbox="12x12" gd:modulate="#ffd700" />
<qg:scene name="main">
<qg:tilemap tileset="k" collision="true">
0,0,0,0,0
23,23,23,23,23
</qg:tilemap>
<qg:character id="player" controller="platformer" sheet="c" x="20" y="10" hitbox="18x22" />
<qg:instance prefab="Coin" x="60" y="10" gd:scale="1.5,1.5" />
<qg:camera follow="player" gd:zoom="2,2" gd:position_smoothing_enabled="true" gd:position_smoothing_speed="8" />
</qg:scene>
</q:application>The games
Each one is written in these tags and nothing else, and replayed in CI from input tapes (tests/godot/test_godot_<name>.py).
Hopper
projects/hopper/hopper.q — a platformer: a world map, four levels (one drawn in Tiled), a block with a power-up, walkers to stomp, a pit, a checkpoint, spikes, a flag, a game over; the fourth has water to swim, snow slid on, a ladder, spike tiles and an enemy made of parts that hops.
<q:application id="hopper" type="game">
<!-- Hopper: the platformer that drives the game language (PLAN_GAMES_2.md).
A title (a new game, or the saved one), a world map and three
levels. A character that walks and jumps on a
tilemap, blocks with power-ups in them, coins, walkers to stomp or be
hurt by, pits, checkpoints, spikes, a flag at the end of each level, a
game over. Art and sounds: Kenney's Pixel Platformer (CC0), in
assets/kenney/. -->
<!-- The game's state: kept across scenes. -->
<q:set name="lives" value="3" type="number" />
<q:set name="score" value="0" type="number" />
<q:set name="cleared" value="[]" type="array" />
<q:set name="map_at" value="level-1" />
<!-- the checkpoint reached: its level and where the character starts again there -->
<q:set name="check_at" value="" />
<q:set name="check_x" value="0" type="number" />
<q:set name="check_y" value="0" type="number" />
<!-- Jump on space, Z, X, up or W; the other actions keep their default keys. -->
<qg:input action="jump" keys="Space, Z, X, Up, W" />
<!-- level 4's tiles of a kind (qg:tile kind=): a ladder, water, snow slid on as ice, spikes that hurt -->
<qg:tileset name="kenney" src="assets/kenney/tilemap_packed.png" tile="18">
<qg:tile frame="71" kind="ladder" />
<qg:tile frame="33" kind="water" />
<qg:tile frame="74" kind="water" />
<qg:tile frame="81" kind="ice" />
<qg:tile frame="68" kind="hurt" shape="0,10; 18,10; 18,18; 0,18" />
</qg:tileset>
<qg:spritesheet name="chars" src="assets/kenney/tilemap-characters_packed.png" tile="24" />
<qg:sound bus="effects" name="jump" src="assets/kenney/audio/jump.ogg" />
<qg:sound bus="effects" name="coin" src="assets/kenney/audio/coin.ogg" />
<qg:sound bus="effects" name="stomp" src="assets/kenney/audio/stomp.ogg" />
<qg:sound bus="effects" name="hurt" src="assets/kenney/audio/hurt.ogg" />
<qg:sound bus="effects" name="block" src="assets/kenney/audio/block.ogg" />
<qg:sound bus="effects" name="powerup" src="assets/kenney/audio/powerup.ogg" />
<qg:sound bus="effects" name="win" src="assets/kenney/audio/win.ogg" />
<!-- the bouncer's hop: heard from where it is, to one side, softer the farther off -->
<qg:sound bus="effects" name="hop" src="assets/kenney/audio/jump.ogg" />
<!-- effects: only seen, the game's state never depends on them -->
<qg:particles name="sparkle" preset="stars" color="#ffe060" />
<qg:particles name="pop" preset="puff" color="#f0e0c0" />
<qg:particles name="power" preset="sparks" color="#ff7070" />
<qg:prefab name="Coin" tag="coin" sheet="kenney" frame="151" hitbox="12x12" />
<qg:prefab name="QBlock" tag="qblock" sheet="kenney" frame="10" hitbox="18x18" solid="true" />
<qg:prefab name="UsedBlock" tag="usedblock" sheet="kenney" frame="11" hitbox="18x18" solid="true" />
<qg:prefab name="Shroom" tag="shroom" sheet="kenney" frame="128" hitbox="12x12"
ai="patrol" speed="40" direction="right" turns-at="wall" />
<qg:prefab name="Spikes" tag="spikes" sheet="kenney" frame="68" hitbox="18x8" />
<qg:prefab name="Checkpoint" tag="checkpoint" sheet="kenney" frame="111" hitbox="18x18" />
<qg:prefab name="CheckpointOn" tag="checkpoint-on" sheet="kenney" frame="112" hitbox="18x18" />
<qg:prefab name="Flag" tag="flag" sheet="kenney" frame="153" hitbox="18x18" />
<!-- A ledge to jump through from below and stand on; a lift that rises and comes back. -->
<qg:prefab name="Ledge" tag="ledge" sheet="kenney" frame="23" hitbox="18x6" solid="true" one-way="true" />
<qg:prefab name="Lift" tag="lift" sheet="kenney" frame="43" hitbox="18x8" solid="true"
ai="shuttle" dy="-80" period="240" />
<!-- an enemy made of parts: it hops to and fro on the snow, and over what it bumps;
each hop is heard from where it is, so it is heard before it is seen -->
<qg:prefab name="Bouncer" tag="enemy" sheet="chars" frame="11" hitbox="18x18" ai="behavior" speed="36"
turns-at="edge">
<qg:animation name="walk" frames="11,12" fps="4" />
<qg:behavior>
<qg:gravity />
<qg:leap height="20" every="45" when="{true}" sound="hop" />
<qg:patrol heading="left" distance="18" />
</qg:behavior>
</qg:prefab>
<qg:prefab name="Walker" tag="enemy" sheet="chars" frame="18" hitbox="18x18"
ai="patrol" speed="30" direction="left" turns-at="edge">
<qg:animation name="walk" frames="18,19,20" fps="6" />
</qg:prefab>
<!-- the game in Portuguese and Spanish (a web page's ?lang=pt, the command line's q-lang, or the system's language) -->
<!-- on a phone: what the keys do, under a finger, in every scene -->
<qg:touch-stick x="40" y="184" />
<qg:touch-button action="jump" label="A" x="220" y="184" />
<qg:language lang="pt" name="Português">
<qg:phrase text="SCORE" as="PONTOS" />
<qg:phrase text="LIVES" as="VIDAS" />
<qg:phrase text="COINS" as="MOEDAS" />
<qg:phrase text="TIME" as="TEMPO" />
<qg:phrase text="YOU WIN" as="VOCÊ VENCEU" />
<qg:phrase text="GAME OVER - PRESS JUMP" as="FIM DE JOGO - APERTE PULAR" />
<qg:phrase text="New game" as="Novo jogo" />
<qg:phrase text="Continue" as="Continuar" />
<qg:phrase text="Language" as="Idioma" />
<qg:phrase text="Options" as="Opções" />
<qg:phrase text="OPTIONS" as="OPÇÕES" />
<qg:phrase text="Sounds" as="Sons" />
<qg:phrase text="Full screen" as="Tela cheia" />
<qg:phrase text="on" as="sim" />
<qg:phrase text="off" as="não" />
<qg:phrase text="Back" as="Voltar" />
<qg:phrase text="Jump" as="Pular" />
</qg:language>
<qg:language lang="es" name="Español">
<qg:phrase text="SCORE" as="PUNTOS" />
<qg:phrase text="LIVES" as="VIDAS" />
<qg:phrase text="COINS" as="MONEDAS" />
<qg:phrase text="TIME" as="TIEMPO" />
<qg:phrase text="YOU WIN" as="HAS GANADO" />
<qg:phrase text="GAME OVER - PRESS JUMP" as="FIN DEL JUEGO - PULSA SALTAR" />
<qg:phrase text="New game" as="Nueva partida" />
<qg:phrase text="Continue" as="Continuar" />
<qg:phrase text="Language" as="Idioma" />
<qg:phrase text="Options" as="Opciones" />
<qg:phrase text="OPTIONS" as="OPCIONES" />
<qg:phrase text="Sounds" as="Sonidos" />
<qg:phrase text="Full screen" as="Pantalla completa" />
<qg:phrase text="on" as="sí" />
<qg:phrase text="off" as="no" />
<qg:phrase text="Back" as="Volver" />
<qg:phrase text="Jump" as="Saltar" />
</qg:language>
<!-- ===== The title: a new game, or the saved one (each level cleared saves it) ===== -->
<qg:scene name="title" width="256" height="224" background="#3b7d4f">
<qg:hud position="top-center" size="32"><qg:text value="{'HOPPER'}" /></qg:hud>
<qg:menu position="center">
<qg:button label="New game"><qg:goto-scene name="map" /></qg:button>
<qg:button label="Continue" if="{has_save()}"><qg:continue /></qg:button>
<qg:button label="Options"><qg:goto-scene name="options" /></qg:button>
<qg:button label="{tr('Language') + ': ' + language()}"><qg:next-language /></qg:button>
</qg:menu>
</qg:scene>
<!-- ===== The world map ===== -->
<qg:scene name="map" width="256" height="224" background="#3b7d4f">
<qg:map-node name="level-1" x="48" y="112" sheet="kenney" frame="153" scene="level-1" />
<qg:map-node name="level-2" x="128" y="80" sheet="kenney" frame="153" scene="level-2" />
<qg:map-node name="level-3" x="208" y="112" sheet="kenney" frame="153" scene="level-3" />
<qg:map-node name="level-4" x="128" y="168" sheet="kenney" frame="153" scene="level-4" />
<qg:map-path from="level-1" to="level-2" requires="level-1" />
<qg:map-path from="level-2" to="level-3" requires="level-2" />
<qg:map-path from="level-3" to="level-4" requires="level-3" />
<qg:character id="player" controller="map" sheet="chars" frame="0" at="{map_at}" speed="60"
x="0" y="0" hitbox="18x22">
<qg:animation name="idle" frames="0" />
<qg:animation name="walk" frames="1,2" fps="8" />
</qg:character>
<qg:hud position="top-left">
<qg:counter bind="score" label="SCORE" />
<qg:counter bind="lives" label="LIVES" />
</qg:hud>
</qg:scene>
<!-- ===== Level 1 ===== -->
<qg:scene name="level-1" width="256" height="224" background="#5c94fc" seed="7">
<q:if condition="{check_at == 'level-1'}">
<qg:put target="player" x="{check_x}" y="{check_y}" />
</q:if>
<q:set name="coins" value="0" type="number" />
<q:set name="message" value="" />
<q:set name="time" value="99" type="number" />
<!-- The clock: a second off every 60 ticks; at zero the level is lost. -->
<qg:timer every="60">
<q:set name="time" value="{time - 1}" />
<q:if condition="{time <= 0}">
<q:set name="lives" value="{lives - 1}" />
<q:set name="check_at" value="" />
<qg:goto-scene name="map" />
</q:if>
</qg:timer>
<q:function name="die" params="me">
<qg:play sound="hurt" />
<q:set name="lives" value="{lives - 1}" />
<q:if condition="{lives <= 0}">
<qg:goto-scene name="game-over" />
<q:else>
<qg:goto-scene name="level-1" /> <!-- the level again, enemies and all: from the start or its checkpoint -->
</q:else>
</q:if>
</q:function>
<!-- 24 columns x 13 rows of 18 px = 432 x 234. 0 is empty; 23 is the grass
top, 25 the dirt below it. A platform at row 6, a pit at columns 13-14. -->
<qg:tilemap tileset="kenney" collision="true">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,23,23,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
23,23,23,23,23,23,23,23,23,23,23,23,23,0,0,23,23,23,23,23,23,23,23,23
25,25,25,25,25,25,25,25,25,25,25,25,25,0,0,25,25,25,25,25,25,25,25,25
25,25,25,25,25,25,25,25,25,25,25,25,25,0,0,25,25,25,25,25,25,25,25,25
</qg:tilemap>
<qg:character id="player" controller="platformer" sheet="chars" frame="0"
x="40" y="160" hitbox="18x22"
run-speed="90" jump-height="64" variable-jump="true" coyote-frames="6" buffer-frames="6"
jump-sound="jump">
<qg:state name="small" hitbox="18x22" frame="0" initial="true">
<qg:animation name="idle" frames="0" />
<qg:animation name="walk" frames="1,2" fps="8" />
<qg:animation name="jump" frames="1" />
</qg:state>
<qg:state name="big" hitbox="18x22" frame="3">
<qg:animation name="idle" frames="3" />
<qg:animation name="walk" frames="4,5" fps="8" />
<qg:animation name="jump" frames="4" />
</qg:state>
<qg:on-collision with="coin">
<qg:emit particles="sparkle" at="other" />
<qg:destroy target="other" />
<qg:play sound="coin" />
<q:set name="coins" value="{coins + 1}" />
<q:set name="score" value="{score + 10}" />
</qg:on-collision>
<qg:on-collision with="qblock" side="bottom">
<qg:swap target="other" prefab="UsedBlock" />
<qg:spawn prefab="Shroom" at="other" dy="-18" />
<qg:play sound="block" />
</qg:on-collision>
<qg:on-collision with="shroom">
<qg:destroy target="other" />
<qg:play sound="powerup" />
<qg:emit particles="power" at="me" />
<qg:become target="me" state="big" />
<q:set name="score" value="{score + 50}" />
</qg:on-collision>
<qg:on-collision with="enemy" side="top">
<qg:emit particles="pop" at="other" />
<qg:destroy target="other" />
<qg:bounce target="me" height="32" />
<qg:play sound="stomp" />
<q:set name="score" value="{score + 100}" />
</qg:on-collision>
<qg:on-collision with="enemy" side="any" cooldown="60">
<q:if condition="{me.state == 'big'}">
<qg:play sound="hurt" />
<qg:become target="me" state="small" />
<q:else>
<q:call function="die" args="me" />
</q:else>
</q:if>
</qg:on-collision>
<qg:on-collision with="spikes" cooldown="60">
<q:call function="die" args="me" />
</qg:on-collision>
<qg:on-collision with="checkpoint">
<qg:checkpoint target="me" at="other" />
<qg:swap target="other" prefab="CheckpointOn" />
<q:set name="check_at" value="level-1" />
<q:set name="check_x" value="297" />
<q:set name="check_y" value="169" />
</qg:on-collision>
<qg:on-collision with="flag" cooldown="100000">
<qg:play sound="win" />
<q:set name="score" value="{score + 500}" />
<q:set name="message" value="YOU WIN" />
<q:set name="cleared" value="{cleared + ['level-1']}" />
<q:set name="check_at" value="" />
<qg:save scene="map" />
<qg:goto-scene name="map" />
</qg:on-collision>
<qg:on-fall>
<q:call function="die" args="me" />
</qg:on-fall>
</qg:character>
<qg:instance prefab="QBlock" x="45" y="135" />
<qg:instance prefab="Coin" x="120" y="160" />
<qg:instance prefab="Coin" x="171" y="96" />
<qg:instance prefab="Walker" x="200" y="160" />
<qg:instance prefab="Checkpoint" x="297" y="171" if="{check_at != 'level-1'}" />
<qg:instance prefab="CheckpointOn" x="297" y="171" if="{check_at == 'level-1'}" />
<qg:instance prefab="Spikes" x="351" y="176" />
<qg:instance prefab="Flag" x="405" y="171" />
<qg:camera follow="player" bounds="tilemap" smooth="8" lead="32" dead-zone="16x56" />
<qg:hud position="top-left">
<qg:counter bind="score" label="SCORE" />
<qg:counter bind="coins" label="COINS" />
<qg:counter bind="lives" label="LIVES" />
<qg:counter bind="time" label="TIME" />
<qg:text bind="message" />
</qg:hud>
</qg:scene>
<!-- ===== Level 2: platforms and two walkers ===== -->
<qg:scene name="level-2" width="256" height="224" background="#4a7cc9" seed="11">
<q:if condition="{check_at == 'level-2'}">
<qg:put target="player" x="{check_x}" y="{check_y}" />
</q:if>
<q:set name="coins" value="0" type="number" />
<q:function name="die" params="me">
<qg:play sound="hurt" />
<q:set name="lives" value="{lives - 1}" />
<q:if condition="{lives <= 0}">
<qg:goto-scene name="game-over" />
<q:else>
<qg:goto-scene name="level-2" /> <!-- the level again, enemies and all: from the start or its checkpoint -->
</q:else>
</q:if>
</q:function>
<!-- The level is drawn in Tiled (levels/level-2.tmx): a decor layer, a ground
layer with a collision property, and an object layer whose points place
the coins, the walkers and the flag by their class. -->
<qg:tilemap tileset="kenney" src="levels/level-2.tmx" />
<qg:character id="player" controller="platformer" sheet="chars" frame="0"
x="40" y="160" hitbox="18x22"
run-speed="90" jump-height="64" variable-jump="true" coyote-frames="6" buffer-frames="6"
jump-sound="jump">
<qg:animation name="idle" frames="0" />
<qg:animation name="walk" frames="1,2" fps="8" />
<qg:animation name="jump" frames="1" />
<qg:on-collision with="coin">
<qg:emit particles="sparkle" at="other" />
<qg:destroy target="other" />
<qg:play sound="coin" />
<q:set name="coins" value="{coins + 1}" />
<q:set name="score" value="{score + 10}" />
</qg:on-collision>
<qg:on-collision with="enemy" side="top">
<qg:emit particles="pop" at="other" />
<qg:destroy target="other" />
<qg:bounce target="me" height="32" />
<qg:play sound="stomp" />
<q:set name="score" value="{score + 100}" />
</qg:on-collision>
<qg:on-collision with="enemy" side="any" cooldown="60">
<q:call function="die" args="me" />
</qg:on-collision>
<qg:on-collision with="flag" cooldown="100000">
<qg:play sound="win" />
<q:set name="score" value="{score + 500}" />
<q:set name="cleared" value="{cleared + ['level-2']}" />
<q:set name="check_at" value="" />
<qg:save scene="map" />
<qg:goto-scene name="map" />
</qg:on-collision>
<qg:on-fall>
<q:call function="die" args="me" />
</qg:on-fall>
</qg:character>
<qg:camera follow="player" bounds="tilemap" smooth="8" lead="32" dead-zone="16x56" />
<qg:hud position="top-left">
<qg:counter bind="score" label="SCORE" />
<qg:counter bind="coins" label="COINS" />
<qg:counter bind="lives" label="LIVES" />
</qg:hud>
</qg:scene>
<!-- ===== Level 3: the spikes gauntlet ===== -->
<qg:scene name="level-3" width="256" height="224" background="#7a4fa8" seed="13">
<q:if condition="{check_at == 'level-3'}">
<qg:put target="player" x="{check_x}" y="{check_y}" />
</q:if>
<q:set name="coins" value="0" type="number" />
<q:function name="die" params="me">
<qg:play sound="hurt" />
<q:set name="lives" value="{lives - 1}" />
<q:if condition="{lives <= 0}">
<qg:goto-scene name="game-over" />
<q:else>
<qg:goto-scene name="level-3" /> <!-- the level again, enemies and all: from the start or its checkpoint -->
</q:else>
</q:if>
</q:function>
<qg:tilemap tileset="kenney" collision="true">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,23,23,0,0,0,0,23,23,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
</qg:tilemap>
<qg:character id="player" controller="platformer" sheet="chars" frame="0"
x="40" y="160" hitbox="18x22"
run-speed="90" jump-height="64" variable-jump="true" coyote-frames="6" buffer-frames="6"
jump-sound="jump">
<qg:animation name="idle" frames="0" />
<qg:animation name="walk" frames="1,2" fps="8" />
<qg:animation name="jump" frames="1" />
<qg:on-collision with="coin">
<qg:emit particles="sparkle" at="other" />
<qg:destroy target="other" />
<qg:play sound="coin" />
<q:set name="coins" value="{coins + 1}" />
<q:set name="score" value="{score + 10}" />
</qg:on-collision>
<qg:on-collision with="spikes" cooldown="60">
<q:call function="die" args="me" />
</qg:on-collision>
<qg:on-collision with="checkpoint">
<qg:checkpoint target="me" at="other" />
<qg:swap target="other" prefab="CheckpointOn" />
<q:set name="check_at" value="level-3" />
<q:set name="check_x" value="207" />
<q:set name="check_y" value="169" />
</qg:on-collision>
<qg:on-collision with="flag" cooldown="100000">
<qg:play sound="win" />
<q:set name="score" value="{score + 1000}" />
<q:set name="cleared" value="{cleared + ['level-3']}" />
<q:set name="check_at" value="" />
<qg:save scene="map" />
<qg:goto-scene name="map" />
</qg:on-collision>
<qg:on-fall>
<q:call function="die" args="me" />
</qg:on-fall>
</qg:character>
<qg:instance prefab="Spikes" x="135" y="176" />
<qg:instance prefab="Spikes" x="153" y="176" />
<qg:instance prefab="Coin" x="144" y="96" />
<qg:instance prefab="Checkpoint" x="207" y="171" if="{check_at != 'level-3'}" />
<qg:instance prefab="CheckpointOn" x="207" y="171" if="{check_at == 'level-3'}" />
<qg:instance prefab="Spikes" x="243" y="176" />
<qg:instance prefab="Spikes" x="261" y="176" />
<qg:instance prefab="Spikes" x="279" y="176" />
<qg:instance prefab="Coin" x="252" y="96" />
<qg:instance prefab="Flag" x="405" y="171" />
<qg:camera follow="player" bounds="tilemap" smooth="8" lead="32" dead-zone="16x56" />
<qg:hud position="top-left">
<qg:counter bind="score" label="SCORE" />
<qg:counter bind="coins" label="COINS" />
<qg:counter bind="lives" label="LIVES" />
</qg:hud>
</qg:scene>
<!-- ===== Level 4: the falls. Water to swim, snow that slides, a ladder up a wall, spikes below it. ===== -->
<qg:scene name="level-4" width="256" height="224" background="#4f8fb0" seed="17">
<q:set name="coins" value="0" type="number" />
<q:function name="die" params="me">
<qg:play sound="hurt" />
<q:set name="lives" value="{lives - 1}" />
<q:if condition="{lives <= 0}">
<qg:goto-scene name="game-over" />
<q:else>
<qg:goto-scene name="level-4" />
</q:else>
</q:if>
</q:function>
<!-- 23 grass, 25 dirt; 34 and 75 water (a pool two tiles deep), 82 snow (ice), 72 the ladder up
the wall at columns 15-16, 69 the spikes beyond it. -->
<qg:tilemap tileset="kenney" collision="true">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,72,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,72,23,23,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,72,25,25,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,72,25,25,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,72,25,25,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,72,25,25,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,72,25,25,0,0,0,0,0,0,0
23,23,23,23,23,23,34,34,34,34,82,82,82,82,23,25,25,69,69,69,69,23,23,23
25,25,25,25,25,25,75,75,75,75,25,25,25,25,25,25,25,25,25,25,25,25,25,25
25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
</qg:tilemap>
<qg:character id="player" controller="platformer" sheet="chars" frame="0"
x="40" y="160" hitbox="18x22"
run-speed="90" jump-height="64" variable-jump="true" coyote-frames="6" buffer-frames="6"
jump-sound="jump">
<qg:animation name="idle" frames="0" />
<qg:animation name="walk" frames="1,2" fps="8" />
<qg:animation name="jump" frames="1" />
<qg:on-collision with="coin">
<qg:emit particles="sparkle" at="other" />
<qg:destroy target="other" />
<qg:play sound="coin" />
<q:set name="coins" value="{coins + 1}" />
<q:set name="score" value="{score + 10}" />
</qg:on-collision>
<qg:on-collision with="enemy" side="top">
<qg:emit particles="pop" at="other" />
<qg:destroy target="other" />
<qg:bounce target="me" height="32" />
<qg:play sound="stomp" />
<q:set name="score" value="{score + 100}" />
</qg:on-collision>
<qg:on-collision with="enemy" side="any" cooldown="60">
<q:call function="die" args="me" />
</qg:on-collision>
<!-- the spikes are tiles of a kind, not things: touching one is qg:on-tile -->
<qg:on-tile kind="hurt" cooldown="60">
<q:call function="die" args="me" />
</qg:on-tile>
<qg:on-collision with="flag" cooldown="100000">
<qg:play sound="win" />
<q:set name="score" value="{score + 1500}" />
<q:set name="cleared" value="{cleared + ['level-4']}" />
<q:set name="check_at" value="" />
<qg:save scene="map" />
<qg:goto-scene name="map" />
</qg:on-collision>
<qg:on-fall>
<q:call function="die" args="me" />
</qg:on-fall>
</qg:character>
<qg:instance prefab="Coin" x="144" y="200" />
<qg:instance prefab="Bouncer" x="225" y="171" />
<qg:instance prefab="Coin" x="279" y="54" />
<qg:instance prefab="Flag" x="405" y="171" />
<qg:camera follow="player" bounds="tilemap" smooth="8" lead="32" dead-zone="16x56" />
<qg:hud position="top-left">
<qg:counter bind="score" label="SCORE" />
<qg:counter bind="coins" label="COINS" />
<qg:counter bind="lives" label="LIVES" />
</qg:hud>
</qg:scene>
<!-- ===== Game over: jump to start again ===== -->
<qg:scene name="game-over" width="256" height="224" background="#000000">
<q:set name="message" value="GAME OVER - PRESS JUMP" />
<qg:on-input action="jump">
<q:set name="lives" value="3" />
<q:set name="score" value="0" />
<q:set name="cleared" value="[]" />
<q:set name="map_at" value="level-1" />
<q:set name="check_at" value="" />
<qg:goto-scene name="map" />
</qg:on-input>
<qg:hud position="top-center">
<qg:text bind="message" />
</qg:hud>
</qg:scene>
<!-- ===== Options: how loud, the full screen, the keys; kept between runs ===== -->
<qg:scene name="options" width="256" height="224" background="#3b7d4f">
<qg:hud position="top-center" size="24"><qg:text value="{tr('OPTIONS')}" /></qg:hud>
<qg:menu position="center" size="12">
<qg:slider label="Sounds" bus="effects" />
<qg:button label="{tr('Full screen') + ': ' + (tr('on') if fullscreen() else tr('off'))}"><qg:fullscreen /></qg:button>
<qg:rebind label="Jump" action="jump" />
<qg:button label="Back"><qg:goto-scene name="title" /></qg:button>
</qg:menu>
</qg:scene>
</q:application>Keep
projects/keep/keep.q — a top-down adventure: six rooms, slimes that wander and bats that chase, a sword, a key and a locked door, a switch that opens a gate in another room, a chest.
<q:application id="keep" type="game">
<!-- Keep: the top-down adventure that drives the game language (PLAN_GAMES_2.md).
Six rooms of a keep, seen from above. A character that walks in eight
directions and swings a sword; slimes that wander and bats that chase;
hearts; a key and a locked door; a switch that opens a gate in another
room; a chest at the end. A title to start anew or continue: each
room entered saves the game. The dungeon, the hero and the monsters are drawn
for it by scripts/art/keep.py (CC0); the sounds are Kenney's Pixel
Platformer (CC0). -->
<!-- The game's state, kept across rooms. -->
<q:set name="hearts" value="3" type="number" />
<q:set name="keys" value="0" type="number" />
<q:set name="taken" value="[]" type="array" /> <!-- things picked up, by name -->
<q:set name="opened" value="[]" type="array" /> <!-- doors and gates opened, by name -->
<q:set name="message" value="" />
<!-- a new game: the state as the keep was found -->
<q:function name="anew">
<q:set name="hearts" value="3" />
<q:set name="keys" value="0" />
<q:set name="taken" value="[]" />
<q:set name="opened" value="[]" />
<q:set name="message" value="" />
</q:function>
<!-- three games, each kept apart (qg:save slot=): one goes on where it was, an empty one starts -->
<q:function name="game_label" params="n">
<q:return value="{tr('Game') + ' ' + str(n) + ': ' + (tr('go on') if has_save(n) else tr('new'))}" />
</q:function>
<q:function name="play" params="n">
<q:if condition="{has_save(n)}">
<qg:continue slot="{n}" />
<q:else>
<q:call function="anew" />
<qg:save slot="{n}" scene="room-1" />
<qg:goto-scene name="room-1" />
</q:else>
</q:if>
</q:function>
<!-- a hurt, the same in every room: a function of the game -->
<q:function name="hurt" params="me">
<qg:play sound="hurt" />
<q:set name="hearts" value="{hearts - 1}" />
<q:if condition="{hearts <= 0}">
<qg:goto-scene name="game-over" />
</q:if>
</q:function>
<!-- 1 is the floor (walked on: no collision), 2 the wall. -->
<qg:tileset name="dungeon" src="assets/dungeon.png" tile="18">
<qg:tile frame="0" shape="none" />
</qg:tileset>
<qg:spritesheet name="hero" src="assets/hero.png" tile="24" />
<qg:sound name="hit" bus="effects" src="assets/kenney/audio/stomp.ogg" />
<qg:sound name="hurt" bus="effects" src="assets/kenney/audio/hurt.ogg" />
<qg:sound name="pick" bus="effects" src="assets/kenney/audio/coin.ogg" />
<qg:sound name="open" bus="effects" src="assets/kenney/audio/block.ogg" />
<qg:sound name="win" bus="effects" src="assets/kenney/audio/win.ogg" />
<!-- on a phone: a stick to walk and a button for the sword, in every scene -->
<qg:touch-stick x="40" y="184" />
<qg:touch-button action="jump" label="A" x="220" y="184" />
<!-- effects: only seen, the game's state never depends on them -->
<qg:particles name="sparkle" preset="stars" color="#fff0a0" />
<qg:particles name="slain" preset="smoke" color="#b0a0c0" />
<qg:particles name="treasure" preset="confetti" />
<qg:prefab name="Key" tag="key" sheet="dungeon" frame="8" hitbox="12x12" />
<qg:prefab name="Heart" tag="heart" sheet="dungeon" frame="9" hitbox="12x12" />
<qg:prefab name="Door" tag="door" sheet="dungeon" frame="2" hitbox="18x18" solid="true" />
<qg:prefab name="Gate" tag="gate" sheet="dungeon" frame="3" hitbox="18x18" solid="true" />
<qg:prefab name="Switch" tag="switch" sheet="dungeon" frame="4" hitbox="14x14" />
<qg:prefab name="SwitchOn" tag="switch-on" sheet="dungeon" frame="5" hitbox="14x14" />
<qg:prefab name="Sign" tag="sign" sheet="dungeon" frame="6" hitbox="18x18" solid="true" />
<qg:prefab name="Chest" tag="chest" sheet="dungeon" frame="7" hitbox="16x16" />
<qg:prefab name="Slime" tag="enemy" sheet="hero" frame="10" hitbox="14x14"
ai="wander" speed="25">
<qg:animation name="walk" frames="10,11,12,11" fps="6" />
</qg:prefab>
<qg:prefab name="Bat" tag="enemy" sheet="hero" frame="13" hitbox="14x14"
ai="chase" speed="40" sight="90">
<qg:animation name="walk" frames="13,14,15,14" fps="10" />
</qg:prefab>
<!-- the game in Portuguese and Spanish (a web page's ?lang=pt, the command line's q-lang, or the system's language) -->
<qg:language lang="pt" name="Português">
<qg:phrase text="HEARTS" as="CORAÇÕES" />
<qg:phrase text="KEYS" as="CHAVES" />
<qg:phrase text="THE KEY IS NORTH. THE SWITCH IS SOUTH." as="A CHAVE ESTÁ AO NORTE. A ALAVANCA, AO SUL." />
<qg:phrase text="LOCKED. FIND THE KEY." as="TRANCADO. ACHE A CHAVE." />
<qg:phrase text="SOMETHING OPENED FAR AWAY." as="ALGO SE ABRIU AO LONGE." />
<qg:phrase text="A GATE. A SWITCH MUST OPEN IT." as="UM PORTÃO. UMA ALAVANCA DEVE ABRI-LO." />
<qg:phrase text="THE TREASURE OF THE KEEP IS YOURS." as="O TESOURO DA FORTALEZA É SEU." />
<qg:phrase text="YOU FELL. PRESS JUMP." as="VOCÊ CAIU. APERTE PULAR." />
<qg:phrase text="New game" as="Novo jogo" />
<qg:phrase text="Continue" as="Continuar" />
<qg:phrase text="Language" as="Idioma" />
<qg:phrase text="The keep is quiet again." as="A fortaleza está quieta outra vez." />
<qg:phrase text="Once more" as="Mais uma vez" />
<qg:phrase text="To the title" as="Para o início" />
<qg:phrase text="Options" as="Opções" />
<qg:phrase text="OPTIONS" as="OPÇÕES" />
<qg:phrase text="Sounds" as="Sons" />
<qg:phrase text="Full screen" as="Tela cheia" />
<qg:phrase text="on" as="sim" />
<qg:phrase text="off" as="não" />
<qg:phrase text="Back" as="Voltar" />
<qg:phrase text="Sword" as="Espada" />
<qg:phrase text="Game" as="Jogo" />
<qg:phrase text="go on" as="continuar" />
<qg:phrase text="new" as="novo" />
</qg:language>
<qg:language lang="es" name="Español">
<qg:phrase text="HEARTS" as="CORAZONES" />
<qg:phrase text="KEYS" as="LLAVES" />
<qg:phrase text="THE KEY IS NORTH. THE SWITCH IS SOUTH." as="LA LLAVE ESTÁ AL NORTE. LA PALANCA, AL SUR." />
<qg:phrase text="LOCKED. FIND THE KEY." as="CERRADO. BUSCA LA LLAVE." />
<qg:phrase text="SOMETHING OPENED FAR AWAY." as="ALGO SE ABRIÓ A LO LEJOS." />
<qg:phrase text="A GATE. A SWITCH MUST OPEN IT." as="UNA REJA. UNA PALANCA DEBE ABRIRLA." />
<qg:phrase text="THE TREASURE OF THE KEEP IS YOURS." as="EL TESORO DE LA FORTALEZA ES TUYO." />
<qg:phrase text="YOU FELL. PRESS JUMP." as="HAS CAÍDO. PULSA SALTAR." />
<qg:phrase text="New game" as="Nueva partida" />
<qg:phrase text="Continue" as="Continuar" />
<qg:phrase text="Language" as="Idioma" />
<qg:phrase text="The keep is quiet again." as="La fortaleza vuelve a estar en calma." />
<qg:phrase text="Once more" as="Otra vez" />
<qg:phrase text="To the title" as="Al inicio" />
<qg:phrase text="Options" as="Opciones" />
<qg:phrase text="OPTIONS" as="OPCIONES" />
<qg:phrase text="Sounds" as="Sonidos" />
<qg:phrase text="Full screen" as="Pantalla completa" />
<qg:phrase text="on" as="sí" />
<qg:phrase text="off" as="no" />
<qg:phrase text="Back" as="Volver" />
<qg:phrase text="Sword" as="Espada" />
<qg:phrase text="Game" as="Partida" />
<qg:phrase text="go on" as="seguir" />
<qg:phrase text="new" as="nueva" />
</qg:language>
<!-- ===== The title: a new game, or the saved one (each room entered saves it) ===== -->
<qg:scene name="title" width="256" height="224" background="#2b2b3a">
<qg:hud position="top-center" size="32"><qg:text value="{'KEEP'}" /></qg:hud>
<qg:menu position="center" size="14">
<qg:button label="{game_label(1)}"><q:call function="play" args="1" /></qg:button>
<qg:button label="{game_label(2)}"><q:call function="play" args="2" /></qg:button>
<qg:button label="{game_label(3)}"><q:call function="play" args="3" /></qg:button>
<qg:button label="Options"><qg:goto-scene name="options" /></qg:button>
<qg:button label="{tr('Language') + ': ' + language()}"><qg:next-language /></qg:button>
</qg:menu>
</qg:scene>
<!-- ===== Options: how loud, the full screen, the sword's key; kept between runs ===== -->
<qg:scene name="options" width="256" height="224" background="#2b2b3a">
<qg:hud position="top-center" size="24"><qg:text value="{tr('OPTIONS')}" /></qg:hud>
<qg:menu position="center" size="12">
<qg:slider label="Sounds" bus="effects" />
<qg:button label="{tr('Full screen') + ': ' + (tr('on') if fullscreen() else tr('off'))}"><qg:fullscreen /></qg:button>
<qg:rebind label="Sword" action="jump" />
<qg:button label="Back"><qg:goto-scene name="title" /></qg:button>
</qg:menu>
</qg:scene>
<!-- ===== Room 1: the entrance. East to room 2. ===== -->
<qg:scene name="room-1" width="256" height="224" background="#2b2b3a" seed="1" save="true">
<qg:tilemap tileset="dungeon" collision="true">
2,2,2,2,2,2,2,2,2,2,2,2,2,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,1
2,1,1,1,1,1,1,1,1,1,1,1,1,1
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,2,2,2,2,2,2,2,2,2,2,2,2,2
</qg:tilemap>
<qg:character id="player" controller="topdown" sheet="hero" frame="0"
x="60" y="108" hitbox="14x14" speed="70"
attack-action="jump" attack-reach="16" attack-frames="12" attack-sound="hit">
<qg:animation name="idle" frames="0" />
<qg:animation name="walk" frames="1,2" fps="8" />
<qg:animation name="walk-up" frames="3,4" fps="8" />
<qg:animation name="walk-down" frames="5,6" fps="8" />
<qg:animation name="attack" frames="7" />
<qg:animation name="attack-up" frames="8" />
<qg:animation name="attack-down" frames="9" />
<qg:on-collision with="sign">
<q:set name="message" value="THE KEY IS NORTH. THE SWITCH IS SOUTH." />
</qg:on-collision>
<qg:on-collision with="enemy" cooldown="60">
<q:call function="hurt" args="me" />
</qg:on-collision>
<qg:on-hit with="enemy">
<qg:emit particles="slain" at="other" />
<qg:destroy target="other" />
</qg:on-hit>
</qg:character>
<qg:instance prefab="Sign" x="99" y="63" />
<qg:instance prefab="Slime" name="slime-1" x="180" y="150" if="{'slime-1' not in taken}" />
<qg:exit name="east" x="252" y="99" width="8" height="36" to="room-2" at="west" />
<qg:hud position="top-left">
<qg:icon sheet="dungeon" frame="9" />
<qg:counter bind="hearts" label="HEARTS" color="#ff8080" />
<qg:icon sheet="dungeon" frame="8" />
<qg:counter bind="keys" label="KEYS" color="#ffe070" />
</qg:hud>
<qg:hud position="bottom-left" if="{message != ''}"><qg:text bind="message" /></qg:hud>
</qg:scene>
<!-- ===== Room 2: the crossing. West to 1, north to 3, south to 4, east (locked) to 5. ===== -->
<qg:scene name="room-2" width="256" height="224" background="#2b2b3a" seed="2" save="true">
<qg:tilemap tileset="dungeon" collision="true">
2,2,2,2,2,2,1,1,2,2,2,2,2,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
1,1,1,1,1,1,1,1,1,1,1,1,1,1
1,1,1,1,1,1,1,1,1,1,1,1,1,1
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,2,2,2,2,2,1,1,2,2,2,2,2,2
</qg:tilemap>
<qg:character id="player" controller="topdown" sheet="hero" frame="0"
x="126" y="108" hitbox="14x14" speed="70"
attack-action="jump" attack-reach="16" attack-frames="12" attack-sound="hit">
<qg:animation name="idle" frames="0" />
<qg:animation name="walk" frames="1,2" fps="8" />
<qg:animation name="walk-up" frames="3,4" fps="8" />
<qg:animation name="walk-down" frames="5,6" fps="8" />
<qg:animation name="attack" frames="7" />
<qg:animation name="attack-up" frames="8" />
<qg:animation name="attack-down" frames="9" />
<qg:on-collision with="enemy" cooldown="60">
<q:call function="hurt" args="me" />
</qg:on-collision>
<qg:on-hit with="enemy">
<qg:emit particles="slain" at="other" />
<qg:destroy target="other" />
</qg:on-hit>
<qg:on-collision with="door">
<q:if condition="{keys > 0}">
<q:set name="keys" value="{keys - 1}" />
<q:set name="opened" value="{opened + [other.name]}" />
<qg:play sound="open" />
<qg:destroy target="other" />
<q:else>
<q:set name="message" value="LOCKED. FIND THE KEY." />
</q:else>
</q:if>
</qg:on-collision>
</qg:character>
<qg:instance prefab="Bat" name="bat-1" x="190" y="60" if="{'bat-1' not in taken}" />
<qg:instance prefab="Door" name="door-1" x="225" y="108" if="{'door-1' not in opened}" />
<qg:exit name="west" x="-4" y="99" width="8" height="36" to="room-1" at="east" />
<qg:exit name="north" x="117" y="-4" width="36" height="8" to="room-3" at="south" />
<qg:exit name="south" x="117" y="220" width="36" height="8" to="room-4" at="north" />
<qg:exit name="east" x="252" y="99" width="8" height="36" to="room-5" at="west" />
<qg:hud position="top-left">
<qg:icon sheet="dungeon" frame="9" />
<qg:counter bind="hearts" label="HEARTS" color="#ff8080" />
<qg:icon sheet="dungeon" frame="8" />
<qg:counter bind="keys" label="KEYS" color="#ffe070" />
</qg:hud>
<qg:hud position="bottom-left" if="{message != ''}"><qg:text bind="message" /></qg:hud>
</qg:scene>
<!-- ===== Room 3: the key, guarded. South to 2. ===== -->
<qg:scene name="room-3" width="256" height="224" background="#2b2b3a" seed="3" save="true">
<qg:tilemap tileset="dungeon" collision="true">
2,2,2,2,2,2,2,2,2,2,2,2,2,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,2,2,1,1,2,2,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,2,2,1,1,2,2,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,2,2,2,2,2,1,1,2,2,2,2,2,2
</qg:tilemap>
<qg:character id="player" controller="topdown" sheet="hero" frame="0"
x="126" y="190" hitbox="14x14" speed="70"
attack-action="jump" attack-reach="16" attack-frames="12" attack-sound="hit">
<qg:animation name="idle" frames="0" />
<qg:animation name="walk" frames="1,2" fps="8" />
<qg:animation name="walk-up" frames="3,4" fps="8" />
<qg:animation name="walk-down" frames="5,6" fps="8" />
<qg:animation name="attack" frames="7" />
<qg:animation name="attack-up" frames="8" />
<qg:animation name="attack-down" frames="9" />
<qg:on-collision with="enemy" cooldown="60">
<q:call function="hurt" args="me" />
</qg:on-collision>
<qg:on-hit with="enemy">
<qg:emit particles="slain" at="other" />
<qg:destroy target="other" />
</qg:on-hit>
<qg:on-collision with="key">
<qg:play sound="pick" />
<qg:emit particles="sparkle" at="other" />
<q:set name="keys" value="{keys + 1}" />
<q:set name="taken" value="{taken + [other.name]}" />
<qg:destroy target="other" />
</qg:on-collision>
<qg:on-collision with="heart">
<qg:play sound="pick" />
<qg:emit particles="sparkle" at="other" />
<q:set name="hearts" value="{hearts + 1}" />
<q:set name="taken" value="{taken + [other.name]}" />
<qg:destroy target="other" />
</qg:on-collision>
</qg:character>
<qg:instance prefab="Key" name="key-1" x="126" y="99" if="{'key-1' not in taken}" />
<qg:instance prefab="Heart" name="heart-1" x="45" y="45" if="{'heart-1' not in taken}" />
<!-- what can be picked up breathes, so the eye finds it (only its picture: where it is stays) -->
<qg:timer after="1">
<qg:tween target="key-1" property="scale" to="1.3" seconds="0.6" repeat="-1" yoyo="true" />
<qg:tween target="heart-1" property="scale" to="1.3" seconds="0.6" repeat="-1" yoyo="true" />
</qg:timer>
<qg:instance prefab="Slime" name="slime-3" x="190" y="99" if="{'slime-3' not in taken}" />
<qg:exit name="south" x="117" y="220" width="36" height="8" to="room-2" at="north" />
<qg:hud position="top-left">
<qg:icon sheet="dungeon" frame="9" />
<qg:counter bind="hearts" label="HEARTS" color="#ff8080" />
<qg:icon sheet="dungeon" frame="8" />
<qg:counter bind="keys" label="KEYS" color="#ffe070" />
</qg:hud>
<qg:hud position="bottom-left" if="{message != ''}"><qg:text bind="message" /></qg:hud>
</qg:scene>
<!-- ===== Room 4: the switch. North to 2. ===== -->
<qg:scene name="room-4" width="256" height="224" background="#2b2b3a" seed="4" save="true">
<qg:tilemap tileset="dungeon" collision="true">
2,2,2,2,2,2,1,1,2,2,2,2,2,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,2,2,2,2,2,2,2,2,2,2,2,2,2
</qg:tilemap>
<qg:character id="player" controller="topdown" sheet="hero" frame="0"
x="126" y="36" hitbox="14x14" speed="70"
attack-action="jump" attack-reach="16" attack-frames="12" attack-sound="hit">
<qg:animation name="idle" frames="0" />
<qg:animation name="walk" frames="1,2" fps="8" />
<qg:animation name="walk-up" frames="3,4" fps="8" />
<qg:animation name="walk-down" frames="5,6" fps="8" />
<qg:animation name="attack" frames="7" />
<qg:animation name="attack-up" frames="8" />
<qg:animation name="attack-down" frames="9" />
<qg:on-collision with="enemy" cooldown="60">
<q:call function="hurt" args="me" />
</qg:on-collision>
<qg:on-hit with="enemy">
<qg:emit particles="slain" at="other" />
<qg:destroy target="other" />
</qg:on-hit>
<qg:on-hit with="switch">
<qg:play sound="open" />
<q:set name="opened" value="{opened + ['gate-1']}" />
<q:set name="message" value="SOMETHING OPENED FAR AWAY." />
<qg:swap target="other" prefab="SwitchOn" />
</qg:on-hit>
</qg:character>
<qg:instance prefab="Switch" x="126" y="171" if="{'gate-1' not in opened}" />
<qg:instance prefab="SwitchOn" x="126" y="171" if="{'gate-1' in opened}" />
<qg:instance prefab="Bat" name="bat-2" x="60" y="150" if="{'bat-2' not in taken}" />
<qg:exit name="north" x="117" y="-4" width="36" height="8" to="room-2" at="south" />
<qg:hud position="top-left">
<qg:icon sheet="dungeon" frame="9" />
<qg:counter bind="hearts" label="HEARTS" color="#ff8080" />
<qg:icon sheet="dungeon" frame="8" />
<qg:counter bind="keys" label="KEYS" color="#ffe070" />
</qg:hud>
<qg:hud position="bottom-left" if="{message != ''}"><qg:text bind="message" /></qg:hud>
</qg:scene>
<!-- ===== Room 5: behind the locked door; the gate the switch opens. West to 2, east to 6. ===== -->
<qg:scene name="room-5" width="256" height="224" background="#2b2b3a" seed="5" save="true">
<qg:tilemap tileset="dungeon" collision="true">
2,2,2,2,2,2,2,2,2,2,2,2,2,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,2,2,2,2,2
1,1,1,1,1,1,1,1,1,1,1,1,1,1
1,1,1,1,1,1,1,1,1,1,1,1,1,1
2,1,1,1,1,1,1,1,1,2,2,2,2,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,2,2,2,2,2,2,2,2,2,2,2,2,2
</qg:tilemap>
<qg:character id="player" controller="topdown" sheet="hero" frame="0"
x="36" y="108" hitbox="14x14" speed="70"
attack-action="jump" attack-reach="16" attack-frames="12" attack-sound="hit">
<qg:animation name="idle" frames="0" />
<qg:animation name="walk" frames="1,2" fps="8" />
<qg:animation name="walk-up" frames="3,4" fps="8" />
<qg:animation name="walk-down" frames="5,6" fps="8" />
<qg:animation name="attack" frames="7" />
<qg:animation name="attack-up" frames="8" />
<qg:animation name="attack-down" frames="9" />
<qg:on-collision with="enemy" cooldown="60">
<q:call function="hurt" args="me" />
</qg:on-collision>
<qg:on-hit with="enemy">
<qg:emit particles="slain" at="other" />
<qg:destroy target="other" />
</qg:on-hit>
<qg:on-collision with="gate">
<q:set name="message" value="A GATE. A SWITCH MUST OPEN IT." />
</qg:on-collision>
</qg:character>
<!-- The gate stands in the corridor until the switch in room 4 is thrown. -->
<qg:instance prefab="Gate" name="gate-1" x="171" y="99" if="{'gate-1' not in opened}" />
<qg:instance prefab="Gate" name="gate-1b" x="171" y="117" if="{'gate-1' not in opened}" />
<qg:instance prefab="Slime" name="slime-4" x="90" y="60" if="{'slime-4' not in taken}" />
<qg:exit name="west" x="-4" y="99" width="8" height="36" to="room-2" at="east" />
<qg:exit name="east" x="252" y="99" width="8" height="36" to="room-6" at="west" />
<qg:hud position="top-left">
<qg:icon sheet="dungeon" frame="9" />
<qg:counter bind="hearts" label="HEARTS" color="#ff8080" />
<qg:icon sheet="dungeon" frame="8" />
<qg:counter bind="keys" label="KEYS" color="#ffe070" />
</qg:hud>
<qg:hud position="bottom-left" if="{message != ''}"><qg:text bind="message" /></qg:hud>
</qg:scene>
<!-- ===== Room 6: the chest. ===== -->
<qg:scene name="room-6" width="256" height="224" background="#2b2b3a" seed="6" save="true">
<qg:tilemap tileset="dungeon" collision="true">
2,2,2,2,2,2,2,2,2,2,2,2,2,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
1,1,1,1,1,1,1,1,1,1,1,1,1,2
1,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,2,2,2,2,2,2,2,2,2,2,2,2,2
</qg:tilemap>
<qg:character id="player" controller="topdown" sheet="hero" frame="0"
x="36" y="108" hitbox="14x14" speed="70"
attack-action="jump" attack-reach="16" attack-frames="12" attack-sound="hit">
<qg:animation name="idle" frames="0" />
<qg:animation name="walk" frames="1,2" fps="8" />
<qg:animation name="walk-up" frames="3,4" fps="8" />
<qg:animation name="walk-down" frames="5,6" fps="8" />
<qg:animation name="attack" frames="7" />
<qg:animation name="attack-up" frames="8" />
<qg:animation name="attack-down" frames="9" />
<qg:on-collision with="chest">
<qg:play sound="win" />
<qg:emit particles="treasure" at="other" />
<q:set name="message" value="THE TREASURE OF THE KEEP IS YOURS." />
<qg:goto-scene name="the-end" />
</qg:on-collision>
</qg:character>
<qg:instance prefab="Chest" name="chest" x="190" y="108" />
<qg:timer after="1">
<qg:tween target="chest" property="rotation" from="-8" to="8" seconds="0.4" repeat="-1" yoyo="true" />
</qg:timer>
<qg:exit name="west" x="-4" y="99" width="8" height="36" to="room-5" at="east" />
<qg:hud position="top-left">
<qg:icon sheet="dungeon" frame="9" />
<qg:counter bind="hearts" label="HEARTS" color="#ff8080" />
<qg:icon sheet="dungeon" frame="8" />
<qg:counter bind="keys" label="KEYS" color="#ffe070" />
</qg:hud>
<qg:hud position="bottom-left" if="{message != ''}"><qg:text bind="message" /></qg:hud>
</qg:scene>
<!-- ===== The end, and the game over ===== -->
<qg:scene name="the-end" width="256" height="224" background="#102030">
<qg:hud position="top-center">
<qg:text bind="message" />
</qg:hud>
<!-- a moment later, what now: the keep again, or the title -->
<qg:dialogue name="end" size="12" speed="30">
<qg:line who="KEEP" text="The keep is quiet again.">
<qg:choice label="Once more"><q:call function="anew" /><qg:goto-scene name="room-1" /></qg:choice>
<qg:choice label="To the title"><qg:goto-scene name="title" /></qg:choice>
</qg:line>
</qg:dialogue>
<qg:timer after="90"><qg:say dialogue="end" /></qg:timer>
</qg:scene>
<qg:scene name="game-over" width="256" height="224" background="#000000">
<q:set name="title" value="YOU FELL. PRESS JUMP." />
<qg:on-input action="jump">
<q:call function="anew" />
<qg:goto-scene name="room-1" />
</qg:on-input>
<qg:hud position="top-center">
<qg:text bind="title" />
</qg:hud>
</qg:scene>
</q:application>Delve
projects/delve/delve.q — a dungeon crawl for one, or two over the network: a wall a lever opens and the skeletons behind it that find their way through, crushers that slide to and fro, a vault two levers keep shut, bats that flutter until they see a delver and a skeleton archer, both made of parts; torches, hearts and gems carried as items and shown by the inventory, each crypt saved, three languages.
<q:application id="delve" type="game">
<!-- Delve: a dungeon crawl for one delver, or two over the network.
Three crypts, down and down: a wall a lever opens (qg:set-tile), and
the skeletons behind it that find their way through as soon as it does
(ai="pathfind"); crushers that slide to and fro (qg:tween); a vault
two levers keep shut, and its treasure, in the dark (ambient=): its
torches and the delvers' lanterns light it and its pillars cast shadows
(qg:light). Torches burn and gems sparkle (qg:particles). A skeleton archer, made of parts, keeps its distance and
shoots. The hearts and the gems are what the delvers carry
(qg:item), shown by the HUD's inventory. Each crypt entered is saved (Continue, alone); the
game speaks English, Portuguese and Spanish. The art is drawn for it by
scripts/art/delve.py and the music written by scripts/art/delve_music.py
(CC0); the sounds are Kenney's (CC0). -->
<!-- The delve's state, kept from crypt to crypt; the first crypt starts it anew. -->
<q:set name="message" value="" />
<!-- a hurt, the same in every crypt: a function of the game -->
<q:function name="hurt" params="me">
<qg:play sound="hurt" vary="0.1" />
<qg:shake at="me" frames="8" strength="2" />
<qg:take item="heart" />
<q:if condition="{held('heart') <= 0}">
<qg:goto-scene name="fallen" fade="0.6" />
</q:if>
</q:function>
<!-- Two players, each on their own machine: the delve starts in crypt-1 once both are there. -->
<qg:multiplayer players="2" start="crypt-1" />
<!-- 1 is the floor and 12 the rubble of a wall broken open (walked on); 2 the wall, 3 a wall with a torch. -->
<qg:tileset name="crypt" src="assets/crypt.png" tile="18">
<qg:tile frame="0" shape="none" />
<qg:tile frame="11" shape="none" />
</qg:tileset>
<qg:spritesheet name="delvers" src="assets/delvers.png" tile="24" />
<qg:sound bus="effects" name="hit" src="assets/kenney/audio/stomp.ogg" />
<qg:sound bus="effects" name="hurt" src="assets/kenney/audio/hurt.ogg" />
<qg:sound bus="effects" name="pick" src="assets/kenney/audio/coin.ogg" />
<qg:sound bus="effects" name="heal" src="assets/kenney/audio/powerup.ogg" />
<qg:sound bus="effects" name="open" src="assets/kenney/audio/block.ogg" />
<qg:sound bus="effects" name="win" src="assets/kenney/audio/win.ogg" />
<qg:sound bus="effects" name="twang" src="assets/kenney/audio/jump.ogg" />
<qg:sound bus="music" name="torchlight" src="assets/torchlight.ogg" loop="true" />
<qg:sound bus="music" name="crypt" src="assets/crypt.ogg" loop="true" />
<qg:sound bus="music" name="warden" src="assets/warden.ogg" loop="true" />
<!-- a skeleton falling apart: one of three, so no two fall alike -->
<qg:sound bus="effects" name="bones1" src="assets/bones1.ogg" />
<qg:sound bus="effects" name="bones2" src="assets/bones2.ogg" />
<qg:sound bus="effects" name="bones3" src="assets/bones3.ogg" />
<!-- effects: only seen, the game's state never depends on them -->
<qg:particles name="torch" preset="fire" amount="20" size="3" lifetime="0.7" speed="30" />
<qg:particles name="sparkle" preset="stars" color="#a8f0ff" />
<qg:particles name="glow" preset="sparks" color="#ff8098" amount="12" />
<qg:particles name="slain" preset="smoke" color="#c8c0b0" />
<qg:particles name="rumble" preset="dust" color="#8a8098" amount="30" speed="60" size="3" />
<qg:particles name="treasure" preset="confetti" />
<qg:particles name="dust" preset="snow" color="#6a6478" amount="16" speed="8" size="1" />
<!-- What the delvers carry, kept from crypt to crypt and in a save: hearts (five at most; one
less at each wound, and none is the fall) and gems. A heart picked up at five stays where it is. -->
<qg:item name="heart" label="HEARTS" sheet="crypt" frame="6" start="5" max="5" sound="heal" particles="glow" />
<qg:item name="gem" label="GEMS" sheet="crypt" frame="5" sound="pick" particles="sparkle" />
<qg:prefab name="Gem" tag="gem" sheet="crypt" frame="5" hitbox="10x12" gives="gem" />
<qg:prefab name="Heart" tag="heart" sheet="crypt" frame="6" hitbox="12x12" gives="heart" />
<qg:prefab name="Lever" tag="lever" sheet="crypt" frame="3" hitbox="14x14" />
<qg:prefab name="LeverOn" tag="lever-on" sheet="crypt" frame="4" hitbox="14x14" />
<qg:prefab name="Stairs" tag="stairs" sheet="crypt" frame="8" hitbox="10x10" />
<qg:prefab name="Slab" tag="slab" sheet="crypt" frame="9" hitbox="16x16" />
<qg:prefab name="Chest" tag="chest" sheet="crypt" frame="7" hitbox="16x16" />
<qg:prefab name="Skeleton" tag="enemy" sheet="delvers" frame="20" hitbox="12x14"
ai="pathfind" speed="30" sight="400">
<qg:animation name="walk" frames="20,21,20,22" fps="6" />
</qg:prefab>
<!-- a slain skeleton falls to a heap of bones, once, and the heap goes up in smoke -->
<qg:prefab name="Fallen" tag="fallen" sheet="delvers" frame="26" hitbox="8x8">
<qg:animation name="walk" frames="26,27,28,28,28" fps="8" loop="false" />
<qg:on-animation-end name="walk">
<qg:emit particles="slain" at="me" />
<qg:destroy target="me" />
</qg:on-animation-end>
</qg:prefab>
<!-- a bat flutters about until it sees a delver, then dives at it -->
<qg:prefab name="Bat" tag="enemy" sheet="delvers" frame="23" hitbox="14x12"
ai="behavior" speed="42" sight="110">
<qg:animation name="walk" frames="23,24,25,24" fps="10" />
<qg:behavior>
<qg:seek way="straight" />
<qg:wander speed="16" />
</qg:behavior>
</qg:prefab>
<!-- an enemy made of parts: it keeps its distance from the nearest delver it sees, shoots
at one within range, and wanders while it sees none -->
<qg:prefab name="Archer" tag="enemy" sheet="delvers" frame="29" hitbox="12x14"
ai="behavior" speed="26" sight="150">
<qg:animation name="walk" frames="29,30,29,31" fps="6" />
<qg:behavior>
<qg:keep-distance min="54" max="90" />
<qg:shoot prefab="Arrow" every="90" range="130" sound="twang" />
<qg:wander speed="12" />
</qg:behavior>
</qg:prefab>
<qg:prefab name="Arrow" tag="arrow" sheet="delvers" frame="32" hitbox="6x4"
ai="fly" speed="100" walls="stop" rotate="true" lifetime="180" />
<!-- the game in Portuguese and Spanish (a web page's ?lang=pt, the command line's q-lang, or the system's language) -->
<!-- on a phone: what the keys do, under a finger, in every scene -->
<qg:touch-stick x="40" y="184" />
<qg:touch-button action="jump" label="A" x="220" y="184" />
<qg:language lang="pt" name="Português">
<qg:phrase text="HEARTS" as="CORAÇÕES" />
<qg:phrase text="GEMS" as="GEMAS" />
<qg:phrase text="Play alone" as="Jogar sozinho" />
<qg:phrase text="Play together" as="Jogar junto" />
<qg:phrase text="Continue" as="Continuar" />
<qg:phrase text="Language" as="Idioma" />
<qg:phrase text="Try again" as="Tentar de novo" />
<qg:phrase text="To the title" as="Voltar ao início" />
<qg:phrase text="DOWN TO THE VAULT, ALONE OR WITH A FRIEND" as="ATÉ O COFRE, SOZINHO OU COM UM AMIGO" />
<qg:phrase text="TWO DELVERS, TWO MACHINES. ESCAPE: BACK." as="DOIS EXPLORADORES, DUAS MÁQUINAS. ESC: VOLTAR." />
<qg:phrase text="A SEALED WALL. A LEVER MUST OPEN IT." as="UMA PAREDE SELADA. UMA ALAVANCA DEVE ABRI-LA." />
<qg:phrase text="THE WALL OPENS. THE DEAD WAKE." as="A PAREDE SE ABRE. OS MORTOS DESPERTAM." />
<qg:phrase text="THE CRUSHERS. MIND THEIR TIME." as="OS ESMAGADORES. ATENÇÃO AO TEMPO DELES." />
<qg:phrase text="THE VAULT. TWO LEVERS HOLD IT SHUT." as="O COFRE. DUAS ALAVANCAS O MANTÊM FECHADO." />
<qg:phrase text="ONE MORE LEVER." as="FALTA UMA ALAVANCA." />
<qg:phrase text="THE VAULT IS OPEN." as="O COFRE ESTÁ ABERTO." />
<qg:phrase text="THE VAULT IS YOURS." as="O COFRE É SEU." />
<qg:phrase text="THE CRYPT KEEPS YOU." as="A CRIPTA FICA COM VOCÊ." />
<qg:phrase text="Options" as="Opções" />
<qg:phrase text="OPTIONS" as="OPÇÕES" />
<qg:phrase text="Music" as="Música" />
<qg:phrase text="Sounds" as="Sons" />
<qg:phrase text="Full screen" as="Tela cheia" />
<qg:phrase text="on" as="sim" />
<qg:phrase text="off" as="não" />
<qg:phrase text="Back" as="Voltar" />
<qg:phrase text="Sword" as="Espada" />
</qg:language>
<qg:language lang="es" name="Español">
<qg:phrase text="HEARTS" as="CORAZONES" />
<qg:phrase text="GEMS" as="GEMAS" />
<qg:phrase text="Play alone" as="Jugar solo" />
<qg:phrase text="Play together" as="Jugar juntos" />
<qg:phrase text="Continue" as="Continuar" />
<qg:phrase text="Language" as="Idioma" />
<qg:phrase text="Try again" as="Intentar de nuevo" />
<qg:phrase text="To the title" as="Volver al inicio" />
<qg:phrase text="DOWN TO THE VAULT, ALONE OR WITH A FRIEND" as="HASTA LA BÓVEDA, SOLO O CON UN AMIGO" />
<qg:phrase text="TWO DELVERS, TWO MACHINES. ESCAPE: BACK." as="DOS EXPLORADORES, DOS MÁQUINAS. ESC: VOLVER." />
<qg:phrase text="A SEALED WALL. A LEVER MUST OPEN IT." as="UN MURO SELLADO. UNA PALANCA DEBE ABRIRLO." />
<qg:phrase text="THE WALL OPENS. THE DEAD WAKE." as="EL MURO SE ABRE. LOS MUERTOS DESPIERTAN." />
<qg:phrase text="THE CRUSHERS. MIND THEIR TIME." as="LOS APLASTADORES. CUIDA SU RITMO." />
<qg:phrase text="THE VAULT. TWO LEVERS HOLD IT SHUT." as="LA BÓVEDA. DOS PALANCAS LA MANTIENEN CERRADA." />
<qg:phrase text="ONE MORE LEVER." as="FALTA UNA PALANCA." />
<qg:phrase text="THE VAULT IS OPEN." as="LA BÓVEDA ESTÁ ABIERTA." />
<qg:phrase text="THE VAULT IS YOURS." as="LA BÓVEDA ES TUYA." />
<qg:phrase text="THE CRYPT KEEPS YOU." as="LA CRIPTA SE QUEDA CONTIGO." />
<qg:phrase text="Options" as="Opciones" />
<qg:phrase text="OPTIONS" as="OPCIONES" />
<qg:phrase text="Music" as="Música" />
<qg:phrase text="Sounds" as="Sonidos" />
<qg:phrase text="Full screen" as="Pantalla completa" />
<qg:phrase text="on" as="sí" />
<qg:phrase text="off" as="no" />
<qg:phrase text="Back" as="Volver" />
<qg:phrase text="Sword" as="Espada" />
</qg:language>
<!-- ===== The title: alone, together, or the saved delve ===== -->
<qg:scene name="title" width="256" height="224" background="#16141e">
<qg:music sound="torchlight" />
<qg:sprite sheet="delvers" frame="0" x="34" y="150" gd:scale="2,2" />
<qg:sprite sheet="delvers" frame="10" x="222" y="150" gd:scale="2,2" gd:flip_h="true" />
<qg:emitter particles="torch" x="34" y="100" />
<qg:emitter particles="torch" x="222" y="100" />
<qg:emitter particles="dust" x="128" y="0" width="256" />
<qg:hud position="top-center" size="32"><qg:text value="{'DELVE'}" /></qg:hud>
<qg:hud position="bottom-center" size="10"><qg:text value="{tr('DOWN TO THE VAULT, ALONE OR WITH A FRIEND')}" /></qg:hud>
<qg:menu position="center">
<qg:button label="Play alone"><qg:goto-scene name="crypt-1" fade="0.4" /></qg:button>
<qg:button label="Play together"><qg:goto-scene name="together" /></qg:button>
<qg:button label="Continue" if="{has_save()}"><qg:continue /></qg:button>
<qg:button label="Options"><qg:goto-scene name="options" /></qg:button>
<qg:button label="{tr('Language') + ': ' + language()}"><qg:next-language /></qg:button>
</qg:menu>
</qg:scene>
<!-- Two machines: one hosts, the other joins its address; the delve starts in crypt-1. -->
<qg:scene name="together" width="256" height="224" background="#16141e">
<qg:music sound="torchlight" />
<qg:hud position="top-center" size="10"><qg:text value="{tr('TWO DELVERS, TWO MACHINES. ESCAPE: BACK.')}" /></qg:hud>
<qg:lobby local="crypt-1" local-label="Play alone" size="12" />
<qg:on-input action="cancel">
<qg:leave />
<qg:goto-scene name="title" />
</qg:on-input>
</qg:scene>
<!-- ===== Crypt 1: a sealed wall, and what waits behind it. Down the stairs to crypt 2. ===== -->
<qg:scene name="crypt-1" width="256" height="224" background="#100e16" seed="1" save="true">
<qg:music sound="crypt" />
<!-- the delve starts here anew: five hearts, no gems, every gem back in its place -->
<q:function name="anew" params=""><qg:reset-items /></q:function>
<q:call function="anew" />
<q:set name="message" value="A SEALED WALL. A LEVER MUST OPEN IT." />
<q:set name="leaving" value="false" type="boolean" />
<q:function name="slay" params="foe">
<qg:play sound="hit" vary="0.15" />
<qg:play sounds="bones1, bones2, bones3" vary="0.1" />
<qg:swap target="foe" prefab="Fallen" />
</q:function>
<q:function name="pull" params="lever">
<qg:play sound="open" pitch="0.75" />
<qg:swap target="lever" prefab="LeverOn" />
<qg:set-tile col="8" row="5" tile="12" />
<qg:set-tile col="8" row="6" tile="12" />
<qg:emit particles="rumble" at="lever" dx="72" dy="-63" />
<!-- the camera goes to show the gap, and back; the dead do not wait for it -->
<qg:camera-to x="153" y="108" zoom="2" seconds="0.4" hold="0.6" pause="false" />
<q:set name="message" value="THE WALL OPENS. THE DEAD WAKE." />
</q:function>
<q:function name="down" params="">
<q:if condition="{not leaving}">
<q:set name="leaving" value="true" />
<qg:goto-scene name="crypt-2" fade="0.5" />
</q:if>
</q:function>
<qg:tilemap tileset="crypt" collision="true">
2,2,2,2,2,2,2,2,2,2,2,2,2,2
2,1,1,1,1,1,1,1,2,1,1,1,1,2
2,1,1,1,1,1,1,1,3,1,1,1,1,2
3,1,1,1,1,1,1,1,2,1,1,1,1,3
2,1,1,1,1,1,1,1,2,1,1,1,1,2
2,1,1,1,1,1,1,1,2,1,1,1,1,2
2,1,1,1,1,1,1,1,2,1,1,1,1,2
2,1,1,1,1,1,1,1,2,1,1,1,1,2
3,1,1,1,1,1,1,1,2,1,1,1,1,3
2,1,1,1,1,1,1,1,2,1,1,1,1,2
2,1,1,1,1,1,1,1,2,1,1,1,1,2
2,2,2,2,2,2,2,2,2,2,2,2,2,2
</qg:tilemap>
<qg:emitter particles="torch" x="9" y="57" />
<qg:emitter particles="torch" x="9" y="147" />
<qg:emitter particles="torch" x="243" y="57" />
<qg:emitter particles="torch" x="243" y="147" />
<qg:emitter particles="torch" x="153" y="39" />
<qg:character id="p1" controller="topdown" player="1" sheet="delvers" frame="0"
x="45" y="99" hitbox="12x14" speed="70"
attack-action="jump" attack-reach="16" attack-frames="12" attack-sound="hit">
<qg:animation name="idle" frames="0" />
<qg:animation name="walk" frames="1,2" fps="8" />
<qg:animation name="walk-up" frames="3,4" fps="8" />
<qg:animation name="walk-down" frames="5,6" fps="8" />
<qg:animation name="attack" frames="7" />
<qg:animation name="attack-up" frames="8" />
<qg:animation name="attack-down" frames="9" />
<qg:on-collision with="enemy" cooldown="60"><q:call function="hurt" args="me" /></qg:on-collision>
<qg:on-collision with="lever"><q:call function="pull" args="other" /></qg:on-collision>
<qg:on-collision with="stairs"><q:call function="down" /></qg:on-collision>
<qg:on-hit with="enemy"><q:call function="slay" args="other" /></qg:on-hit>
</qg:character>
<qg:character id="p2" controller="topdown" player="2" sheet="delvers" frame="10"
x="45" y="135" hitbox="12x14" speed="70" if="{net_status() != 'offline'}"
attack-action="jump" attack-reach="16" attack-frames="12" attack-sound="hit">
<qg:animation name="idle" frames="10" />
<qg:animation name="walk" frames="11,12" fps="8" />
<qg:animation name="walk-up" frames="13,14" fps="8" />
<qg:animation name="walk-down" frames="15,16" fps="8" />
<qg:animation name="attack" frames="17" />
<qg:animation name="attack-up" frames="18" />
<qg:animation name="attack-down" frames="19" />
<qg:on-collision with="enemy" cooldown="60"><q:call function="hurt" args="me" /></qg:on-collision>
<qg:on-collision with="lever"><q:call function="pull" args="other" /></qg:on-collision>
<qg:on-collision with="stairs"><q:call function="down" /></qg:on-collision>
<qg:on-hit with="enemy"><q:call function="slay" args="other" /></qg:on-hit>
</qg:character>
<qg:camera follow="p1" bounds="tilemap" />
<qg:instance prefab="Lever" name="lever" x="81" y="171" />
<qg:instance prefab="Gem" name="gem-1" x="99" y="45" />
<qg:instance prefab="Gem" name="gem-2" x="117" y="153" />
<qg:instance prefab="Gem" name="gem-3" x="225" y="45" />
<qg:instance prefab="Skeleton" name="skeleton-1" x="190" y="72" />
<qg:instance prefab="Skeleton" name="skeleton-2" x="190" y="144" />
<qg:instance prefab="Stairs" name="stairs" x="225" y="171" />
<qg:hud position="top-left" size="10">
<qg:inventory />
<qg:text bind="message" />
</qg:hud>
</qg:scene>
<!-- ===== Crypt 2: three crushers slide across the way. Down the stairs to crypt 3. ===== -->
<qg:scene name="crypt-2" width="256" height="224" background="#100e16" seed="2" save="true">
<qg:music sound="crypt" />
<q:set name="message" value="THE CRUSHERS. MIND THEIR TIME." />
<q:set name="leaving" value="false" type="boolean" />
<q:function name="slay" params="foe">
<qg:play sound="hit" vary="0.15" />
<qg:play sounds="bones1, bones2, bones3" vary="0.1" />
<qg:emit particles="slain" at="foe" />
<qg:destroy target="foe" />
</q:function>
<q:function name="down" params="">
<q:if condition="{not leaving}">
<q:set name="leaving" value="true" />
<qg:goto-scene name="crypt-3" fade="0.5" />
</q:if>
</q:function>
<qg:tilemap tileset="crypt" collision="true">
2,2,2,2,2,2,2,2,2,2,2,2,2,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
3,1,1,1,1,1,1,1,1,1,1,1,1,3
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
3,1,1,1,1,1,1,1,1,1,1,1,1,3
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,2,2,2,2,2,2,2,2,2,2,2,2,2
</qg:tilemap>
<qg:emitter particles="torch" x="9" y="57" />
<qg:emitter particles="torch" x="9" y="147" />
<qg:emitter particles="torch" x="243" y="57" />
<qg:emitter particles="torch" x="243" y="147" />
<qg:character id="p1" controller="topdown" player="1" sheet="delvers" frame="0"
x="27" y="99" hitbox="12x14" speed="70"
attack-action="jump" attack-reach="16" attack-frames="12" attack-sound="hit">
<qg:animation name="idle" frames="0" />
<qg:animation name="walk" frames="1,2" fps="8" />
<qg:animation name="walk-up" frames="3,4" fps="8" />
<qg:animation name="walk-down" frames="5,6" fps="8" />
<qg:animation name="attack" frames="7" />
<qg:animation name="attack-up" frames="8" />
<qg:animation name="attack-down" frames="9" />
<qg:on-collision with="enemy" cooldown="60"><q:call function="hurt" args="me" /></qg:on-collision>
<qg:on-collision with="slab" cooldown="60"><q:call function="hurt" args="me" /></qg:on-collision>
<qg:on-collision with="stairs"><q:call function="down" /></qg:on-collision>
<qg:on-hit with="enemy"><q:call function="slay" args="other" /></qg:on-hit>
</qg:character>
<qg:character id="p2" controller="topdown" player="2" sheet="delvers" frame="10"
x="27" y="135" hitbox="12x14" speed="70" if="{net_status() != 'offline'}"
attack-action="jump" attack-reach="16" attack-frames="12" attack-sound="hit">
<qg:animation name="idle" frames="10" />
<qg:animation name="walk" frames="11,12" fps="8" />
<qg:animation name="walk-up" frames="13,14" fps="8" />
<qg:animation name="walk-down" frames="15,16" fps="8" />
<qg:animation name="attack" frames="17" />
<qg:animation name="attack-up" frames="18" />
<qg:animation name="attack-down" frames="19" />
<qg:on-collision with="enemy" cooldown="60"><q:call function="hurt" args="me" /></qg:on-collision>
<qg:on-collision with="slab" cooldown="60"><q:call function="hurt" args="me" /></qg:on-collision>
<qg:on-collision with="stairs"><q:call function="down" /></qg:on-collision>
<qg:on-hit with="enemy"><q:call function="slay" args="other" /></qg:on-hit>
</qg:character>
<!-- the crushers: each slides from one wall to the other and back, for ever, at its own pace -->
<qg:instance prefab="Slab" name="slab-1" x="81" y="27" />
<qg:instance prefab="Slab" name="slab-2" x="135" y="189" />
<qg:instance prefab="Slab" name="slab-3" x="189" y="27" />
<qg:timer after="1">
<qg:tween target="slab-1" property="y" to="189" seconds="1.6" trans="sine" ease="in-out" repeat="-1" yoyo="true" />
<qg:tween target="slab-2" property="y" to="27" seconds="1.2" trans="sine" ease="in-out" repeat="-1" yoyo="true" />
<qg:tween target="slab-3" property="y" to="189" seconds="2" trans="sine" ease="in-out" repeat="-1" yoyo="true" />
</qg:timer>
<qg:instance prefab="Heart" name="heart-1" x="162" y="108" />
<qg:instance prefab="Gem" name="gem-4" x="108" y="189" />
<qg:instance prefab="Gem" name="gem-5" x="162" y="45" />
<qg:instance prefab="Bat" name="bat-1" x="216" y="54" />
<qg:instance prefab="Bat" name="bat-2" x="216" y="171" />
<qg:instance prefab="Stairs" name="stairs" x="225" y="117" />
<qg:hud position="top-left" size="10">
<qg:inventory />
<qg:text bind="message" />
</qg:hud>
</qg:scene>
<!-- ===== Crypt 3: the vault, which two levers keep shut. ===== -->
<!-- The vault is dark (ambient=): what the torches and the delvers' lanterns light, and the walls cast
their shadows. Only seen: the game is the same in the dark. -->
<qg:scene name="crypt-3" width="256" height="224" background="#100e16" seed="3" save="true" ambient="#1c1828">
<qg:music sound="crypt" />
<q:set name="message" value="THE VAULT. TWO LEVERS HOLD IT SHUT." />
<q:set name="pulled" value="0" type="number" />
<q:set name="won" value="false" type="boolean" />
<!-- The warden, the skeleton archer: its fight has a theme of its own, from
when it first sees a delver until it falls (the music changes in the
scene; what is heard only, so the game is the same). -->
<qg:when condition="{distance(archer, p1) < 110 or distance(archer, p2) < 110}">
<qg:music sound="warden" fade="0.5" />
</qg:when>
<q:function name="slay" params="foe">
<qg:play sound="hit" vary="0.15" />
<qg:play sounds="bones1, bones2, bones3" vary="0.1" />
<q:if condition="{foe.name == 'archer'}"><qg:music sound="crypt" fade="2" /></q:if>
<qg:swap target="foe" prefab="Fallen" />
</q:function>
<q:function name="pull" params="lever">
<qg:play sound="open" pitch="0.75" />
<qg:swap target="lever" prefab="LeverOn" />
<q:set name="pulled" value="{pulled + 1}" />
<q:if condition="{pulled == 2}">
<qg:set-tile col="6" row="8" tile="12" />
<qg:set-tile col="7" row="8" tile="12" />
<qg:emit particles="rumble" at="chest" dy="-27" />
<q:set name="message" value="THE VAULT IS OPEN." />
<q:else>
<q:set name="message" value="ONE MORE LEVER." />
</q:else>
</q:if>
</q:function>
<q:function name="treasure" params="it">
<q:if condition="{not won}">
<q:set name="won" value="true" />
<qg:play sound="win" />
<qg:emit particles="treasure" at="it" />
<q:set name="message" value="THE VAULT IS YOURS." />
<qg:goto-scene name="the-end" fade="1.5" />
</q:if>
</q:function>
<qg:tilemap tileset="crypt" collision="true">
2,2,2,2,2,2,2,2,2,2,2,2,2,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
3,1,1,1,1,1,1,1,1,1,1,1,1,3
2,1,1,2,1,1,1,1,1,1,2,1,1,2
2,1,1,2,1,1,1,1,1,1,2,1,1,2
2,1,1,2,1,1,1,1,1,1,2,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,2
2,2,2,2,2,3,2,2,3,2,2,2,2,2
2,2,2,2,2,1,1,1,1,2,2,2,2,2
2,2,2,2,2,1,1,1,1,2,2,2,2,2
2,2,2,2,2,2,2,2,2,2,2,2,2,2
</qg:tilemap>
<qg:emitter particles="torch" x="9" y="57" />
<qg:emitter particles="torch" x="243" y="57" />
<qg:emitter particles="torch" x="99" y="147" />
<qg:emitter particles="torch" x="153" y="147" />
<!-- each torch's light, just off its wall, flickering as a flame; and the treasure's glow in the vault -->
<qg:light x="24" y="63" radius="72" color="#ffa850" energy="1.4" flicker="0.25" shadows="true" />
<qg:light x="232" y="63" radius="72" color="#ffa850" energy="1.4" flicker="0.25" shadows="true" />
<qg:light x="99" y="138" radius="64" color="#ffa850" energy="1.4" flicker="0.25" shadows="true" />
<qg:light x="153" y="138" radius="64" color="#ffa850" energy="1.4" flicker="0.25" shadows="true" />
<qg:light x="126" y="176" radius="40" color="#ffd860" energy="1.4" shadows="true" />
<qg:character id="p1" controller="topdown" player="1" sheet="delvers" frame="0"
x="45" y="45" hitbox="12x14" speed="70"
attack-action="jump" attack-reach="16" attack-frames="12" attack-sound="hit">
<qg:light radius="52" color="#fff0d8" shadows="true" /> <!-- a lantern -->
<qg:animation name="idle" frames="0" />
<qg:animation name="walk" frames="1,2" fps="8" />
<qg:animation name="walk-up" frames="3,4" fps="8" />
<qg:animation name="walk-down" frames="5,6" fps="8" />
<qg:animation name="attack" frames="7" />
<qg:animation name="attack-up" frames="8" />
<qg:animation name="attack-down" frames="9" />
<qg:on-collision with="enemy" cooldown="60"><q:call function="hurt" args="me" /></qg:on-collision>
<qg:on-collision with="lever"><q:call function="pull" args="other" /></qg:on-collision>
<qg:on-collision with="chest"><q:call function="treasure" args="other" /></qg:on-collision>
<qg:on-collision with="arrow"><qg:destroy target="other" /><q:call function="hurt" args="me" /></qg:on-collision>
<qg:on-hit with="enemy"><q:call function="slay" args="other" /></qg:on-hit>
</qg:character>
<qg:character id="p2" controller="topdown" player="2" sheet="delvers" frame="10"
x="207" y="45" hitbox="12x14" speed="70" if="{net_status() != 'offline'}"
attack-action="jump" attack-reach="16" attack-frames="12" attack-sound="hit">
<qg:light radius="52" color="#fff0d8" shadows="true" /> <!-- a lantern -->
<qg:animation name="idle" frames="10" />
<qg:animation name="walk" frames="11,12" fps="8" />
<qg:animation name="walk-up" frames="13,14" fps="8" />
<qg:animation name="walk-down" frames="15,16" fps="8" />
<qg:animation name="attack" frames="17" />
<qg:animation name="attack-up" frames="18" />
<qg:animation name="attack-down" frames="19" />
<qg:on-collision with="enemy" cooldown="60"><q:call function="hurt" args="me" /></qg:on-collision>
<qg:on-collision with="lever"><q:call function="pull" args="other" /></qg:on-collision>
<qg:on-collision with="chest"><q:call function="treasure" args="other" /></qg:on-collision>
<qg:on-collision with="arrow"><qg:destroy target="other" /><q:call function="hurt" args="me" /></qg:on-collision>
<qg:on-hit with="enemy"><q:call function="slay" args="other" /></qg:on-hit>
</qg:character>
<qg:instance prefab="Lever" name="lever-west" x="27" y="135" />
<qg:instance prefab="Lever" name="lever-east" x="225" y="135" />
<qg:instance prefab="Chest" name="chest" x="126" y="180" />
<qg:timer after="1">
<qg:tween target="chest" property="y" to="176" seconds="0.8" trans="sine" ease="in-out" repeat="-1" yoyo="true" />
</qg:timer>
<qg:instance prefab="Gem" name="gem-6" x="225" y="27" />
<qg:instance prefab="Gem" name="gem-7" x="126" y="99" />
<qg:instance prefab="Skeleton" name="skeleton-1" x="126" y="63" />
<qg:instance prefab="Skeleton" name="skeleton-2" x="81" y="117" />
<qg:instance prefab="Skeleton" name="skeleton-3" x="171" y="117" />
<qg:instance prefab="Archer" name="archer" x="207" y="81" />
<qg:hud position="top-left" size="10">
<qg:inventory />
<qg:text bind="message" />
</qg:hud>
</qg:scene>
<!-- ===== The end, and the fall ===== -->
<qg:scene name="the-end" width="256" height="224" background="#141020">
<qg:music sound="torchlight" />
<qg:emitter particles="treasure" x="128" y="150" />
<qg:sprite sheet="crypt" frame="7" x="128" y="120" gd:scale="3,3" />
<qg:hud position="top-center" size="16">
<qg:text bind="message" />
<qg:counter item="gem" label="GEMS" />
</qg:hud>
<qg:menu position="bottom-center">
<qg:button label="To the title"><qg:leave /><qg:goto-scene name="title" /></qg:button>
</qg:menu>
</qg:scene>
<qg:scene name="fallen" width="256" height="224" background="#000000">
<qg:emitter particles="dust" x="128" y="0" width="256" />
<qg:hud position="top-center" size="16"><qg:text value="{tr('THE CRYPT KEEPS YOU.')}" /></qg:hud>
<qg:menu position="center">
<!-- alone: back to the start of the crypt where the delver fell (it was saved on entering);
together: from the first crypt, on both machines -->
<qg:button label="Try again">
<q:if condition="{net_status() == 'offline' and has_save()}">
<qg:continue />
<q:else>
<qg:goto-scene name="crypt-1" />
</q:else>
</q:if>
</qg:button>
<qg:button label="To the title"><qg:leave /><qg:goto-scene name="title" /></qg:button>
</qg:menu>
</qg:scene>
<!-- ===== Options: how loud, the full screen, the keys; kept between runs ===== -->
<qg:scene name="options" width="256" height="224" background="#16141e">
<qg:hud position="top-center" size="24"><qg:text value="{tr('OPTIONS')}" /></qg:hud>
<qg:menu position="center" size="12">
<qg:slider label="Music" bus="music" />
<qg:slider label="Sounds" bus="effects" />
<qg:button label="{tr('Full screen') + ': ' + (tr('on') if fullscreen() else tr('off'))}"><qg:fullscreen /></qg:button>
<qg:rebind label="Sword" action="jump" />
<qg:button label="Back"><qg:goto-scene name="title" /></qg:button>
</qg:menu>
</qg:scene>
</q:application>Drift
projects/drift/drift.q — a vertical shooter: three waves from the scene's seed, drones and tanks that shoot, a boss with two phases, a high score kept between runs.
<q:application id="drift" type="game">
<!-- Drift: the vertical shooter that drives the game language (PLAN_GAMES_2.md).
A ship at the bottom of the screen, three waves of drones and tanks that
come down and shoot, then a boss with two phases; lives, score, and a high
score kept between runs. The ship, the drones, the tanks, the boss and the
sky are drawn for it by scripts/art/drift.py (CC0); the sounds are
Kenney's Pixel Platformer (CC0). -->
<q:set name="score" value="0" type="number" />
<q:set name="lives" value="3" type="number" />
<q:set name="high_score" value="0" type="number" saved="true" />
<!-- The ships, the boss and the sky are drawn for this game by scripts/art/drift.py (CC0). -->
<qg:spritesheet name="ships" src="assets/ships.png" tile="24" />
<qg:spritesheet name="boss" src="assets/boss.png" tile="40" />
<qg:spritesheet name="space" src="assets/space.png" tile="256x224" />
<qg:sound bus="effects" name="shoot" src="assets/kenney/audio/jump.ogg" />
<qg:sound bus="effects" name="hit" src="assets/kenney/audio/stomp.ogg" />
<qg:sound bus="effects" name="boom" src="assets/kenney/audio/block.ogg" />
<qg:sound bus="effects" name="hurt" src="assets/kenney/audio/hurt.ogg" />
<qg:sound bus="effects" name="win" src="assets/kenney/audio/win.ogg" />
<!-- The ship's shot: flies up, gone after 80 ticks or when it leaves the screen. -->
<qg:prefab name="Shot" tag="shot" sheet="ships" frame="8" hitbox="18x8"
ai="fly" heading="up" speed="240" lifetime="80">
<qg:on-collision with="enemy">
<qg:damage target="other" amount="1" />
<qg:destroy target="me" />
</qg:on-collision>
<qg:on-collision with="boss">
<qg:damage target="other" amount="1" />
<qg:destroy target="me" />
</qg:on-collision>
</qg:prefab>
<qg:prefab name="EnemyShot" tag="enemy-shot" sheet="ships" frame="9" hitbox="6x6"
ai="fly" heading="down" speed="120" lifetime="150" />
<!-- A drone: comes straight down, one hit. -->
<qg:prefab name="Drone" tag="enemy" sheet="ships" frame="2" hitbox="14x14"
ai="fly" heading="down" speed="45" lifetime="400" health="1">
<qg:animation name="walk" frames="2,3,4" fps="6" />
<qg:on-death>
<qg:play sound="boom" />
<qg:burst at="me" color="#ffcc44" count="10" />
<q:set name="score" value="{score + 100}" />
</qg:on-death>
</qg:prefab>
<!-- A tank: slow, three hits, shoots. -->
<qg:prefab name="Tank" tag="enemy" sheet="ships" frame="5" hitbox="16x16"
ai="fly" heading="down" speed="20" lifetime="900" health="3"
fire-prefab="EnemyShot" fire-every="90">
<qg:animation name="walk" frames="5,6,7" fps="8" />
<qg:on-damage>
<qg:play sound="hit" />
</qg:on-damage>
<qg:on-death>
<qg:play sound="boom" />
<qg:burst at="me" color="#ff6644" count="16" />
<q:set name="score" value="{score + 300}" />
</qg:on-death>
</qg:prefab>
<!-- The boss: sways across the top, twenty hits; angry below half. -->
<qg:prefab name="Boss" tag="boss" sheet="boss" frame="0" hitbox="20x20"
ai="sway" speed="30" health="12" fire-prefab="EnemyShot" fire-every="90">
<qg:state name="calm" frame="0" speed="30" fire-every="90" initial="true" />
<qg:state name="angry" frame="1" speed="70" fire-every="60" />
<qg:on-damage>
<qg:play sound="hit" />
<q:if condition="{me.health <= 6 and me.state == 'calm'}">
<qg:become target="me" state="angry" />
</q:if>
</qg:on-damage>
<qg:on-death>
<qg:play sound="win" />
<qg:burst at="me" color="#ffffff" count="40" />
<qg:shake at="me" frames="20" strength="4" />
<q:set name="score" value="{score + 5000}" />
</qg:on-death>
</qg:prefab>
<!-- the game in Portuguese and Spanish (a web page's ?lang=pt, the command line's q-lang, or the system's language) -->
<!-- on a phone: what the keys do, under a finger, in every scene -->
<qg:touch-stick x="40" y="184" />
<qg:touch-button action="jump" label="A" x="220" y="184" />
<qg:language lang="pt" name="Português">
<qg:phrase text="SCORE" as="PONTOS" />
<qg:phrase text="LIVES" as="VIDAS" />
<qg:phrase text="HIGH" as="RECORDE" />
<qg:phrase text="HERE IT COMES" as="LÁ VEM" />
<qg:phrase text="THE DRIFT IS CLEAR" as="O CAMPO ESTÁ LIMPO" />
<qg:phrase text="THE DRIFT IS CLEAR. PRESS JUMP." as="O CAMPO ESTÁ LIMPO. APERTE PULAR." />
<qg:phrase text="LOST IN THE DRIFT. PRESS JUMP." as="PERDIDO NO CAMPO. APERTE PULAR." />
<qg:phrase text="Resume" as="Continuar" />
<qg:phrase text="Sounds" as="Sons" />
<qg:phrase text="Full screen" as="Tela cheia" />
<qg:phrase text="on" as="sim" />
<qg:phrase text="off" as="não" />
<qg:phrase text="Fire" as="Atirar" />
</qg:language>
<qg:language lang="es" name="Español">
<qg:phrase text="SCORE" as="PUNTOS" />
<qg:phrase text="LIVES" as="VIDAS" />
<qg:phrase text="HIGH" as="RÉCORD" />
<qg:phrase text="HERE IT COMES" as="AHÍ VIENE" />
<qg:phrase text="THE DRIFT IS CLEAR" as="EL CAMPO ESTÁ DESPEJADO" />
<qg:phrase text="THE DRIFT IS CLEAR. PRESS JUMP." as="EL CAMPO ESTÁ DESPEJADO. PULSA SALTAR." />
<qg:phrase text="LOST IN THE DRIFT. PRESS JUMP." as="PERDIDO EN EL CAMPO. PULSA SALTAR." />
<qg:phrase text="Resume" as="Continuar" />
<qg:phrase text="Sounds" as="Sonidos" />
<qg:phrase text="Full screen" as="Pantalla completa" />
<qg:phrase text="on" as="sí" />
<qg:phrase text="off" as="no" />
<qg:phrase text="Fire" as="Disparar" />
</qg:language>
<qg:scene name="play" width="256" height="224" background="#0a0a1e" seed="3">
<qg:sprite sheet="space" x="128" y="112" />
<!-- Escape (or the joypad's B) pauses, with the options; again, or Resume, goes on -->
<qg:on-input action="cancel">
<q:if condition="{paused()}"><qg:resume /><q:else><qg:pause /></q:else></q:if>
</qg:on-input>
<qg:menu if="{paused()}" size="12">
<qg:button label="Resume"><qg:resume /></qg:button>
<qg:slider label="Sounds" bus="effects" />
<qg:button label="{tr('Full screen') + ': ' + (tr('on') if fullscreen() else tr('off'))}"><qg:fullscreen /></qg:button>
<qg:rebind label="Fire" action="jump" />
</qg:menu>
<q:set name="message" value="" />
<q:set name="boss_down" value="false" type="boolean" />
<q:function name="hurt" params="me">
<qg:play sound="hurt" />
<qg:shake at="me" frames="10" strength="3" />
<q:set name="lives" value="{lives - 1}" />
<q:if condition="{lives <= 0}">
<qg:goto-scene name="game-over" />
</q:if>
</q:function>
<qg:character id="ship" controller="ship" sheet="ships" frame="0" x="128" y="196" hitbox="12x12"
speed="120" bounds="scene"
fire-action="jump" fire-prefab="Shot" fire-every="8" fire-sound="shoot">
<qg:animation name="idle" frames="0,1" fps="12" />
<qg:on-collision with="enemy" cooldown="90">
<qg:destroy target="other" />
<q:call function="hurt" args="me" />
</qg:on-collision>
<qg:on-collision with="enemy-shot" cooldown="90">
<qg:destroy target="other" />
<q:call function="hurt" args="me" />
</qg:on-collision>
<qg:on-collision with="boss" cooldown="90">
<q:call function="hurt" args="me" />
</qg:on-collision>
</qg:character>
<!-- Three waves, then the boss. x="random" is from the scene's seed. -->
<qg:spawner prefab="Drone" from="60" every="40" count="8" x="random" y="-12" />
<qg:spawner prefab="Tank" from="500" every="120" count="3" x="random" y="-12" />
<qg:spawner prefab="Drone" from="900" every="25" count="12" x="random" y="-12" />
<qg:spawner prefab="Tank" from="950" every="150" count="2" x="random" y="-12" />
<qg:spawner prefab="Boss" from="1500" every="1" count="1" x="128" y="40" />
<qg:timer after="1440">
<q:set name="message" value="HERE IT COMES" />
</qg:timer>
<qg:on-death of="boss">
<q:set name="boss_down" value="true" />
<q:set name="message" value="THE DRIFT IS CLEAR" />
<qg:goto-scene name="victory" />
</qg:on-death>
<qg:hud position="top-left">
<qg:counter bind="score" label="SCORE" />
<qg:counter bind="lives" label="LIVES" />
<qg:counter bind="high_score" label="HIGH" />
<qg:text bind="message" />
</qg:hud>
</qg:scene>
<qg:scene name="victory" width="256" height="224" background="#102040">
<qg:sprite sheet="space" x="128" y="112" />
<q:set name="title" value="THE DRIFT IS CLEAR. PRESS JUMP." />
<q:if condition="{score > high_score}">
<q:set name="high_score" value="{score}" />
</q:if>
<qg:on-input action="jump">
<q:set name="score" value="0" />
<q:set name="lives" value="3" />
<qg:goto-scene name="play" />
</qg:on-input>
<qg:hud position="top-center">
<qg:text bind="title" />
<qg:counter bind="score" label="SCORE" />
<qg:counter bind="high_score" label="HIGH" />
</qg:hud>
</qg:scene>
<qg:scene name="game-over" width="256" height="224" background="#000000">
<qg:sprite sheet="space" x="128" y="112" />
<q:set name="title" value="LOST IN THE DRIFT. PRESS JUMP." />
<q:if condition="{score > high_score}">
<q:set name="high_score" value="{score}" />
</q:if>
<qg:on-input action="jump">
<q:set name="score" value="0" />
<q:set name="lives" value="3" />
<qg:goto-scene name="play" />
</qg:on-input>
<qg:hud position="top-center">
<qg:text bind="title" />
<qg:counter bind="score" label="SCORE" />
<qg:counter bind="high_score" label="HIGH" />
</qg:hud>
</qg:scene>
</q:application>Pong
projects/pong/pong.q — Godot's own Pong demo transcribed tag for node: two players, a ball that flies and bounces, zones at the edges (projects/pong/README.md maps the original to it).
<q:application id="pong" type="game">
<!-- Pong: Godot's own "Pong with GDScript" demo (godotengine/godot-demo-projects,
2d/pong, MIT), transcribed into the game language node for node and line
for line. README.md next to this file maps each piece of the original to
its tag, and lists what the language had to grow to say it. The sprites
are the demo's (assets/LICENSE.md). -->
<qg:spritesheet name="paddle" src="assets/paddle.png" tile="8x32" />
<qg:spritesheet name="ball" src="assets/ball.png" tile="8" />
<qg:spritesheet name="separator" src="assets/separator.png" tile="2x400" />
<!-- The left paddle is player 1 on W/S, the right one player 2 on the arrows —
on one keyboard. Over the network each machine is one player (the
title's Host and Join), and the game runs in lockstep, the same on
both (README.md). -->
<qg:input player="1" action="up" keys="W" />
<qg:input player="1" action="down" keys="S" />
<qg:input player="2" action="up" keys="Up" />
<qg:input player="2" action="down" keys="Down" />
<qg:multiplayer players="2" delay="3" start="court" />
<!-- The ball flies left at 100 px/s, 2 px/s faster every second; it bounces
off the ceiling and the floor, and goes back to its start, as it was,
when it passes a paddle. -->
<qg:prefab name="Ball" tag="ball" sheet="ball" hitbox="8x8" ai="fly" heading="left" speed="100" accel="2">
<qg:on-collision with="edge">
<qg:deflect target="me" axis="y" />
</qg:on-collision>
<qg:on-collision with="wall">
<qg:respawn target="me" />
</qg:on-collision>
</qg:prefab>
<!-- The title: two on one keyboard, or one on each machine — host, or join at
an address. The court starts on both machines once both are there. -->
<!-- the game in Portuguese and Spanish (a web page's ?lang=pt, the command line's q-lang, or the system's language) -->
<!-- on a phone: what the keys do, under a finger, in every scene -->
<qg:touch-button action="up" label="^" x="48" y="300" size="64" />
<qg:touch-button action="down" label="v" x="48" y="360" size="64" />
<qg:language lang="pt" name="Português">
<qg:phrase text="PONG" as="PONG" />
<qg:phrase text="Two players, one keyboard" as="Dois jogadores, um teclado" />
<qg:phrase text="OPTIONS" as="OPÇÕES" />
<qg:phrase text="Full screen" as="Tela cheia" />
<qg:phrase text="on" as="sim" />
<qg:phrase text="off" as="não" />
<qg:phrase text="Back" as="Voltar" />
<qg:phrase text="P1 up" as="J1 cima" />
<qg:phrase text="P2 up" as="J2 cima" />
<qg:phrase text="P1 down" as="J1 baixo" />
<qg:phrase text="P2 down" as="J2 baixo" />
</qg:language>
<qg:language lang="es" name="Español">
<qg:phrase text="PONG" as="PONG" />
<qg:phrase text="Two players, one keyboard" as="Dos jugadores, un teclado" />
<qg:phrase text="OPTIONS" as="OPCIONES" />
<qg:phrase text="Full screen" as="Pantalla completa" />
<qg:phrase text="on" as="sí" />
<qg:phrase text="off" as="no" />
<qg:phrase text="Back" as="Volver" />
<qg:phrase text="P1 up" as="J1 arriba" />
<qg:phrase text="P2 up" as="J2 arriba" />
<qg:phrase text="P1 down" as="J1 abajo" />
<qg:phrase text="P2 down" as="J2 abajo" />
</qg:language>
<qg:scene name="title" width="640" height="400" background="#24272a">
<qg:hud position="top-center" size="48"><qg:text value="{'PONG'}" /></qg:hud>
<qg:lobby local="court" local-label="Two players, one keyboard" size="20" options="options" />
</qg:scene>
<qg:scene name="court" width="640" height="400" background="#24272a" seed="1">
<qg:sprite sheet="separator" x="320" y="200" />
<!-- A paddle moves up and down at 100 px/s, kept on the screen; the ball
it touches leaves towards the other side, at a random slant. -->
<qg:character id="left" controller="ship" player="1" axis="vertical" speed="100"
sheet="paddle" x="67.6285" y="192.594" hitbox="8x32" gd:modulate="#00ffff">
<qg:on-collision with="ball">
<qg:deflect target="other" dx="1" dy="{random(-1, 1)}" />
</qg:on-collision>
</qg:character>
<qg:character id="right" controller="ship" player="2" axis="vertical" speed="100"
sheet="paddle" x="563.815" y="188.919" hitbox="8x32" gd:modulate="#ff00ff">
<qg:on-collision with="ball">
<qg:deflect target="other" dx="-1" dy="{random(-1, 1)}" />
</qg:on-collision>
</qg:character>
<qg:instance prefab="Ball" name="ball" x="320.5" y="191.124" />
<qg:zone name="ceiling" tag="edge" x="0" y="-20" width="640" height="20" />
<qg:zone name="floor" tag="edge" x="0" y="400" width="640" height="20" />
<qg:zone name="left-wall" tag="wall" x="-20" y="0" width="20" height="400" />
<qg:zone name="right-wall" tag="wall" x="640" y="0" width="20" height="400" />
</qg:scene>
<!-- ===== Options: how loud, the full screen, the keys; kept between runs ===== -->
<qg:scene name="options" width="640" height="400" background="#24272a">
<qg:hud position="top-center" size="36"><qg:text value="{tr('OPTIONS')}" /></qg:hud>
<qg:menu position="center" size="18" columns="2">
<qg:rebind label="P1 up" action="up" />
<qg:rebind label="P2 up" action="up" player="2" />
<qg:rebind label="P1 down" action="down" />
<qg:rebind label="P2 down" action="down" player="2" />
<qg:button label="{tr('Full screen') + ': ' + (tr('on') if fullscreen() else tr('off'))}"><qg:fullscreen /></qg:button>
<qg:button label="Back"><qg:goto-scene name="title" /></qg:button>
</qg:menu>
</qg:scene>
</q:application>Creeps
projects/creeps/creeps.q — Godot's "Dodge the Creeps" tutorial game transcribed: a title, creeps from the border at random speeds, a score a second, a game over (projects/creeps/README.md).
<q:application id="creeps" type="game">
<!-- Creeps: Godot's own "Dodge the Creeps" demo (godotengine/godot-demo-projects,
2d/dodge_the_creeps, MIT — the game of the "Your first 2D game" tutorial)
transcribed into the game language. README.md next to this file maps each
piece of the original to its tag and lists what the language had to grow.
The art, music and font are the demo's (assets/LICENSE.md). -->
<q:set name="score" value="0" type="number" />
<qg:spritesheet name="player" src="assets/player.png" tile="112x136" />
<qg:spritesheet name="creeps" src="assets/creeps.png" tile="100x140" />
<qg:sound bus="music" name="music" src="assets/music.ogg" loop="true" />
<qg:sound bus="effects" name="death" src="assets/gameover.wav" />
<qg:input action="jump" keys="Space, Enter, JoyA, JoyStart" />
<!-- Three kinds of creep: each flies straight at a speed of its own, turned to
face where it goes, and is gone once off the screen. -->
<qg:prefab name="Flyer" tag="creep" sheet="creeps" frame="0" hitbox="75x56" ai="fly" speed="150..250" rotate="true">
<qg:animation name="walk" frames="0, 1" fps="3" />
</qg:prefab>
<qg:prefab name="Swimmer" tag="creep" sheet="creeps" frame="2" hitbox="75x56" ai="fly" speed="150..250" rotate="true">
<qg:animation name="walk" frames="2, 3" fps="4" />
</qg:prefab>
<qg:prefab name="Walker" tag="creep" sheet="creeps" frame="4" hitbox="75x56" ai="fly" speed="150..250" rotate="true">
<qg:animation name="walk" frames="4, 5" fps="4" />
</qg:prefab>
<!-- the game in Portuguese and Spanish (a web page's ?lang=pt, the command line's q-lang, or the system's language) -->
<!-- on a phone: what the keys do, under a finger, in every scene -->
<qg:touch-stick x="90" y="630" size="140" />
<qg:language lang="pt" name="Português">
<qg:phrase text="Start" as="Começar" />
<qg:phrase text="Dodge the Creeps" as="Desvie das Criaturas" />
<qg:phrase text="Get Ready" as="Prepare-se" />
<qg:phrase text="Game Over" as="Fim de jogo" />
<qg:phrase text="Options" as="Opções" />
<qg:phrase text="OPTIONS" as="OPÇÕES" />
<qg:phrase text="Music" as="Música" />
<qg:phrase text="Sounds" as="Sons" />
<qg:phrase text="Full screen" as="Tela cheia" />
<qg:phrase text="on" as="sim" />
<qg:phrase text="off" as="não" />
<qg:phrase text="Back" as="Voltar" />
</qg:language>
<qg:language lang="es" name="Español">
<qg:phrase text="Start" as="Empezar" />
<qg:phrase text="Dodge the Creeps" as="Esquiva a las Criaturas" />
<qg:phrase text="Get Ready" as="Prepárate" />
<qg:phrase text="Game Over" as="Fin del juego" />
<qg:phrase text="Options" as="Opciones" />
<qg:phrase text="OPTIONS" as="OPCIONES" />
<qg:phrase text="Music" as="Música" />
<qg:phrase text="Sounds" as="Sonidos" />
<qg:phrase text="Full screen" as="Pantalla completa" />
<qg:phrase text="on" as="sí" />
<qg:phrase text="off" as="no" />
<qg:phrase text="Back" as="Volver" />
</qg:language>
<qg:scene name="title" width="480" height="720" background="#385f61">
<q:set name="message" value="Dodge the Creeps" />
<qg:hud position="center" font="fonts/Xolonium-Regular.ttf" size="60">
<qg:text bind="message" />
</qg:hud>
<!-- The demo's StartButton: clicked, or Enter, or the joypad's A. -->
<qg:menu position="bottom-center" font="fonts/Xolonium-Regular.ttf" size="60">
<qg:button label="Start"><qg:goto-scene name="play" /></qg:button>
<qg:button label="Options"><qg:goto-scene name="options" /></qg:button>
</qg:menu>
<qg:on-input action="jump">
<qg:goto-scene name="play" />
</qg:on-input>
</qg:scene>
<!-- Two seconds of "Get Ready", then a creep every half second from a random
point on the border, headed in at up to 45° off straight, and a point a
second. Touching a creep ends the run. -->
<qg:scene name="play" width="480" height="720" background="#385f61" seed="7">
<q:set name="score" value="0" />
<q:set name="message" value="Get Ready" />
<qg:play sound="music" />
<qg:hud position="top-center" font="fonts/Xolonium-Regular.ttf" size="60">
<qg:counter bind="score" />
</qg:hud>
<qg:hud position="center" font="fonts/Xolonium-Regular.ttf" size="60">
<qg:text bind="message" />
</qg:hud>
<qg:character id="player" controller="topdown" sheet="player" frame="0" x="240" y="450" hitbox="54x68"
speed="400" bounds="scene">
<qg:animation name="idle" frames="0" />
<qg:animation name="walk" frames="0, 1" fps="5" />
<qg:animation name="walk-up" frames="2, 3" fps="5" />
<qg:on-collision with="creep">
<qg:stop sound="music" />
<qg:play sound="death" />
<qg:goto-scene name="over" />
</qg:on-collision>
</qg:character>
<qg:timer after="120">
<q:set name="message" value="" />
</qg:timer>
<qg:spawner prefab="Flyer, Swimmer, Walker" along="edges" heading="inward" spread="45" from="120" every="30" count="0" />
<qg:timer every="60" from="120">
<q:set name="score" value="{score + 1}" />
</qg:timer>
</qg:scene>
<!-- "Game Over" for two seconds, the title for one more, then the start line;
the score stays on the screen. -->
<qg:scene name="over" width="480" height="720" background="#385f61">
<q:set name="message" value="Game Over" />
<q:set name="can_start" value="false" type="boolean" />
<qg:hud position="top-center" font="fonts/Xolonium-Regular.ttf" size="60">
<qg:counter bind="score" />
</qg:hud>
<qg:hud position="center" font="fonts/Xolonium-Regular.ttf" size="60">
<qg:text bind="message" />
</qg:hud>
<qg:timer after="120">
<q:set name="message" value="Dodge the Creeps" />
</qg:timer>
<qg:timer after="180">
<q:set name="can_start" value="true" />
</qg:timer>
<qg:menu position="bottom-center" font="fonts/Xolonium-Regular.ttf" size="60">
<qg:button label="Start" if="{can_start}"><qg:goto-scene name="play" /></qg:button>
</qg:menu>
<qg:on-input action="jump">
<q:if condition="{can_start}">
<qg:goto-scene name="play" />
</q:if>
</qg:on-input>
</qg:scene>
<!-- ===== Options: how loud, the full screen, the keys; kept between runs ===== -->
<qg:scene name="options" width="480" height="720" background="#385f61">
<qg:hud position="top-center" size="48"><qg:text value="{tr('OPTIONS')}" /></qg:hud>
<qg:menu position="center" font="fonts/Xolonium-Regular.ttf" size="28">
<qg:slider label="Music" bus="music" />
<qg:slider label="Sounds" bus="effects" />
<qg:button label="{tr('Full screen') + ': ' + (tr('on') if fullscreen() else tr('off'))}"><qg:fullscreen /></qg:button>
<qg:button label="Back"><qg:goto-scene name="title" /></qg:button>
</qg:menu>
</qg:scene>
</q:application>Towers
projects/towers/towers.q — a tower defense transcribed from an MIT Godot template: a cursor, turrets that shoot the nearest dino, dinos down a path in waves, gold and a base; two players over the network (projects/towers/README.md).
<q:application id="towers" type="game">
<!-- Towers: the "Godot 4 Tower Defense Template" (github.com/ape1121, MIT)
transcribed into the game language, with its map, dinos and turrets
(assets/LICENSE.md). README.md next to this file maps the original to
its tags and lists what the language had to grow. Two players can
build on the same map over the network (qg:multiplayer). -->
<q:set name="gold" value="100" type="number" />
<q:set name="base_hp" value="10" type="number" />
<q:set name="wave" value="0" type="number" />
<qg:spritesheet name="map" src="assets/map.png" tile="1156x745" />
<qg:spritesheet name="gatling" src="assets/gatling.png" tile="96" />
<qg:spritesheet name="explosive" src="assets/explosive.png" tile="32" />
<qg:spritesheet name="bullet" src="assets/bullet1.png" tile="16" />
<qg:spritesheet name="red" src="assets/dino1.png" tile="48" />
<qg:spritesheet name="blue" src="assets/dino2.png" tile="48" />
<qg:spritesheet name="yellow" src="assets/dino3.png" tile="48" />
<qg:spritesheet name="green" src="assets/dino4.png" tile="48" />
<qg:input action="buy-gatling" keys="1" />
<qg:input action="buy-explosive" keys="2" />
<!-- the mouse's other buttons: the right one sells a tower, the wheel changes the one to build -->
<qg:input action="sell" keys="MouseRight, X" />
<qg:input action="other-tower" keys="WheelUp, WheelDown, Tab" />
<qg:multiplayer players="2" delay="3" start="map" />
<!-- The gatling shoots a bullet at the nearest dino in range every half
second; upgraded, twice as often. The explosive hurts every dino in
reach every second. -->
<qg:prefab name="Bullet" tag="bullet" sheet="bullet" frame="0" hitbox="12x12" ai="fly" speed="200" lifetime="90" rotate="true">
<qg:animation name="walk" frames="0, 1, 2, 3, 4, 5" fps="12" />
<qg:on-collision with="dino">
<qg:damage target="other" amount="10" />
<qg:destroy target="me" />
</qg:on-collision>
</qg:prefab>
<qg:prefab name="Gatling" tag="tower" sheet="gatling" frame="0" hitbox="48x48" ai="turret" targets="dino"
range="200" fire-prefab="Bullet" fire-every="30" rotate="true">
<qg:state name="level1" initial="true" />
<qg:state name="level2" fire-every="15" />
</qg:prefab>
<qg:prefab name="Explosive" tag="tower" sheet="explosive" frame="0" hitbox="32x32" ai="turret" targets="dino"
range="100" attack="area" damage="5" fire-every="60">
<qg:state name="level1" initial="true" />
<qg:state name="level2" fire-every="40" />
</qg:prefab>
<!-- Four dinos, as the template has them: health, speed (100 px/s per unit),
what one costs the base at the end of the road, what it pays when it dies. -->
<qg:prefab name="RedDino" tag="dino" sheet="red" frame="4" hitbox="26x26" ai="path" speed="100" health="10">
<qg:animation name="walk" frames="4, 5, 6, 7, 8, 9" fps="10" />
<qg:on-collision with="base"><q:set name="base_hp" value="{base_hp - 5}" /><qg:destroy target="me" /></qg:on-collision>
<qg:on-death><q:set name="gold" value="{gold + 10}" /></qg:on-death>
</qg:prefab>
<qg:prefab name="BlueDino" tag="dino" sheet="blue" frame="4" hitbox="26x26" ai="path" speed="200" health="5">
<qg:animation name="walk" frames="4, 5, 6, 7, 8, 9" fps="10" />
<qg:on-collision with="base"><q:set name="base_hp" value="{base_hp - 5}" /><qg:destroy target="me" /></qg:on-collision>
<qg:on-death><q:set name="gold" value="{gold + 10}" /></qg:on-death>
</qg:prefab>
<qg:prefab name="YellowDino" tag="dino" sheet="yellow" frame="4" hitbox="26x26" ai="path" speed="500" health="10">
<qg:animation name="walk" frames="4, 5, 6, 7, 8, 9" fps="10" />
<qg:on-collision with="base"><q:set name="base_hp" value="{base_hp - 1}" /><qg:destroy target="me" /></qg:on-collision>
<qg:on-death><q:set name="gold" value="{gold + 10}" /></qg:on-death>
</qg:prefab>
<qg:prefab name="GreenDino" tag="dino" sheet="green" frame="4" hitbox="26x26" ai="path" speed="1000" health="10">
<qg:animation name="walk" frames="4, 5, 6, 7, 8, 9" fps="10" />
<qg:on-collision with="base"><q:set name="base_hp" value="{base_hp - 1}" /><qg:destroy target="me" /></qg:on-collision>
<qg:on-death><q:set name="gold" value="{gold + 10}" /></qg:on-death>
</qg:prefab>
<!-- the game in Portuguese and Spanish (a web page's ?lang=pt, the command line's q-lang, or the system's language) -->
<!-- on a phone: what the keys do, under a finger, in every scene -->
<qg:touch-button action="buy-gatling" label="1" x="1050" y="590" size="64" />
<qg:touch-button action="buy-explosive" label="2" x="1120" y="590" size="64" />
<qg:language lang="pt" name="Português">
<qg:phrase text="TOWERS" as="TORRES" />
<qg:phrase text="Play alone" as="Jogar sozinho" />
<qg:phrase text="Gold" as="Ouro" />
<qg:phrase text="Base" as="Base" />
<qg:phrase text="Wave" as="Onda" />
<qg:phrase text="Dinos" as="Dinos" />
<qg:phrase text="Gold left" as="Ouro restante" />
<qg:phrase text="Reached wave" as="Onda alcançada" />
<qg:phrase text="Gatling" as="Metralhadora" />
<qg:phrase text="Explosive" as="Explosiva" />
<qg:phrase text="1: Gatling (50) 2: Explosive (70) click a cell to build, click a tower to upgrade (50)" as="1: Metralhadora (50) 2: Explosiva (70) clique numa casa para construir, numa torre para melhorar (50)" />
<qg:phrase text="The base stands. Press space to play again." as="A base resistiu. Aperte espaço para jogar de novo." />
<qg:phrase text="The base fell. Press space to try again." as="A base caiu. Aperte espaço para tentar de novo." />
<qg:phrase text="OPTIONS" as="OPÇÕES" />
<qg:phrase text="Full screen" as="Tela cheia" />
<qg:phrase text="on" as="sim" />
<qg:phrase text="off" as="não" />
<qg:phrase text="Back" as="Voltar" />
<qg:phrase text="Turret 1" as="Torre 1" />
<qg:phrase text="Turret 2" as="Torre 2" />
<qg:phrase text="Right click sells this tower for" as="Clique direito vende esta torre por" />
<qg:phrase text="Right click: sell a tower Wheel or Tab: the other tower" as="Clique direito: vender uma torre Roda ou Tab: a outra torre" />
</qg:language>
<qg:language lang="es" name="Español">
<qg:phrase text="TOWERS" as="TORRES" />
<qg:phrase text="Play alone" as="Jugar solo" />
<qg:phrase text="Gold" as="Oro" />
<qg:phrase text="Base" as="Base" />
<qg:phrase text="Wave" as="Oleada" />
<qg:phrase text="Dinos" as="Dinos" />
<qg:phrase text="Gold left" as="Oro restante" />
<qg:phrase text="Reached wave" as="Oleada alcanzada" />
<qg:phrase text="Gatling" as="Ametralladora" />
<qg:phrase text="Explosive" as="Explosiva" />
<qg:phrase text="1: Gatling (50) 2: Explosive (70) click a cell to build, click a tower to upgrade (50)" as="1: Ametralladora (50) 2: Explosiva (70) haz clic en una casilla para construir, en una torre para mejorarla (50)" />
<qg:phrase text="The base stands. Press space to play again." as="La base resiste. Pulsa espacio para jugar otra vez." />
<qg:phrase text="The base fell. Press space to try again." as="La base cayó. Pulsa espacio para intentarlo otra vez." />
<qg:phrase text="OPTIONS" as="OPCIONES" />
<qg:phrase text="Full screen" as="Pantalla completa" />
<qg:phrase text="on" as="sí" />
<qg:phrase text="off" as="no" />
<qg:phrase text="Back" as="Volver" />
<qg:phrase text="Turret 1" as="Torre 1" />
<qg:phrase text="Turret 2" as="Torre 2" />
<qg:phrase text="Right click sells this tower for" as="Clic derecho vende esta torre por" />
<qg:phrase text="Right click: sell a tower Wheel or Tab: the other tower" as="Clic derecho: vender una torre Rueda o Tab: la otra torre" />
</qg:language>
<!-- The title: alone, or two builders on two machines. -->
<qg:scene name="title" width="1152" height="648" background="#1a1a1a">
<qg:sprite sheet="map" x="578" y="372" gd:modulate="#ffffff60" />
<qg:hud position="top-center" size="48"><qg:text value="{'TOWERS'}" /></qg:hud>
<qg:lobby local="map" local-label="Play alone" size="22" options="options" />
</qg:scene>
<qg:scene name="map" width="1152" height="648" background="#1a1a1a" seed="11">
<q:set name="gold" value="100" />
<q:set name="base_hp" value="10" />
<q:set name="wave" value="0" />
<q:set name="to_spawn" value="0" type="number" />
<q:set name="kinds" value="1" type="number" />
<q:set name="enemies" value="0" type="number" />
<q:set name="choice" value="Gatling" />
<q:set name="status" value="1: Gatling (50) 2: Explosive (70) click a cell to build, click a tower to upgrade (50)" />
<qg:sprite sheet="map" x="578" y="372" />
<qg:path name="road" points="508,645; 522,498; 857,490; 870,210; 733,200; 716,380; 183,390; 186,537; 372,552; 381,86; 570,93; 562,264; -1,265" />
<qg:zone name="base" tag="base" x="-40" y="235" width="40" height="60" />
<!-- where the road is, no tower goes -->
<qg:zone name="road-1" tag="road" x="480" y="470" width="400" height="60" />
<qg:zone name="road-2" tag="road" x="840" y="180" width="60" height="330" />
<qg:zone name="road-3" tag="road" x="700" y="170" width="200" height="60" />
<qg:zone name="road-4" tag="road" x="690" y="170" width="60" height="240" />
<qg:zone name="road-5" tag="road" x="160" y="360" width="590" height="60" />
<qg:zone name="road-6" tag="road" x="160" y="360" width="60" height="210" />
<qg:zone name="road-7" tag="road" x="160" y="520" width="240" height="60" />
<qg:zone name="road-8" tag="road" x="350" y="60" width="60" height="520" />
<qg:zone name="road-9" tag="road" x="350" y="60" width="250" height="60" />
<qg:zone name="road-10" tag="road" x="540" y="60" width="60" height="230" />
<qg:zone name="road-11" tag="road" x="0" y="235" width="590" height="60" />
<qg:zone name="road-12" tag="road" x="480" y="480" width="60" height="170" />
<qg:cursor player="1" grid="48" sheet="explosive" frame="0" gd:modulate="#ffffff80" />
<qg:cursor player="2" grid="48" sheet="explosive" frame="0" gd:modulate="#80c0ff80" />
<qg:on-input action="buy-gatling"><q:set name="choice" value="Gatling" /></qg:on-input>
<qg:on-input action="buy-explosive"><q:set name="choice" value="Explosive" /></qg:on-input>
<qg:on-input action="other-tower">
<q:set name="choice" value="{'Explosive' if choice == 'Gatling' else 'Gatling'}" />
</qg:on-input>
<!-- A click: on a tower, its upgrade; on free ground, the chosen tower, if the gold is there. -->
<qg:on-select>
<q:if condition="{other != null and other.tag == 'tower' and gold >= 50 and other.state == 'level1'}">
<q:set name="gold" value="{gold - 50}" />
<qg:become target="other" state="level2" />
</q:if>
<q:if condition="{other == null and thing_at('road', cursor.x, cursor.y) == null}">
<q:if condition="{choice == 'Gatling' and gold >= 50}">
<q:set name="gold" value="{gold - 50}" />
<qg:spawn prefab="Gatling" at="cursor" />
</q:if>
<q:if condition="{choice == 'Explosive' and gold >= 70}">
<q:set name="gold" value="{gold - 70}" />
<qg:spawn prefab="Explosive" at="cursor" />
</q:if>
</q:if>
</qg:on-select>
<!-- A right click on a tower sells it: 25 gold, 50 upgraded. -->
<qg:on-select action="sell">
<q:if condition="{other != null and other.tag == 'tower'}">
<q:set name="gold" value="{gold + (50 if other.state == 'level2' else 25)}" />
<qg:destroy target="other" />
</q:if>
</qg:on-select>
<!-- Waves: ten of them, each 5 + 3 × wave dinos, a new kind every two
waves; the next starts five seconds after the last dino of this one
is gone. A dino every 0.2 s while the wave has some to send. -->
<qg:timer every="300">
<q:if condition="{to_spawn == 0 and enemies == 0 and base_hp > 0}">
<q:if condition="{wave >= 10}">
<qg:goto-scene name="won" />
<q:else>
<q:set name="wave" value="{wave + 1}" />
<q:set name="to_spawn" value="{5 + 3 * wave}" />
<q:set name="kinds" value="{min(4, 1 + wave // 2)}" />
</q:else>
</q:if>
</q:if>
</qg:timer>
<qg:timer every="12">
<q:if condition="{to_spawn > 0}">
<q:set name="to_spawn" value="{to_spawn - 1}" />
<q:set name="kind" value="{int(random(0, kinds))}" />
<q:if condition="{kind == 0}"><qg:spawn prefab="RedDino" at="path" path="road" /></q:if>
<q:if condition="{kind == 1}"><qg:spawn prefab="BlueDino" at="path" path="road" /></q:if>
<q:if condition="{kind == 2}"><qg:spawn prefab="YellowDino" at="path" path="road" /></q:if>
<q:if condition="{kind >= 3}"><qg:spawn prefab="GreenDino" at="path" path="road" /></q:if>
</q:if>
</qg:timer>
<q:set name="kind" value="0" type="number" />
<qg:timer every="1">
<q:set name="enemies" value="{count('dino')}" />
<q:if condition="{base_hp <= 0}"><qg:goto-scene name="lost" /></q:if>
</qg:timer>
<qg:hud position="top-left" size="20">
<qg:counter bind="gold" label="Gold" />
<qg:counter bind="base_hp" label="Base" />
<qg:counter bind="wave" label="Wave" />
<qg:counter bind="enemies" label="Dinos" />
<qg:text bind="choice" />
<qg:text bind="status" />
<!-- the mouse over a tower: what it sells for (this machine's player's cursor) -->
<qg:text value="{(tr('Right click sells this tower for') + ' ' + str(50 if hovered(max(net_player(), 1)).state == 'level2' else 25)) if hovered(max(net_player(), 1)) != null and hovered(max(net_player(), 1)).tag == 'tower' else tr('Right click: sell a tower Wheel or Tab: the other tower')}" />
</qg:hud>
</qg:scene>
<qg:scene name="won" width="1152" height="648" background="#1a3a1a">
<q:set name="message" value="The base stands. Press space to play again." />
<qg:hud position="center" size="40"><qg:text bind="message" /><qg:counter bind="gold" label="Gold left" /></qg:hud>
<qg:on-input action="jump"><qg:goto-scene name="map" /></qg:on-input>
</qg:scene>
<qg:scene name="lost" width="1152" height="648" background="#3a1a1a">
<q:set name="message" value="The base fell. Press space to try again." />
<qg:hud position="center" size="40"><qg:text bind="message" /><qg:counter bind="wave" label="Reached wave" /></qg:hud>
<qg:on-input action="jump"><qg:goto-scene name="map" /></qg:on-input>
</qg:scene>
<!-- ===== Options: how loud, the full screen, the keys; kept between runs ===== -->
<qg:scene name="options" width="1152" height="648" background="#1a1a1a">
<qg:hud position="top-center" size="48"><qg:text value="{tr('OPTIONS')}" /></qg:hud>
<qg:menu position="center" size="22">
<qg:rebind label="Turret 1" action="buy-gatling" />
<qg:rebind label="Turret 2" action="buy-explosive" />
<qg:button label="{tr('Full screen') + ': ' + (tr('on') if fullscreen() else tr('off'))}"><qg:fullscreen /></qg:button>
<qg:button label="Back"><qg:goto-scene name="title" /></qg:button>
</qg:menu>
</qg:scene>
</q:application>Arena
projects/arena/arena.q — a one-on-one fighting game: moves with frame data, blocking, hit stun, health bars, rounds on a clock; two players over the network, which is the fighting games' own netcode (projects/arena/README.md).
<q:application id="arena" type="game">
<!-- Arena: a one-on-one fighting game in the shape of the arcade classics —
two fighters, walk, jump, crouch, block, a punch and a kick with frame
data, best of three rounds on a clock — written to see what a fighting
game asks of the language, and played by two over the network in
lockstep, which is the fighting games' own netcode. The sprites are
placeholders drawn for the repository (assets/LICENSE.md). -->
<q:set name="wins_1" value="0" type="number" />
<q:set name="wins_2" value="0" type="number" />
<qg:spritesheet name="red" src="assets/red.png" tile="64x96" />
<qg:spritesheet name="blue" src="assets/blue.png" tile="64x96" />
<qg:spritesheet name="stage" src="assets/stage.png" tile="640x360" />
<!-- Player 1: A/D, W to jump, S to crouch, J punch, K kick. Player 2 on the
same keyboard: the arrows, . punch, / kick. Over the network each peer
is one player on player 1's keys. Down, forward, punch: the blast. -->
<qg:input action="jump" keys="W, Space, JoyA" />
<qg:input action="punch" keys="J, JoyX" />
<qg:input action="kick" keys="K, JoyB" />
<qg:input player="2" action="left" keys="Left" />
<qg:input player="2" action="right" keys="Right" />
<qg:input player="2" action="down" keys="Down" />
<qg:input player="2" action="jump" keys="Up" />
<qg:input player="2" action="punch" keys="Period" />
<qg:input player="2" action="kick" keys="Slash" />
<qg:multiplayer players="2" delay="2" rollback="8" start="fight" />
<!-- the game in Portuguese and Spanish (a web page's ?lang=pt, the command line's q-lang, or the system's language) -->
<!-- on a phone: what the keys do, under a finger, in every scene -->
<qg:touch-stick x="70" y="290" size="96" />
<qg:touch-button action="punch" label="P" x="520" y="300" size="64" />
<qg:touch-button action="kick" label="K" x="595" y="250" size="64" />
<qg:touch-button action="jump" label="J" x="595" y="325" size="48" />
<qg:language lang="pt" name="Português">
<qg:phrase text="ARENA" as="ARENA" />
<qg:phrase text="Two players, one keyboard" as="Dois jogadores, um teclado" />
<qg:phrase text="ROUND 1 - FIGHT" as="ROUND 1 - LUTEM" />
<qg:phrase text="ROUND 2 - FIGHT" as="ROUND 2 - LUTEM" />
<qg:phrase text="ROUND 3 - FIGHT" as="ROUND 3 - LUTEM" />
<qg:phrase text="PLAYER 1 WINS THE ROUND" as="JOGADOR 1 VENCE O ROUND" />
<qg:phrase text="PLAYER 2 WINS THE ROUND" as="JOGADOR 2 VENCE O ROUND" />
<qg:phrase text="TIME - PLAYER 1 TAKES THE ROUND" as="TEMPO - O ROUND É DO JOGADOR 1" />
<qg:phrase text="TIME - PLAYER 2 TAKES THE ROUND" as="TEMPO - O ROUND É DO JOGADOR 2" />
<qg:phrase text="PLAYER 1 WINS - press jump" as="JOGADOR 1 VENCE - aperte pular" />
<qg:phrase text="PLAYER 2 WINS - press jump" as="JOGADOR 2 VENCE - aperte pular" />
<qg:phrase text="P1 rounds" as="Rounds J1" />
<qg:phrase text="P2 rounds" as="Rounds J2" />
<qg:phrase text="OPTIONS" as="OPÇÕES" />
<qg:phrase text="Full screen" as="Tela cheia" />
<qg:phrase text="on" as="sim" />
<qg:phrase text="off" as="não" />
<qg:phrase text="Back" as="Voltar" />
<qg:phrase text="P1 punch" as="J1 soco" />
<qg:phrase text="P2 punch" as="J2 soco" />
<qg:phrase text="P1 kick" as="J1 chute" />
<qg:phrase text="P2 kick" as="J2 chute" />
<qg:phrase text="P1 jump" as="J1 pulo" />
<qg:phrase text="P2 jump" as="J2 pulo" />
<qg:phrase text="P1 crouch" as="J1 agachar" />
<qg:phrase text="P2 crouch" as="J2 agachar" />
</qg:language>
<qg:language lang="es" name="Español">
<qg:phrase text="ARENA" as="ARENA" />
<qg:phrase text="Two players, one keyboard" as="Dos jugadores, un teclado" />
<qg:phrase text="ROUND 1 - FIGHT" as="ASALTO 1 - A PELEAR" />
<qg:phrase text="ROUND 2 - FIGHT" as="ASALTO 2 - A PELEAR" />
<qg:phrase text="ROUND 3 - FIGHT" as="ASALTO 3 - A PELEAR" />
<qg:phrase text="PLAYER 1 WINS THE ROUND" as="EL JUGADOR 1 GANA EL ASALTO" />
<qg:phrase text="PLAYER 2 WINS THE ROUND" as="EL JUGADOR 2 GANA EL ASALTO" />
<qg:phrase text="TIME - PLAYER 1 TAKES THE ROUND" as="TIEMPO - EL ASALTO ES DEL JUGADOR 1" />
<qg:phrase text="TIME - PLAYER 2 TAKES THE ROUND" as="TIEMPO - EL ASALTO ES DEL JUGADOR 2" />
<qg:phrase text="PLAYER 1 WINS - press jump" as="GANA EL JUGADOR 1 - pulsa saltar" />
<qg:phrase text="PLAYER 2 WINS - press jump" as="GANA EL JUGADOR 2 - pulsa saltar" />
<qg:phrase text="P1 rounds" as="Asaltos J1" />
<qg:phrase text="P2 rounds" as="Asaltos J2" />
<qg:phrase text="OPTIONS" as="OPCIONES" />
<qg:phrase text="Full screen" as="Pantalla completa" />
<qg:phrase text="on" as="sí" />
<qg:phrase text="off" as="no" />
<qg:phrase text="Back" as="Volver" />
<qg:phrase text="P1 punch" as="J1 puñetazo" />
<qg:phrase text="P2 punch" as="J2 puñetazo" />
<qg:phrase text="P1 kick" as="J1 patada" />
<qg:phrase text="P2 kick" as="J2 patada" />
<qg:phrase text="P1 jump" as="J1 salto" />
<qg:phrase text="P2 jump" as="J2 salto" />
<qg:phrase text="P1 crouch" as="J1 agacharse" />
<qg:phrase text="P2 crouch" as="J2 agacharse" />
</qg:language>
<!-- The title: two on one keyboard, or one on each machine. -->
<qg:scene name="title" width="640" height="360" background="#201820">
<qg:sprite sheet="stage" x="320" y="180" />
<qg:hud position="top-center" size="40"><qg:text value="{'ARENA'}" /></qg:hud>
<qg:lobby local="fight" local-label="Two players, one keyboard" size="18" options="options" />
</qg:scene>
<qg:scene name="fight" width="640" height="360" background="#201820" seed="1">
<q:set name="wins_1" value="0" />
<q:set name="wins_2" value="0" />
<q:set name="round" value="1" type="number" />
<q:set name="clock" value="99" type="number" />
<q:set name="message" value="ROUND 1 - FIGHT" />
<q:set name="reset_in" value="0" type="number" />
<qg:sprite sheet="stage" x="320" y="180" />
<qg:character id="p1" controller="fighter" player="1" sheet="red" frame="0" x="200" y="252" hitbox="40x90"
speed="140" jump-height="100" health="100" facing="right">
<qg:animation name="idle" frames="0" />
<qg:animation name="walk" frames="1, 2" fps="8" />
<qg:animation name="jump" frames="3" />
<qg:animation name="crouch" frames="4" />
<qg:animation name="block" frames="5" />
<qg:animation name="hit" frames="12" />
<qg:animation name="ko" frames="13" />
<qg:move name="punch" action="punch" frames="6, 7, 8" fps="15" active="1" reach="36x20" at="40,-10" damage="8" stun="14" push="40" />
<qg:move name="kick" action="kick" frames="9, 10, 11" fps="10" active="1" reach="44x20" at="44,8" damage="12" stun="20" push="70" />
<qg:on-ko>
<q:set name="wins_2" value="{wins_2 + 1}" />
<q:set name="message" value="PLAYER 2 WINS THE ROUND" />
<q:set name="reset_in" value="120" />
<qg:rumble player="1" strength="1" seconds="0.6" />
</qg:on-ko>
</qg:character>
<qg:character id="p2" controller="fighter" player="2" sheet="blue" frame="0" x="440" y="252" hitbox="40x90"
speed="140" jump-height="100" health="100" facing="left">
<qg:animation name="idle" frames="0" />
<qg:animation name="walk" frames="1, 2" fps="8" />
<qg:animation name="jump" frames="3" />
<qg:animation name="crouch" frames="4" />
<qg:animation name="block" frames="5" />
<qg:animation name="hit" frames="12" />
<qg:animation name="ko" frames="13" />
<qg:move name="punch" action="punch" frames="6, 7, 8" fps="15" active="1" reach="36x20" at="40,-10" damage="8" stun="14" push="40" />
<qg:move name="kick" action="kick" frames="9, 10, 11" fps="10" active="1" reach="44x20" at="44,8" damage="12" stun="20" push="70" />
<qg:on-ko>
<q:set name="wins_1" value="{wins_1 + 1}" />
<q:set name="message" value="PLAYER 1 WINS THE ROUND" />
<q:set name="reset_in" value="120" />
<qg:rumble player="2" strength="1" seconds="0.6" />
</qg:on-ko>
</qg:character>
<!-- The special move: down, then forward (the way the fighter faces), then
punch, all within half a second, sends a blast at the other fighter
when they are near enough. It hits at once (the rollback takes no
flying prefab), is blocked by holding away like a move, and is felt
on the joypad of the one it hits. -->
<qg:on-input sequence="down, forward, punch" within="30" player="1">
<q:call function="blast" args="p1, p2, 2" />
</qg:on-input>
<qg:on-input sequence="down, forward, punch" within="30" player="2">
<q:call function="blast" args="p2, p1, 1" />
</qg:on-input>
<q:function name="blast" params="attacker, target, hurt">
<q:if condition="{abs(attacker.x - target.x) < 180 and attacker.health > 0 and target.health > 0 and reset_in == 0}">
<qg:beam from="attacker" to="target" seconds="0.15" color="#ffe080" width="4" />
<qg:damage target="target" amount="12" />
<qg:rumble player="{hurt}" strength="0.7" seconds="0.3" />
</q:if>
</q:function>
<!-- The clock: a second a second; at zero the healthier fighter takes the round. -->
<qg:timer every="60">
<q:if condition="{clock > 0 and reset_in == 0}">
<q:set name="clock" value="{clock - 1}" />
<q:if condition="{clock == 0}">
<q:if condition="{p1.health >= p2.health}">
<q:set name="wins_1" value="{wins_1 + 1}" />
<q:set name="message" value="TIME - PLAYER 1 TAKES THE ROUND" />
<q:else>
<q:set name="wins_2" value="{wins_2 + 1}" />
<q:set name="message" value="TIME - PLAYER 2 TAKES THE ROUND" />
</q:else>
</q:if>
<q:set name="reset_in" value="120" />
</q:if>
</q:if>
</qg:timer>
<!-- Between rounds: two seconds of the message, then both back at their marks,
or the match's end at two rounds. -->
<qg:timer every="1">
<q:if condition="{reset_in > 0}">
<q:set name="reset_in" value="{reset_in - 1}" />
<q:if condition="{reset_in == 0}">
<q:if condition="{wins_1 >= 2 or wins_2 >= 2}">
<qg:goto-scene name="result" />
</q:if>
<q:set name="round" value="{round + 1}" />
<q:set name="clock" value="99" />
<q:set name="message" value="{'ROUND ' + str(round) + ' - FIGHT'}" />
<q:call function="reset_fighters" />
</q:if>
</q:if>
</qg:timer>
<q:function name="reset_fighters">
<qg:respawn target="p1" />
<qg:respawn target="p2" />
</q:function>
<qg:hud position="top-left" size="12">
<qg:bar bind="p1.health" max="100" width="200" height="12" color="#e04040" />
<qg:counter bind="wins_1" label="P1 rounds" />
</qg:hud>
<qg:hud position="top-right" size="12">
<qg:bar bind="p2.health" max="100" width="200" height="12" color="#4060e0" />
<qg:counter bind="wins_2" label="P2 rounds" />
</qg:hud>
<qg:hud position="top-center" size="16">
<qg:counter bind="clock" />
<qg:text bind="message" />
</qg:hud>
</qg:scene>
<qg:scene name="result" width="640" height="360" background="#101018">
<q:set name="message" value="{'PLAYER ' + ('1' if wins_1 > wins_2 else '2') + ' WINS - press jump'}" />
<qg:hud position="center" size="24"><qg:text bind="message" /></qg:hud>
<qg:on-input action="jump"><qg:goto-scene name="fight" /></qg:on-input>
</qg:scene>
<!-- ===== Options: how loud, the full screen, the keys; kept between runs ===== -->
<qg:scene name="options" width="640" height="360" background="#201820">
<qg:hud position="top-center" size="32"><qg:text value="{tr('OPTIONS')}" /></qg:hud>
<qg:menu position="center" size="16" columns="2" rows="4">
<qg:rebind label="P1 punch" action="punch" />
<qg:rebind label="P2 punch" action="punch" player="2" />
<qg:rebind label="P1 kick" action="kick" />
<qg:rebind label="P2 kick" action="kick" player="2" />
<qg:rebind label="P1 jump" action="jump" />
<qg:rebind label="P2 jump" action="jump" player="2" />
<qg:rebind label="P1 crouch" action="down" />
<qg:rebind label="P2 crouch" action="down" player="2" />
<qg:button label="{tr('Full screen') + ': ' + (tr('on') if fullscreen() else tr('off'))}"><qg:fullscreen /></qg:button>
<qg:button label="Back"><qg:goto-scene name="title" /></qg:button>
</qg:menu>
</qg:scene>
</q:application>Chess
projects/chess/chess.q — chess: the rules — castling, en passant, promotion, check, mate, stalemate — in q:functions over an array of 64 squares, the pieces mirroring it; white and black over the network (projects/chess/README.md).
<q:application id="chess" type="game">
<!-- Chess: the rules of github.com/Prashanna135/chess (MIT) — legal moves,
castling, en passant, promotion, check, checkmate and stalemate —
written in the game language's own statements and expressions over an
array of 64 squares; the pieces on the screen mirror it. White is
player 1, black player 2: on one machine both click the same board;
over the network each machine is one colour, in lockstep. README.md
maps the original to this file. -->
<qg:tileset name="board" src="assets/board.png" tile="62" />
<qg:spritesheet name="pieces" src="assets/pieces.png" tile="60" />
<qg:multiplayer players="2" delay="2" start="game" />
<qg:prefab name="wK" tag="piece" sheet="pieces" frame="0" hitbox="60x60" />
<qg:prefab name="wQ" tag="piece" sheet="pieces" frame="1" hitbox="60x60" />
<qg:prefab name="wR" tag="piece" sheet="pieces" frame="2" hitbox="60x60" />
<qg:prefab name="wB" tag="piece" sheet="pieces" frame="3" hitbox="60x60" />
<qg:prefab name="wN" tag="piece" sheet="pieces" frame="4" hitbox="60x60" />
<qg:prefab name="wP" tag="piece" sheet="pieces" frame="5" hitbox="60x60" />
<qg:prefab name="bK" tag="piece" sheet="pieces" frame="6" hitbox="60x60" />
<qg:prefab name="bQ" tag="piece" sheet="pieces" frame="7" hitbox="60x60" />
<qg:prefab name="bR" tag="piece" sheet="pieces" frame="8" hitbox="60x60" />
<qg:prefab name="bB" tag="piece" sheet="pieces" frame="9" hitbox="60x60" />
<qg:prefab name="bN" tag="piece" sheet="pieces" frame="10" hitbox="60x60" />
<qg:prefab name="bP" tag="piece" sheet="pieces" frame="11" hitbox="60x60" />
<!-- What the board shows besides the pieces (assets/board.png): the picked square, a dot on each
square the picked piece may go to (a ring round a piece it may take), the last move, a king in check. -->
<qg:prefab name="Marker" tag="marker" sheet="board" frame="2" hitbox="4x4" />
<qg:prefab name="Dot" tag="dot" sheet="board" frame="3" hitbox="4x4" gd:z_index="1" />
<qg:prefab name="Ring" tag="dot" sheet="board" frame="4" hitbox="4x4" gd:z_index="1" />
<qg:prefab name="Last" tag="last" sheet="board" frame="6" hitbox="4x4" />
<qg:prefab name="Check" tag="check" sheet="board" frame="7" hitbox="4x4" />
<!-- the game in Portuguese and Spanish (a web page's ?lang=pt, the command line's q-lang, or the system's language) -->
<qg:language lang="pt" name="Português">
<qg:phrase text="CHESS" as="XADREZ" />
<qg:phrase text="Two players, one board" as="Dois jogadores, um tabuleiro" />
<qg:phrase text="White to move - click a piece, then where it goes" as="Brancas jogam - clique numa peça, depois aonde ela vai" />
<qg:phrase text="White to move" as="Brancas jogam" />
<qg:phrase text="Black to move" as="Pretas jogam" />
<qg:phrase text="White to move - check" as="Brancas jogam - xeque" />
<qg:phrase text="Black to move - check" as="Pretas jogam - xeque" />
<qg:phrase text="White to move - that piece cannot move" as="Brancas jogam - essa peça não pode se mover" />
<qg:phrase text="Black to move - that piece cannot move" as="Pretas jogam - essa peça não pode se mover" />
<qg:phrase text="White to move - that piece cannot move - check" as="Brancas jogam - essa peça não pode se mover - xeque" />
<qg:phrase text="Black to move - that piece cannot move - check" as="Pretas jogam - essa peça não pode se mover - xeque" />
<qg:phrase text="Checkmate - White wins" as="Xeque-mate - as brancas vencem" />
<qg:phrase text="Checkmate - Black wins" as="Xeque-mate - as pretas vencem" />
<qg:phrase text="Stalemate" as="Afogamento - empate" />
<qg:phrase text="OPTIONS" as="OPÇÕES" />
<qg:phrase text="Full screen" as="Tela cheia" />
<qg:phrase text="on" as="sim" />
<qg:phrase text="off" as="não" />
<qg:phrase text="Back" as="Voltar" />
</qg:language>
<qg:language lang="es" name="Español">
<qg:phrase text="CHESS" as="AJEDREZ" />
<qg:phrase text="Two players, one board" as="Dos jugadores, un tablero" />
<qg:phrase text="White to move - click a piece, then where it goes" as="Juegan blancas - haz clic en una pieza y luego adónde va" />
<qg:phrase text="White to move" as="Juegan blancas" />
<qg:phrase text="Black to move" as="Juegan negras" />
<qg:phrase text="White to move - check" as="Juegan blancas - jaque" />
<qg:phrase text="Black to move - check" as="Juegan negras - jaque" />
<qg:phrase text="White to move - that piece cannot move" as="Juegan blancas - esa pieza no puede moverse" />
<qg:phrase text="Black to move - that piece cannot move" as="Juegan negras - esa pieza no puede moverse" />
<qg:phrase text="White to move - that piece cannot move - check" as="Juegan blancas - esa pieza no puede moverse - jaque" />
<qg:phrase text="Black to move - that piece cannot move - check" as="Juegan negras - esa pieza no puede moverse - jaque" />
<qg:phrase text="Checkmate - White wins" as="Jaque mate - ganan las blancas" />
<qg:phrase text="Checkmate - Black wins" as="Jaque mate - ganan las negras" />
<qg:phrase text="Stalemate" as="Rey ahogado - tablas" />
<qg:phrase text="OPTIONS" as="OPCIONES" />
<qg:phrase text="Full screen" as="Pantalla completa" />
<qg:phrase text="on" as="sí" />
<qg:phrase text="off" as="no" />
<qg:phrase text="Back" as="Volver" />
</qg:language>
<!-- The title: both colours on one board, or white and black on two machines. -->
<qg:scene name="title" width="496" height="540" background="#2a2520">
<qg:hud position="top-center" size="40"><qg:text value="{'CHESS'}" /></qg:hud>
<qg:lobby local="game" local-label="Two players, one board" size="18" options="options" />
</qg:scene>
<qg:scene name="game" width="496" height="540" background="#2a2520">
<!-- row 0 is the eighth rank (black's home), row 7 the first; a square is board[row * 8 + col] -->
<q:set name="board" type="array" value="{['bR','bN','bB','bQ','bK','bB','bN','bR', 'bP','bP','bP','bP','bP','bP','bP','bP', '','','','','','','','', '','','','','','','','', '','','','','','','','', '','','','','','','','', 'wP','wP','wP','wP','wP','wP','wP','wP', 'wR','wN','wB','wQ','wK','wB','wN','wR']}" />
<q:set name="turn" value="w" />
<q:set name="sel_r" value="-1" type="number" />
<q:set name="sel_c" value="-1" type="number" />
<q:set name="legal" type="array" value="{[]}" />
<q:set name="ep_col" value="-1" type="number" />
<q:set name="moved" type="array" value="{[]}" />
<q:set name="status" value="White to move - click a piece, then where it goes" />
<q:set name="finished" value="false" type="boolean" />
<qg:tilemap tileset="board">
1,2,1,2,1,2,1,2
2,1,2,1,2,1,2,1
1,2,1,2,1,2,1,2
2,1,2,1,2,1,2,1
1,2,1,2,1,2,1,2
2,1,2,1,2,1,2,1
1,2,1,2,1,2,1,2
2,1,2,1,2,1,2,1
</qg:tilemap>
<!-- the pointer: the mouse, or the arrows (Enter picks and puts); drawn as a frame round its square -->
<qg:cursor player="1" grid="62" sheet="board" frame="5" />
<qg:cursor player="2" grid="62" />
<!-- the marks, kept off the board until they mean something; under the pieces -->
<qg:instance prefab="Last" name="last_from" x="-100" y="-100" />
<qg:instance prefab="Last" name="last_to" x="-100" y="-100" />
<qg:instance prefab="Check" name="check_mark" x="-100" y="-100" />
<qg:instance prefab="Marker" name="mark" x="-100" y="-100" />
<qg:instance prefab="bR" x="31" y="31" /><qg:instance prefab="bN" x="93" y="31" /><qg:instance prefab="bB" x="155" y="31" /><qg:instance prefab="bQ" x="217" y="31" />
<qg:instance prefab="bK" x="279" y="31" /><qg:instance prefab="bB" x="341" y="31" /><qg:instance prefab="bN" x="403" y="31" /><qg:instance prefab="bR" x="465" y="31" />
<qg:instance prefab="bP" x="31" y="93" /><qg:instance prefab="bP" x="93" y="93" /><qg:instance prefab="bP" x="155" y="93" /><qg:instance prefab="bP" x="217" y="93" />
<qg:instance prefab="bP" x="279" y="93" /><qg:instance prefab="bP" x="341" y="93" /><qg:instance prefab="bP" x="403" y="93" /><qg:instance prefab="bP" x="465" y="93" />
<qg:instance prefab="wP" x="31" y="403" /><qg:instance prefab="wP" x="93" y="403" /><qg:instance prefab="wP" x="155" y="403" /><qg:instance prefab="wP" x="217" y="403" />
<qg:instance prefab="wP" x="279" y="403" /><qg:instance prefab="wP" x="341" y="403" /><qg:instance prefab="wP" x="403" y="403" /><qg:instance prefab="wP" x="465" y="403" />
<qg:instance prefab="wR" x="31" y="465" /><qg:instance prefab="wN" x="93" y="465" /><qg:instance prefab="wB" x="155" y="465" /><qg:instance prefab="wQ" x="217" y="465" />
<qg:instance prefab="wK" x="279" y="465" /><qg:instance prefab="wB" x="341" y="465" /><qg:instance prefab="wN" x="403" y="465" /><qg:instance prefab="wR" x="465" y="465" />
<qg:hud position="bottom-center" size="14"><qg:text bind="status" /></qg:hud>
<!-- what is on a square ("x" off the board), whose it is -->
<q:function name="at" params="r, c">
<q:if condition="{r < 0 or r > 7 or c < 0 or c > 7}"><q:return value="{'x'}" /></q:if>
<q:return value="{board[r * 8 + c]}" />
</q:function>
<q:function name="own" params="p, color">
<q:return value="{p != '' and p != 'x' and p[0:1] == color}" />
</q:function>
<q:function name="enemy" params="p, color">
<q:return value="{p != '' and p != 'x' and p[0:1] != color}" />
</q:function>
<q:function name="other_color" params="color">
<q:return value="{'b' if color == 'w' else 'w'}" />
</q:function>
<!-- where a piece may go by its own rule (attacks only, for a pawn, when atk) -->
<q:function name="pseudo" params="r, c, atk">
<q:set name="result" value="{[]}" />
<q:set name="p" value="{at(r, c)}" />
<q:set name="color" value="{p[0:1]}" />
<q:set name="kind" value="{p[1:2]}" />
<q:if condition="{kind == 'P'}">
<q:set name="dir" value="{-1 if color == 'w' else 1}" />
<q:if condition="{not atk and at(r + dir, c) == ''}">
<q:set name="result" value="{result + [[r + dir, c]]}" />
<q:if condition="{(r == 6 and color == 'w' or r == 1 and color == 'b') and at(r + 2 * dir, c) == ''}">
<q:set name="result" value="{result + [[r + 2 * dir, c]]}" />
</q:if>
</q:if>
<q:loop var="dc" items="{[-1, 1]}">
<q:if condition="{atk or enemy(at(r + dir, c + dc), color)}">
<q:if condition="{at(r + dir, c + dc) != 'x'}">
<q:set name="result" value="{result + [[r + dir, c + dc]]}" />
</q:if>
</q:if>
<q:if condition="{not atk and ep_col == c + dc and r == (3 if color == 'w' else 4)}">
<q:set name="result" value="{result + [[r + dir, c + dc]]}" />
</q:if>
</q:loop>
</q:if>
<q:if condition="{kind == 'N' or kind == 'K'}">
<q:set name="steps" value="{[[1, 2], [2, 1], [2, -1], [1, -2], [-1, -2], [-2, -1], [-2, 1], [-1, 2]] if kind == 'N' else [[1, 0], [1, 1], [0, 1], [-1, 1], [-1, 0], [-1, -1], [0, -1], [1, -1]]}" />
<q:loop var="s" items="{steps}">
<q:set name="q" value="{at(r + s[0], c + s[1])}" />
<q:if condition="{q != 'x' and not own(q, color)}">
<q:set name="result" value="{result + [[r + s[0], c + s[1]]]}" />
</q:if>
</q:loop>
</q:if>
<q:if condition="{kind == 'R' or kind == 'B' or kind == 'Q'}">
<q:set name="dirs" value="{[]}" />
<q:if condition="{kind != 'B'}"><q:set name="dirs" value="{dirs + [[1, 0], [-1, 0], [0, 1], [0, -1]]}" /></q:if>
<q:if condition="{kind != 'R'}"><q:set name="dirs" value="{dirs + [[1, 1], [1, -1], [-1, 1], [-1, -1]]}" /></q:if>
<q:loop var="d" items="{dirs}">
<q:set name="blocked" value="false" />
<q:loop var="n" from="1" to="7">
<q:if condition="{not blocked}">
<q:set name="q" value="{at(r + d[0] * n, c + d[1] * n)}" />
<q:if condition="{q == 'x' or own(q, color)}">
<q:set name="blocked" value="true" />
<q:else>
<q:set name="result" value="{result + [[r + d[0] * n, c + d[1] * n]]}" />
<q:if condition="{q != ''}"><q:set name="blocked" value="true" /></q:if>
</q:else>
</q:if>
</q:if>
</q:loop>
</q:loop>
</q:if>
<q:return value="{result}" />
</q:function>
<q:function name="attacked" params="r, c, by">
<q:loop var="i" from="0" to="63">
<q:if condition="{own(board[i], by)}">
<q:if condition="{[r, c] in pseudo(i // 8, i % 8, true)}"><q:return value="{true}" /></q:if>
</q:if>
</q:loop>
<q:return value="{false}" />
</q:function>
<q:function name="in_check" params="color">
<q:loop var="i" from="0" to="63">
<q:if condition="{board[i] == color + 'K'}">
<q:return value="{attacked(i // 8, i % 8, other_color(color))}" />
</q:if>
</q:loop>
<q:return value="{false}" />
</q:function>
<!-- the board after a move: the rook of a castling, the pawn of an en passant, a promotion -->
<q:function name="apply" params="r1, c1, r2, c2">
<q:set name="p" value="{board[r1 * 8 + c1]}" />
<q:if condition="{p[1:2] == 'P' and c1 != c2 and board[r2 * 8 + c2] == ''}">
<q:set name="board" index="{r1 * 8 + c2}" value="" />
</q:if>
<q:if condition="{p[1:2] == 'K' and c2 - c1 == 2}">
<q:set name="board" index="{r1 * 8 + 5}" value="{board[r1 * 8 + 7]}" />
<q:set name="board" index="{r1 * 8 + 7}" value="" />
</q:if>
<q:if condition="{p[1:2] == 'K' and c1 - c2 == 2}">
<q:set name="board" index="{r1 * 8 + 3}" value="{board[r1 * 8]}" />
<q:set name="board" index="{r1 * 8}" value="" />
</q:if>
<q:set name="board" index="{r1 * 8 + c1}" value="" />
<q:set name="board" index="{r2 * 8 + c2}" value="{p[0:1] + 'Q' if p[1:2] == 'P' and (r2 == 0 or r2 == 7) else p}" />
</q:function>
<!-- the moves that leave the king safe, castling included -->
<q:function name="legal_moves" params="r, c">
<q:set name="p" value="{at(r, c)}" />
<q:set name="color" value="{p[0:1]}" />
<q:set name="candidates" value="{pseudo(r, c, false)}" />
<q:if condition="{p[1:2] == 'K' and not (color + 'K') in moved and not in_check(color)}">
<q:if condition="{not (color + 'Rh') in moved and at(r, 5) == '' and at(r, 6) == '' and at(r, 7) == color + 'R' and not attacked(r, 5, other_color(color)) and not attacked(r, 6, other_color(color))}">
<q:set name="candidates" value="{candidates + [[r, 6]]}" />
</q:if>
<q:if condition="{not (color + 'Ra') in moved and at(r, 1) == '' and at(r, 2) == '' and at(r, 3) == '' and at(r, 0) == color + 'R' and not attacked(r, 3, other_color(color)) and not attacked(r, 2, other_color(color))}">
<q:set name="candidates" value="{candidates + [[r, 2]]}" />
</q:if>
</q:if>
<q:set name="result" value="{[]}" />
<q:loop var="m" items="{candidates}">
<q:set name="saved" value="{board[:]}" />
<q:call function="apply" args="r, c, m[0], m[1]" />
<q:set name="ok" value="{not in_check(color)}" />
<q:set name="board" value="{saved}" />
<q:if condition="{ok}"><q:set name="result" value="{result + [m]}" /></q:if>
</q:loop>
<q:return value="{result}" />
</q:function>
<q:function name="any_legal" params="color">
<q:loop var="i" from="0" to="63">
<q:if condition="{own(board[i], color)}">
<q:if condition="{len(legal_moves(i // 8, i % 8)) > 0}"><q:return value="{true}" /></q:if>
</q:if>
</q:loop>
<q:return value="{false}" />
</q:function>
<q:function name="clear_mark">
<qg:put target="mark" x="-100" y="-100" />
<q:loop var="m" items="{legal}">
<q:set name="d" value="{thing_at('dot', m[1] * 62 + 31, m[0] * 62 + 31)}" />
<q:if condition="{d != null}"><qg:destroy target="d" /></q:if>
</q:loop>
<q:set name="sel_r" value="-1" />
<q:set name="sel_c" value="-1" />
<q:set name="legal" value="{[]}" />
</q:function>
<!-- the red glow under the king of the side to move, when it is in check -->
<q:function name="show_check">
<qg:put target="check_mark" x="-100" y="-100" />
<q:if condition="{in_check(turn)}">
<q:loop var="i" from="0" to="63">
<q:if condition="{board[i] == turn + 'K'}">
<qg:put target="check_mark" x="{(i % 8) * 62 + 31}" y="{(i // 8) * 62 + 31}" />
</q:if>
</q:loop>
</q:if>
</q:function>
<!-- a click: pick up one of your pieces, or put the picked one on a legal square. On one machine
the same pointer plays both colours; over the network each machine plays its own. -->
<qg:on-select>
<q:if condition="{not finished and (net_status() != 'playing' or cursor.player == 1 and turn == 'w' or cursor.player == 2 and turn == 'b')}">
<q:set name="r" value="{cursor.row}" />
<q:set name="c" value="{cursor.col}" />
<q:set name="act" value="{'move' if sel_r >= 0 and [r, c] in legal else ('pick' if own(at(r, c), turn) else 'clear')}" />
<q:if condition="{act == 'move'}">
<q:set name="mover" value="{thing_at('piece', sel_c * 62 + 31, sel_r * 62 + 31)}" />
<q:set name="p" value="{board[sel_r * 8 + sel_c]}" />
<!-- what the move takes: the piece on the square, or the pawn passed en passant -->
<q:set name="taken" value="{thing_at('piece', c * 62 + 31, r * 62 + 31)}" />
<q:if condition="{taken == null and p[1:2] == 'P' and c != sel_c}">
<q:set name="taken" value="{thing_at('piece', c * 62 + 31, sel_r * 62 + 31)}" />
</q:if>
<q:if condition="{taken != null}"><qg:destroy target="taken" /></q:if>
<q:if condition="{p[1:2] == 'K' and c - sel_c == 2}">
<q:set name="taken" value="{thing_at('piece', 7 * 62 + 31, r * 62 + 31)}" />
<qg:put target="taken" x="{5 * 62 + 31}" y="{r * 62 + 31}" />
</q:if>
<q:if condition="{p[1:2] == 'K' and sel_c - c == 2}">
<q:set name="taken" value="{thing_at('piece', 31, r * 62 + 31)}" />
<qg:put target="taken" x="{3 * 62 + 31}" y="{r * 62 + 31}" />
</q:if>
<qg:put target="mover" x="{c * 62 + 31}" y="{r * 62 + 31}" />
<q:if condition="{p[1:2] == 'P' and (r == 0 or r == 7)}">
<q:if condition="{turn == 'w'}"><qg:swap target="mover" prefab="wQ" /><q:else><qg:swap target="mover" prefab="bQ" /></q:else></q:if>
</q:if>
<q:call function="apply" args="sel_r, sel_c, r, c" />
<!-- the rights and the en passant square after the move -->
<q:if condition="{p[1:2] == 'K'}"><q:set name="moved" value="{moved + [turn + 'K']}" /></q:if>
<q:if condition="{p[1:2] == 'R' and sel_c == 0}"><q:set name="moved" value="{moved + [turn + 'Ra']}" /></q:if>
<q:if condition="{p[1:2] == 'R' and sel_c == 7}"><q:set name="moved" value="{moved + [turn + 'Rh']}" /></q:if>
<q:set name="ep_col" value="{c if p[1:2] == 'P' and abs(r - sel_r) == 2 else -1}" />
<qg:put target="last_from" x="{sel_c * 62 + 31}" y="{sel_r * 62 + 31}" />
<qg:put target="last_to" x="{c * 62 + 31}" y="{r * 62 + 31}" />
<q:set name="turn" value="{other_color(turn)}" />
<q:call function="clear_mark" />
<q:call function="show_check" />
<q:set name="status" value="{('White' if turn == 'w' else 'Black') + ' to move'}" />
<q:if condition="{not any_legal(turn)}">
<q:set name="finished" value="true" />
<q:set name="status" value="{('Checkmate - ' + ('Black' if turn == 'w' else 'White') + ' wins') if in_check(turn) else 'Stalemate'}" />
<q:elseif condition="{in_check(turn)}">
<q:set name="status" value="{status + ' - check'}" />
</q:elseif>
</q:if>
</q:if>
<q:if condition="{act == 'pick'}">
<q:call function="clear_mark" />
<q:set name="sel_r" value="{r}" />
<q:set name="sel_c" value="{c}" />
<q:set name="legal" value="{legal_moves(r, c)}" />
<qg:put target="mark" x="{cursor.x}" y="{cursor.y}" />
<q:loop var="m" items="{legal}">
<q:if condition="{at(m[0], m[1]) == ''}">
<qg:spawn prefab="Dot" x="{m[1] * 62 + 31}" y="{m[0] * 62 + 31}" />
<q:else>
<qg:spawn prefab="Ring" x="{m[1] * 62 + 31}" y="{m[0] * 62 + 31}" />
</q:else>
</q:if>
</q:loop>
<q:set name="status" value="{('White' if turn == 'w' else 'Black') + (' to move' if len(legal) > 0 else ' to move - that piece cannot move')}" />
<q:if condition="{len(legal) > 0 and in_check(turn)}"><q:set name="status" value="{status + ' - check'}" /></q:if>
</q:if>
<q:if condition="{act == 'clear'}">
<q:call function="clear_mark" />
</q:if>
</q:if>
</qg:on-select>
</qg:scene>
<!-- ===== Options: how loud, the full screen, the keys; kept between runs ===== -->
<qg:scene name="options" width="496" height="540" background="#2a2520">
<qg:hud position="top-center" size="36"><qg:text value="{tr('OPTIONS')}" /></qg:hud>
<qg:menu position="center" size="18">
<qg:button label="{tr('Full screen') + ': ' + (tr('on') if fullscreen() else tr('off'))}"><qg:fullscreen /></qg:button>
<qg:button label="Back"><qg:goto-scene name="title" /></qg:button>
</qg:menu>
</qg:scene>
</q:application>Robot
projects/robot/robot.q — Godot's "Platformer 2D" demo transcribed: its level tile for tile, with slopes and ledges, a robot that runs, jumps twice and shoots, crawling enemies, coins, lifts and a pause menu (projects/robot/README.md).
<q:application id="robot" type="game">
<!-- Robot: Godot's "Platformer 2D" demo (godot-demo-projects, 2d/platformer, MIT),
transcribed. One closed level of slopes, ledges, moving platforms, coins and
crawling enemies; the robot runs, jumps twice, shoots, and cannot be hurt.
The demo's art and sounds are in assets/ (assets/LICENSE.md); README.md maps
the original, piece by piece. -->
<!-- The demo's keys: jump on Up, W or the joypad's A; shoot on Space, Z, Ctrl or
the joypad's X; pause on Escape or Start. Left and right keep their defaults. -->
<qg:input action="jump" keys="Up, W, JoyA" />
<qg:input action="shoot" keys="Space, Z, Ctrl, JoyX" />
<qg:input action="pause" keys="Escape, JoyStart" />
<!-- The tiles are 64 px. Most are full squares; these are not: a grassy top that
starts 10 px down, two thin ledges, a slope to walk up and jump through. -->
<qg:tileset name="tiles" src="assets/tiles.png" tile="64">
<qg:tile frame="0" shape="0,10; 64,10; 64,64; 0,64" />
<qg:tile frame="1" shape="0,10; 64,10; 64,64; 0,64" />
<qg:tile frame="2" shape="0,10; 64,10; 64,46; 0,46" />
<qg:tile frame="3" shape="0,10; 64,10; 64,43; 0,43" />
<qg:tile frame="18" shape="0,7.25; 58.18,64; 0,64" one-way="true" />
<qg:tile frame="27" shape="0,10; 64,10; 64,64; 0,64" />
<qg:tile frame="28" shape="0,10; 64,10; 64,64; 0,64" />
<qg:tile frame="29" shape="0,10; 64,10; 64,64; 0,64" />
</qg:tileset>
<qg:spritesheet name="robot" src="assets/robot.png" tile="64" />
<qg:spritesheet name="enemy" src="assets/enemy.png" tile="128" />
<qg:spritesheet name="coin" src="assets/coin.png" tile="32" />
<qg:spritesheet name="bullet" src="assets/bullet.png" tile="16" />
<qg:spritesheet name="platform" src="assets/platform.png" tile="256x64" />
<!-- The demo's ParallaxBackground, composed by scripts/art/robot_sky.py. -->
<qg:spritesheet name="sky" src="assets/sky.png" tile="2458x480" />
<qg:spritesheet name="hills" src="assets/hills.png" tile="2048x480" />
<qg:sound bus="effects" name="jump" src="assets/jump.wav" />
<qg:sound bus="effects" name="shoot" src="assets/shoot.wav" />
<qg:sound bus="effects" name="coin" src="assets/coin_pickup.wav" />
<qg:sound bus="effects" name="explode" src="assets/explode.wav" />
<qg:prefab name="Coin" tag="coin" sheet="coin" frame="0" hitbox="20x20" scale="0.65">
<qg:animation name="walk" frames="0,1,2,3,2,1" fps="6" />
</qg:prefab>
<!-- Crawls at 22 px/s and turns at walls and before falling off. -->
<qg:prefab name="Enemy" tag="enemy" sheet="enemy" frame="0" hitbox="50x36" scale="0.8"
ai="patrol" speed="22" direction="right" turns-at="edge" gravity="2100">
<qg:animation name="walk" frames="0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15" fps="16" />
</qg:prefab>
<!-- 850 px/s the way the robot faces, gone after a second or on a wall. -->
<qg:prefab name="Bullet" tag="bullet" sheet="bullet" frame="0" hitbox="16x16"
ai="fly" heading="right" speed="850" lifetime="60" walls="stop">
<qg:on-collision with="enemy">
<qg:burst at="other" color="#ffd27f" count="24" />
<qg:play sound="explode" />
<qg:destroy target="other" />
<qg:destroy target="me" />
</qg:on-collision>
</qg:prefab>
<!-- 192x27 to stand on, 7.5 px above the picture's middle; jumped through from below. -->
<qg:prefab name="LiftUp" tag="lift" sheet="platform" frame="0" hitbox="192x27" solid="true" one-way="true"
shape="-96,-21; 96,-21; 96,6; -96,6" ai="shuttle" dy="-210" period="240" />
<qg:prefab name="LiftSlow" tag="lift" sheet="platform" frame="0" hitbox="192x27" solid="true" one-way="true"
shape="-96,-21; 96,-21; 96,6; -96,6" ai="shuttle" dy="-295" period="480" />
<!-- The tilted platform at the top: the demo's polygon, rotated with the instance. -->
<qg:prefab name="Ledge" tag="ledge" sheet="platform" frame="0" hitbox="192x27" solid="true"
shape="-174.04,-0.43; -94.04,-23.61; 91.84,-28.64; 80.29,-5.16; -54.32,0.04; -179.99,26.44" />
<!-- the game in Portuguese and Spanish (a web page's ?lang=pt, the command line's q-lang, or the system's language) -->
<!-- on a phone: what the keys do, under a finger, in every scene -->
<qg:touch-stick x="80" y="400" size="110" />
<qg:touch-button action="jump" label="A" x="720" y="410" size="72" />
<qg:touch-button action="shoot" label="B" x="640" y="430" size="64" />
<qg:touch-button action="pause" label="II" x="770" y="30" size="40" />
<qg:language lang="pt" name="Português">
<qg:phrase text="COINS" as="MOEDAS" />
<qg:phrase text="Resume" as="Continuar" />
<qg:phrase text="Restart" as="Recomeçar" />
<qg:phrase text="Sounds" as="Sons" />
<qg:phrase text="Full screen" as="Tela cheia" />
<qg:phrase text="on" as="sim" />
<qg:phrase text="off" as="não" />
<qg:phrase text="Jump" as="Pular" />
<qg:phrase text="Shoot" as="Atirar" />
</qg:language>
<qg:language lang="es" name="Español">
<qg:phrase text="COINS" as="MONEDAS" />
<qg:phrase text="Resume" as="Continuar" />
<qg:phrase text="Restart" as="Reiniciar" />
<qg:phrase text="Sounds" as="Sonidos" />
<qg:phrase text="Full screen" as="Pantalla completa" />
<qg:phrase text="on" as="sí" />
<qg:phrase text="off" as="no" />
<qg:phrase text="Jump" as="Saltar" />
<qg:phrase text="Shoot" as="Disparar" />
</qg:language>
<qg:scene name="level" width="800" height="480" background="#52c9ff">
<!-- level/background/parallax_background.tscn: a base scale of 0.1 times each layer's -->
<qg:parallax sheet="sky" scroll="0.02" />
<qg:parallax sheet="hills" scroll="0.04" />
<q:set name="coins" value="0" type="number" />
<!-- 35 x 24 tiles of 64 px; the demo's map, whose top-left cell is (-12, -11), so
every position here is the demo's plus (768, 704). A negative number is a
tile flipped left to right. -->
<qg:tilemap tileset="tiles" collision="true">
-2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2
-10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10
-10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10
-10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10
-10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10
-10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10
-10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10
-10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10
-10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10
-10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10
-10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10
-10,1,1,1,1,1,1,1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-2,-1,1,1,1,1,1,1,10
-10,9,9,9,9,9,9,9,10,0,0,0,0,0,-4,3,3,-3,4,0,0,0,0,0,0,0,-10,-9,9,9,9,9,9,9,10
-10,9,9,9,9,9,9,9,10,0,0,-4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,-10,-9,9,9,9,9,9,9,10
-10,9,9,9,9,9,9,9,10,0,0,0,0,0,0,0,0,0,-4,-3,4,0,0,0,0,0,-10,-9,9,9,9,9,9,9,10
-10,9,9,9,9,9,9,9,18,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-10,-9,9,9,9,9,9,9,10
-10,9,9,9,9,9,9,9,9,10,0,0,0,-4,3,4,0,0,0,0,0,0,0,0,0,0,-10,-9,9,9,9,9,9,9,10
-10,9,9,9,9,9,9,9,-14,10,0,0,0,0,0,0,0,0,-2,28,30,28,2,0,0,0,-10,-9,9,9,9,9,9,9,10
-10,9,9,9,9,9,9,9,9,10,0,0,0,-4,-3,-3,4,0,-16,-7,15,15,16,0,0,0,-10,-9,9,9,9,9,9,9,10
-10,9,9,9,9,9,9,9,9,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-10,-9,9,9,9,9,9,9,10
-10,9,9,9,9,9,9,9,-14,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-19,-18,-9,9,9,9,9,9,9,10
-10,9,9,9,9,9,9,9,9,18,30,28,28,30,30,28,28,30,28,30,28,30,28,28,30,-27,-9,-9,9,9,9,9,9,9,10
-10,9,9,9,9,9,9,9,9,9,9,9,14,9,14,9,14,9,9,9,-14,14,9,9,-14,9,-9,-9,9,9,9,9,9,9,10
-10,9,9,9,9,9,9,9,-15,-15,15,15,15,15,15,15,15,14,15,15,15,15,15,15,15,15,-9,-9,9,9,9,9,9,9,10
</qg:tilemap>
<qg:instance prefab="LiftUp" x="1568" y="1114" />
<qg:instance prefab="LiftSlow" x="740" y="1349" />
<qg:instance prefab="Ledge" x="1379" y="839" gd:rotation="0.355618" />
<qg:sprite sheet="platform" frame="0" x="1292.2" y="818" gd:rotation="0.109325" gd:z_index="-1" />
<qg:instance prefab="Coin" x="1468" y="1314" />
<qg:instance prefab="Coin" x="1498" y="1314" />
<qg:instance prefab="Coin" x="1528" y="1314" />
<qg:instance prefab="Coin" x="922" y="747" />
<qg:instance prefab="Coin" x="952" y="747" />
<qg:instance prefab="Coin" x="982" y="747" />
<qg:instance prefab="Coin" x="968" y="1314" />
<qg:instance prefab="Coin" x="998" y="1314" />
<qg:instance prefab="Coin" x="1028" y="1314" />
<qg:instance prefab="Coin" x="1075" y="1298" />
<qg:instance prefab="Coin" x="1105" y="1288" />
<qg:instance prefab="Coin" x="1135" y="1298" />
<qg:instance prefab="Coin" x="1221" y="1036" />
<qg:instance prefab="Coin" x="1251" y="1026" />
<qg:instance prefab="Coin" x="1281" y="1036" />
<qg:instance prefab="Coin" x="890" y="1110" />
<qg:instance prefab="Coin" x="920" y="1100" />
<qg:instance prefab="Coin" x="950" y="1110" />
<qg:instance prefab="Coin" x="1362" y="1036" />
<qg:instance prefab="Coin" x="1392" y="1026" />
<qg:instance prefab="Coin" x="1422" y="1036" />
<qg:instance prefab="Enemy" x="1312" y="1059" />
<qg:instance prefab="Enemy" x="1260" y="1314" />
<qg:instance prefab="Enemy" x="1163" y="757" />
<qg:character id="robot" controller="platformer" sheet="robot" frame="30" scale="0.8"
x="858" y="1329" hitbox="34x44"
run-speed="300" accel="1800" jump-speed="725" gravity="2100" max-fall="700"
coyote-frames="0" air-jumps="1" air-jump-boost="2.5" jump-cut="0.6"
jump-sound="jump"
fire-action="shoot" fire-prefab="Bullet" fire-every="18" fire-sound="shoot">
<qg:animation name="idle" frames="30,31,32,33" fps="8" />
<qg:animation name="walk" frames="0,1,2,3,4,5,6,7,8,9" fps="16" />
<qg:animation name="jump" frames="45" />
<qg:animation name="fall" frames="48" />
<qg:on-collision with="coin">
<qg:destroy target="other" />
<qg:play sound="coin" />
<q:set name="coins" value="{coins + 1}" />
</qg:on-collision>
</qg:character>
<!-- The camera stays inside the level's walls, a little below the robot. -->
<qg:camera follow="robot" bounds="none" gd:offset="0,39"
gd:limit_left="53" gd:limit_top="454" gd:limit_right="2193" gd:limit_bottom="1394" />
<qg:hud position="top-left" size="24">
<qg:counter bind="coins" label="COINS" />
</qg:hud>
<!-- Escape pauses and resumes; while paused, the menu. -->
<qg:on-input action="pause">
<q:if condition="{paused()}">
<qg:resume />
<q:else>
<qg:pause />
</q:else>
</q:if>
</qg:on-input>
<qg:menu if="{paused()}" size="24">
<qg:button label="Resume">
<qg:resume />
</qg:button>
<qg:button label="Restart">
<qg:goto-scene name="level" />
</qg:button>
<!-- beyond the demo: the options, kept between runs -->
<qg:slider label="Sounds" bus="effects" />
<qg:button label="{tr('Full screen') + ': ' + (tr('on') if fullscreen() else tr('off'))}"><qg:fullscreen /></qg:button>
<qg:rebind label="Jump" action="jump" />
<qg:rebind label="Shoot" action="shoot" />
</qg:menu>
</qg:scene>
</q:application>Bricks
projects/bricks/bricks.q — a brick breaker for one, or two to four over the network: levels written as lines of letters and built by a q:loop, steel bricks of two hits, a bomb that breaks the bricks around it (qg:for-each), the serve as jump is let go (on="release"), a ball lost off the bottom (qg:on-screen-exit), the last brick in slow motion (qg:time-scale) and a sequence of tweens (qg:on-end).
<q:application id="bricks" type="game">
<!-- Bricks: a brick breaker, alone or for two to four over the network.
A paddle (one each, over the network) keeps a ball in play, and the
ball breaks the bricks it hits. Steel bricks take two hits, a bomb
brick breaks the bricks around it, and the last brick of a level
breaks in slow motion. Each level is a few lines of text, one letter
a brick. The art is drawn by scripts/art/bricks.py (CC0); the sounds
are Kenney's Pixel Platformer (CC0). -->
<q:set name="score" value="0" type="number" />
<q:set name="lives" value="3" type="number" />
<q:set name="level" value="1" type="number" />
<!-- what each player is called, over the network: the names they send -->
<q:set name="names" type="array" value='["", "", "", ""]' />
<!-- the levels, nine bricks to a row: R O Y G B the colours (one hit), S steel
(two), X a bomb, . none -->
<q:set name="levels" type="array" value='[
["RRRRRRRRR", "OOOOOOOOO", "YYYYXYYYY", "GGGGGGGGG", "BBBBBBBBB"],
["S.S.S.S.S", "RRRRRRRRR", "OOXOOOXOO", "YYYYYYYYY", ".GGGGGGG.", "..BBBBB.."],
["SSSSSSSSS", "R.XRRRX.R", "OOOOOOOOO", "YSYYXYYSY", "GGGGGGGGG", "BBBXBXBBB", "S.......S"]
]' />
<qg:spritesheet name="bricks" src="assets/bricks.png" tile="32x12" />
<qg:spritesheet name="paddles" src="assets/paddles.png" tile="48x8" />
<qg:spritesheet name="ball" src="assets/ball.png" tile="8" />
<qg:sound bus="effects" name="bounce" src="assets/kenney/audio/jump.ogg" />
<qg:sound bus="effects" name="break" src="assets/kenney/audio/block.ogg" />
<qg:sound bus="effects" name="clank" src="assets/kenney/audio/stomp.ogg" />
<qg:sound bus="effects" name="boom" src="assets/kenney/audio/powerup.ogg" />
<qg:sound bus="effects" name="lost" src="assets/kenney/audio/hurt.ogg" />
<qg:sound bus="effects" name="clear" src="assets/kenney/audio/win.ogg" />
<!-- The serve: Space, Up, W or the joypad's A, as it is let go. -->
<qg:input action="jump" keys="Space, Up, W, JoyA" />
<!-- Two to four players, each on their own machine. The game starts as the
fourth joins, or when the host chooses to start with two or three:
first who is who, then the first level. -->
<qg:multiplayer players="4" min-players="2" start="names" />
<!-- A player's name, or "Player 2" until they give one. -->
<q:function name="who" params="n">
<q:if condition="{names[n - 1] == ''}"><q:return value="{tr('Player') + ' ' + str(n)}" /></q:if>
<q:return value="{names[n - 1]}" />
</q:function>
<!-- The ball flies at 150 px/s, a little faster every second. It bounces
off the sides and the top, and off a brick by the side it hit, which
takes a hit; the ball flashes as it does. -->
<qg:prefab name="Ball" tag="ball" sheet="ball" hitbox="8x8" ai="fly" heading="0.4,-1" speed="150" accel="1">
<qg:on-collision with="side">
<qg:deflect target="me" axis="x" />
<qg:play sound="bounce" volume="-8" vary="0.1" />
</qg:on-collision>
<qg:on-collision with="top">
<qg:deflect target="me" axis="y" />
<qg:play sound="bounce" volume="-8" vary="0.1" />
</qg:on-collision>
<qg:on-collision with="brick">
<q:if condition="{abs(me.x - other.x) > 16}">
<qg:deflect target="me" axis="x" />
<q:else><qg:deflect target="me" axis="y" /></q:else>
</q:if>
<qg:tween target="me" property="tint" from="#ffd060" to="#ffffff" seconds="0.3" />
<qg:damage target="other" amount="1" />
</qg:on-collision>
<qg:on-collision with="bomb">
<qg:deflect target="me" axis="y" />
<qg:tween target="me" property="tint" from="#ff5040" to="#ffffff" seconds="0.5" />
<qg:damage target="other" amount="1" />
</qg:on-collision>
</qg:prefab>
<!-- The bricks: five colours of one hit, steel of two (cracked after the
first), and the bomb, which breaks the bricks around it (the level's
qg:on-death of="bomb"). -->
<qg:prefab name="Red" tag="brick" sheet="bricks" frame="0" hitbox="32x12">
<qg:on-death>
<qg:play sound="break" vary="0.2" />
<qg:burst at="me" color="#d64040" count="10" />
<q:set name="score" value="{score + 50}" />
</qg:on-death>
</qg:prefab>
<qg:prefab name="Orange" tag="brick" sheet="bricks" frame="1" hitbox="32x12">
<qg:on-death>
<qg:play sound="break" vary="0.2" />
<qg:burst at="me" color="#e88030" count="10" />
<q:set name="score" value="{score + 40}" />
</qg:on-death>
</qg:prefab>
<qg:prefab name="Yellow" tag="brick" sheet="bricks" frame="2" hitbox="32x12">
<qg:on-death>
<qg:play sound="break" vary="0.2" />
<qg:burst at="me" color="#ecc838" count="10" />
<q:set name="score" value="{score + 30}" />
</qg:on-death>
</qg:prefab>
<qg:prefab name="Green" tag="brick" sheet="bricks" frame="3" hitbox="32x12">
<qg:on-death>
<qg:play sound="break" vary="0.2" />
<qg:burst at="me" color="#58b858" count="10" />
<q:set name="score" value="{score + 20}" />
</qg:on-death>
</qg:prefab>
<qg:prefab name="Blue" tag="brick" sheet="bricks" frame="4" hitbox="32x12">
<qg:on-death>
<qg:play sound="break" vary="0.2" />
<qg:burst at="me" color="#4884dc" count="10" />
<q:set name="score" value="{score + 10}" />
</qg:on-death>
</qg:prefab>
<qg:prefab name="Steel" tag="brick" sheet="bricks" frame="5" hitbox="32x12" health="2">
<qg:state name="whole" frame="5" initial="true" />
<qg:state name="cracked" frame="6" />
<qg:on-damage>
<qg:play sound="clank" />
<qg:become target="me" state="cracked" />
</qg:on-damage>
<qg:on-death>
<qg:play sound="break" pitch="0.8" />
<qg:burst at="me" color="#969cac" count="16" />
<q:set name="score" value="{score + 80}" />
</qg:on-death>
</qg:prefab>
<qg:prefab name="Bomb" tag="bomb" sheet="bricks" frame="7" hitbox="32x12">
<qg:on-death>
<qg:play sound="boom" />
<qg:burst at="me" color="#ff7040" count="24" />
<q:set name="score" value="{score + 100}" />
</qg:on-death>
</qg:prefab>
<!-- the game in Portuguese and Spanish (a web page's ?lang=pt, the command line's q-lang, or the system's language) -->
<!-- on a phone: what the keys do, under a finger, in every scene -->
<qg:touch-button action="left" label="<" x="34" y="206" size="48" />
<qg:touch-button action="right" label=">" x="90" y="206" size="48" />
<qg:touch-button action="jump" label="A" x="286" y="206" size="48" />
<qg:language lang="pt" name="Português">
<qg:phrase text="BRICKS" as="TIJOLOS" />
<qg:phrase text="Play alone" as="Jogar sozinho" />
<qg:phrase text="Play together" as="Jogar junto" />
<qg:phrase text="Quit" as="Sair" />
<qg:phrase text="Language" as="Idioma" />
<qg:phrase text="To the title" as="Para o título" />
<qg:phrase text="SCORE" as="PONTOS" />
<qg:phrase text="LIVES" as="VIDAS" />
<qg:phrase text="LEVEL" as="FASE" />
<qg:phrase text="SPACE: SERVE" as="ESPAÇO: SACAR" />
<qg:phrase text="TWO TO FOUR PLAYERS, EACH ON THEIR OWN MACHINE" as="DE DOIS A QUATRO JOGADORES, CADA UM NA SUA MÁQUINA" />
<qg:phrase text="EVERY BRICK IS BROKEN" as="TODOS OS TIJOLOS QUEBRADOS" />
<qg:phrase text="THE BALLS ARE GONE" as="AS BOLAS ACABARAM" />
<qg:phrase text="Options" as="Opções" />
<qg:phrase text="OPTIONS" as="OPÇÕES" />
<qg:phrase text="Sounds" as="Sons" />
<qg:phrase text="Full screen" as="Tela cheia" />
<qg:phrase text="on" as="sim" />
<qg:phrase text="off" as="não" />
<qg:phrase text="Back" as="Voltar" />
<qg:phrase text="Serve" as="Sacar" />
<qg:phrase text="Left" as="Esquerda" />
<qg:phrase text="Right" as="Direita" />
<qg:phrase text="WHO IS WHO" as="QUEM É QUEM" />
<qg:phrase text="Player" as="Jogador" />
<qg:phrase text="Your name" as="Seu nome" />
<qg:phrase text="PLAYER 1: SPACE TO START" as="JOGADOR 1: ESPAÇO PARA COMEÇAR" />
</qg:language>
<qg:language lang="es" name="Español">
<qg:phrase text="BRICKS" as="LADRILLOS" />
<qg:phrase text="Play alone" as="Jugar solo" />
<qg:phrase text="Play together" as="Jugar juntos" />
<qg:phrase text="Quit" as="Salir" />
<qg:phrase text="Language" as="Idioma" />
<qg:phrase text="To the title" as="Al título" />
<qg:phrase text="SCORE" as="PUNTOS" />
<qg:phrase text="LIVES" as="VIDAS" />
<qg:phrase text="LEVEL" as="NIVEL" />
<qg:phrase text="SPACE: SERVE" as="ESPACIO: SACAR" />
<qg:phrase text="TWO TO FOUR PLAYERS, EACH ON THEIR OWN MACHINE" as="DE DOS A CUATRO JUGADORES, CADA UNO EN SU MÁQUINA" />
<qg:phrase text="EVERY BRICK IS BROKEN" as="TODOS LOS LADRILLOS ROTOS" />
<qg:phrase text="THE BALLS ARE GONE" as="SE ACABARON LAS PELOTAS" />
<qg:phrase text="Options" as="Opciones" />
<qg:phrase text="OPTIONS" as="OPCIONES" />
<qg:phrase text="Sounds" as="Sonidos" />
<qg:phrase text="Full screen" as="Pantalla completa" />
<qg:phrase text="on" as="sí" />
<qg:phrase text="off" as="no" />
<qg:phrase text="Back" as="Volver" />
<qg:phrase text="Serve" as="Sacar" />
<qg:phrase text="Left" as="Izquierda" />
<qg:phrase text="Right" as="Derecha" />
<qg:phrase text="WHO IS WHO" as="QUIÉN ES QUIÉN" />
<qg:phrase text="Player" as="Jugador" />
<qg:phrase text="Your name" as="Tu nombre" />
<qg:phrase text="PLAYER 1: SPACE TO START" as="JUGADOR 1: ESPACIO PARA EMPEZAR" />
</qg:language>
<qg:scene name="title" width="320" height="240" background="#1c1a2c">
<qg:sprite sheet="bricks" frame="0" x="64" y="56" />
<qg:sprite sheet="bricks" frame="1" x="112" y="56" />
<qg:sprite sheet="bricks" frame="2" x="160" y="56" />
<qg:sprite sheet="bricks" frame="3" x="208" y="56" />
<qg:sprite sheet="bricks" frame="4" x="256" y="56" />
<qg:hud position="top-center" size="32"><qg:text value="{tr('BRICKS')}" /></qg:hud>
<qg:menu position="center" size="14">
<qg:button label="Play alone"><q:call function="anew" /><qg:goto-scene name="level" fade="0.3" /></qg:button>
<qg:button label="Play together"><q:call function="anew" /><qg:goto-scene name="together" /></qg:button>
<qg:button label="Options"><qg:goto-scene name="options" /></qg:button>
<qg:button label="{tr('Language') + ': ' + language()}"><qg:next-language /></qg:button>
<!-- a desktop build quits; a page in a browser has nothing to quit to -->
<qg:button label="Quit" if="{can_quit()}"><qg:quit /></qg:button>
</qg:menu>
<q:function name="anew">
<q:set name="score" value="0" />
<q:set name="lives" value="3" />
<q:set name="level" value="1" />
</q:function>
</qg:scene>
<!-- Two to four machines: one hosts, the others join its address. The game
starts as the fourth joins, or when the host chooses to start sooner. -->
<qg:scene name="together" width="320" height="240" background="#1c1a2c">
<qg:hud position="top-center" size="10"><qg:text value="{tr('TWO TO FOUR PLAYERS, EACH ON THEIR OWN MACHINE')}" /></qg:hud>
<qg:lobby local="level" local-label="Play alone" size="12" />
<qg:on-input action="cancel">
<qg:leave />
<qg:goto-scene name="title" />
</qg:on-input>
</qg:scene>
<!-- Who is who: each player types a name at their own machine (Enter opens
the box, Enter sends it). The field is shared: what is sent reaches every
machine on the same tick, as the keys do, and each shows it beside that
player's paddle. Player 1 starts the first level with the serve. -->
<qg:scene name="names" width="320" height="240" background="#1c1a2c">
<q:set name="typed" value="" />
<qg:hud position="top-center" size="14"><qg:text value="{tr('WHO IS WHO')}" /></qg:hud>
<qg:hud position="center" size="12">
<qg:icon sheet="paddles" frame="0" />
<qg:text value="{who(1)}" color="#40dce6" />
<qg:icon sheet="paddles" frame="1" />
<qg:text value="{who(2)}" color="#e650c8" />
<qg:icon sheet="paddles" frame="2" />
<qg:text value="{who(3) if net_players() >= 3 else '-'}" color="#96e646" />
<qg:icon sheet="paddles" frame="3" />
<qg:text value="{who(4) if net_players() >= 4 else '-'}" color="#f0b030" />
<qg:text value="{tr('PLAYER 1: SPACE TO START')}" />
</qg:hud>
<qg:menu local="true" position="bottom-center" size="12">
<qg:field bind="typed" label="Your name" shared="true" max-length="10">
<q:set name="names" index="{sender - 1}" value="{text}" />
</qg:field>
</qg:menu>
<qg:on-input action="jump" on="release">
<qg:goto-scene name="level" fade="0.3" />
</qg:on-input>
</qg:scene>
<!-- A level: the bricks of levels[level - 1], a paddle for each player, the
ball served from the first paddle. -->
<qg:scene name="level" width="320" height="240" background="#1c1a2c" seed="1">
<q:set name="serving" value="true" type="boolean" />
<q:set name="done" value="false" type="boolean" />
<q:call function="build" args="{levels[int(level) - 1]}" />
<qg:hud position="top-left" size="10"><qg:text value="{tr('SCORE') + ' ' + str(score)}" /></qg:hud>
<qg:hud position="top-center" size="10"><qg:text value="{tr('LEVEL') + ' ' + str(int(level))}" /></qg:hud>
<qg:hud position="top-right" size="10"><qg:text value="{tr('LIVES') + ' ' + str(int(lives))}" /></qg:hud>
<qg:hud position="bottom-center" size="10"><qg:text value="{tr('SPACE: SERVE') if serving else ''}" /></qg:hud>
<!-- the walls: the sides and the top; the bottom is open -->
<qg:zone name="left-wall" tag="side" x="-20" y="0" width="20" height="240" />
<qg:zone name="right-wall" tag="side" x="320" y="0" width="20" height="240" />
<qg:zone name="ceiling" tag="top" x="0" y="0" width="320" height="16" />
<!-- A paddle runs along the bottom, kept on the screen. The ball leaves
it at a slant: straight up from its middle, more aslant towards its
ends. It squashes a little as it does. -->
<qg:character id="p1" controller="ship" player="1" axis="horizontal" speed="240"
sheet="paddles" frame="0" x="160" y="226" hitbox="48x8">
<qg:on-collision with="ball"><q:call function="hit" args="me, other" /></qg:on-collision>
</qg:character>
<qg:character id="p2" controller="ship" player="2" axis="horizontal" speed="240" if="{net_players() >= 2}"
sheet="paddles" frame="1" x="80" y="226" hitbox="48x8">
<qg:on-collision with="ball"><q:call function="hit" args="me, other" /></qg:on-collision>
</qg:character>
<qg:character id="p3" controller="ship" player="3" axis="horizontal" speed="240" if="{net_players() >= 3}"
sheet="paddles" frame="2" x="240" y="226" hitbox="48x8">
<qg:on-collision with="ball"><q:call function="hit" args="me, other" /></qg:on-collision>
</qg:character>
<qg:character id="p4" controller="ship" player="4" axis="horizontal" speed="240" if="{net_players() >= 4}"
sheet="paddles" frame="3" x="120" y="226" hitbox="48x8">
<qg:on-collision with="ball"><q:call function="hit" args="me, other" /></qg:on-collision>
</qg:character>
<q:function name="hit" params="paddle, ball">
<qg:deflect target="ball" dx="{(ball.x - paddle.x) / 24}" dy="-1" />
<qg:tween target="paddle" property="scale" from="1.2" to="1" seconds="0.2" trans="back" />
<qg:play sound="bounce" vary="0.1" />
</q:function>
<!-- The serve: as the action is let go, a ball leaves the first paddle. -->
<qg:on-input action="jump" on="release">
<q:if condition="{serving and not done}">
<q:set name="serving" value="false" />
<qg:spawn prefab="Ball" at="p1" dy="-10" />
</q:if>
</qg:on-input>
<!-- A ball out at the bottom is lost; with none left, a life. -->
<qg:on-screen-exit of="ball" side="bottom">
<qg:destroy target="other" />
<q:if condition="{count('ball') == 0 and not done}">
<qg:play sound="lost" />
<q:set name="lives" value="{lives - 1}" />
<q:if condition="{lives <= 0}">
<qg:goto-scene name="over" fade="0.5" />
<q:else><q:set name="serving" value="true" /></q:else>
</q:if>
</q:if>
</qg:on-screen-exit>
<!-- A bomb breaks every brick within a brick and a half of it. -->
<qg:on-death of="bomb">
<qg:shake at="other" frames="12" strength="3" />
<qg:for-each tag="brick" var="near">
<q:if condition="{distance(near, other) < 48}">
<qg:damage target="near" amount="2" />
</q:if>
</qg:for-each>
</qg:on-death>
<!-- The last brick: slow motion, the balls fade out, the paddle hops, the next level. -->
<qg:when condition="{count('brick') + count('bomb') == 0}">
<q:set name="done" value="true" />
<qg:play sound="clear" />
<qg:time-scale scale="0.25" seconds="1.5" />
<qg:for-each tag="ball" var="b">
<qg:tween target="b" property="alpha" to="0" seconds="0.3">
<qg:on-end><qg:destroy target="me" /></qg:on-end>
</qg:tween>
</qg:for-each>
<qg:tween target="p1" property="y" to="214" seconds="0.4" trans="quad" yoyo="true" repeat="1">
<qg:on-end><q:call function="next" /></qg:on-end>
</qg:tween>
</qg:when>
<q:function name="next">
<q:if condition="{level >= len(levels)}">
<qg:goto-scene name="won" fade="0.5" />
<q:else>
<q:set name="level" value="{level + 1}" />
<qg:goto-scene name="level" fade="0.3" />
</q:else>
</q:if>
</q:function>
<!-- the level: a letter a brick, nine to a row -->
<q:function name="build" params="lines">
<q:loop var="r" from="0" to="{len(lines) - 1}">
<q:loop var="c" from="0" to="{len(lines[r]) - 1}">
<q:call function="place" args="{lines[r][c]}, {32 + c * 32}, {40 + r * 12}" />
</q:loop>
</q:loop>
</q:function>
<q:function name="place" params="kind, x, y">
<q:if condition="{kind == 'R'}"><qg:spawn prefab="Red" x="{x}" y="{y}" />
<q:elseif condition="{kind == 'O'}"><qg:spawn prefab="Orange" x="{x}" y="{y}" /></q:elseif>
<q:elseif condition="{kind == 'Y'}"><qg:spawn prefab="Yellow" x="{x}" y="{y}" /></q:elseif>
<q:elseif condition="{kind == 'G'}"><qg:spawn prefab="Green" x="{x}" y="{y}" /></q:elseif>
<q:elseif condition="{kind == 'B'}"><qg:spawn prefab="Blue" x="{x}" y="{y}" /></q:elseif>
<q:elseif condition="{kind == 'S'}"><qg:spawn prefab="Steel" x="{x}" y="{y}" /></q:elseif>
<q:elseif condition="{kind == 'X'}"><qg:spawn prefab="Bomb" x="{x}" y="{y}" /></q:elseif>
</q:if>
</q:function>
</qg:scene>
<qg:scene name="won" width="320" height="240" background="#1c1a2c">
<qg:hud position="top-center" size="14"><qg:text value="{tr('EVERY BRICK IS BROKEN')}" /></qg:hud>
<qg:hud position="center" size="12"><qg:text value="{tr('SCORE') + ' ' + str(score)}" /></qg:hud>
<qg:menu position="bottom-center" size="12">
<qg:button label="To the title"><qg:leave /><qg:goto-scene name="title" /></qg:button>
</qg:menu>
</qg:scene>
<qg:scene name="over" width="320" height="240" background="#1c1a2c">
<qg:hud position="top-center" size="14"><qg:text value="{tr('THE BALLS ARE GONE')}" /></qg:hud>
<qg:hud position="center" size="12"><qg:text value="{tr('SCORE') + ' ' + str(score)}" /></qg:hud>
<qg:menu position="bottom-center" size="12">
<qg:button label="To the title"><qg:leave /><qg:goto-scene name="title" /></qg:button>
</qg:menu>
</qg:scene>
<!-- ===== Options: how loud, the full screen, the keys; kept between runs ===== -->
<qg:scene name="options" width="320" height="240" background="#1c1a2c">
<qg:hud position="top-center" size="24"><qg:text value="{tr('OPTIONS')}" /></qg:hud>
<qg:menu position="center" size="14">
<qg:slider label="Sounds" bus="effects" />
<qg:button label="{tr('Full screen') + ': ' + (tr('on') if fullscreen() else tr('off'))}"><qg:fullscreen /></qg:button>
<qg:rebind label="Serve" action="jump" />
<qg:rebind label="Left" action="left" />
<qg:rebind label="Right" action="right" />
<qg:button label="Back"><qg:goto-scene name="title" /></qg:button>
</qg:menu>
</qg:scene>
</q:application>Siege
projects/siege/siege.q — a keep under siege seen from above, for one or two over the network: raiders in waves (qg:when, qg:after) that keep apart (spacing=) and make for the tower, an archer who aims at the pointer (qg:spawn heading=, a qg:cursor's id) and charges a bolt (pressed_for()) that glances off the walls (walls="bounce") with a trail, numbers that float up (qg:emit text=), a tower that zaps (qg:beam), a brute made from a raider (extends=) with its health over it (qg:label), a ram shielded in front (side="left"), and a knight who steers a horse (qg:steer).
<q:application id="siege" type="game">
<!-- Siege: a keep under siege, seen from above, alone or for two over the
network. Raiders come in waves from the edges and make for the tower in
the keep's middle. The archer walks with WASD (or the left stick), aims
with the mouse (or the right stick) and shoots as fire is let go: held
for most of a second, the shot is a bolt that goes through raiders and
off the walls. With two, the second player is a knight on a horse who
turns and spurs it on, and rides the raiders down. The tower zaps the
nearest raider now and then. The art is drawn by scripts/art/siege.py
(CC0); the sounds are Kenney's Pixel Platformer (CC0). -->
<q:set name="score" value="0" type="number" />
<q:set name="hearts" value="5" type="number" />
<q:set name="wave" value="0" type="number" />
<!-- the five best scores, kept between runs -->
<q:set name="best" value="[]" type="array" saved="true" />
<!-- the tower is hurt, and the siege may be over (once: two raiders may reach it on one tick) -->
<q:function name="breach">
<q:if condition="{hearts > 0}">
<qg:play sound="hurt" />
<q:set name="hearts" value="{hearts - 1}" />
<q:if condition="{hearts <= 0}">
<q:set name="best" value="{sort(best + [score], true)[:5]}" />
<qg:goto-scene name="fallen" fade="0.6" />
</q:if>
</q:if>
</q:function>
<!-- 16-pixel tiles: 1 grass, 2 grass with flowers, 3 the keep's floor (walked on), 4 a wall -->
<qg:tileset name="ground" src="assets/ground.png" tile="16">
<qg:tile frame="0" shape="none" />
<qg:tile frame="1" shape="none" />
<qg:tile frame="2" shape="none" />
</qg:tileset>
<qg:spritesheet name="siege" src="assets/siege.png" tile="16" />
<qg:sound bus="effects" name="twang" src="assets/kenney/audio/jump.ogg" />
<qg:sound bus="effects" name="hit" src="assets/kenney/audio/stomp.ogg" />
<qg:sound bus="effects" name="clank" src="assets/kenney/audio/block.ogg" />
<qg:sound bus="effects" name="zap" src="assets/kenney/audio/powerup.ogg" />
<qg:sound bus="effects" name="hurt" src="assets/kenney/audio/hurt.ogg" />
<qg:sound bus="effects" name="horn" src="assets/kenney/audio/win.ogg" />
<!-- fire: the mouse's left button, J, or the right trigger; the right stick aims -->
<qg:input action="fire" keys="MouseLeft, J, JoyR2" />
<qg:input action="aim_left" keys="JoyRightStickLeft" />
<qg:input action="aim_right" keys="JoyRightStickRight" />
<qg:input action="aim_up" keys="JoyRightStickUp" />
<qg:input action="aim_down" keys="JoyRightStickDown" />
<qg:multiplayer players="2" start="keep" />
<qg:particles name="fire" preset="fire" color="#ff9030" amount="12" />
<qg:particles name="dust" preset="dust" color="#c8b080" amount="6" />
<qg:particles name="fallen" preset="smoke" color="#a09080" />
<!-- An arrow flies straight, over the walls; a bolt, the charged shot, is
faster, goes through what it hits and glances off the walls' stones (a
shot round a corner), leaving a trail. -->
<qg:prefab name="Arrow" tag="arrow" sheet="siege" frame="8" hitbox="6x6"
ai="fly" speed="240" rotate="true" lifetime="60" />
<qg:prefab name="Bolt" tag="bolt" sheet="siege" frame="9" hitbox="8x8"
ai="fly" speed="300" rotate="true" walls="bounce" lifetime="120" trail="#ffd070" />
<!-- Raiders make for the tower through the gates, knowing where it is
behind the walls, and keep a little apart so a wave comes as a crowd,
not a pile. A hit squashes one and shows what it took. -->
<qg:prefab name="Raider" tag="raider" sheet="siege" frame="4" hitbox="12x12" health="2"
ai="behavior" speed="34" sight="400" spacing="14">
<qg:animation name="walk" frames="4,5" fps="6" />
<qg:behavior><qg:seek target="tower" through-walls="true" /></qg:behavior>
<qg:on-collision with="arrow"><q:call function="struck" args="me, other, 1" /></qg:on-collision>
<qg:on-collision with="bolt"><q:call function="struck" args="me, null, 3" /></qg:on-collision>
<qg:on-collision with="tower"><q:call function="reached" args="me, other" /></qg:on-collision>
<qg:on-death>
<q:set name="score" value="{score + 10}" />
<qg:emit particles="fallen" at="me" />
<qg:emit text="+10" at="me" dy="-10" color="#ffe070" />
</qg:on-death>
</qg:prefab>
<!-- A brute is a raider (extends=: its way to the tower and what hits it), slow, that takes
eight hits; how many it has left is over its head. -->
<qg:prefab name="Brute" extends="Raider" frame="6" hitbox="14x14" health="8" scale="1.5" speed="20" spacing="20">
<qg:animation name="walk" frames="6,7" fps="4" />
<qg:label value="{me.health}" dy="-16" size="8" color="#ff8080" />
<qg:on-death>
<q:set name="score" value="{score + 50}" />
<qg:emit particles="fallen" at="me" />
<qg:emit text="+50" at="me" dy="-14" color="#ffe070" />
</qg:on-death>
</qg:prefab>
<!-- A ram comes from the left with its shield in front: an arrow that meets
the shield (the ram hits it from the left) glances off; one from behind
or the sides breaks it. It carries a torch. -->
<qg:prefab name="Ram" tag="raider" sheet="siege" frame="10" hitbox="16x12" health="4"
ai="behavior" speed="18" sight="400" particles="fire">
<qg:behavior><qg:seek target="tower" way="straight" through-walls="true" /></qg:behavior>
<qg:on-collision with="arrow" side="left">
<qg:play sound="clank" />
<qg:destroy target="other" />
</qg:on-collision>
<qg:on-collision with="arrow"><q:call function="struck" args="me, other, 1" /></qg:on-collision>
<qg:on-collision with="bolt"><q:call function="struck" args="me, null, 3" /></qg:on-collision>
<qg:on-collision with="tower"><q:call function="reached" args="me, other" /></qg:on-collision>
<qg:on-death>
<q:set name="score" value="{score + 30}" />
<qg:emit particles="fallen" at="me" />
<qg:emit text="+30" at="me" dy="-10" color="#ffe070" />
</qg:on-death>
</qg:prefab>
<!-- the tower in the keep's middle, with a fire on top; a raider that reaches it
is a breach: the raider is gone, and the tower loses a heart -->
<qg:prefab name="Tower" tag="tower" sheet="siege" frame="11" hitbox="16x16" solid="true" particles="fire" />
<q:function name="reached" params="raider, tower">
<qg:destroy target="raider" />
<qg:shake at="tower" frames="10" strength="3" />
<q:call function="breach" />
</q:function>
<!-- a hit: what it took floats up, the raider squashes and flashes; an arrow stops there -->
<q:function name="struck" params="raider, shot, amount">
<qg:play sound="hit" vary="0.15" />
<qg:damage target="raider" amount="{amount}" />
<qg:emit text="{str(amount)}" at="raider" dy="-8" color="#ffffff" />
<qg:tween target="raider" property="scale-x" from="1.4" to="1" seconds="0.2" trans="back" />
<qg:tween target="raider" property="scale-y" from="0.6" to="1" seconds="0.2" trans="back" />
<qg:tween target="raider" property="tint" from="#ff6060" to="#ffffff" seconds="0.25" />
<q:if condition="{shot != null}"><qg:destroy target="shot" /></q:if>
</q:function>
<!-- the game in Portuguese and Spanish (a web page's ?lang=pt, the command line's q-lang, or the system's language) -->
<!-- on a phone: what the keys do, under a finger, in every scene -->
<qg:touch-stick x="40" y="200" />
<qg:touch-button action="fire" label="A" x="284" y="200" />
<qg:language lang="pt" name="Português">
<qg:phrase text="SIEGE" as="CERCO" />
<qg:phrase text="Play alone" as="Jogar sozinho" />
<qg:phrase text="Play together" as="Jogar juntos" />
<qg:phrase text="Language" as="Idioma" />
<qg:phrase text="Quit" as="Sair" />
<qg:phrase text="SCORE" as="PONTOS" />
<qg:phrase text="WAVE" as="ONDA" />
<qg:phrase text="TOWER" as="TORRE" />
<qg:phrase text="ARCHER" as="ARQUEIRO" />
<qg:phrase text="KNIGHT" as="CAVALEIRO" />
<qg:phrase text="HERE THEY COME" as="LÁ VÊM ELES" />
<qg:phrase text="THE KEEP HAS FALLEN" as="A FORTALEZA CAIU" />
<qg:phrase text="BEST" as="MELHORES" />
<qg:phrase text="To the title" as="Para o início" />
<qg:phrase text="TWO PLAYERS, EACH ON THEIR OWN MACHINE" as="DOIS JOGADORES, CADA UM NA SUA MÁQUINA" />
<qg:phrase text="Options" as="Opções" />
<qg:phrase text="OPTIONS" as="OPÇÕES" />
<qg:phrase text="Sounds" as="Sons" />
<qg:phrase text="Full screen" as="Tela cheia" />
<qg:phrase text="on" as="sim" />
<qg:phrase text="off" as="não" />
<qg:phrase text="Back" as="Voltar" />
<qg:phrase text="Fire" as="Atirar" />
</qg:language>
<qg:language lang="es" name="Español">
<qg:phrase text="SIEGE" as="ASEDIO" />
<qg:phrase text="Play alone" as="Jugar solo" />
<qg:phrase text="Play together" as="Jugar juntos" />
<qg:phrase text="Language" as="Idioma" />
<qg:phrase text="Quit" as="Salir" />
<qg:phrase text="SCORE" as="PUNTOS" />
<qg:phrase text="WAVE" as="OLEADA" />
<qg:phrase text="TOWER" as="TORRE" />
<qg:phrase text="ARCHER" as="ARQUERO" />
<qg:phrase text="KNIGHT" as="CABALLERO" />
<qg:phrase text="HERE THEY COME" as="AHÍ VIENEN" />
<qg:phrase text="THE KEEP HAS FALLEN" as="LA FORTALEZA HA CAÍDO" />
<qg:phrase text="BEST" as="MEJORES" />
<qg:phrase text="To the title" as="Al inicio" />
<qg:phrase text="TWO PLAYERS, EACH ON THEIR OWN MACHINE" as="DOS JUGADORES, CADA UNO EN SU MÁQUINA" />
<qg:phrase text="Options" as="Opciones" />
<qg:phrase text="OPTIONS" as="OPCIONES" />
<qg:phrase text="Sounds" as="Sonidos" />
<qg:phrase text="Full screen" as="Pantalla completa" />
<qg:phrase text="on" as="sí" />
<qg:phrase text="off" as="no" />
<qg:phrase text="Back" as="Volver" />
<qg:phrase text="Fire" as="Disparar" />
</qg:language>
<qg:scene name="title" width="320" height="240" background="#2e3b24">
<qg:hud position="top-center" size="32"><qg:text value="{tr('SIEGE')}" /></qg:hud>
<qg:menu position="center" size="14">
<qg:button label="Play alone"><q:call function="anew" /><qg:goto-scene name="keep" fade="0.3" /></qg:button>
<qg:button label="Play together"><q:call function="anew" /><qg:goto-scene name="together" /></qg:button>
<qg:button label="Options"><qg:goto-scene name="options" /></qg:button>
<qg:button label="{tr('Language') + ': ' + language()}"><qg:next-language /></qg:button>
<qg:button label="Quit" if="{can_quit()}"><qg:quit /></qg:button>
</qg:menu>
<q:function name="anew">
<q:set name="score" value="0" />
<q:set name="hearts" value="5" />
<q:set name="wave" value="0" />
</q:function>
</qg:scene>
<qg:scene name="together" width="320" height="240" background="#2e3b24">
<qg:hud position="top-center" size="10"><qg:text value="{tr('TWO PLAYERS, EACH ON THEIR OWN MACHINE')}" /></qg:hud>
<qg:lobby local="keep" local-label="Play alone" size="12" />
<qg:on-input action="cancel">
<qg:leave />
<qg:goto-scene name="title" />
</qg:on-input>
</qg:scene>
<!-- The keep: grass all round, the walls with a gate on each side, the tower
in the middle. Things are drawn by how low they stand (y-sort), so a
raider behind the tower is hidden by it. -->
<qg:scene name="keep" width="320" height="240" background="#3c5a2c" seed="5" y-sort="true">
<q:set name="message" value="" />
<qg:tilemap tileset="ground" collision="true">
1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,2,1,1,1
1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1
1,1,1,1,1,2,1,1,2,1,1,1,1,1,1,1,1,2,1,2
1,1,2,1,1,4,4,4,4,4,3,4,4,4,4,4,1,1,1,1
1,1,2,1,1,4,3,3,3,3,3,3,3,3,3,4,1,1,1,2
1,1,1,1,1,4,3,3,3,3,3,3,3,3,3,4,1,1,1,1
1,1,1,1,1,4,3,3,3,3,3,3,3,3,3,4,1,1,1,1
1,1,1,1,1,3,3,3,3,3,3,3,3,3,3,3,1,1,1,1
2,1,1,1,1,4,3,3,3,3,3,3,3,3,3,4,1,1,1,1
1,1,1,1,1,4,3,3,3,3,3,3,3,3,3,4,2,1,1,1
1,1,1,2,1,4,3,3,3,3,3,3,3,3,3,4,1,1,2,1
1,1,1,1,1,4,4,4,4,4,3,4,4,4,4,4,1,1,1,1
1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
</qg:tilemap>
<qg:instance prefab="Tower" name="tower" x="168" y="120" />
<!-- the archer: walks, aims at the pointer (or with the right stick) -->
<qg:character id="p1" controller="topdown" player="1" sheet="siege" frame="0"
x="140" y="120" hitbox="12x12" speed="80" bounds="scene">
<qg:animation name="idle" frames="0" />
<qg:animation name="walk" frames="0,1" fps="8" />
<qg:label value="{tr('ARCHER')}" dy="-14" size="8" if="{net_status() != 'offline'}" />
</qg:character>
<qg:cursor id="aim" player="1" step="0" sheet="siege" frame="12" />
<!-- the knight, with two: left and right turn the horse, up spurs it on -->
<qg:character id="p2" controller="behavior" player="2" sheet="siege" frame="2"
x="196" y="120" hitbox="14x14" if="{net_status() != 'offline'}">
<qg:behavior>
<qg:steer turn="200" thrust="260" drag="1.6" max="140" />
<qg:keep-inside />
</qg:behavior>
<qg:label value="{tr('KNIGHT')}" dy="-14" size="8" />
<qg:on-collision with="raider" cooldown="20">
<q:if condition="{abs(me.vx) + abs(me.vy) > 60}">
<q:call function="struck" args="other, null, 2" />
</q:if>
</qg:on-collision>
</qg:character>
<!-- A shot leaves as fire is let go, at the pointer or the way the right
stick points: held 40 ticks or more, a bolt. While it is charging, a
line shows where it will go. -->
<qg:on-input action="fire" on="release">
<q:set name="sx" value="{strength('aim_right') - strength('aim_left')}" />
<q:set name="sy" value="{strength('aim_down') - strength('aim_up')}" />
<q:set name="dx" value="{sx if sx != 0 or sy != 0 else aim.x - p1.x}" />
<q:set name="dy" value="{sy if sx != 0 or sy != 0 else aim.y - p1.y}" />
<qg:play sound="twang" vary="0.1" />
<q:if condition="{pressed_for('fire') >= 40}">
<qg:spawn prefab="Bolt" at="p1" heading="{dx},{dy}" />
<q:else><qg:spawn prefab="Arrow" at="p1" heading="{dx},{dy}" /></q:else>
</q:if>
</qg:on-input>
<qg:timer every="2">
<q:if condition="{pressing('fire') and pressed_for('fire') >= 40}">
<qg:beam from="p1" to="aim" seconds="0.05" color="#ffe080" width="1" />
</q:if>
</qg:timer>
<!-- every three seconds the tower zaps the raider nearest it, within 90 px -->
<qg:timer every="180" from="120">
<q:set name="near" value="{nearest(tower, 'raider')}" />
<q:if condition="{near != null and distance(tower, near) < 90}">
<qg:play sound="zap" />
<qg:beam from="tower" to="near" seconds="0.2" color="#80d0ff" width="2" />
<q:call function="struck" args="near, null, 2" />
</q:if>
</qg:timer>
<!-- The waves: none left, the next comes after a breath, bigger; each
third wave brings a brute, each fourth a ram from the left. -->
<qg:when condition="{count('raider') == 0}">
<q:set name="wave" value="{wave + 1}" />
<q:set name="message" value="{tr('HERE THEY COME')}" />
<qg:play sound="horn" />
<qg:after seconds="2">
<q:set name="message" value="" />
<!-- from the edges: by turns the sides and the top or bottom, the rest from the seed -->
<q:loop var="i" from="1" to="{3 + wave * 2}">
<q:if condition="{i % 2 == 0}">
<qg:spawn prefab="Raider" x="{8 if random(0, 1) < 0.5 else 312}" y="{random(16, 224)}" />
<q:else><qg:spawn prefab="Raider" x="{random(16, 304)}" y="{8 if random(0, 1) < 0.5 else 232}" /></q:else>
</q:if>
</q:loop>
<q:if condition="{wave % 3 == 0}"><qg:spawn prefab="Brute" x="168" y="10" /></q:if>
<q:if condition="{wave % 4 == 0}"><qg:spawn prefab="Ram" x="10" y="120" /></q:if>
</qg:after>
</qg:when>
<qg:hud position="top-left" size="10"><qg:text value="{tr('SCORE') + ' ' + str(score)}" /></qg:hud>
<qg:hud position="top-center" size="10"><qg:text value="{tr('WAVE') + ' ' + str(int(wave))}" /></qg:hud>
<qg:hud position="top-right" size="10"><qg:text value="{tr('TOWER') + ' ' + str(int(hearts))}" /></qg:hud>
<qg:hud position="center" size="14"><qg:text bind="message" /></qg:hud>
</qg:scene>
<qg:scene name="fallen" width="320" height="240" background="#1c1a14">
<qg:hud position="top-center" size="14"><qg:text value="{tr('THE KEEP HAS FALLEN')}" /></qg:hud>
<qg:hud position="center" size="12">
<qg:text value="{tr('SCORE') + ' ' + str(score)}" />
<qg:text value="{tr('BEST') + ' ' + (str(best[0]) if len(best) > 0 else '-')}" />
</qg:hud>
<qg:menu position="bottom-center" size="12">
<qg:button label="To the title"><qg:leave /><qg:goto-scene name="title" /></qg:button>
</qg:menu>
</qg:scene>
<!-- ===== Options: how loud, the full screen, the keys; kept between runs ===== -->
<qg:scene name="options" width="320" height="240" background="#2e3b24">
<qg:hud position="top-center" size="24"><qg:text value="{tr('OPTIONS')}" /></qg:hud>
<qg:menu position="center" size="14">
<qg:slider label="Sounds" bus="effects" />
<qg:button label="{tr('Full screen') + ': ' + (tr('on') if fullscreen() else tr('off'))}"><qg:fullscreen /></qg:button>
<qg:rebind label="Fire" action="fire" />
<qg:button label="Back"><qg:goto-scene name="title" /></qg:button>
</qg:menu>
</qg:scene>
</q:application>Crates
projects/crates/crates.q — a box-pushing puzzle in three rooms drawn in Tiled (two tilesets, flipped tiles, animated water, the doors, a sign, a gull's path and each room's title from the maps): a worker that steps or walks where it is clicked (controller="pointer") and pushes crates (pushable=), barrels made from crates (extends=), a foreman whose words are typed in beside his face with a choice (qg:choice), a gate the camera goes to show open (qg:camera-to), and a key carried over the worker's head (qg:attach).
<q:application id="crates" type="game">
<!-- Crates: a box-pushing puzzle on a dockside, the game that drives Phase 3
of PLAN_GAMES_3.md. Three rooms drawn in Tiled (rooms/*.tmx): their
walls, the harbour's water, the pads and every crate, gate and key are
the maps', and so are the doors between them, the sign by each door, a
gull's way over the water and each room's title and hint. The worker
walks a cell at a time with the keys, or goes where it is clicked by
the shortest way, and pushes what it walks into. The art is drawn for
it by scripts/art/crates.py (CC0); the sounds are Kenney's Pixel
Platformer (CC0). -->
<q:set name="message" value="" />
<!-- the crates of the room standing on a pad: a pad is a tile of the map's pads layer -->
<q:function name="placed">
<q:set name="n" value="0" type="number" />
<qg:for-each tag="crate" var="c">
<q:if condition="{tile_at(c.col, c.row, 'pads') != 0}">
<q:set name="n" value="{n + 1}" />
</q:if>
</qg:for-each>
<q:return value="{n}" />
</q:function>
<!-- the gates that close a room open, and the camera goes to show it -->
<q:function name="open_gates">
<qg:for-each tag="gate" var="g">
<q:if condition="{g.state == 'shut'}">
<qg:camera-to target="g" zoom="2" seconds="0.5" hold="0.8" />
<qg:emit particles="dust" at="g" />
<qg:destroy target="g" />
</q:if>
</qg:for-each>
<qg:play sound="open" />
</q:function>
<qg:input action="restart" keys="R" />
<!-- warehouse.png: 1 concrete, 2 brick, 3 a corner, 4 an edge, 5 a pad, 6 planks, 7 an arrow, 8 stripes.
water.png: the harbour's four frames in turn, and a pier. -->
<qg:tileset name="warehouse" src="assets/warehouse.png" tile="16" />
<qg:tileset name="water" src="assets/water.png" tile="16">
<qg:tile frame="0" frames="4" fps="4" />
</qg:tileset>
<qg:spritesheet name="people" src="assets/people.png" tile="16" />
<qg:spritesheet name="faces" src="assets/faces.png" tile="32" />
<qg:sound bus="effects" name="push" src="assets/kenney/audio/block.ogg" />
<qg:sound bus="effects" name="open" src="assets/kenney/audio/powerup.ogg" />
<qg:sound bus="effects" name="pick" src="assets/kenney/audio/coin.ogg" />
<qg:sound bus="effects" name="done" src="assets/kenney/audio/win.ogg" />
<qg:particles name="dust" preset="smoke" color="#c8c0b0" />
<qg:particles name="cheer" preset="confetti" />
<!-- a crate; a barrel is one, drawn round -->
<qg:prefab name="Crate" tag="crate" sheet="people" frame="6" hitbox="14x14" solid="true" pushable="true" />
<qg:prefab name="Barrel" extends="Crate" frame="7" />
<qg:prefab name="Foreman" tag="foreman" sheet="people" frame="4" hitbox="14x14" solid="true">
<qg:state name="waiting" initial="true" />
<qg:state name="pleased" frame="5" />
</qg:prefab>
<!-- a gate is shut until the room's crates are in place; one with state locked in the map opens with the key -->
<qg:prefab name="Gate" tag="gate" sheet="people" frame="8" hitbox="16x16" solid="true">
<qg:state name="shut" initial="true" />
<qg:state name="locked" frame="9" />
</qg:prefab>
<qg:prefab name="Key" tag="key" sheet="people" frame="10" hitbox="12x12" />
<qg:prefab name="Gull" tag="gull" sheet="people" frame="11" hitbox="8x8" ai="path" speed="40" layer="2">
<qg:animation name="fly" frames="11,12" fps="4" />
<qg:on-screen-exit><qg:destroy /></qg:on-screen-exit>
</qg:prefab>
<!-- on a phone: what the keys do, under a finger, in every scene -->
<qg:touch-button action="restart" label="R" x="304" y="176" size="24" />
<qg:language lang="pt" name="Português">
<qg:phrase text="New game" as="Novo jogo" />
<qg:phrase text="Continue" as="Continuar" />
<qg:phrase text="Language" as="Idioma" />
<qg:phrase text="PADS" as="PLATAFORMAS" />
<qg:phrase text="THE DOCK" as="O CAIS" />
<qg:phrase text="THE YARD" as="O PÁTIO" />
<qg:phrase text="THE LOFT" as="O SÓTÃO" />
<qg:phrase text="PUSH THE CRATES ONTO THE PADS." as="EMPURRE AS CAIXAS PARA AS PLATAFORMAS." />
<qg:phrase text="THE KEY IS ON THE PIER." as="A CHAVE ESTÁ NO PÍER." />
<qg:phrase text="BARRELS PUSH LIKE CRATES." as="BARRIS SE EMPURRAM COMO CAIXAS." />
<qg:phrase text="THE GATE OPENS." as="O PORTÃO SE ABRE." />
<qg:phrase text="SHUT TILL THE CRATES ARE IN." as="FECHADO ATÉ AS CAIXAS ESTAREM NO LUGAR." />
<qg:phrase text="LOCKED. THE KEY IS ON THE PIER." as="TRANCADO. A CHAVE ESTÁ NO PÍER." />
<qg:phrase text="GOT THE KEY." as="PEGOU A CHAVE." />
<qg:phrase text="UNLOCKED." as="DESTRANCADO." />
<qg:phrase text="ALL STOWED. GOOD WORK." as="TUDO GUARDADO. BOM TRABALHO." />
<qg:phrase text="R: START THE ROOM OVER" as="R: RECOMEÇAR A SALA" />
<qg:phrase text="FOREMAN" as="CAPATAZ" />
<qg:phrase text="Morning. Two crates came in on the boat." as="Bom dia. Chegaram duas caixas no barco." />
<qg:phrase text="Push them onto the yellow pads. Need a hand?" as="Empurre-as para as plataformas amarelas. Precisa de ajuda?" />
<qg:phrase text="How do I push?" as="Como eu empurro?" />
<qg:phrase text="On it, boss." as="Deixa comigo, chefe." />
<qg:phrase text="Walk into a crate and it moves on, if nothing is behind it." as="Ande contra uma caixa e ela avança, se nada estiver atrás dela." />
<qg:phrase text="Or click a crate next to you." as="Ou clique numa caixa ao seu lado." />
<qg:phrase text="Stuck? R starts the room over." as="Travou? R recomeça a sala." />
<qg:phrase text="The gate opens when they are in place." as="O portão abre quando elas estiverem no lugar." />
<qg:phrase text="Last room. The barrels too." as="Última sala. Os barris também." />
<qg:phrase text="That's the lot. Good work!" as="É tudo. Bom trabalho!" />
<qg:phrase text="Another shift?" as="Mais um turno?" />
<qg:phrase text="Home time." as="Hora de ir para casa." />
<qg:phrase text="Options" as="Opções" />
<qg:phrase text="OPTIONS" as="OPÇÕES" />
<qg:phrase text="Sounds" as="Sons" />
<qg:phrase text="Full screen" as="Tela cheia" />
<qg:phrase text="on" as="sim" />
<qg:phrase text="off" as="não" />
<qg:phrase text="Back" as="Voltar" />
<qg:phrase text="Restart" as="Recomeçar" />
</qg:language>
<qg:language lang="es" name="Español">
<qg:phrase text="New game" as="Nueva partida" />
<qg:phrase text="Continue" as="Continuar" />
<qg:phrase text="Language" as="Idioma" />
<qg:phrase text="PADS" as="PLATAFORMAS" />
<qg:phrase text="THE DOCK" as="EL MUELLE" />
<qg:phrase text="THE YARD" as="EL PATIO" />
<qg:phrase text="THE LOFT" as="EL DESVÁN" />
<qg:phrase text="PUSH THE CRATES ONTO THE PADS." as="EMPUJA LAS CAJAS A LAS PLATAFORMAS." />
<qg:phrase text="THE KEY IS ON THE PIER." as="LA LLAVE ESTÁ EN EL MUELLE." />
<qg:phrase text="BARRELS PUSH LIKE CRATES." as="LOS BARRILES SE EMPUJAN COMO LAS CAJAS." />
<qg:phrase text="THE GATE OPENS." as="LA PUERTA SE ABRE." />
<qg:phrase text="SHUT TILL THE CRATES ARE IN." as="CERRADA HASTA QUE LAS CAJAS ESTÉN EN SU SITIO." />
<qg:phrase text="LOCKED. THE KEY IS ON THE PIER." as="CERRADA CON LLAVE. LA LLAVE ESTÁ EN EL MUELLE." />
<qg:phrase text="GOT THE KEY." as="TIENES LA LLAVE." />
<qg:phrase text="UNLOCKED." as="ABIERTA." />
<qg:phrase text="ALL STOWED. GOOD WORK." as="TODO GUARDADO. BUEN TRABAJO." />
<qg:phrase text="R: START THE ROOM OVER" as="R: EMPEZAR LA SALA DE NUEVO" />
<qg:phrase text="FOREMAN" as="CAPATAZ" />
<qg:phrase text="Morning. Two crates came in on the boat." as="Buenos días. Llegaron dos cajas en el barco." />
<qg:phrase text="Push them onto the yellow pads. Need a hand?" as="Empújalas a las plataformas amarillas. ¿Necesitas ayuda?" />
<qg:phrase text="How do I push?" as="¿Cómo empujo?" />
<qg:phrase text="On it, boss." as="Enseguida, jefe." />
<qg:phrase text="Walk into a crate and it moves on, if nothing is behind it." as="Camina contra una caja y avanza, si nada hay detrás." />
<qg:phrase text="Or click a crate next to you." as="O haz clic en una caja a tu lado." />
<qg:phrase text="Stuck? R starts the room over." as="¿Atascado? R empieza la sala de nuevo." />
<qg:phrase text="The gate opens when they are in place." as="La puerta se abre cuando estén en su sitio." />
<qg:phrase text="Last room. The barrels too." as="Última sala. Los barriles también." />
<qg:phrase text="That's the lot. Good work!" as="Eso es todo. ¡Buen trabajo!" />
<qg:phrase text="Another shift?" as="¿Otro turno?" />
<qg:phrase text="Home time." as="Hora de volver a casa." />
<qg:phrase text="Options" as="Opciones" />
<qg:phrase text="OPTIONS" as="OPCIONES" />
<qg:phrase text="Sounds" as="Sonidos" />
<qg:phrase text="Full screen" as="Pantalla completa" />
<qg:phrase text="on" as="sí" />
<qg:phrase text="off" as="no" />
<qg:phrase text="Back" as="Volver" />
<qg:phrase text="Restart" as="Reiniciar" />
</qg:language>
<!-- ===== The title ===== -->
<qg:scene name="title" width="320" height="192" background="#20283a">
<qg:hud position="top-center" size="32"><qg:text value="{'CRATES'}" /></qg:hud>
<qg:menu position="center">
<qg:button label="New game"><qg:goto-scene name="dock" fade="0.3" /></qg:button>
<qg:button label="Continue" if="{has_save()}"><qg:continue /></qg:button>
<qg:button label="Options"><qg:goto-scene name="options" /></qg:button>
<qg:button label="{tr('Language') + ': ' + language()}"><qg:next-language /></qg:button>
</qg:menu>
</qg:scene>
<!-- ===== The dock: two crates, and the foreman to talk to ===== -->
<qg:scene name="dock" width="320" height="192" background="#20283a" save="true">
<qg:tilemap tileset="warehouse" src="rooms/dock.tmx" />
<qg:cursor player="1" grid="16" sheet="people" frame="13" />
<qg:character id="worker" controller="pointer" sheet="people" frame="0" x="8" y="88" hitbox="12x12"
step-frames="10">
<qg:animation name="idle" frames="0" />
<qg:animation name="walk" frames="1,2" fps="12" />
<qg:animation name="bump" frames="3" />
<qg:on-collision with="crate"><qg:play sound="push" /></qg:on-collision>
<qg:on-collision with="sign"><q:set name="message" value="{tiled('hint')}" /></qg:on-collision>
<qg:on-collision with="foreman"><qg:say dialogue="foreman" /></qg:on-collision>
<qg:on-collision with="gate"><q:set name="message" value="SHUT TILL THE CRATES ARE IN." /></qg:on-collision>
</qg:character>
<qg:camera follow="worker" bounds="tilemap" />
<qg:dialogue name="foreman" size="9" speed="40" sheet="faces">
<qg:line who="FOREMAN" portrait="0" text="Morning. Two crates came in on the boat." />
<qg:line who="FOREMAN" portrait="0" text="Push them onto the yellow pads. Need a hand?">
<qg:choice label="How do I push?"><qg:say dialogue="how" /></qg:choice>
<qg:choice label="On it, boss." />
</qg:line>
<qg:line who="FOREMAN" portrait="0" text="The gate opens when they are in place." />
</qg:dialogue>
<qg:dialogue name="how" size="9" speed="40" sheet="faces">
<qg:line who="FOREMAN" portrait="0" text="Walk into a crate and it moves on, if nothing is behind it." />
<qg:line who="FOREMAN" portrait="0" text="Or click a crate next to you." />
<qg:line who="FOREMAN" portrait="0" text="Stuck? R starts the room over." />
</qg:dialogue>
<qg:when condition="{placed() == count('crate')}">
<q:set name="message" value="THE GATE OPENS." />
<q:call function="open_gates" />
</qg:when>
<qg:on-input action="restart"><qg:goto-scene name="dock" /></qg:on-input>
<qg:hud position="top-left" size="12">
<qg:text value="{tr(tiled('title')) + ' ' + tr('PADS') + ' ' + str(placed()) + '/' + str(count('crate'))}" />
<qg:text bind="message" />
</qg:hud>
</qg:scene>
<!-- ===== The yard: the harbour, a key on the pier, a locked gate ===== -->
<qg:scene name="yard" width="320" height="192" background="#20283a" save="true">
<qg:tilemap tileset="warehouse" src="rooms/yard.tmx" />
<qg:cursor player="1" grid="16" sheet="people" frame="13" />
<qg:character id="worker" controller="pointer" sheet="people" frame="0" x="8" y="88" hitbox="12x12"
step-frames="10">
<qg:animation name="idle" frames="0" />
<qg:animation name="walk" frames="1,2" fps="12" />
<qg:animation name="bump" frames="3" />
<qg:on-collision with="crate"><qg:play sound="push" /></qg:on-collision>
<qg:on-collision with="sign"><q:set name="message" value="{tiled('hint')}" /></qg:on-collision>
<qg:on-collision with="key">
<qg:attach target="other" dy="-10" />
<qg:play sound="pick" />
<q:set name="message" value="GOT THE KEY." />
</qg:on-collision>
<qg:on-collision with="gate">
<q:if condition="{other.state == 'locked' and carrying(me, 'key') != null}">
<qg:for-each tag="key" var="k"><qg:destroy target="k" /></qg:for-each>
<qg:emit particles="dust" at="other" />
<qg:destroy target="other" />
<qg:play sound="open" />
<q:set name="message" value="UNLOCKED." />
<q:elseif condition="{other.state == 'locked'}">
<q:set name="message" value="LOCKED. THE KEY IS ON THE PIER." />
</q:elseif>
<q:else><q:set name="message" value="SHUT TILL THE CRATES ARE IN." /></q:else>
</q:if>
</qg:on-collision>
</qg:character>
<qg:camera follow="worker" bounds="tilemap" />
<qg:when condition="{placed() == count('crate')}">
<q:set name="message" value="THE GATE OPENS." />
<q:call function="open_gates" />
</qg:when>
<!-- now and then a gull, along the way drawn in the map -->
<qg:timer every="420" from="60"><qg:spawn prefab="Gull" at="path" path="gull" /></qg:timer>
<qg:on-input action="restart"><qg:goto-scene name="yard" /></qg:on-input>
<qg:hud position="top-left" size="12">
<qg:text value="{tr(tiled('title')) + ' ' + tr('PADS') + ' ' + str(placed()) + '/' + str(count('crate'))}" />
<qg:text bind="message" />
</qg:hud>
</qg:scene>
<!-- ===== The loft: crates and barrels, and the foreman waiting at the end ===== -->
<qg:scene name="loft" width="320" height="192" background="#20283a" save="true">
<qg:tilemap tileset="warehouse" src="rooms/loft.tmx" />
<qg:cursor player="1" grid="16" sheet="people" frame="13" />
<qg:character id="worker" controller="pointer" sheet="people" frame="0" x="8" y="88" hitbox="12x12"
step-frames="10">
<qg:animation name="idle" frames="0" />
<qg:animation name="walk" frames="1,2" fps="12" />
<qg:animation name="bump" frames="3" />
<qg:on-collision with="crate"><qg:play sound="push" /></qg:on-collision>
<qg:on-collision with="sign"><q:set name="message" value="{tiled('hint')}" /></qg:on-collision>
<qg:on-collision with="foreman">
<q:if condition="{other.state == 'pleased'}">
<qg:say dialogue="done" />
<q:else><qg:say dialogue="last" /></q:else>
</q:if>
</qg:on-collision>
</qg:character>
<qg:camera follow="worker" bounds="tilemap" />
<qg:dialogue name="last" size="9" speed="40" sheet="faces">
<qg:line who="FOREMAN" portrait="0" text="Last room. The barrels too." />
</qg:dialogue>
<qg:dialogue name="done" size="9" speed="40" sheet="faces">
<qg:line who="FOREMAN" portrait="1" text="That's the lot. Good work!">
<qg:choice label="Another shift?"><qg:goto-scene name="dock" fade="0.5" /></qg:choice>
<qg:choice label="Home time."><qg:goto-scene name="title" fade="0.5" /></qg:choice>
</qg:line>
</qg:dialogue>
<qg:when condition="{placed() == count('crate')}">
<q:set name="message" value="ALL STOWED. GOOD WORK." />
<qg:play sound="done" />
<qg:for-each tag="foreman" var="f">
<qg:become target="f" state="pleased" />
<qg:camera-to target="f" zoom="2" seconds="0.5" hold="1" />
<qg:emit particles="cheer" at="f" />
</qg:for-each>
</qg:when>
<qg:on-input action="restart"><qg:goto-scene name="loft" /></qg:on-input>
<qg:hud position="top-left" size="12">
<qg:text value="{tr(tiled('title')) + ' ' + tr('PADS') + ' ' + str(placed()) + '/' + str(count('crate'))}" />
<qg:text bind="message" />
</qg:hud>
</qg:scene>
<!-- ===== Options: how loud, the full screen, the keys; kept between runs ===== -->
<qg:scene name="options" width="320" height="192" background="#20283a">
<qg:hud position="top-center" size="24"><qg:text value="{tr('OPTIONS')}" /></qg:hud>
<qg:menu position="center" size="12">
<qg:slider label="Sounds" bus="effects" />
<qg:button label="{tr('Full screen') + ': ' + (tr('on') if fullscreen() else tr('off'))}"><qg:fullscreen /></qg:button>
<qg:rebind label="Restart" action="restart" />
<qg:button label="Back"><qg:goto-scene name="title" /></qg:button>
</qg:menu>
</qg:scene>
</q:application>Rally
projects/rally/rally.q — two cars round a circuit drawn in Tiled, wider and taller than the screen: a camera for each (qg:camera player=), so on one keyboard the screen splits in two and over the network each machine follows its own car; a barrier that lifts at the green light (a solid prefab destroyed by a qg:timer), and laps counted at the line only after halfway round (the map's zones).
<q:application id="rally" type="game">
<!-- Rally: two cars round a circuit seen from above, on one keyboard or over
the network. The circuit is wider and taller than the screen, so each
car has a camera of its own (qg:camera player=): on one machine the
screen splits in two, red on the left, blue on the right; over the
network each machine shows its own car on the whole screen. The barrier
lifts at the green light; the first to finish three laps wins. A lap
counts at the line only after halfway round, at the top of the loop in
the infield. The art and the circuit (a Tiled map) are drawn by
scripts/art/rally.py (CC0); the sounds are Kenney's Pixel Platformer
(CC0). -->
<!-- how many laps win the race -->
<q:set name="race_laps" value="3" type="number" />
<!-- the last winner: 1 red, 2 blue -->
<q:set name="winner" value="0" type="number" />
<qg:tileset name="track" src="assets/track.png" tile="16" />
<qg:spritesheet name="cars" src="assets/cars.png" tile="16" />
<qg:spritesheet name="gate" src="assets/gate.png" tile="16x80" />
<qg:sound bus="effects" name="beep" src="assets/kenney/audio/block.ogg" />
<qg:sound bus="effects" name="go" src="assets/kenney/audio/powerup.ogg" />
<qg:sound bus="effects" name="lap" src="assets/kenney/audio/coin.ogg" />
<qg:sound bus="effects" name="flag" src="assets/kenney/audio/win.ogg" />
<!-- Red drives with W A S D, blue with the arrows; over the network, each with W A S D. -->
<qg:input action="up" keys="W, JoyUp" player="1" />
<qg:input action="down" keys="S, JoyDown" player="1" />
<qg:input action="left" keys="A, JoyLeft" player="1" />
<qg:input action="right" keys="D, JoyRight" player="1" />
<qg:input action="up" keys="Up" player="2" />
<qg:input action="down" keys="Down" player="2" />
<qg:input action="left" keys="Left" player="2" />
<qg:input action="right" keys="Right" player="2" />
<qg:multiplayer players="2" start="race" />
<!-- The barrier across the road at the start; it lifts at the green light. -->
<qg:prefab name="Gate" tag="gate" sheet="gate" hitbox="16x80" solid="true" />
<!-- on a phone: a stick for the car, under a finger, in every scene -->
<qg:touch-stick x="48" y="168" size="56" />
<qg:language lang="pt" name="Português">
<qg:phrase text="RALLY" as="RALI" />
<qg:phrase text="Two players, one keyboard" as="Dois jogadores, um teclado" />
<qg:phrase text="Over the network" as="Pela rede" />
<qg:phrase text="Options" as="Opções" />
<qg:phrase text="Language" as="Idioma" />
<qg:phrase text="Quit" as="Sair" />
<qg:phrase text="RED: W A S D BLUE: THE ARROWS" as="VERMELHO: W A S D AZUL: AS SETAS" />
<qg:phrase text="TWO CARS, TWO MACHINES. ESCAPE: BACK." as="DOIS CARROS, DUAS MÁQUINAS. ESC: VOLTAR." />
<qg:phrase text="RED" as="VERMELHO" />
<qg:phrase text="BLUE" as="AZUL" />
<qg:phrase text="LAP" as="VOLTA" />
<qg:phrase text="GO!" as="JÁ!" />
<qg:phrase text="WINS" as="VENCE" />
<qg:phrase text="Again" as="De novo" />
<qg:phrase text="To the title" as="Para o título" />
<qg:phrase text="OPTIONS" as="OPÇÕES" />
<qg:phrase text="Sounds" as="Sons" />
<qg:phrase text="Full screen" as="Tela cheia" />
<qg:phrase text="on" as="sim" />
<qg:phrase text="off" as="não" />
<qg:phrase text="Red up" as="Vermelho sobe" />
<qg:phrase text="Red down" as="Vermelho desce" />
<qg:phrase text="Red left" as="Vermelho esquerda" />
<qg:phrase text="Red right" as="Vermelho direita" />
<qg:phrase text="Blue up" as="Azul sobe" />
<qg:phrase text="Blue down" as="Azul desce" />
<qg:phrase text="Blue left" as="Azul esquerda" />
<qg:phrase text="Blue right" as="Azul direita" />
<qg:phrase text="Back" as="Voltar" />
</qg:language>
<qg:language lang="es" name="Español">
<qg:phrase text="RALLY" as="RALLY" />
<qg:phrase text="Two players, one keyboard" as="Dos jugadores, un teclado" />
<qg:phrase text="Over the network" as="Por la red" />
<qg:phrase text="Options" as="Opciones" />
<qg:phrase text="Language" as="Idioma" />
<qg:phrase text="Quit" as="Salir" />
<qg:phrase text="RED: W A S D BLUE: THE ARROWS" as="ROJO: W A S D AZUL: LAS FLECHAS" />
<qg:phrase text="TWO CARS, TWO MACHINES. ESCAPE: BACK." as="DOS COCHES, DOS MÁQUINAS. ESC: VOLVER." />
<qg:phrase text="RED" as="ROJO" />
<qg:phrase text="BLUE" as="AZUL" />
<qg:phrase text="LAP" as="VUELTA" />
<qg:phrase text="GO!" as="¡YA!" />
<qg:phrase text="WINS" as="GANA" />
<qg:phrase text="Again" as="Otra vez" />
<qg:phrase text="To the title" as="Al título" />
<qg:phrase text="OPTIONS" as="OPCIONES" />
<qg:phrase text="Sounds" as="Sonidos" />
<qg:phrase text="Full screen" as="Pantalla completa" />
<qg:phrase text="on" as="sí" />
<qg:phrase text="off" as="no" />
<qg:phrase text="Red up" as="Rojo arriba" />
<qg:phrase text="Red down" as="Rojo abajo" />
<qg:phrase text="Red left" as="Rojo izquierda" />
<qg:phrase text="Red right" as="Rojo derecha" />
<qg:phrase text="Blue up" as="Azul arriba" />
<qg:phrase text="Blue down" as="Azul abajo" />
<qg:phrase text="Blue left" as="Azul izquierda" />
<qg:phrase text="Blue right" as="Azul derecha" />
<qg:phrase text="Back" as="Volver" />
</qg:language>
<qg:scene name="title" width="384" height="216" background="#2c5a2a">
<qg:sprite sheet="cars" frame="0" x="150" y="56" gd:scale="3,3" />
<qg:sprite sheet="cars" frame="3" x="234" y="56" gd:scale="3,3" />
<qg:hud position="top-center" size="28"><qg:text value="{tr('RALLY')}" /></qg:hud>
<qg:menu position="center" size="14">
<qg:button label="Two players, one keyboard"><qg:goto-scene name="race" fade="0.3" /></qg:button>
<qg:button label="Over the network"><qg:goto-scene name="together" /></qg:button>
<qg:button label="Options"><qg:goto-scene name="options" /></qg:button>
<qg:button label="{tr('Language') + ': ' + language()}"><qg:next-language /></qg:button>
<!-- a desktop build quits; a page in a browser has nothing to quit to -->
<qg:button label="Quit" if="{can_quit()}"><qg:quit /></qg:button>
</qg:menu>
<qg:hud position="bottom-center" size="10"><qg:text value="{tr('RED: W A S D BLUE: THE ARROWS')}" /></qg:hud>
</qg:scene>
<!-- Two machines: one hosts, the other joins its address; the race starts as it does. -->
<qg:scene name="together" width="384" height="216" background="#2c5a2a">
<qg:hud position="top-center" size="10"><qg:text value="{tr('TWO CARS, TWO MACHINES. ESCAPE: BACK.')}" /></qg:hud>
<qg:lobby local="race" local-label="Two players, one keyboard" size="12" />
<qg:on-input action="cancel">
<qg:leave />
<qg:goto-scene name="title" />
</qg:on-input>
</qg:scene>
<!-- The race: the circuit, the two cars on the grid behind the barrier, a
camera for each, and the laps each has done. -->
<qg:scene name="race" width="384" height="216" background="#2c5a2a" seed="1">
<q:set name="laps" type="array" value="[0, 0]" />
<!-- halfway round on this lap: the line counts it only then -->
<q:set name="half" type="array" value="[false, false]" />
<qg:tilemap tileset="track" src="track/track.tmx" />
<qg:character id="red" controller="topdown" player="1" sheet="cars" frame="0" x="440" y="56"
hitbox="12x12" speed="150">
<qg:animation name="walk" frames="0" />
<qg:animation name="walk-up" frames="1" />
<qg:animation name="walk-down" frames="2" />
<qg:on-collision with="half"><q:call function="halfway" args="1" /></qg:on-collision>
<qg:on-collision with="finish"><q:call function="across" args="1" /></qg:on-collision>
</qg:character>
<qg:character id="blue" controller="topdown" player="2" sheet="cars" frame="3" x="440" y="88"
hitbox="12x12" speed="150">
<qg:animation name="walk" frames="3" />
<qg:animation name="walk-up" frames="4" />
<qg:animation name="walk-down" frames="5" />
<qg:on-collision with="half"><q:call function="halfway" args="2" /></qg:on-collision>
<qg:on-collision with="finish"><q:call function="across" args="2" /></qg:on-collision>
</qg:character>
<qg:instance prefab="Gate" name="gate" x="472" y="72" />
<!-- one view each: the screen splits on one machine -->
<qg:camera player="1" follow="red" smooth="8" lead="24" />
<qg:camera player="2" follow="blue" smooth="8" lead="24" />
<!-- the lights: three beeps, and the barrier lifts -->
<qg:timer after="30"><qg:play sound="beep" /></qg:timer>
<qg:timer after="90"><qg:play sound="beep" /></qg:timer>
<qg:timer after="150"><qg:play sound="beep" /></qg:timer>
<qg:timer after="210">
<qg:play sound="go" />
<qg:destroy target="gate" />
</qg:timer>
<q:function name="halfway" params="n">
<q:set name="half" index="{n - 1}" value="{true}" />
</q:function>
<!-- over the line: a lap, if it went halfway round first; the last one wins -->
<q:function name="across" params="n">
<q:if condition="{half[n - 1]}">
<q:set name="half" index="{n - 1}" value="{false}" />
<q:set name="laps" index="{n - 1}" value="{laps[n - 1] + 1}" />
<qg:play sound="lap" />
<q:if condition="{laps[n - 1] >= race_laps and winner == 0}">
<q:set name="winner" value="{n}" />
<qg:play sound="flag" />
<qg:goto-scene name="flag" fade="0.5" />
</q:if>
</q:if>
</q:function>
<qg:hud position="top-left" size="10">
<qg:text value="{tr('RED') + ' ' + tr('LAP') + ' ' + str(min(laps[0] + 1, race_laps)) + '/' + str(race_laps)}"
color="#ff8070" />
</qg:hud>
<qg:hud position="top-right" size="10">
<qg:text value="{tr('BLUE') + ' ' + tr('LAP') + ' ' + str(min(laps[1] + 1, race_laps)) + '/' + str(race_laps)}"
color="#88b0ff" />
</qg:hud>
<qg:hud position="center" size="24">
<qg:text value="{'3' if ticks() < 90 else '2' if ticks() < 150 else '1' if ticks() < 210 else tr('GO!') if ticks() < 270 else ''}" />
</qg:hud>
</qg:scene>
<qg:scene name="flag" width="384" height="216" background="#1c2a1c">
<qg:hud position="top-center" size="20" if="{winner == 1}">
<qg:text value="{tr('RED') + ' ' + tr('WINS')}" color="#ff8070" />
</qg:hud>
<qg:hud position="top-center" size="20" if="{winner == 2}">
<qg:text value="{tr('BLUE') + ' ' + tr('WINS')}" color="#88b0ff" />
</qg:hud>
<qg:hud position="center"><qg:icon sheet="cars" frame="{0 if winner == 1 else 3}" scale="4" /></qg:hud>
<qg:menu position="bottom-center" size="12">
<qg:button label="Again"><q:set name="winner" value="0" /><qg:goto-scene name="race" fade="0.3" /></qg:button>
<qg:button label="To the title"><q:set name="winner" value="0" /><qg:leave /><qg:goto-scene name="title" /></qg:button>
</qg:menu>
</qg:scene>
<!-- ===== Options: how loud, the full screen, the keys; kept between runs ===== -->
<qg:scene name="options" width="384" height="216" background="#2c5a2a">
<qg:hud position="top-center" size="24"><qg:text value="{tr('OPTIONS')}" /></qg:hud>
<qg:menu position="center" size="12" columns="2" rows="4">
<qg:slider label="Sounds" bus="effects" />
<qg:button label="{tr('Full screen') + ': ' + (tr('on') if fullscreen() else tr('off'))}"><qg:fullscreen /></qg:button>
<qg:rebind label="Red up" action="up" />
<qg:rebind label="Blue up" action="up" player="2" />
<qg:rebind label="Red down" action="down" />
<qg:rebind label="Blue down" action="down" player="2" />
<qg:rebind label="Red left" action="left" />
<qg:rebind label="Blue left" action="left" player="2" />
<qg:rebind label="Red right" action="right" />
<qg:rebind label="Blue right" action="right" player="2" />
<qg:button label="Back"><qg:goto-scene name="title" /></qg:button>
</qg:menu>
</qg:scene>
</q:application>Marches
projects/marches/marches.q — a skirmish on a map of hexes (qg:tileset shape="hex"), turn by turn: riders that ride where they are clicked by the cheapest way (qg:travel budget=), forests, hills and marshes that cost more to cross (qg:tile cost=), a pointer that snaps to the hexes (qg:cursor cells=), what a ride there would cost (cost()), and raiders that ride at the nearest rider in their turn (qg:for-each, qg:travel).
<q:application id="marches" type="game">
<!-- Marches: a skirmish on a map of hexes, turn by turn. Three riders hold the
keep by the river against four raiders who come from the east by the
fords. Click a rider, then a hex: it rides there by the cheapest way, four
moves a turn, a forest or a ford costing two, the hills and the marsh three;
the river and the rocks stop it. A rider next to a raider strikes it with a
click on the raider, and is spent for the turn. End the turn (E, or the
button), and the raiders ride at the nearest rider, three moves each, and
strike. The raiders all fallen, the march is held; the riders all fallen, or
a raider in the keep, it is lost. Written for PLAN_GAMES_3.md's Phase 6: hex
maps, terrain costs, qg:travel and cost(). The art is drawn by
scripts/art/marches.py (CC0); the sounds are Kenney's (CC0). -->
<qg:input action="end-turn" keys="E" />
<!-- on a phone a tap is a click; and the end of the turn under a finger -->
<qg:touch-button action="end-turn" label="E" x="380" y="230" size="40" />
<!-- the land: hexes with their points up, and what a step onto each costs -->
<qg:tileset name="land" src="assets/land.png" tile="28x32" shape="hex">
<qg:tile frame="0" shape="none" /> <!-- grass -->
<qg:tile frame="1" shape="none" cost="2" /> <!-- forest -->
<qg:tile frame="2" shape="none" cost="3" /> <!-- hills -->
<qg:tile frame="3" shape="none" cost="3" /> <!-- marsh -->
<!-- 4, the river, and 5, the rocks: solid -->
<qg:tile frame="6" shape="none" cost="2" /> <!-- a ford -->
<qg:tile frame="7" shape="none" /> <!-- the keep -->
</qg:tileset>
<qg:spritesheet name="units" src="assets/units.png" tile="24" />
<qg:spritesheet name="marks" src="assets/marks.png" tile="28x32" />
<qg:sound bus="effects" name="ride" src="assets/kenney/audio/jump.ogg" />
<qg:sound bus="effects" name="blow" src="assets/kenney/audio/stomp.ogg" />
<qg:sound bus="effects" name="fall" src="assets/kenney/audio/hurt.ogg" />
<qg:sound bus="effects" name="won" src="assets/kenney/audio/win.ogg" />
<qg:prefab name="Rider" tag="rider" sheet="units" frame="0" hitbox="14x14" health="3">
<qg:state name="ready" frame="0" initial="true" />
<qg:state name="chosen" frame="4" />
<qg:state name="spent" frame="1" />
<qg:label value="{str(me.health)}" dy="-15" size="8" color="#c8d8ff" />
<qg:on-death><qg:play sound="fall" /></qg:on-death>
</qg:prefab>
<qg:prefab name="Raider" tag="raider" sheet="units" frame="2" hitbox="14x14" health="2">
<qg:state name="whole" frame="2" initial="true" />
<qg:state name="hurt" frame="3" />
<qg:label value="{str(me.health)}" dy="-15" size="8" color="#ffc8c0" />
<qg:on-damage><qg:become target="me" state="hurt" /></qg:on-damage>
<qg:on-death><qg:play sound="fall" /></qg:on-death>
</qg:prefab>
<qg:language lang="pt" name="Português">
<qg:phrase text="MARCHES" as="MARCAS" />
<qg:phrase text="Hold the ford: three riders, four raiders." as="Segure o vau: três cavaleiros, quatro saqueadores." />
<qg:phrase text="Play" as="Jogar" />
<qg:phrase text="Options" as="Opções" />
<qg:phrase text="Language" as="Idioma" />
<qg:phrase text="Click a rider." as="Clique num cavaleiro." />
<qg:phrase text="Your turn: click a rider." as="Sua vez: clique num cavaleiro." />
<qg:phrase text="The raiders ride." as="Os saqueadores avançam." />
<qg:phrase text="Wait for the raiders." as="Espere os saqueadores." />
<qg:phrase text="Where to? Four moves." as="Para onde? Quatro movimentos." />
<qg:phrase text="A blow!" as="Um golpe!" />
<qg:phrase text="Strike a raider next to it, or choose another rider." as="Golpeie um saqueador ao lado, ou escolha outro cavaleiro." />
<qg:phrase text="Choose another rider, or end the turn (E)." as="Escolha outro cavaleiro, ou encerre a vez (E)." />
<qg:phrase text="Too far, or no way there." as="Longe demais, ou sem caminho." />
<qg:phrase text="That rider is spent." as="Esse cavaleiro já agiu." />
<qg:phrase text="End turn" as="Encerrar a vez" />
<qg:phrase text="The march is held." as="A marca resiste." />
<qg:phrase text="The march is lost." as="A marca caiu." />
<qg:phrase text="Again" as="De novo" />
<qg:phrase text="Grass" as="Campo" />
<qg:phrase text="Forest" as="Floresta" />
<qg:phrase text="Hills" as="Colinas" />
<qg:phrase text="Marsh" as="Pântano" />
<qg:phrase text="River" as="Rio" />
<qg:phrase text="Rocks" as="Rochas" />
<qg:phrase text="Ford" as="Vau" />
<qg:phrase text="Keep" as="Torre" />
<qg:phrase text="move" as="movimento" />
<qg:phrase text="moves" as="movimentos" />
<qg:phrase text="OPTIONS" as="OPÇÕES" />
<qg:phrase text="Sounds" as="Sons" />
<qg:phrase text="Full screen" as="Tela cheia" />
<qg:phrase text="on" as="sim" />
<qg:phrase text="off" as="não" />
<qg:phrase text="Back" as="Voltar" />
</qg:language>
<qg:language lang="es" name="Español">
<qg:phrase text="MARCHES" as="MARCAS" />
<qg:phrase text="Hold the ford: three riders, four raiders." as="Defiende el vado: tres jinetes, cuatro saqueadores." />
<qg:phrase text="Play" as="Jugar" />
<qg:phrase text="Options" as="Opciones" />
<qg:phrase text="Language" as="Idioma" />
<qg:phrase text="Click a rider." as="Haz clic en un jinete." />
<qg:phrase text="Your turn: click a rider." as="Tu turno: haz clic en un jinete." />
<qg:phrase text="The raiders ride." as="Los saqueadores avanzan." />
<qg:phrase text="Wait for the raiders." as="Espera a los saqueadores." />
<qg:phrase text="Where to? Four moves." as="¿Adónde? Cuatro movimientos." />
<qg:phrase text="A blow!" as="¡Un golpe!" />
<qg:phrase text="Strike a raider next to it, or choose another rider." as="Golpea a un saqueador a su lado, o elige otro jinete." />
<qg:phrase text="Choose another rider, or end the turn (E)." as="Elige otro jinete, o termina el turno (E)." />
<qg:phrase text="Too far, or no way there." as="Demasiado lejos, o sin camino." />
<qg:phrase text="That rider is spent." as="Ese jinete ya actuó." />
<qg:phrase text="End turn" as="Terminar turno" />
<qg:phrase text="The march is held." as="La marca resiste." />
<qg:phrase text="The march is lost." as="La marca ha caído." />
<qg:phrase text="Again" as="Otra vez" />
<qg:phrase text="Grass" as="Pradera" />
<qg:phrase text="Forest" as="Bosque" />
<qg:phrase text="Hills" as="Colinas" />
<qg:phrase text="Marsh" as="Pantano" />
<qg:phrase text="River" as="Río" />
<qg:phrase text="Rocks" as="Rocas" />
<qg:phrase text="Ford" as="Vado" />
<qg:phrase text="Keep" as="Torre" />
<qg:phrase text="move" as="movimiento" />
<qg:phrase text="moves" as="movimientos" />
<qg:phrase text="OPTIONS" as="OPCIONES" />
<qg:phrase text="Sounds" as="Sonidos" />
<qg:phrase text="Full screen" as="Pantalla completa" />
<qg:phrase text="on" as="sí" />
<qg:phrase text="off" as="no" />
<qg:phrase text="Back" as="Volver" />
</qg:language>
<qg:scene name="title" width="406" height="280" background="#1c2a1c">
<qg:hud position="top-center" size="32"><qg:text value="{'MARCHES'}" /></qg:hud>
<qg:menu position="center" size="14">
<qg:button label="Play"><qg:goto-scene name="field" fade="0.5" /></qg:button>
<qg:button label="Options"><qg:goto-scene name="options" /></qg:button>
<qg:button label="{tr('Language') + ': ' + language()}"><qg:next-language /></qg:button>
</qg:menu>
<qg:hud position="bottom-center" size="10"><qg:text value="{'Hold the ford: three riders, four raiders.'}" /></qg:hud>
</qg:scene>
<qg:scene name="field" width="406" height="280" background="#1c2a1c">
<q:set name="turn" value="riders" />
<q:set name="chosen" value="{null}" />
<q:set name="moved" value="[]" type="array" /> <!-- the riders that rode this turn, by name -->
<q:set name="spent" value="[]" type="array" /> <!-- the riders that struck this turn -->
<q:set name="message" value="Click a rider." />
<q:set name="terrain" value="{['', 'Grass', 'Forest', 'Hills', 'Marsh', 'River', 'Rocks', 'Ford', 'Keep']}" type="array" />
<!-- a rider of the player's chosen: it rides (once a turn, four moves at most) and strikes (once) -->
<q:function name="choose" params="rider">
<q:if condition="{chosen != null and not (chosen.name in spent)}">
<qg:become target="chosen" state="ready" />
</q:if>
<q:set name="chosen" value="{rider}" />
<qg:become target="rider" state="chosen" />
</q:function>
<q:function name="strike" params="foe">
<qg:play sound="blow" />
<qg:damage target="foe" amount="1" />
</q:function>
<!-- the raiders' turn: each rides at the nearest rider, three moves, and strikes it if it is next to it -->
<q:function name="raiders_ride">
<q:set name="turn" value="raiders" />
<q:set name="message" value="The raiders ride." />
<q:if condition="{chosen != null and not (chosen.name in spent)}">
<qg:become target="chosen" state="ready" />
</q:if>
<q:set name="chosen" value="{null}" />
<qg:for-each tag="raider" var="raider">
<qg:travel target="raider" to="{nearest(raider, 'rider')}" budget="3" step="10">
<qg:on-end>
<q:if condition="{tile_at(me.col, me.row) == 8}">
<qg:goto-scene name="lost" fade="1" />
</q:if>
<q:if condition="{count('rider') > 0 and distance(me, nearest(me, 'rider')) < 30}">
<q:call function="strike" args="nearest(me, 'rider')" />
</q:if>
</qg:on-end>
</qg:travel>
</qg:for-each>
<qg:after seconds="1.5">
<q:set name="turn" value="riders" />
<q:set name="moved" value="[]" />
<q:set name="spent" value="[]" />
<q:set name="message" value="Your turn: click a rider." />
<qg:for-each tag="rider" var="rider"><qg:become target="rider" state="ready" /></qg:for-each>
</qg:after>
</q:function>
<qg:tilemap tileset="land" collision="true">
1,1,2,2,1,4,5,5,4,1,2,2,1,1
1,2,2,1,1,4,5,5,4,1,1,2,3,1
1,1,1,1,3,4,5,5,4,4,1,1,3,1
1,8,1,1,3,1,7,7,1,4,1,1,1,1
1,1,1,2,1,1,5,5,1,1,2,2,1,1
1,1,2,2,1,4,5,5,4,1,2,6,6,1
1,1,1,1,1,4,5,5,1,1,1,6,1,1
6,1,1,3,3,1,5,7,4,1,3,1,1,1
6,6,1,1,1,1,5,5,4,1,3,3,1,1
6,6,6,1,1,4,5,5,4,1,1,1,1,1
</qg:tilemap>
<!-- each at the middle of its hex: column 2 of row 2, column 3 of row 3 (an odd row, half a hex on)... -->
<qg:instance prefab="Rider" name="ash" x="70" y="64" />
<qg:instance prefab="Rider" name="birch" x="112" y="88" />
<qg:instance prefab="Rider" name="cedar" x="70" y="112" />
<qg:instance prefab="Raider" name="raider-1" x="364" y="40" />
<qg:instance prefab="Raider" name="raider-2" x="350" y="112" />
<qg:instance prefab="Raider" name="raider-3" x="336" y="184" />
<qg:instance prefab="Raider" name="raider-4" x="350" y="208" />
<!-- the pointer snaps to the hexes -->
<qg:cursor id="aim" player="1" cells="true" sheet="marks" frame="0" />
<qg:on-select>
<q:if condition="{turn != 'riders'}">
<q:set name="message" value="Wait for the raiders." />
</q:if>
<!-- a rider: chosen, unless it has struck -->
<q:elseif condition="{other != null and other.tag == 'rider' and other.name in spent}">
<q:set name="message" value="That rider is spent." />
</q:elseif>
<q:elseif condition="{other != null and other.tag == 'rider'}">
<q:call function="choose" args="other" />
<q:if condition="{other.name in moved}">
<q:set name="message" value="Strike a raider next to it, or choose another rider." />
<q:else>
<q:set name="message" value="Where to? Four moves." />
</q:else>
</q:if>
</q:elseif>
<!-- a raider next to the chosen rider: a blow, and the rider is spent -->
<q:elseif condition="{other != null and other.tag == 'raider' and chosen != null and distance(chosen, other) < 30}">
<q:call function="strike" args="other" />
<qg:become target="chosen" state="spent" />
<q:set name="spent" value="{spent + [chosen.name]}" />
<q:set name="moved" value="{moved + [chosen.name]}" />
<q:set name="chosen" value="{null}" />
<q:set name="message" value="A blow!" />
</q:elseif>
<!-- a free hex in reach of a rider that has not ridden: it rides there -->
<q:elseif condition="{other == null and chosen != null and not (chosen.name in moved) and cost(chosen, aim) <= 4}">
<qg:play sound="ride" />
<q:set name="moved" value="{moved + [chosen.name]}" />
<qg:travel target="chosen" to="aim" budget="4" step="8">
<qg:on-end>
<q:if condition="{count('raider') > 0 and distance(me, nearest(me, 'raider')) < 30}">
<q:set name="message" value="Strike a raider next to it, or choose another rider." />
<q:else>
<q:set name="message" value="Choose another rider, or end the turn (E)." />
</q:else>
</q:if>
</qg:on-end>
</qg:travel>
</q:elseif>
<q:elseif condition="{chosen != null}">
<q:set name="message" value="Too far, or no way there." />
</q:elseif>
</qg:on-select>
<qg:on-input action="end-turn">
<q:if condition="{turn == 'riders'}"><q:call function="raiders_ride" /></q:if>
</qg:on-input>
<qg:when condition="{count('raider') == 0}">
<qg:play sound="won" />
<qg:goto-scene name="held" fade="1" />
</qg:when>
<qg:when condition="{count('rider') == 0}">
<qg:goto-scene name="lost" fade="1" />
</qg:when>
<!-- what the pointer is on, and what riding there costs the chosen rider -->
<qg:hud position="bottom-left" size="10">
<qg:text bind="message" />
<qg:text value="{tr(terrain[tile_at(aim.col, aim.row)]) + (': ' + str(cost(chosen, aim)) + ' ' + tr_n('move', 'moves', cost(chosen, aim)) if chosen != null and cost(chosen, aim) < 99 else '')}" />
</qg:hud>
<qg:menu position="top-right" size="10">
<qg:button label="End turn" if="{turn == 'riders'}"><q:call function="raiders_ride" /></qg:button>
</qg:menu>
</qg:scene>
<qg:scene name="held" width="406" height="280" background="#1c2a1c">
<qg:hud position="top-center" size="20"><qg:text value="{'The march is held.'}" /></qg:hud>
<qg:menu position="center" size="14"><qg:button label="Again"><qg:goto-scene name="field" /></qg:button></qg:menu>
</qg:scene>
<qg:scene name="lost" width="406" height="280" background="#2a1c1c">
<qg:hud position="top-center" size="20"><qg:text value="{'The march is lost.'}" /></qg:hud>
<qg:menu position="center" size="14"><qg:button label="Again"><qg:goto-scene name="field" /></qg:button></qg:menu>
</qg:scene>
<!-- ===== Options: how loud, the full screen, the key; kept between runs ===== -->
<qg:scene name="options" width="406" height="280" background="#1c2a1c">
<qg:hud position="top-center" size="24"><qg:text value="{tr('OPTIONS')}" /></qg:hud>
<qg:menu position="center" size="14">
<qg:slider label="Sounds" bus="effects" />
<qg:button label="{tr('Full screen') + ': ' + (tr('on') if fullscreen() else tr('off'))}"><qg:fullscreen /></qg:button>
<qg:rebind label="End turn" action="end-turn" />
<qg:button label="Back"><qg:goto-scene name="title" /></qg:button>
</qg:menu>
</qg:scene>
</q:application>PandaFoot
projects/pandafoot/pandafoot.q — a football manager after Elifoot 2, drawn as an 80 by 25 text screen: a world made from its number (qg:scene seed="{world}"), squads in tables (qg:table, qg:column, fit(), thousands()) and panels placed by hand (x=, y=, box=), the line-up as a menu of rows (qg:menu source=), records changed in place (q:set name="p.goals"), and the live round: five matches in the same minute at 1x to 16x, reactions that change tactics at their minute with three seconds to undo them; in English, Portuguese and Spanish.
<q:application id="pandafoot" type="game">
<!-- PandaFoot: a football manager after Elifoot 2 (PLAN_PANDAFOOT.md, Phase 2).
There is always a next match: you prepare it, play it, see the result,
and the season goes on. Four divisions of ten clubs, home and away, two
up and two down, for one manager or two to four taking turns at one
machine, each starting at a small club of the fourth. The rules are
pandafoot's GAME_DESIGN.md v0.2: strength (força) 1 to 50, four
positions, seven traits, a genius hidden in a small club, salaries by the
month paid by the week, the live round with every match of it in the
same minute.
Drawn as a text screen of the DOS days: an 80 by 25 grid of a
monospaced font (DejaVu Sans Mono, its licence beside it), white on
blue, boxes drawn by scripts/art/pandafoot.py (CC0). Written in English,
played in Portuguese and Spanish too (qg:language, at the end). -->
<!-- ===== The world: what a new game makes, and what a season changes ===== -->
<q:set name="clubs" value="[]" type="array" /> <!-- the forty clubs, each a record (see new_club); club.div its division -->
<q:set name="fixtures" value="[]" type="array" /> <!-- 18 rounds, each twenty [home, away]: five of each division -->
<q:set name="managers" value="[]" type="array" /> <!-- {name, club, patience, career}: one to four, taking turns -->
<q:set name="jobs" value="[]" type="array" /> <!-- what waits for a manager: {who, kind: sacked or offer, clubs} -->
<q:set name="retired" value="[]" type="array" /> <!-- the managers whose careers ended: {name, career} -->
<q:set name="market" value="[]" type="array" /> <!-- the transfer list: {who, seller (-1: a free agent), fee, wage, left, bids} -->
<q:set name="free_agents" value="[]" type="array" /> <!-- players of no club -->
<q:set name="bidding" value="-1" type="number" /> <!-- the entry of the list a manager is looking at -->
<q:set name="today" value="[]" type="array" /> <!-- the games of the date being played: [home, away] -->
<q:set name="today_kind" value="league" type="string" /> <!-- league, cup or super -->
<q:set name="cup_round" value="0" type="number" /> <!-- the Panda Cup's next round: 0 the preliminary ... 5 the final, 6 over -->
<q:set name="cup_alive" value="[]" type="array" /> <!-- the clubs still in it -->
<q:set name="cup_ties" value="[]" type="array" /> <!-- its next round, drawn: [home, away] -->
<q:set name="cup_winner" value="-1" type="number" />
<q:set name="cup_runner" value="-1" type="number" />
<q:set name="super_tie" value="[]" type="array" /> <!-- the Supercup, the season's first date: [the champion, the cup's winner] -->
<q:set name="turn" value="0" type="number" /> <!-- whose screens these are -->
<q:set name="matchday" value="0" type="number" /> <!-- the next round, from 0 -->
<q:set name="season" value="2026" type="number" />
<q:set name="world" value="7" type="number" /> <!-- the world's number: the same number, the same world -->
<q:set name="slot" value="1" type="number" /> <!-- the save this game goes into -->
<q:set name="news" value="[]" type="array" /> <!-- what the papers say, newest first: {text, arg} -->
<q:set name="last" value="[]" type="array" /> <!-- the last round's matches, for its results -->
<q:set name="champion" value="-1" type="number" />
<q:set name="viewing" value="-1" type="number" /> <!-- the club whose squad is shown, from the table (-1: the manager's) -->
<q:set name="asking" value="100" type="number" /> <!-- selling: the price asked, a share of his worth (50 to 150) -->
<q:set name="peek" value="{ {} }" /> <!-- the live round: the match whose elevens are looked at -->
<q:set name="looking" value="{ {'pos': '', 'div': 0, 'by': 'strength'} }" /> <!-- F4: the players shown -->
<q:set name="seen_career" value="-1" type="number" /> <!-- F8: the manager whose career is shown (-1: the ranking) -->
<q:set name="save_version" value="0" type="number" /> <!-- the version that made this world (22: 2.2); an older one is brought up to it -->
<!-- the beta's reports that have not reached the beta's server yet, kept between runs (at most ten, the newest):
{id, report}; and those on their way now, and what the last one came to -->
<q:set name="outbox" value="[]" type="array" saved="true" />
<q:set name="report_seq" value="0" type="number" saved="true" />
<q:set name="sending" value="[]" type="array" />
<q:set name="report_status" value="" type="string" />
<!-- the numbers of the design, in one place; the names are names in every language -->
<q:set name="club_names" value='["Panda FC", "Bambu United", "Urso Polar", "Tigre Branco", "Leão Dourado", "Onça Pintada", "Gavião Real", "Lobo Guará", "Jacaré FC", "Arara Azul", "Coala City", "Pinguim FC", "Capivara EC", "Tucano FC", "Raposa EC", "Coruja AC", "Falcão United", "Boto Rosa", "Cervo FC", "Zebra City", "Tamanduá", "Jaguatirica", "Mico-Leão", "Tatu-Bola", "Quati AC", "Peixe-Boi", "Ouriço FC", "Preguiça EC", "Flamingo FC", "Pelicano EC", "Sabiá United", "Gambá City", "Lhama FC", "Canguru AC", "Lontra AC", "Tartaruga", "Camelo FC", "Esquilo EC", "Marreco AC", "Siri FC"]' type="array" />
<q:set name="club_short" value='["PAN", "BAM", "URS", "TIG", "LEO", "ONC", "GAV", "LOB", "JAC", "ARA", "COA", "PIN", "CAP", "TUC", "RAP", "COR", "FAL", "BOT", "CER", "ZEB", "TAM", "JAG", "MIC", "TAT", "QUA", "PEI", "OUR", "PRE", "FLA", "PEL", "SAB", "GAM", "LHA", "CAN", "LON", "TAR", "CAM", "ESQ", "MAR", "SIR"]' type="array" />
<q:set name="first_names" value='["João", "Pedro", "Lucas", "Mateus", "Rafael", "Gabriel", "Bruno", "Diego", "Felipe", "Thiago", "André", "Caio", "Danilo", "Everton", "Fábio", "Gustavo", "Hugo", "Igor", "Júlio", "Léo", "Marcos", "Nando", "Otávio", "Paulo", "Renan", "Sérgio", "Tiago", "Vítor", "Wesley", "Zé", "Alex", "Beto", "Cadu", "Davi", "Edu", "Fred", "Gil", "Heitor", "Ivan", "Jorge", "Kleber", "Luan", "Mário", "Nilton", "Oscar", "Rui", "Saulo", "Téo", "Valter", "Vini"]' type="array" />
<q:set name="surnames" value='["Silva", "Santos", "Oliveira", "Souza", "Lima", "Pereira", "Costa", "Ferreira", "Rodrigues", "Almeida", "Nascimento", "Carvalho", "Gomes", "Martins", "Araújo", "Ribeiro", "Barbosa", "Rocha", "Dias", "Moreira", "Teixeira", "Mendes", "Freitas", "Cardoso", "Ramos", "Pinto", "Moura", "Batista", "Viana", "Correia", "Lopes", "Melo", "Nunes", "Castro", "Campos", "Duarte", "Fonseca", "Guimarães", "Lacerda", "Macedo", "Neves", "Pacheco", "Queiroz", "Reis", "Sales", "Tavares", "Vasconcelos", "Xavier", "Brito", "Siqueira"]' type="array" />
<q:set name="nicknames" value='["Bambuzinho", "Pandinha", "Tico", "Biro", "Dudu", "Kaká", "Juninho", "Zinho", "Pelezinho", "Romarinho", "Tatu", "Neguebinha", "Canhota", "Foguete", "Muralhão", "Bolinha", "Pirulito", "Ratinho", "Biscoito", "Sabiá", "Grilo", "Pipoca", "Formiga", "Tucano", "Mosquito", "Baiano", "Mineiro", "Tchê", "Galego", "Russo"]' type="array" />
<q:set name="formations" value='["4-4-2", "4-3-3", "5-3-2", "4-5-1", "3-4-3", "5-4-1"]' type="array" />
<q:set name="styles" value='["Balanced", "Attacking", "All-out attack", "Defensive", "Park the bus", "Counter-attack", "Kamikaze"]' type="array" />
<q:set name="objectives" value='["Win", "Draw", "Avoid defeat", "By hand", "Auto"]' type="array" />
<q:set name="honours" value="[]" type="array" /> <!-- the seasons past, newest first: {season, champions, scorer} -->
<!-- ===== The look: a text screen ===== -->
<qg:spritesheet name="frame" src="assets/frame.png" tile="24" slice="8" /> <!-- a double line, grey on blue -->
<qg:sound name="goal" src="assets/goal.ogg" bus="effects" />
<qg:sound name="whistle" src="assets/whistle.ogg" bus="effects" />
<qg:sound name="final" src="assets/final.ogg" bus="effects" />
<!-- ===== The keys: every screen a hotkey away ===== -->
<qg:input action="lineup" keys="F1" />
<qg:input action="tactics" keys="F2" />
<qg:input action="squad" keys="F3" />
<qg:input action="finances" keys="F5" />
<qg:input action="standings" keys="F7" />
<qg:input action="market" keys="F6" />
<qg:input action="history" keys="F8" />
<qg:input action="sell" keys="S" /> <!-- the market: a player of one's own on the list, or off it -->
<qg:input action="scout" keys="B" /> <!-- another club's squad: bid for one of its players (an auction) -->
<qg:input action="plan" keys="I" /> <!-- a bid: paid at once, or in instalments -->
<qg:input action="pause" keys="Space, P" />
<qg:input action="other" keys="Tab" />
<qg:input action="faster" keys="Equal, KpAdd" />
<qg:input action="slower" keys="Minus, KpSubtract" />
<qg:input action="undo" keys="C" /> <!-- undo an automatic change of tactics -->
<qg:input action="auto" keys="A" /> <!-- the line-up picked by the computer -->
<qg:input action="report" keys="F9" /> <!-- the beta: a bug or a suggestion, in the report panel -->
<qg:input action="ratings" keys="R" /> <!-- the results: the ratings of the manager's match -->
<qg:input action="players" keys="F4" /> <!-- every player of the world, to look at, compare and bid for -->
<qg:input action="order" keys="O" /> <!-- F4: the players by another order -->
<!-- ===== Little helpers ===== -->
<!-- the version a player sees on the title and the dashboard: a function, not the state, so a save of an older
version does not bring its number back. What each version changed: CHANGELOG.md beside this file (and
CHANGELOG.pt.md, CHANGELOG.es.md), on the site at games/pandafoot/changelog/ -->
<q:function name="game_version"><q:return value="{'2.3'}" /></q:function>
<!-- that page, in the player's language -->
<q:function name="changelog_url">
<q:return value="{'https://quantumframework.net/games/pandafoot/changelog/' + ('pt/' if language() == 'Português' else ('es/' if language() == 'Español' else ''))}" />
</q:function>
<!-- ===== The beta's reports (PLAN_PANDAFOOT.md, "The beta's reports") =====
[!] in the corner of every screen, or F9, opens the report panel over whatever is shown, which stands still
under it. A report goes to the beta's server (projects/beta-reports) with where the game is: the screen, the
option the cursor was on, the round, the club; and, if the player says so, the game itself, as its save. It
waits in the outbox, kept between runs, until the server has it: sent again from the title, the dashboard
and the panel. -->
<qg:touch-button action="report" x="622" y="18" size="24" label="!" show="always" />
<!-- where the reports go; '' while the beta's server is not set up: the reports wait in the outbox -->
<q:function name="report_server"><q:return value="{''}" /></q:function>
<!-- where the game is, in a line: enough to find the season and the round again in the same world -->
<q:function name="report_context">
<q:set name="where" value="{'PandaFoot ' + game_version() + ' beta; ' + language() + '; ' + ('desktop' if can_quit() else 'browser') + '; screen ' + scene_name()}" />
<q:if condition="{len(managers) == 0 or len(clubs) != 40}"><q:return value="{where + '; no game in progress'}" /></q:if>
<q:set name="t" value="{managers[min(turn, len(managers) - 1)]}" />
<q:set name="c" value="{clubs[t.club]}" />
<q:return value="{where + '; world ' + str(world) + ', season ' + str(season) + ', round ' + str(matchday) + '/18, next ' + next_kind() + '; ' + t.name + ' at ' + c.name + ' (division ' + str(c.div) + ', ' + str(position_of(t.club)) + 'th, ' + money(c.balance) + '); ' + str(len(managers)) + ' manager(s)'}" />
</q:function>
<!-- who reports: the managers' names -->
<q:function name="report_player">
<q:set name="names" value="{[]}" />
<q:loop items="{managers}" var="m"><q:set name="names" value="{names + [m.name]}" /></q:loop>
<q:return value="{join(names, ', ')}" />
</q:function>
<!-- a report into the outbox, as the server takes it (its lengths cut to what it accepts) -->
<q:function name="queue_report" params="kind, text, seen, option, attach">
<q:set name="report_seq" value="{report_seq + 1}" />
<q:set name="report" value="{ {'game': 'pandafoot', 'version': game_version(), 'kind': kind, 'text': text[0:4000], 'screen': seen[0:60], 'option': option[0:120], 'context': report_context()[0:2000], 'player': report_player()[0:40], 'save': save_text() if attach else ''} }" />
<q:if condition="{len(outbox) >= 10}"><q:set name="outbox" value="{outbox[1:]}" /></q:if>
<q:set name="outbox" value="{outbox + [{'id': report_seq, 'report': report}]}" />
</q:function>
<!-- each report of the outbox not on its way already, to the server -->
<q:function name="send_reports">
<q:if condition="{report_server() == ''}"><q:return /></q:if>
<q:loop items="{outbox}" var="r">
<q:if condition="{not (r.id in sending)}">
<q:set name="sending" value="{sending + [r.id]}" />
<qg:post to="{report_server()}" data="{r.report}" timeout="30">
<qg:on-done><q:call function="report_sent" args="{r.id}, {reply}" /></qg:on-done>
<qg:on-fail><q:call function="report_not_sent" args="{r.id}, {reason}" /></qg:on-fail>
</qg:post>
</q:if>
</q:loop>
</q:function>
<q:function name="report_sent" params="id, reply">
<q:call function="report_out" args="{id}, {true}" />
<q:set name="report_status" value="{tr('Sent: report no.') + ' ' + str(reply.number) + '. ' + tr('Thank you!')}" />
</q:function>
<!-- not sent: kept for later; refused for what it is (a 4xx other than too many), it never would be taken -->
<q:function name="report_not_sent" params="id, reason">
<q:set name="refused" value="{reason[0:1] == '4' and reason[0:3] != '429'}" />
<q:call function="report_out" args="{id}, {refused}" />
<q:if condition="{refused}">
<q:set name="report_status" value="{tr('The report was not accepted:') + ' ' + reason}" />
<q:else>
<q:set name="report_status" value="{tr('Not sent now') + ' (' + reason + '): ' + tr('it is kept and goes later.')}" />
</q:else>
</q:if>
</q:function>
<!-- a report no longer on its way; dropped from the outbox too when it is done with -->
<q:function name="report_out" params="id, done">
<q:set name="still" value="{[]}" />
<q:loop items="{sending}" var="s"><q:if condition="{s != id}"><q:set name="still" value="{still + [s]}" /></q:if></q:loop>
<q:set name="sending" value="{still}" />
<q:if condition="{done}">
<q:set name="kept" value="{[]}" />
<q:loop items="{outbox}" var="r"><q:if condition="{r.id != id}"><q:set name="kept" value="{kept + [r]}" /></q:if></q:loop>
<q:set name="outbox" value="{kept}" />
</q:if>
</q:function>
<!-- the panel, over any screen -->
<qg:overlay name="report" action="report" shade="0.6" background="#000020">
<q:set name="kind" value="bug" type="string" />
<q:set name="text" value="" type="string" />
<q:set name="attach" value="true" type="boolean" />
<q:set name="seen" value="{scene_name()}" type="string" />
<q:set name="option" value="{focused()}" type="string" />
<q:set name="report_status" value="" />
<q:call function="send_reports" />
<qg:hud x="96" y="40" box="frame" font="assets/DejaVuSansMono.ttf" size="14">
<qg:text value="{'BETA - ' + tr('REPORT A BUG OR SUGGEST')}" color="#ffff55" />
<qg:text value="{fit(tr('Where:') + ' ' + seen + (' - ' + option if option != '' else ''), 50)}" />
<qg:text value="{tr('Every report is read. Thank you!')}" color="#55ffff" />
</qg:hud>
<qg:menu x="96" y="140" font="assets/DejaVuSansMono.ttf" size="14" box="frame">
<qg:button label="{tr('Kind:') + ' ' + tr('Bug' if kind == 'bug' else ('Suggestion' if kind == 'suggestion' else 'Something else'))}">
<q:set name="kind" value="{'suggestion' if kind == 'bug' else ('other' if kind == 'suggestion' else 'bug')}" />
</qg:button>
<qg:field bind="text" label="What happened, or your idea:" max-length="600" />
<qg:button label="{tr('Attach my game:') + ' ' + tr('yes' if attach else 'no')}"><q:set name="attach" value="{not attach}" /></qg:button>
<qg:button label="Send">
<q:if condition="{len(text) < 3}">
<q:set name="report_status" value="{tr('Write a few words first.')}" />
<q:else>
<q:call function="queue_report" args="{kind}, {text}, {seen}, {option}, {attach}" />
<q:set name="text" value="" />
<q:set name="report_status" value="{tr('Sending...') if report_server() != '' else tr('Kept: it goes when the beta\'s server is ready.')}" />
<q:call function="send_reports" />
</q:else>
</q:if>
</qg:button>
<qg:button label="Back"><qg:close-overlay /></qg:button>
</qg:menu>
<qg:hud x="96" y="300" font="assets/DejaVuSansMono.ttf" size="12">
<qg:text value="{report_status}" color="#55ff55" />
<qg:text value="{(tr('Waiting to be sent:') + ' ' + str(len(outbox))) if len(outbox) > 0 else ''}" color="#aaaaaa" />
<qg:text value="{tr('[!] or F9 opens this on any screen.') + ' [ESC] ' + tr('Back')}" color="#aaaaaa" />
</qg:hud>
<qg:on-input action="cancel"><qg:close-overlay /></qg:on-input>
</qg:overlay>
<!-- the live round's panel (F3, or a click on "The teams"): the two elevens of the match in front, with what each
did and his rating as it goes; the round waits under it -->
<qg:overlay name="teams" shade="0.6" background="#000020">
<qg:hud x="8" y="6" box="frame" font="assets/DejaVuSansMono.ttf" size="13">
<qg:text value="{clubs[peek.home].name + ' ' + str(peek.hg) + ' x ' + str(peek.ag) + ' ' + clubs[peek.away].name + ' ' + shots_line(peek)}" color="#ffff55" />
</qg:hud>
<qg:hud x="8" y="40" box="frame" font="assets/DejaVuSansMono.ttf" size="11">
<qg:text value="{fit(clubs[peek.home].name, 16) + ' ' + clubs[peek.home].formation + ' ' + tr(clubs[peek.home].style)}" color="#55ffff" />
<qg:table source="{team_rows(peek, peek.home)}" as="row">
<qg:column value="{row[0]}" width="38" color="{row[1]}" />
</qg:table>
</qg:hud>
<qg:hud x="324" y="40" box="frame" font="assets/DejaVuSansMono.ttf" size="11">
<qg:text value="{fit(clubs[peek.away].name, 16) + ' ' + clubs[peek.away].formation + ' ' + tr(clubs[peek.away].style)}" color="#55ffff" />
<qg:table source="{team_rows(peek, peek.away)}" as="row">
<qg:column value="{row[0]}" width="38" color="{row[1]}" />
</qg:table>
</qg:hud>
<qg:hud x="8" y="368" font="assets/DejaVuSansMono.ttf" size="11">
<qg:text value="{tr('Str: strength, Fm: form, Nt: the rating so far') + ' ● ' + tr('goal') + ' ✚ ' + tr('injured') + ' ▮▮ ' + tr('sent off') + ' ⇄ ' + tr('came on')}" color="#aaaaaa" />
<qg:text value="{'[F3] [ESC] ' + tr('Back to the round')}" color="#55ffff" />
</qg:hud>
<qg:on-input action="cancel"><qg:close-overlay /></qg:on-input>
<qg:on-input action="squad"><qg:close-overlay /></qg:on-input>
</qg:overlay>
<!-- a side of a match as the panel shows it: who played, then the bench; [line, colour] -->
<q:function name="team_rows" params="m, ci">
<q:set name="club" value="{clubs[ci]}" />
<q:set name="out" value="{[[fit('', 4) + fit(tr('Name'), 16) + fit(tr('Str'), 4, 'right') + fit(tr('Fm'), 4, 'right') + fit(tr('Nt'), 5, 'right'), '#aaaaaa']]}" />
<q:loop items="{['GK', 'DF', 'MF', 'FW']}" var="place">
<q:loop items="{club.players}" var="q">
<q:if condition="{q.xi == place}">
<q:set name="did" value="{repeat('●', goals_in(m, club, q)) + (' ✚' if q.off == 'injured' else (' ▮▮' if q.off == 'red' else (' ⇄' if q.start == '' else (' ↓' if q.off == 'subbed' else ''))))}" />
<q:set name="r" value="{rating_in(m, club, q)}" />
<q:set name="out" value="{out + [[fit(tr(q.xi), 4) + fit(q.name, 16) + fit(str(q.strength), 4, 'right') + fit(str(q.form), 4, 'right') + fit(r1(r), 5, 'right') + ' ' + did, '#aaaaaa' if q.off != '' else rating_color(r)]]}" />
</q:if>
</q:loop>
</q:loop>
<q:loop items="{club.players}" var="q">
<q:if condition="{q.xi == '' and q.bench}"><q:set name="out" value="{out + [[fit(tr('res'), 4) + fit(q.name, 16) + fit(str(q.strength), 4, 'right') + fit(str(q.form), 4, 'right'), '#777777']]}" /></q:if>
</q:loop>
<q:return value="{out}" />
</q:function>
<!-- a player's goals in a match -->
<q:function name="goals_in" params="m, club, q">
<q:set name="n" value="{0}" />
<q:loop items="{m.events}" var="e"><q:if condition="{e.kind == 'goal' and e.club == club.short and e.who == q.name}"><q:set name="n" value="{n + 1}" /></q:if></q:loop>
<q:return value="{n}" />
</q:function>
<!-- a game saved by 2.1 or before, brought up to 2.2 as it is continued: what every player, club and career has
now (the computer's clubs their facilities by their division), and the free agents up to forty again -->
<q:function name="refit">
<q:loop items="{clubs}" var="c">
<q:if condition="{not ('training' in c)}">
<q:set name="c.training" value="{[2, 1, 1, 0][c.div - 1] if c.manager == 0 else 0}" />
<q:set name="c.academy" value="{[1, 1, 0, 0][c.div - 1] if c.manager == 0 else 0}" />
<q:set name="c.prizes" value="{0}" />
</q:if>
<q:loop items="{c.players}" var="q"><q:call function="refit_player" args="{q}" /></q:loop>
</q:loop>
<q:loop items="{free_agents}" var="q"><q:call function="refit_player" args="{q}" /></q:loop>
<q:loop items="{managers}" var="t">
<q:if condition="{not ('cups' in t.career)}"><q:set name="t.career.cups" value="{0}" /></q:if>
<q:if condition="{not ('stints' in t.career)}"><q:set name="t.career.stints" value="{[new_stint(clubs[t.club].name, clubs[t.club].div)]}" /></q:if>
</q:loop>
<q:loop items="{retired}" var="t">
<q:if condition="{not ('cups' in t.career)}"><q:set name="t.career.cups" value="{0}" /></q:if>
<q:if condition="{not ('stints' in t.career)}"><q:set name="t.career.stints" value="{[]}" /></q:if>
</q:loop>
<q:call function="free_agents_to" args="{40}, {names_in_world()}" />
<q:set name="save_version" value="{22}" />
</q:function>
<q:function name="refit_player" params="q">
<q:if condition="{not ('rsum' in q)}">
<q:loop items="{['rating', 'rsum', 'rn', 'msum', 'mn', 'trend', 'grown', 'award_season']}" var="k"><q:set name="q" index="{k}" value="{0}" /></q:loop>
<q:set name="q.award" value="{''}" />
</q:if>
</q:function>
<!-- a new game starts from nothing: no managers, the first season (a game played before leaves nothing behind) -->
<q:function name="reset_game">
<q:set name="managers" value="{[]}" />
<q:set name="jobs" value="{[]}" />
<q:set name="retired" value="{[]}" />
<q:set name="market" value="{[]}" />
<q:set name="free_agents" value="{[]}" />
<q:set name="turn" value="{0}" />
<q:set name="matchday" value="{0}" />
<q:set name="season" value="{2026}" />
<q:set name="champion" value="{-1}" />
<q:set name="last" value="{[]}" />
<q:set name="news" value="{[]}" />
</q:function>
<!-- an index of a list of weights, drawn by them -->
<q:function name="roll" params="weights">
<q:set name="total" value="{0}" />
<q:loop items="{weights}" var="w"><q:set name="total" value="{total + w}" /></q:loop>
<q:set name="r" value="{random(0, total)}" />
<q:loop var="i" from="0" to="{len(weights) - 1}">
<q:set name="r" value="{r - weights[i]}" />
<q:if condition="{r <= 0}"><q:return value="{i}" /></q:if>
</q:loop>
<q:return value="{len(weights) - 1}" />
</q:function>
<!-- where an item is in a list (-1: not there) -->
<q:function name="place_in" params="list, item">
<q:loop var="k" from="0" to="{len(list) - 1}"><q:if condition="{list[k] == item}"><q:return value="{k}" /></q:if></q:loop>
<q:return value="{-1}" />
</q:function>
<!-- how many times an item is in a list -->
<q:function name="times_in" params="list, item">
<q:set name="n" value="{0}" />
<q:loop items="{list}" var="x"><q:if condition="{x == item}"><q:set name="n" value="{n + 1}" /></q:if></q:loop>
<q:return value="{n}" />
</q:function>
<!-- the one after an item in a list, round to the first -->
<q:function name="after_in" params="list, item">
<q:return value="{list[(place_in(list, item) + 1) % len(list)]}" />
</q:function>
<!-- a list without one of its items -->
<q:function name="without" params="list, item">
<q:set name="out" value="{[]}" />
<q:set name="gone" value="{false}" />
<q:loop items="{list}" var="x">
<q:if condition="{x == item and not gone}"><q:set name="gone" value="{true}" />
<q:else><q:set name="out" value="{out + [x]}" /></q:else>
</q:if>
</q:loop>
<q:return value="{out}" />
</q:function>
<!-- the manager playing now, their club -->
<q:function name="my_club">
<q:return value="{clubs[managers[turn].club]}" />
</q:function>
<!-- money as the papers write it: $1.500.000 -->
<q:function name="money" params="n">
<q:return value="{('-$' if n < 0 else '$') + thousands(abs(round(n)), '.')}" />
</q:function>
<!-- a bar of so many blocks: 0 to 100 -->
<q:function name="blocks" params="n, width">
<q:set name="full" value="{min(width, max(0, round(n * width / 100)))}" />
<q:return value="{repeat('█', full) + repeat('░', width - full)}" />
</q:function>
<!-- what the papers say: a phrase, and what follows it as it is (a year, a club) -->
<q:function name="said" params="text, arg">
<q:set name="news" value="{[ {'text': text, 'arg': arg} ] + news}" />
</q:function>
<!-- ===== A new world (GAME_DESIGN v0.2: how clubs and players are made) ===== -->
<!-- how many traits by band of strength: 0, 1, 2, 3 or 4 -->
<q:function name="trait_count" params="strength">
<q:if condition="{strength <= 20}"><q:return value="{roll([75, 22, 3, 0, 0])}" /></q:if>
<q:if condition="{strength <= 30}"><q:return value="{roll([47, 38, 14, 1, 0])}" /></q:if>
<q:if condition="{strength <= 40}"><q:return value="{roll([25, 40, 27, 8, 0])}" /></q:if>
<q:return value="{roll([10, 33, 35, 14, 8])}" />
</q:function>
<!-- the traits: commons by position, one rare at most (one draw in three, or the slot the commons do not fill) -->
<q:function name="traits_for" params="strength, pos">
<q:set name="n" value="{trait_count(strength)}" />
<q:set name="commons" value="{ {'GK': ['Wall', 'Leader'], 'DF': ['Set pieces', 'Leader', 'Wall'], 'MF': ['Finisher', 'Set pieces', 'Leader'], 'FW': ['Finisher', 'Set pieces', 'Leader']}[pos] }" />
<q:set name="out" value="{[]}" />
<q:if condition="{n > 0 and pos != 'GK' and (chance(1 / 3) or n > len(commons))}">
<q:set name="out" value="{[pick(['Ace', 'Star'])]}" />
</q:if>
<q:loop var="k" from="1" to="{n}">
<q:if condition="{len(out) < n and len(commons) > 0}">
<q:set name="c" value="{pick(commons)}" />
<q:set name="out" value="{out + [c]}" />
<q:set name="commons" value="{without(commons, c)}" />
</q:if>
</q:loop>
<q:return value="{out}" />
</q:function>
<!-- a second position, as versatile as the player is good -->
<q:function name="secondary" params="strength, pos">
<q:set name="near" value="{ {'GK': [], 'DF': ['MF'], 'MF': ['DF', 'FW'], 'FW': ['MF']}[pos] }" />
<q:set name="n" value="{roll([70, 25, 5]) if strength <= 25 else (roll([40, 40, 20]) if strength <= 35 else roll([20, 50, 30]))}" />
<q:set name="out" value="{[]}" />
<q:loop var="k" from="1" to="{n}">
<q:if condition="{len(near) > 0}">
<q:set name="c" value="{pick(near)}" />
<q:set name="out" value="{out + [c]}" />
<q:set name="near" value="{without(near, c)}" />
</q:if>
</q:loop>
<q:return value="{out}" />
</q:function>
<!-- a name no one else of the world has (a Bambuzinho in three clubs was three players of one name): a nickname,
an initial, or a first name and a surname -->
<q:function name="player_name" params="taken">
<q:loop var="k" from="1" to="20">
<q:set name="drawn" value="{pick(first_names) + ' ' + pick(surnames)}" />
<q:if condition="{chance(0.2)}"><q:set name="drawn" value="{pick(nicknames)}" />
<q:elseif condition="{chance(0.12)}"><q:set name="drawn" value="{pick(first_names) + ' ' + pick(surnames)[0] + '.'}" /></q:elseif>
</q:if>
<q:if condition="{not (drawn in taken)}"><q:return value="{drawn}" /></q:if>
</q:loop>
<q:loop items="{[' Jr.', ' Neto', ' II', ' III', ' Filho']}" var="kin"> <!-- a name taken twenty times over: a son's -->
<q:if condition="{not ((drawn + kin) in taken)}"><q:return value="{drawn + kin}" /></q:if>
</q:loop>
<q:return value="{drawn + ' ' + str(len(taken))}" />
</q:function>
<!-- traits as one would count them: a genius is two -->
<q:function name="weight" params="p">
<q:return value="{len(p.traits) + (2 if p.genius else 0)}" />
</q:function>
<!-- the month's salary: strength² × 15, give or take a fifth, times what the traits are worth -->
<q:function name="salary_of" params="p">
<q:set name="times" value="{[1.0, 1.3, 1.75, 3.25, 6.0, 10.0][min(5, weight(p))]}" />
<q:return value="{p.strength * p.strength * 15 * random(0.8, 1.2) * times}" />
</q:function>
<q:function name="new_player" params="strength, pos, num, taken">
<q:set name="p" value="{ {'name': player_name(taken), 'num': num, 'pos': pos, 'sec': secondary(strength, pos), 'strength': strength, 'traits': traits_for(strength, pos), 'genius': false, 'morale': 70, 'form': 100, 'salary': 0, 'goals': 0, 'yellows': 0, 'reds': 0, 'injured': 0, 'suspended': 0, 'xi': '', 'bench': false, 'played': 0, 'booked': false, 'off': '', 'start': '', 'sat': false, 'loan': -1, 'apps': 0, 'total': 0, 'rating': 0, 'rsum': 0, 'rn': 0, 'msum': 0, 'mn': 0, 'trend': 0, 'grown': 0, 'award': '', 'award_season': 0} }" />
<q:set name="p.salary" value="{salary_of(p)}" />
<q:return value="{p}" />
</q:function>
<!-- a club of a division (1 to 4): its 22 by the design's spread of strength for it, its ground and its purse -->
<q:function name="new_club" params="i, div, known">
<q:set name="rows" value="{[[[3, 42, 50], [11, 35, 44], [8, 28, 38]], [[2, 35, 42], [10, 28, 36], [10, 22, 30]], [[1, 30, 35], [9, 24, 32], [12, 15, 26]], [[1, 25, 28], [8, 18, 26], [13, 8, 20]]][div - 1]}" />
<q:set name="strengths" value="{[]}" />
<q:loop items="{rows}" var="row">
<q:loop var="k" from="1" to="{row[0]}"><q:set name="strengths" value="{strengths + [round(random(row[1], row[2]))]}" /></q:loop>
</q:loop>
<q:set name="strengths" value="{sort(strengths, true)}" />
<!-- the eleven strongest are a team, a keeper among them, and the eleven others another: a club's first keeper is
as good as its eleven (drawn from the whole squad, one club in four had its two keepers among its weakest) -->
<q:set name="team" value="{['GK', 'DF', 'DF', 'DF', 'DF', 'MF', 'MF', 'MF', 'MF', 'FW', 'FW']}" />
<q:set name="places" value="{team}" />
<q:set name="players" value="{[]}" />
<q:set name="names" value="{known}" />
<q:loop var="k" from="0" to="21">
<q:if condition="{k == 11}"><q:set name="places" value="{team}" /></q:if>
<q:set name="pos" value="{pick(places)}" />
<q:set name="places" value="{without(places, pos)}" />
<q:set name="p" value="{new_player(strengths[k], pos, k + 1, names)}" />
<q:set name="players" value="{players + [p]}" />
<q:set name="names" value="{names + [p.name]}" />
</q:loop>
<q:set name="capacity" value="{round(random([30000, 15000, 8000, 5000][div - 1], [50000, 30000, 15000, 8000][div - 1]) / 500) * 500}" />
<q:return value="{ {'name': club_names[i], 'short': club_short[i], 'div': div, 'players': players, 'capacity': capacity, 'ticket': 15, 'balance': random([1500000, 600000, 250000, 150000][div - 1], [4000000, 1600000, 700000, 300000][div - 1]), 'points': 0, 'played': 0, 'won': 0, 'drawn': 0, 'lost': 0, 'gf': 0, 'ga': 0, 'form': [], 'manager': 0, 'formation': '4-4-2', 'style': 'Balanced', 'objective': 'Auto', 'plan': 'Draw', 'reactions': [], 'gate': 0, 'crowd': 0, 'wages': 0, 'history': [], 'expect': 5, 'debts': [], 'health': round(random(70, 100)), 'works': 0, 'adding': 0, 'titles': 0, 'fined': 0, 'bankrupt': false, 'taker': '', 'training': [2, 1, 1, 0][div - 1], 'academy': [1, 1, 0, 0][div - 1], 'prizes': 0} }" />
</q:function>
<!-- the world's geniuses, three to five: the best of two to four clubs of the first division, and one hidden in the
third or the fourth (strength 40-42) -->
<q:function name="place_geniuses">
<q:set name="big" value="{in_div(1)}" />
<q:loop var="k" from="1" to="{round(random(2, 4))}">
<q:set name="c" value="{pick(big)}" />
<q:set name="big" value="{without(big, c)}" />
<q:set name="star" value="{best(clubs[c].players)}" />
<q:set name="star.genius" value="{true}" />
<q:set name="star.strength" value="{max(star.strength, 43)}" />
<q:set name="star.traits" value="{star.traits[0:3]}" />
<q:set name="star.salary" value="{salary_of(star)}" />
</q:loop>
<q:set name="gem" value="{best(clubs[pick(in_div(3) + in_div(4))].players)}" />
<q:set name="gem.genius" value="{true}" />
<q:set name="gem.strength" value="{round(random(40, 42))}" />
<q:set name="gem.traits" value="{gem.traits[0:3]}" />
<!-- on a small club's contract: paid for its traits, as no one knows yet what it is -->
<q:set name="gem.salary" value="{gem.strength * gem.strength * 15 * random(0.8, 1.2) * [1.0, 1.3, 1.75, 3.25][len(gem.traits)]}" />
</q:function>
<q:function name="best" params="players">
<q:set name="top" value="{players[0]}" />
<q:loop items="{players}" var="p"><q:if condition="{p.strength > top.strength}"><q:set name="top" value="{p}" /></q:if></q:loop>
<q:return value="{top}" />
</q:function>
<!-- home and away, the circle way, each division on its own (its first club stays, the others turn): a round is
the five matches of each division, the first division's first -->
<q:function name="make_fixtures">
<q:set name="fixtures" value="{[[], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], []]}" />
<q:loop var="d" from="1" to="4">
<q:set name="order" value="{in_div(d)}" />
<q:loop var="r" from="0" to="8">
<q:set name="games" value="{[]}" />
<q:set name="back" value="{[]}" />
<q:loop var="i" from="0" to="4">
<q:if condition="{(r + i) % 2 == 0}"><q:set name="games" value="{games + [[order[i], order[9 - i]]]}" /><q:set name="back" value="{back + [[order[9 - i], order[i]]]}" />
<q:else><q:set name="games" value="{games + [[order[9 - i], order[i]]]}" /><q:set name="back" value="{back + [[order[i], order[9 - i]]]}" /></q:else>
</q:if>
</q:loop>
<q:set name="fixtures" index="{r}" value="{fixtures[r] + games}" />
<q:set name="fixtures" index="{r + 9}" value="{fixtures[r + 9] + back}" />
<q:set name="order" value="{[order[0], order[9]] + order[1:9]}" />
</q:loop>
</q:loop>
</q:function>
<q:function name="new_world">
<q:set name="clubs" value="{[]}" />
<q:set name="known" value="{[]}" />
<q:loop var="i" from="0" to="39">
<q:set name="made" value="{new_club(i, floor(i / 10) + 1, known)}" />
<q:set name="clubs" value="{clubs + [made]}" />
<q:loop items="{made.players}" var="p"><q:set name="known" value="{known + [p.name]}" /></q:loop>
</q:loop>
<q:call function="place_geniuses" />
<q:set name="free_agents" value="{[]}" />
<q:call function="free_agents_to" args="{40}, {known}" />
<q:set name="market" value="{[]}" />
<q:set name="save_version" value="{22}" />
<q:call function="make_fixtures" />
<q:loop items="{managers}" var="m">
<q:set name="c" value="{clubs[m.club]}" />
<q:set name="c.manager" value="{1}" />
</q:loop>
<q:loop items="{clubs}" var="club"><q:call function="auto_lineup" args="{club}" /></q:loop>
<q:call function="set_expectations" />
<q:call function="new_cup" />
<q:set name="super_tie" value="{[]}" />
<q:set name="matchday" value="{0}" />
<q:call function="prepare_round" />
<q:set name="news" value="{[]}" />
<q:call function="said" args="{'The Panda League kicks off:'}, {str(season)}" />
</q:function>
<!-- players of no club, of every level, so many: forty when the world is made, and again each new season (the best
of them stay, who left a club or comes back from abroad joins them), so the list never runs dry -->
<q:function name="free_agents_to" params="n, known">
<q:if condition="{len(free_agents) > n}"> <!-- the best stay; who no club took retires -->
<q:set name="keys" value="{[]}" />
<q:loop var="k" from="0" to="{len(free_agents) - 1}"><q:set name="keys" value="{keys + [[free_agents[k].strength, 1000 - k]]}" /></q:loop>
<q:set name="kept" value="{[]}" />
<q:loop items="{sort(keys, true)[0:n]}" var="key"><q:set name="kept" value="{kept + [free_agents[1000 - key[1]]]}" /></q:loop>
<q:set name="free_agents" value="{kept}" />
</q:if>
<q:loop var="k" from="{len(free_agents) + 1}" to="{n}">
<q:set name="agent" value="{new_player(round(random(10, 40)), pick(['GK', 'DF', 'DF', 'DF', 'MF', 'MF', 'MF', 'FW', 'FW']), 0, known)}" />
<q:set name="known" value="{known + [agent.name]}" />
<q:set name="free_agents" value="{free_agents + [agent]}" />
</q:loop>
</q:function>
<!-- every name of the world: the clubs' players and the free agents -->
<q:function name="names_in_world">
<q:set name="out" value="{[]}" />
<q:loop items="{clubs}" var="c"><q:loop items="{c.players}" var="q"><q:set name="out" value="{out + [q.name]}" /></q:loop></q:loop>
<q:loop items="{free_agents}" var="q"><q:set name="out" value="{out + [q.name]}" /></q:loop>
<q:return value="{out}" />
</q:function>
<!-- ===== The divisions ===== -->
<!-- the clubs of a division, by their number -->
<q:function name="in_div" params="d">
<q:set name="out" value="{[]}" />
<q:loop var="i" from="0" to="{len(clubs) - 1}"><q:if condition="{clubs[i].div == d}"><q:set name="out" value="{out + [i]}" /></q:if></q:loop>
<q:return value="{out}" />
</q:function>
<q:function name="div_name" params="d">
<q:return value="{tr(['1st division', '2nd division', '3rd division', '4th division'][d - 1])}" />
</q:function>
<!-- what a division pays a month (GAME_DESIGN's quotas): the TV, the same to all; the sponsor, by the place (the
top four, the middle, the two going down); and what a club costs: its staff and travel, and $1 a seat -->
<q:function name="tv_of" params="club">
<q:return value="{[500000, 200000, 50000, 20000][club.div - 1]}" />
</q:function>
<q:function name="sponsor_of" params="club, place">
<q:return value="{[[300000, 150000, 50000], [150000, 75000, 25000], [70000, 35000, 12000], [30000, 15000, 5000]][club.div - 1][0 if place < 4 else (2 if place >= 8 else 1)]}" />
</q:function>
<q:function name="costs_of" params="club">
<q:return value="{[100000, 70000, 40000, 20000][club.div - 1] + club.capacity + upkeep(club)}" />
</q:function>
<!-- ===== The club's facilities (decision of 2.2): what the money is for, besides the players and the ground ===== -->
<!-- the training center, 0 to 5: its players grow more easily (the month's bar 0.15 lower a level) and recover a
point more a week a level; the youth academy, 0 to 5: each new season one or two youths, the better the higher
it is. What each level costs to build (a third at once, the rest in seven months), and a month to keep -->
<q:function name="training_cost" params="club">
<q:return value="{[120000, 300000, 800000, 2000000, 5000000][club.training]}" />
</q:function>
<q:function name="academy_cost" params="club">
<q:return value="{[100000, 250000, 600000, 1500000, 4000000][club.academy]}" />
</q:function>
<q:function name="upkeep" params="club">
<q:return value="{([0, 8000, 20000, 45000, 90000, 180000][club.training] + [0, 10000, 20000, 40000, 80000, 150000][club.academy]) if 'training' in club else 0}" />
</q:function>
<!-- a level more ('' when done; else what stops it) -->
<q:function name="build" params="club, what">
<q:set name="level" value="{club.training if what == 'training' else club.academy}" />
<q:if condition="{level >= 5}"><q:return value="{tr('It is at the top level already.')}" /></q:if>
<q:set name="cost" value="{training_cost(club) if what == 'training' else academy_cost(club)}" />
<q:if condition="{crisis(club) >= 2}"><q:return value="{tr('CRISIS: no buying')}" /></q:if>
<q:if condition="{club.balance < cost / 3}"><q:return value="{tr('Not enough money: a third at once is') + ' ' + money(cost / 3)}" /></q:if>
<q:set name="club.balance" value="{club.balance - cost / 3}" />
<q:set name="club.debts" value="{club.debts + [ {'to': -1, 'each': cost * 2 / 3 / 7, 'left': 7, 'what': what} ]}" />
<q:if condition="{what == 'training'}"><q:set name="club.training" value="{club.training + 1}" />
<q:else><q:set name="club.academy" value="{club.academy + 1}" /></q:else>
</q:if>
<q:call function="said" args="{'Training center, level' if what == 'training' else 'Youth academy, level'}, {str(level + 1) + ': ' + club.name}" />
<q:return value="{''}" />
</q:function>
<!-- the academy's youths of a new season: one (two from level 4), 12 to 18 strong at level 1 and 4 more a level, now
and then a promise (6 more); on a youth's wage, half a pro's -->
<q:function name="youths" params="club, known">
<q:set name="out" value="{[]}" />
<q:if condition="{club.academy == 0 or len(club.players) >= 28}"><q:return value="{out}" /></q:if>
<q:loop var="k" from="1" to="{2 if club.academy >= 4 else 1}">
<q:set name="promise" value="{chance(0.08 * club.academy)}" />
<q:set name="kid" value="{new_player(round(random(8, 14) + 4 * club.academy + (6 if promise else 0)), pick(['GK', 'DF', 'DF', 'MF', 'MF', 'FW', 'FW']), next_number(club), known + names_of_players(out))}" />
<q:set name="kid.salary" value="{kid.salary / 2}" />
<q:set name="club.players" value="{club.players + [kid]}" />
<q:set name="out" value="{out + [kid]}" />
</q:loop>
<q:return value="{out}" />
</q:function>
<q:function name="names_of_players" params="players">
<q:set name="out" value="{[]}" />
<q:loop items="{players}" var="q"><q:set name="out" value="{out + [q.name]}" /></q:loop>
<q:return value="{out}" />
</q:function>
<!-- ===== The career (GAME_DESIGN decision 21): the board's patience, the sack, the offers ===== -->
<!-- a career: its totals, and each club's time (a stint: the club, its first and last season, the division it was
in, its titles, cups, ups and downs, and how it ended: '' still there, 'left' for another club, 'sacked',
'retired') -->
<q:function name="new_career" params="club_name, div">
<q:return value="{ {'seasons': 0, 'matches': 0, 'points': 0, 'titles': 0, 'cups': 0, 'ups': 0, 'downs': 0, 'sacked': 0, 'clubs': [club_name], 'stints': [new_stint(club_name, div)]} }" />
</q:function>
<q:function name="new_stint" params="club_name, div">
<q:return value="{ {'club': club_name, 'first': season, 'last': season, 'div': div, 'titles': 0, 'cups': 0, 'ups': 0, 'downs': 0, 'how': ''} }" />
</q:function>
<!-- the stint going on (null: a career of a save before 2.2 has none) -->
<q:function name="stint_of" params="t">
<q:return value="{t.career.stints[len(t.career.stints) - 1] if 'stints' in t.career and len(t.career.stints) > 0 else null}" />
</q:function>
<q:function name="end_stint" params="t, how">
<q:set name="st" value="{stint_of(t)}" />
<q:if condition="{st != null and st.how == ''}"><q:set name="st.how" value="{how}" /><q:set name="st.last" value="{season}" /></q:if>
</q:function>
<!-- what a club's squad is worth: the mean of its sixteen strongest -->
<q:function name="squad_strength" params="club">
<q:set name="top" value="{[]}" />
<q:loop items="{club.players}" var="p"><q:set name="top" value="{top + [p.strength]}" /></q:loop>
<q:set name="best16" value="{sort(top, true)[0:16]}" />
<q:set name="sum" value="{0}" />
<q:loop items="{best16}" var="x"><q:set name="sum" value="{sum + x}" /></q:loop>
<q:return value="{sum / 16}" />
</q:function>
<!-- what each board expects at the season's start: its squad's place among the division's squads -->
<q:function name="set_expectations">
<q:loop var="d" from="1" to="4">
<q:set name="keys" value="{[]}" />
<q:loop items="{in_div(d)}" var="i"><q:set name="keys" value="{keys + [[squad_strength(clubs[i]), 1000 - i]]}" /></q:loop>
<q:set name="rank" value="{1}" />
<q:loop items="{sort(keys, true)}" var="key">
<q:set name="board" value="{clubs[1000 - key[1]]}" />
<q:set name="board.expect" value="{rank}" />
<q:set name="rank" value="{rank + 1}" />
</q:loop>
</q:loop>
</q:function>
<!-- after each round, each board: a win +4, a draw +1, a defeat -3, and half a point for each place above (or below)
what it expects, two at most either way (a board was let down by a manager winning half his matches: the bar
only went down); a purse three months of wages in the red, -6 a round. At 0 the manager is sacked -->
<q:function name="boards">
<q:loop var="mi" from="0" to="{len(managers) - 1}">
<q:set name="t" value="{managers[mi]}" />
<q:set name="c" value="{clubs[t.club]}" />
<q:set name="mark" value="{c.form[len(c.form) - 1] if len(c.form) > 0 else 'D'}" />
<q:set name="t.career.matches" value="{t.career.matches + 1}" />
<q:set name="t.career.points" value="{t.career.points + {'W': 3, 'D': 1, 'L': 0}[mark]}" />
<q:set name="delta" value="{ {'W': 4, 'D': 1, 'L': -3}[mark] + max(-2, min(2, 0.5 * (c.expect - position_of(t.club))))}" />
<q:if condition="{c.balance < -3 * payroll(c)}"><q:set name="delta" value="{delta - 6}" /></q:if>
<q:set name="t.patience" value="{max(0, min(100, t.patience + delta))}" />
<q:if condition="{t.patience <= 0 and not sacked_already(mi)}"><q:call function="sack" args="{mi}" /></q:if>
</q:loop>
</q:function>
<q:function name="sacked_already" params="mi">
<q:loop items="{jobs}" var="j"><q:if condition="{j.who == mi and j.kind == 'sacked'}"><q:return value="{true}" /></q:if></q:loop>
<q:return value="{false}" />
</q:function>
<!-- the board has lost its patience: the club goes to the computer, and two clubs of the division below (or of the
4th) that no manager has are offered; else, the career ends -->
<q:function name="sack" params="mi">
<q:set name="t" value="{managers[mi]}" />
<q:set name="c" value="{clubs[t.club]}" />
<q:set name="t.career.sacked" value="{t.career.sacked + 1}" />
<q:call function="end_stint" args="{t}, {'sacked'}" />
<q:call function="said" args="{'Sacked by the board of'}, {c.name + ': ' + t.name}" />
<q:set name="jobs" value="{jobs + [ {'who': mi, 'kind': 'sacked', 'clubs': free_clubs(min(4, c.div + 1), t.club, 2), 'step': 0} ]}" />
</q:function>
<!-- so many clubs of a division no manager has, but one, drawn -->
<q:function name="free_clubs" params="d, but, n">
<q:set name="vacant" value="{[]}" />
<q:loop items="{in_div(d)}" var="i"><q:if condition="{clubs[i].manager == 0 and i != but}"><q:set name="vacant" value="{vacant + [i]}" /></q:if></q:loop>
<q:set name="out" value="{[]}" />
<q:loop var="k" from="1" to="{n}">
<q:if condition="{len(vacant) > 0}">
<q:set name="drawn" value="{pick(vacant)}" />
<q:set name="vacant" value="{without(vacant, drawn)}" />
<q:set name="out" value="{out + [drawn]}" />
</q:if>
</q:loop>
<q:return value="{out}" />
</q:function>
<!-- the season's verdict, before the clubs move: a board pleased by a place above what it expected (or the promotion)
gains patience, and one let down loses it; a manager who went up, or finished three places above the board's
hopes, will be offered two clubs of the division above the club's next one (drawn once the clubs have moved:
draw_offers) -->
<q:function name="season_verdicts" params="orders">
<q:loop var="mi" from="0" to="{len(managers) - 1}">
<q:set name="t" value="{managers[mi]}" />
<q:set name="c" value="{clubs[t.club]}" />
<q:set name="at" value="{place_in(orders[c.div - 1], t.club) + 1}" />
<q:set name="up" value="{c.div > 1 and at <= 2}" />
<q:set name="down" value="{c.div < 4 and at >= 9}" />
<q:set name="t.career.seasons" value="{t.career.seasons + 1}" />
<q:if condition="{at == 1}"><q:set name="t.career.titles" value="{t.career.titles + 1}" /></q:if>
<q:if condition="{up}"><q:set name="t.career.ups" value="{t.career.ups + 1}" /></q:if>
<q:if condition="{down}"><q:set name="t.career.downs" value="{t.career.downs + 1}" /></q:if>
<q:set name="st" value="{stint_of(t)}" />
<q:if condition="{st != null}">
<q:set name="st.last" value="{season}" />
<q:set name="st.titles" value="{st.titles + (1 if at == 1 else 0)}" />
<q:set name="st.ups" value="{st.ups + (1 if up else 0)}" />
<q:set name="st.downs" value="{st.downs + (1 if down else 0)}" />
</q:if>
<q:set name="t.patience" value="{max(0, min(100, t.patience + 4 * (c.expect - at) + (20 if up else 0) - (30 if down else 0)))}" />
<q:if condition="{t.patience <= 0 and not sacked_already(mi)}">
<q:set name="t.career.sacked" value="{t.career.sacked + 1}" />
<q:call function="end_stint" args="{t}, {'sacked'}" />
<q:call function="said" args="{'Sacked by the board of'}, {c.name + ': ' + t.name}" />
<q:set name="jobs" value="{jobs + [ {'who': mi, 'kind': 'sacked', 'clubs': [], 'step': 1} ]}" />
<q:elseif condition="{up or c.expect - at >= 3}"><q:set name="jobs" value="{jobs + [ {'who': mi, 'kind': 'offer', 'clubs': [], 'step': -1} ]}" /></q:elseif>
</q:if>
</q:loop>
</q:function>
<!-- at the half of the season (the 9th round played): a manager doing well, in the top two of a division under the
first or three places above what the board expects, is offered two clubs of the division above -->
<q:function name="midseason_offers">
<q:loop var="mi" from="0" to="{len(managers) - 1}">
<q:set name="t" value="{managers[mi]}" />
<q:set name="c" value="{clubs[t.club]}" />
<q:set name="at" value="{position_of(t.club)}" />
<q:set name="waiting" value="{false}" />
<q:loop items="{jobs}" var="j"><q:if condition="{j.who == mi}"><q:set name="waiting" value="{true}" /></q:if></q:loop>
<q:if condition="{c.div > 1 and (at <= 2 or c.expect - at >= 3) and not waiting}">
<q:set name="offered" value="{free_clubs(c.div - 1, t.club, 2)}" />
<q:if condition="{len(offered) > 0}"><q:set name="jobs" value="{jobs + [ {'who': mi, 'kind': 'offer', 'clubs': offered, 'step': -1} ]}" /></q:if>
</q:if>
</q:loop>
</q:function>
<!-- the season's jobs, once the clubs have moved: two clubs no manager has, of the division above the club's (an
offer) or below it (the sack); an offer no club makes is no offer -->
<q:function name="draw_offers">
<q:set name="kept" value="{[]}" />
<q:loop items="{jobs}" var="j">
<q:if condition="{len(j.clubs) == 0 and j.step != 0}">
<q:set name="j.clubs" value="{free_clubs(max(1, min(4, clubs[managers[j.who].club].div + j.step)), managers[j.who].club, 2)}" />
</q:if>
<q:if condition="{len(j.clubs) > 0 or j.kind == 'sacked'}"><q:set name="kept" value="{kept + [j]}" /></q:if>
</q:loop>
<q:set name="jobs" value="{kept}" />
</q:function>
<!-- a manager takes another club: the old one goes to the computer -->
<q:function name="take_job" params="mi, club_index">
<q:set name="t" value="{managers[mi]}" />
<q:set name="old" value="{clubs[t.club]}" />
<q:set name="old.manager" value="{0}" />
<q:set name="old.objective" value="{'Auto'}" />
<q:set name="new" value="{clubs[club_index]}" />
<q:set name="new.manager" value="{1}" />
<q:set name="t.club" value="{club_index}" />
<q:set name="t.patience" value="{50}" />
<q:set name="t.career.clubs" value="{t.career.clubs + [new.name]}" />
<q:call function="end_stint" args="{t}, {'left'}" />
<q:if condition="{'stints' in t.career}"><q:set name="t.career.stints" value="{t.career.stints + [new_stint(new.name, new.div)]}" /></q:if>
<q:call function="said" args="{'New manager of'}, {new.name + ': ' + t.name}" />
</q:function>
<!-- a career ends: the club to the computer, the manager to the records -->
<q:function name="end_career" params="mi">
<q:set name="t" value="{managers[mi]}" />
<q:set name="old" value="{clubs[t.club]}" />
<q:set name="old.manager" value="{0}" />
<q:call function="end_stint" args="{t}, {'retired'}" />
<q:set name="retired" value="{retired + [ {'name': t.name, 'career': t.career} ]}" />
<q:set name="left" value="{[]}" />
<q:loop var="k" from="0" to="{len(managers) - 1}"><q:if condition="{k != mi}"><q:set name="left" value="{left + [managers[k]]}" /></q:if></q:loop>
<q:set name="managers" value="{left}" />
<q:set name="turn" value="{0}" />
<!-- the jobs still waiting name managers by their place: one after the leaver moves up a place -->
<q:set name="still" value="{[]}" />
<q:loop items="{jobs}" var="j"><q:if condition="{j.who != mi}"><q:set name="j.who" value="{j.who - 1 if j.who > mi else j.who}" /><q:set name="still" value="{still + [j]}" /></q:if></q:loop>
<q:set name="jobs" value="{still}" />
</q:function>
<!-- ===== Transfers (GAME_DESIGN: Elifoot 2's auction of wages; decisions 4, 9, 22 and 29) ===== -->
<!-- what a player is worth: $300.000 at strength 26, twice as much every 4 above and half every 4 below (a 20 about
$100.000, a 45 about $8M: a small club can buy, and a weak player sells for a little, as in Elifoot 2); 15% more
a trait (a genius two), and a third more the season after a prize (a division's top scorer or best player) -->
<q:function name="value_of" params="p">
<q:return value="{300000 * 2 ** ((p.strength - 26) / 4) * (1 + 0.15 * weight(p)) * (1.3 if award_of(p) != '' else 1)}" />
</q:function>
<!-- a prize of the season just ended, while it lasts (the next season): 'scorer', 'best', 'both'; '' none -->
<q:function name="award_of" params="p">
<q:return value="{p.award if 'award' in p and p.award_season >= season - 1 else ''}" />
</q:function>
<q:function name="upto" params="n">
<q:set name="out" value="{[]}" />
<q:loop var="k" from="0" to="{n - 1}"><q:set name="out" value="{out + [k]}" /></q:loop>
<q:return value="{out}" />
</q:function>
<!-- a listed player, where he is now (null: gone) -->
<q:function name="player_of" params="e">
<q:set name="pool" value="{clubs[e.seller].players if e.seller >= 0 else free_agents}" />
<q:loop items="{pool}" var="q"><q:if condition="{q.name == e.who}"><q:return value="{q}" /></q:if></q:loop>
<q:return value="{null}" />
</q:function>
<q:function name="listed" params="name">
<q:loop items="{market}" var="e"><q:if condition="{e.who == name}"><q:return value="{true}" /></q:if></q:loop>
<q:return value="{false}" />
</q:function>
<!-- a player on the list for three rounds: his fee (none, a free agent) and the wage he asks, his own -->
<q:function name="list_player" params="from_index, p, fee">
<q:set name="market" value="{market + [ {'who': p.name, 'seller': from_index, 'fee': round(fee / 1000) * 1000, 'wage': round(max(p.salary, 500)), 'left': 3, 'bids': [], 'pos': p.pos, 'str': p.strength} ]}" />
</q:function>
<!-- the division a free agent of this strength would play in: 36 and more the first, 30 the second, 24 the third -->
<q:function name="level_div" params="strength">
<q:return value="{1 if strength >= 36 else (2 if strength >= 30 else (3 if strength >= 24 else 4))}" />
</q:function>
<!-- would he come? 50, and: the wage (+20 at half as much again, +30 at twice, +40 at three times), the club's
division (+15 a division up, -25 a division down, -10 for one left out of his eleven: he wants to play; a free
agent weighs the division of his level, level_div: a strong one does not go down to a small club), its
place (the top four +10, the two at the bottom -10),
its players' morale (+5 above 70, -10 under 40), a club in crisis +10 for a leader; a star comes only from 60.
80 or more: he comes; under 20: he does not; between: a draw. With the reason that weighed most -->
<q:function name="acceptance" params="p, from_index, to_index, wage">
<q:set name="to" value="{clubs[to_index]}" />
<q:set name="ratio" value="{wage / max(1, p.salary)}" />
<q:set name="pay" value="{40 if ratio >= 3 else (30 if ratio >= 2 else (20 if ratio >= 1.5 else (10 if ratio >= 1.2 else (0 if ratio >= 1 else -20))))}" />
<q:set name="from_div" value="{clubs[from_index].div if from_index >= 0 else level_div(p.strength)}" />
<q:set name="level" value="{15 * (from_div - to.div) if to.div < from_div else (-10 if p.xi == '' and not p.bench and from_index >= 0 else -25) * (to.div - from_div)}" />
<q:set name="at" value="{position_of(to_index)}" />
<q:set name="table" value="{10 if at <= 4 else (-10 if at >= 9 else 0)}" />
<q:set name="mood" value="{team_morale(to)}" />
<q:set name="spirit" value="{5 if mood > 70 else (-10 if mood < 40 else 0)}" />
<q:set name="lead" value="{10 if 'Leader' in p.traits and to.balance < 0 else 0}" />
<q:set name="total" value="{50 + pay + level + table + spirit + lead}" />
<q:set name="why" value="{tr('Salary') + ' ' + ('+' if ratio >= 1 else '') + str(round(ratio * 100 - 100)) + '%'}" />
<q:set name="most" value="{abs(pay)}" />
<q:if condition="{abs(level) > most}"><q:set name="most" value="{abs(level)}" /><q:set name="why" value="{tr('Higher division') if level > 0 else tr('Lower division')}" /></q:if>
<q:if condition="{abs(table) > most}"><q:set name="most" value="{abs(table)}" /><q:set name="why" value="{tr('Top of the table') if table > 0 else tr('Bottom of the table')}" /></q:if>
<q:if condition="{abs(spirit) > most}"><q:set name="most" value="{abs(spirit)}" /><q:set name="why" value="{tr('The squad\'s morale')}" /></q:if>
<q:if condition="{'Star' in p.traits and total < 60}"><q:set name="total" value="{0}" /><q:set name="why" value="{tr('A star wants more')}" /></q:if>
<q:return value="{ {'chance': max(0, min(100, total)), 'why': why} }" />
</q:function>
<!-- each round: the computer's clubs bid (any club he would play for, one of its sixteen best or so, that can pay a
third of the fee and three months of his wage; a price under his worth draws more of them, one over it fewer); the
auctions whose time is up are decided; the computer's clubs put players on the list, of every level, and the
free agents come on it -->
<q:function name="market_round">
<q:set name="powers" value="{[]}" />
<q:loop items="{clubs}" var="c"><q:set name="powers" value="{powers + [squad_strength(c)]}" /></q:loop>
<q:loop items="{market}" var="e">
<q:set name="p" value="{player_of(e)}" />
<q:set name="lure" value="{(2 if e.fee <= 0 else min(2, value_of(p) / e.fee)) if p != null else 0}" />
<q:loop var="bi" from="0" to="{len(clubs) - 1}">
<q:set name="buyer" value="{clubs[bi]}" />
<q:if condition="{buyer.manager == 0 and bi != e.seller and len(buyer.players) < 26 and e.str >= powers[bi] - 5 and e.str <= powers[bi] + 14 and buyer.balance >= 0.5 * e.fee + 6 * e.wage and not bid_by(e, bi) and chance(0.1 * lure)}">
<q:set name="e.bids" value="{e.bids + [ {'club': bi, 'wage': round(e.wage * random(1.0, 1.5)), 'plan': 'cash' if buyer.balance >= e.fee + 3 * e.wage else 'parts'} ]}" />
</q:if>
</q:loop>
<q:set name="e.left" value="{e.left - 1}" />
</q:loop>
<q:set name="still" value="{[]}" />
<q:loop items="{market}" var="e">
<q:if condition="{e.left <= 0}"><q:call function="decide" args="{e}" />
<q:else><q:set name="still" value="{still + [e]}" /></q:else>
</q:if>
</q:loop>
<q:set name="market" value="{still}" />
<!-- a computer's club of more than 24 lets its worst reserve go: he is a free agent (his wage saved) -->
<q:loop items="{clubs}" var="c">
<q:if condition="{c.manager == 0 and len(c.players) > 24}">
<q:set name="spare" value="{[]}" />
<q:loop items="{c.players}" var="q"><q:if condition="{q.xi == '' and not q.bench and q.loan < 0 and not q.genius and not listed(q.name)}"><q:set name="spare" value="{spare + [q]}" /></q:if></q:loop>
<q:if condition="{len(spare) > 0}">
<q:set name="gone" value="{worst(spare)}" />
<q:set name="c.players" value="{without_named(c.players, gone.name)}" />
<q:set name="gone.num" value="{0}" />
<q:set name="free_agents" value="{free_agents + [gone]}" />
</q:if>
</q:if>
</q:loop>
<q:loop var="k" from="1" to="4">
<q:set name="ci" value="{min(39, floor(random(0, 40)))}" />
<q:set name="seller" value="{clubs[ci]}" />
<q:if condition="{seller.manager == 0 and len(seller.players) > 18 and len(market) < 30}">
<q:set name="sale" value="{for_sale(seller)}" />
<q:if condition="{sale != null}"><q:call function="list_player" args="{ci}, {sale.who}, {value_of(sale.who) * sale.times}" /></q:if>
</q:if>
</q:loop>
<q:set name="agents" value="{0}" />
<q:loop items="{market}" var="e"><q:if condition="{e.seller < 0}"><q:set name="agents" value="{agents + 1}" /></q:if></q:loop>
<q:set name="unlisted" value="{[]}" />
<q:loop items="{free_agents}" var="q"><q:if condition="{not listed(q.name)}"><q:set name="unlisted" value="{unlisted + [q]}" /></q:if></q:loop>
<q:loop var="k" from="1" to="{max(0, 6 - agents)}">
<q:if condition="{len(unlisted) > 0}">
<q:set name="agent" value="{pick(unlisted)}" />
<q:set name="unlisted" value="{without_named(unlisted, agent.name)}" />
<q:call function="list_player" args="{-1}, {agent}, {0}" />
</q:if>
</q:loop>
</q:function>
<q:function name="bid_by" params="e, ci">
<q:loop items="{e.bids}" var="b"><q:if condition="{b.club == ci}"><q:return value="{true}" /></q:if></q:loop>
<q:return value="{false}" />
</q:function>
<!-- what a computer's club lets go, and at what share of his worth: one of the eleven or the bench at his worth when
it is short of money; its best (a genius never) when he has outgrown it, six above its sixteen best's mean, in
the 2nd division and below: the big clubs buy him (the beta's ask: no strong player ever came on the list, and
the divisions grew alike); one of its best three now and then, dear (he wants to leave); else now and then one
of its regulars at a price that tempts it, or one left out: the list has players of every level -->
<q:function name="for_sale" params="club">
<q:set name="spare" value="{[]}" />
<q:set name="regulars" value="{[]}" />
<q:set name="stars" value="{[]}" />
<q:set name="keep" value="{best_three(club)}" />
<q:loop items="{club.players}" var="q">
<q:if condition="{q.loan < 0 and not q.genius and not listed(q.name)}">
<q:if condition="{q.xi == '' and not q.bench}"><q:set name="spare" value="{spare + [q]}" />
<q:elseif condition="{not (q.name in keep)}"><q:set name="regulars" value="{regulars + [q]}" /></q:elseif>
<q:else><q:set name="stars" value="{stars + [q]}" /></q:else>
</q:if>
</q:if>
</q:loop>
<q:if condition="{len(regulars) > 0 and club.balance < payroll(club)}"><q:return value="{ {'who': pick(regulars), 'times': 1.0} }" /></q:if>
<q:if condition="{len(stars) > 0}">
<q:set name="top" value="{best(stars)}" />
<q:if condition="{club.div >= 2 and top.strength >= squad_strength(club) + 6 and chance(0.35)}"><q:return value="{ {'who': top, 'times': random(1.2, 1.6)} }" /></q:if>
<q:if condition="{chance(0.08)}"><q:return value="{ {'who': pick(stars), 'times': random(1.5, 2.0)} }" /></q:if>
</q:if>
<q:if condition="{len(regulars) > 0 and chance(0.45)}"><q:return value="{ {'who': pick(regulars), 'times': random(1.1, 1.5)} }" /></q:if>
<q:if condition="{len(spare) > 0 and chance(0.3)}"><q:return value="{ {'who': pick(spare), 'times': random(0.8, 1.2)} }" /></q:if>
<q:return value="{null}" />
</q:function>
<q:function name="worst" params="players">
<q:set name="low" value="{players[0]}" />
<q:loop items="{players}" var="q"><q:if condition="{q.strength < low.strength}"><q:set name="low" value="{q}" /></q:if></q:loop>
<q:return value="{low}" />
</q:function>
<!-- an auction decided: the bids from the highest wage down, each drawn by its chance; the first he takes, he goes
(if the club can still pay); none taken, he stays -->
<q:function name="decide" params="e">
<q:set name="p" value="{player_of(e)}" />
<q:if condition="{p == null}"><q:return value="{false}" /></q:if>
<q:set name="keys" value="{[]}" />
<q:loop var="bi" from="0" to="{len(e.bids) - 1}"><q:set name="keys" value="{keys + [[e.bids[bi].wage, bi]]}" /></q:loop>
<q:loop items="{sort(keys, true)}" var="key">
<q:set name="b" value="{e.bids[key[1]]}" />
<q:set name="buyer" value="{clubs[b.club]}" />
<q:if condition="{len(buyer.players) < 30 and crisis(buyer) < 2 and buyer.balance >= (e.fee if b.plan == 'cash' else 0.3 * e.fee)}">
<q:set name="yes" value="{acceptance(p, e.seller, b.club, b.wage)}" />
<q:if condition="{yes.chance >= 80 or (yes.chance >= 20 and chance(yes.chance / 100))}">
<q:call function="transfer" args="{e}, {p}, {b}, {yes.chance}" />
<q:return value="{true}" />
</q:if>
</q:if>
</q:loop>
<q:if condition="{(e.seller >= 0 and clubs[e.seller].manager > 0) or managed_bid(e)}"><q:call function="said" args="{'No deal:'}, {p.name + ' (' + (clubs[e.seller].short if e.seller >= 0 else tr('free')) + ')'}" /></q:if>
<q:return value="{false}" />
</q:function>
<q:function name="managed_bid" params="e">
<q:loop items="{e.bids}" var="b"><q:if condition="{clubs[b.club].manager > 0}"><q:return value="{true}" /></q:if></q:loop>
<q:return value="{false}" />
</q:function>
<!-- he goes: off the seller's squad (or the free agents), on the buyer's with the wage of the bid; the fee paid at
once, or 30% now and 7 months of 10% -->
<q:function name="transfer" params="e, p, b, odds">
<q:set name="buyer" value="{clubs[b.club]}" />
<q:if condition="{e.seller >= 0}">
<q:set name="seller" value="{clubs[e.seller]}" />
<q:set name="seller.players" value="{without_named(seller.players, p.name)}" />
<q:set name="down" value="{e.fee if b.plan == 'cash' else 0.3 * e.fee}" />
<q:set name="seller.balance" value="{seller.balance + down}" />
<q:else><q:set name="free_agents" value="{without_named(free_agents, p.name)}" /><q:set name="down" value="{0}" /></q:else>
</q:if>
<q:set name="buyer.balance" value="{buyer.balance - (e.fee if b.plan == 'cash' else 0.3 * e.fee)}" />
<q:if condition="{b.plan != 'cash' and e.fee > 0}"><q:set name="buyer.debts" value="{buyer.debts + [ {'to': e.seller, 'each': 0.1 * e.fee, 'left': 7, 'what': p.name} ]}" /></q:if>
<q:set name="p.salary" value="{b.wage}" />
<q:set name="p.xi" value="{''}" />
<q:set name="p.bench" value="{false}" />
<q:set name="p.start" value="{''}" />
<q:set name="p.sat" value="{false}" />
<q:set name="p.num" value="{next_number(buyer)}" />
<q:set name="buyer.players" value="{buyer.players + [p]}" />
<q:call function="said" args="{'Surprise! He took the long shot:' if odds < 50 else 'Signed:'}, {p.name + ' → ' + buyer.name + (' (' + money(e.fee) + ')' if e.fee > 0 else '')}" />
</q:function>
<q:function name="without_named" params="players, name">
<q:set name="out" value="{[]}" />
<q:loop items="{players}" var="q"><q:if condition="{q.name != name}"><q:set name="out" value="{out + [q]}" /></q:if></q:loop>
<q:return value="{out}" />
</q:function>
<q:function name="next_number" params="club">
<q:set name="top" value="{0}" />
<q:loop items="{club.players}" var="q"><q:if condition="{q.num > top}"><q:set name="top" value="{q.num}" /></q:if></q:loop>
<q:return value="{top + 1}" />
</q:function>
<!-- the month's instalments: paid, and the seller's received -->
<q:function name="pay_instalments" params="club">
<q:set name="owing" value="{[]}" />
<q:loop items="{club.debts}" var="debt">
<q:set name="club.balance" value="{club.balance - debt.each}" />
<q:if condition="{debt.to >= 0}"><q:set name="creditor" value="{clubs[debt.to]}" /><q:set name="creditor.balance" value="{creditor.balance + debt.each}" /></q:if>
<q:set name="debt.left" value="{debt.left - 1}" />
<q:if condition="{debt.left > 0}"><q:set name="owing" value="{owing + [debt]}" /></q:if>
</q:loop>
<q:set name="club.debts" value="{owing}" />
</q:function>
<q:function name="instalments_of" params="club">
<q:set name="sum" value="{0}" />
<q:loop items="{club.debts}" var="debt"><q:set name="sum" value="{sum + debt.each}" /></q:loop>
<q:return value="{sum}" />
</q:function>
<!-- a manager's bid on the list: the wage (a share of what he asks) and how it is paid; the club's bid before is
replaced -->
<q:function name="place_bid" params="e, club_index, wage, plan">
<q:set name="kept" value="{[]}" />
<q:loop items="{e.bids}" var="b"><q:if condition="{b.club != club_index}"><q:set name="kept" value="{kept + [b]}" /></q:if></q:loop>
<q:set name="e.bids" value="{kept + [ {'club': club_index, 'wage': wage, 'plan': plan} ]}" />
</q:function>
<!-- borrowed for the rest of the season, a tenth of the fee to his club; his wage paid by the borrower; back with
the new season (decision 29) -->
<q:function name="borrow" params="ei, club_index">
<q:set name="e" value="{market[ei]}" />
<q:set name="p" value="{player_of(e)}" />
<q:set name="lender" value="{clubs[e.seller]}" />
<q:set name="borrower" value="{clubs[club_index]}" />
<q:set name="borrower.balance" value="{borrower.balance - 0.1 * e.fee}" />
<q:set name="lender.balance" value="{lender.balance + 0.1 * e.fee}" />
<q:set name="lender.players" value="{without_named(lender.players, p.name)}" />
<q:set name="p.loan" value="{e.seller}" />
<q:set name="p.xi" value="{''}" />
<q:set name="p.bench" value="{false}" />
<q:set name="p.num" value="{next_number(borrower)}" />
<q:set name="borrower.players" value="{borrower.players + [p]}" />
<q:set name="left_on" value="{[]}" />
<q:loop var="k" from="0" to="{len(market) - 1}"><q:if condition="{k != ei}"><q:set name="left_on" value="{left_on + [market[k]]}" /></q:if></q:loop>
<q:set name="market" value="{left_on}" />
<q:call function="said" args="{'On loan:'}, {p.name + ' → ' + borrower.name}" />
</q:function>
<q:function name="return_loans">
<q:loop var="ci" from="0" to="{len(clubs) - 1}">
<q:set name="holder" value="{clubs[ci]}" />
<q:set name="kept" value="{[]}" />
<q:loop items="{holder.players}" var="q">
<q:if condition="{q.loan >= 0}">
<q:set name="home" value="{clubs[q.loan]}" />
<q:set name="q.loan" value="{-1}" />
<q:set name="q.num" value="{next_number(home)}" />
<q:set name="home.players" value="{home.players + [q]}" />
<q:else><q:set name="kept" value="{kept + [q]}" /></q:else>
</q:if>
</q:loop>
<q:set name="holder.players" value="{kept}" />
</q:loop>
</q:function>
<!-- a row of the list: who, where, what he costs and asks, the best bid, the rounds left -->
<q:function name="market_row" params="e">
<q:set name="best" value="{0}" />
<q:loop items="{e.bids}" var="b"><q:if condition="{b.wage > best}"><q:set name="best" value="{b.wage}" /></q:if></q:loop>
<q:set name="mine" value="{bid_by(e, managers[turn].club)}" />
<q:return value="{fit(e.who, 16) + fit(tr(e.pos), 4) + fit(str(e.str), 4, 'right') + ' ' + fit(clubs[e.seller].short if e.seller >= 0 else tr('free'), 6) + fit(money(e.fee) if e.fee > 0 else '-', 13, 'right') + fit(money(e.wage), 10, 'right') + fit(money(best) if best > 0 else '-', 10, 'right') + fit(str(e.left), 4, 'right') + (' ✓' if mine else '')}" />
</q:function>
<!-- ===== The ground (GAME_DESIGN: the stadium system) ===== -->
<!-- how much of the gate a ground's health lets in: all of it from 90, 97% from 70, 87% from 50, 70% from 30, half
below -->
<q:function name="gate_share" params="club">
<q:return value="{1.0 if club.health >= 90 else (0.97 if club.health >= 70 else (0.87 if club.health >= 50 else (0.7 if club.health >= 30 else 0.5)))}" />
</q:function>
<!-- the seats a ground can open: all of them from 50, nine in ten from 30, seven from 15, else half -->
<q:function name="open_seats" params="club">
<q:return value="{round(club.capacity * (1.0 if club.health >= 50 else (0.9 if club.health >= 30 else (0.7 if club.health >= 15 else 0.5))))}" />
</q:function>
<q:function name="health_word" params="h">
<q:return value="{tr('spotless') if h >= 90 else (tr('needs care') if h >= 70 else (tr('run down') if h >= 50 else tr('critical')))}" />
</q:function>
<!-- the works: maintenance, $3 a seat, 20 points of health at once; an expansion, a thousand seats or a quarter more,
at a price a seat by the division ($200 in the first to $80 in the fourth), three months (twelve rounds), the
ground new when it is done (health over 60 to start it). A third is paid at once, the rest in seven months -->
<q:function name="maintenance_cost" params="club">
<q:return value="{club.capacity * 3}" />
</q:function>
<q:function name="expansion_seats" params="club, big">
<q:return value="{max(1000, round(club.capacity / 4 / 500) * 500) if big else 1000}" />
</q:function>
<q:function name="expansion_cost" params="club, big">
<q:return value="{expansion_seats(club, big) * [200, 150, 110, 80][club.div - 1]}" />
</q:function>
<!-- what the button says when it cannot be done ('' when done) -->
<q:function name="maintain" params="club">
<q:if condition="{club.balance < maintenance_cost(club)}"><q:return value="{tr('Not enough money: it costs') + ' ' + money(maintenance_cost(club))}" /></q:if>
<q:set name="club.balance" value="{club.balance - maintenance_cost(club)}" />
<q:set name="club.health" value="{min(100, club.health + 20)}" />
<q:return value="{''}" />
</q:function>
<!-- what stops a ground from growing, money aside ('' nothing) -->
<q:function name="expand_block" params="club">
<q:if condition="{club.works > 0}"><q:return value="{tr('The works going on must end first.')}" /></q:if>
<q:if condition="{club.health <= 60}"><q:return value="{tr('Health over 60 first: Maintenance.')}" /></q:if>
<q:if condition="{crisis(club) >= 2}"><q:return value="{tr('In crisis: no works.')}" /></q:if>
<q:return value="{''}" />
</q:function>
<q:function name="expand" params="club, big">
<q:set name="cost" value="{expansion_cost(club, big)}" />
<q:if condition="{club.works > 0}"><q:return value="{tr('The works going on must end first.')}" /></q:if>
<q:if condition="{club.health <= 60}"><q:return value="{tr('The ground needs care first (health over 60): Maintenance.')}" /></q:if>
<q:if condition="{crisis(club) >= 2}"><q:return value="{tr('CRISIS: no buying')}" /></q:if>
<q:if condition="{club.balance < cost / 3}"><q:return value="{tr('Not enough money: a third at once is') + ' ' + money(cost / 3)}" /></q:if>
<q:set name="club.balance" value="{club.balance - cost / 3}" />
<q:set name="club.debts" value="{club.debts + [ {'to': -1, 'each': cost * 2 / 3 / 7, 'left': 7, 'what': 'works'} ]}" />
<q:set name="club.adding" value="{expansion_seats(club, big)}" />
<q:set name="club.works" value="{12}" />
<q:if condition="{club.manager > 0}"><q:call function="said" args="{'Works begin:'}, {club.name + ', +' + thousands(club.adding, '.') + ' ' + tr('seats')}" /></q:if>
<q:return value="{''}" />
</q:function>
<!-- ===== The crisis (GAME_DESIGN: financial crisis and bankruptcy) ===== -->
<!-- 0 in the black; 1 an alert (to -$500.000); 2 the crisis (to -$2M): no buying, the squad's morale falls; 3 grave
(to -$5M): six points off once a season and the best player auctioned; 4 bankruptcy -->
<q:function name="crisis" params="club">
<q:return value="{0 if club.balance >= 0 else (1 if club.balance >= -500000 else (2 if club.balance >= -2000000 else (3 if club.balance >= -5000000 else 4)))}" />
</q:function>
<q:function name="crisis_text" params="club">
<q:return value="{['', tr('Watch the finances!'), tr('CRISIS: no buying'), tr('GRAVE: points off, best sold'), tr('BANKRUPT')][crisis(club)]}" />
</q:function>
<!-- a club's month: its ground wears and its works go on, the computer keeps its ground up, and the crisis bites -->
<q:function name="month_of" params="ci">
<q:set name="club" value="{clubs[ci]}" />
<q:set name="club.health" value="{max(0, club.health - 0.2)}" />
<q:if condition="{club.works > 0}">
<q:set name="club.works" value="{max(0, club.works - 4)}" />
<q:if condition="{club.works == 0}">
<q:set name="club.capacity" value="{club.capacity + club.adding}" />
<q:set name="club.adding" value="{0}" />
<q:set name="club.health" value="{100}" />
<q:if condition="{club.manager > 0}"><q:call function="said" args="{'The new stand is open:'}, {club.name + ', ' + thousands(club.capacity, '.') + ' ' + tr('seats')}" /></q:if>
</q:if>
</q:if>
<q:if condition="{club.manager == 0 and club.health < 60 and club.balance >= 2 * maintenance_cost(club)}"><q:call function="maintain" args="{club}" /></q:if>
<!-- the computer grows a ground that fills, when it can pay for it four times over -->
<q:if condition="{club.manager == 0 and club.works == 0 and club.health > 60 and club.crowd >= 0.9 * club.capacity and club.balance >= 4 * expansion_cost(club, false)}"><q:call function="expand" args="{club}, {false}" /></q:if>
<q:set name="level" value="{crisis(club)}" />
<q:if condition="{level >= 2}"><q:loop items="{club.players}" var="q"><q:set name="q.morale" value="{max(20, q.morale - 5)}" /></q:loop></q:if>
<q:if condition="{level >= 3 and club.fined != season}">
<q:set name="club.points" value="{club.points - 6}" />
<q:set name="club.fined" value="{season}" />
<q:call function="said" args="{'Six points off for the debts:'}, {club.name}" />
<q:set name="top" value="{best(club.players)}" />
<q:if condition="{not listed(top.name) and len(club.players) > 16}"><q:call function="list_player" args="{ci}, {top}, {value_of(top) * 0.8}" /></q:if>
</q:if>
<q:if condition="{level >= 4}">
<q:call function="said" args="{'Bankrupt: rescued, and down a division:'}, {club.name}" />
<q:set name="club.balance" value="{0}" />
<q:set name="club.debts" value="{[]}" />
<q:set name="club.bankrupt" value="{true}" />
<q:loop var="mi" from="0" to="{len(managers) - 1}">
<q:if condition="{managers[mi].club == ci and not sacked_already(mi)}"><q:call function="sack" args="{mi}" /></q:if>
</q:loop>
</q:if>
</q:function>
<!-- ===== The season makes the players (decisions 23 and 32; the months' growth is the ratings', month_growth) ===== -->
<!-- the season's end for the players: a player of more than 30 who hardly played (four matches) and did not grow in
training loses a point (every reserve did, and the world grew weaker by half a point a season once the months
made the growth); twelve goals
make a forward or a midfielder a Star; what each grew is told, and the season's goals, matches and ratings start
again. No age, no retirement -->
<q:function name="growth">
<q:loop items="{clubs}" var="c">
<q:set name="better" value="{[]}" />
<q:loop items="{c.players}" var="q">
<q:if condition="{q.apps < 4 and q.strength > 30 and q.grown <= 0}"><q:set name="q.strength" value="{q.strength - 1}" /><q:set name="q.grown" value="{q.grown - 1}" /></q:if>
<q:if condition="{q.grown > 0}"><q:set name="better" value="{better + [q.name + ' +' + str(q.grown)]}" /></q:if>
<q:if condition="{q.goals >= 12 and q.pos != 'GK' and not ('Star' in q.traits)}">
<q:set name="q.traits" value="{q.traits + ['Star']}" />
<q:call function="said" args="{'A star is born:'}, {q.name + ' (' + c.short + ', ' + str(q.goals) + ' ' + tr('goals') + ')'}" />
</q:if>
<q:set name="q.total" value="{q.total + q.goals}" />
<q:set name="q.apps" value="{0}" />
<q:set name="q.rsum" value="{0}" />
<q:set name="q.rn" value="{0}" />
<q:set name="q.grown" value="{0}" />
</q:loop>
<q:if condition="{c.manager > 0 and len(better) > 0}"><q:call function="said" args="{'Better this season:'}, {join(better[0:4], ', ')}" /></q:if>
</q:loop>
</q:function>
<!-- ===== The season's prizes (the beta's ask: a top scorer who matters) ===== -->
<!-- a player of the world by his name: [club, player] (club -1: a free agent; null: no one) -->
<q:function name="find_player" params="name">
<q:loop var="ci" from="0" to="{len(clubs) - 1}">
<q:loop items="{clubs[ci].players}" var="q"><q:if condition="{q.name == name}"><q:return value="{[ci, q]}" /></q:if></q:loop>
</q:loop>
<q:loop items="{free_agents}" var="q"><q:if condition="{q.name == name}"><q:return value="{[-1, q]}" /></q:if></q:loop>
<q:return value="{null}" />
</q:function>
<!-- a division's best player of the season: the best mean rating of ten matches at least (null: none yet) -->
<q:function name="best_of_div" params="d">
<q:set name="top" value="{null}" />
<q:set name="at" value="{-1}" />
<q:loop items="{in_div(d)}" var="ci">
<q:loop items="{clubs[ci].players}" var="q">
<q:if condition="{'rn' in q and q.rn >= 10 and (top == null or mean_rating(q) > mean_rating(top))}"><q:set name="top" value="{q}" /><q:set name="at" value="{ci}" /></q:if>
</q:loop>
</q:loop>
<q:return value="{null if top == null else [at, top]}" />
</q:function>
<!-- a division's top scorer of the season: [club, player] (null: no goal yet) -->
<q:function name="scorer_of_div" params="d">
<q:set name="top" value="{top_scorers(1, d)}" />
<q:return value="{null if len(top) == 0 else find_player(top[0].name)}" />
</q:function>
<!-- what a division pays: its champion, its runner-up, its top scorer and its best player -->
<q:function name="prize_of" params="d, what">
<q:return value="{ {'champion': [3000000, 1200000, 400000, 150000], 'runner-up': [1500000, 600000, 200000, 75000], 'scorer': [400000, 200000, 80000, 40000], 'best': [400000, 200000, 80000, 40000]}[what][d - 1] }" />
</q:function>
<q:function name="pay_prize" params="ci, amount, why">
<q:if condition="{ci < 0}"><q:return /></q:if>
<q:set name="club" value="{clubs[ci]}" />
<q:set name="club.balance" value="{club.balance + amount}" />
<q:set name="club.prizes" value="{(club.prizes if 'prizes' in club else 0) + amount}" />
<q:if condition="{club.manager > 0}"><q:call function="said" args="{why}, {club.name + ' +' + money(amount)}" /></q:if>
</q:function>
<!-- the season's prizes, as it ends: each division's champion and runner-up, top scorer and best player (who shines
in gold the next season, and is worth a third more); what they were goes into the honours -->
<q:function name="season_prizes" params="orders">
<q:set name="lines" value="{[]}" />
<q:loop var="d" from="1" to="4">
<q:call function="pay_prize" args="{orders[d - 1][0]}, {prize_of(d, 'champion')}, {'Prize for the title:'}" />
<q:call function="pay_prize" args="{orders[d - 1][1]}, {prize_of(d, 'runner-up')}, {'Prize for the runner-up:'}" />
<q:set name="sc" value="{scorer_of_div(d)}" />
<q:set name="bp" value="{best_of_div(d)}" />
<q:if condition="{sc != null}">
<q:set name="who" value="{sc[1]}" />
<q:set name="who.award" value="{'scorer'}" />
<q:set name="who.award_season" value="{season}" />
<q:call function="pay_prize" args="{sc[0]}, {prize_of(d, 'scorer')}, {'Prize for the top scorer:'}" />
</q:if>
<q:if condition="{bp != null}">
<q:set name="who" value="{bp[1]}" />
<q:set name="who.award" value="{'both' if who.award == 'scorer' and who.award_season == season else 'best'}" />
<q:set name="who.award_season" value="{season}" />
<q:call function="pay_prize" args="{bp[0]}, {prize_of(d, 'best')}, {'Prize for the best player:'}" />
</q:if>
<q:set name="lines" value="{lines + [ {'scorer': '-' if sc == null else sc[1].name + ' (' + clubs[sc[0]].short + ', ' + str(sc[1].goals) + ')', 'best': '-' if bp == null else bp[1].name + ' (' + clubs[bp[0]].short + ', ' + r1(mean_rating(bp[1])) + ')'} ]}" />
</q:loop>
<q:return value="{lines}" />
</q:function>
<!-- a prize's mark and colour: ✪ the top scorer, ✦ the best player, in gold -->
<q:function name="award_mark" params="p">
<q:set name="a" value="{award_of(p)}" />
<q:return value="{'✪' if a == 'scorer' else ('✦' if a == 'best' else ('✪✦' if a == 'both' else ''))}" />
</q:function>
<!-- a club's three best: the strongest, the fitter first of the same strength -->
<q:function name="best_three" params="club">
<q:set name="keys" value="{[]}" />
<q:loop items="{club.players}" var="q"><q:set name="keys" value="{keys + [[q.strength, q.form, q.name]]}" /></q:loop>
<q:set name="out" value="{[]}" />
<q:loop items="{sort(keys, true)[0:3]}" var="k"><q:set name="out" value="{out + [k[2]]}" /></q:loop>
<q:return value="{out}" />
</q:function>
<!-- ===== The line-up ===== -->
<!-- a formation's shape: how many defenders, midfielders, forwards -->
<q:function name="shape" params="formation">
<q:return value="{ {'4-4-2': [4, 4, 2], '4-3-3': [4, 3, 3], '5-3-2': [5, 3, 2], '4-5-1': [4, 5, 1], '3-4-3': [3, 4, 3], '5-4-1': [5, 4, 1]}[formation] }" />
</q:function>
<q:function name="available" params="p">
<q:return value="{p.injured == 0 and p.suspended == 0}" />
</q:function>
<!-- how well a player plays a place: their own 100%, a second one 80%, another 50% -->
<q:function name="fit_for" params="p, place">
<q:return value="{1.0 if p.pos == place else (0.8 if place in p.sec else 0.5)}" />
</q:function>
<!-- what a player plays at in a place: strength, an ace +12, a genius +25; morale a tenth either way, and how
fresh: a tired player (form 40) plays a fifth worse than a rested one; a genius or an ace feels both by half -->
<q:function name="eff" params="p, place">
<q:set name="base" value="{p.strength + (12 if 'Ace' in p.traits else 0) + (25 if p.genius else 0)}" />
<q:set name="mood" value="{(0.9 + 0.1 * p.morale / 100) * (0.7 + 0.3 * p.form / 100)}" />
<q:if condition="{p.genius or 'Ace' in p.traits}"><q:set name="mood" value="{(mood + 1) / 2}" /></q:if>
<q:return value="{base * fit_for(p, place) * mood}" />
</q:function>
<!-- the best eleven for the club's formation, picked again from the whole squad -->
<!-- a manager's eleven: who he picked (the line-up screen, F1; at first the eleven he was given), each back in his
place once he can play again; who stood in for him steps back; the gaps filled as the computer would -->
<q:function name="picked" params="p">
<q:return value="{p.pick if 'pick' in p else ''}" />
</q:function>
<q:function name="mark_picks" params="club">
<q:loop items="{club.players}" var="p"><q:set name="p.pick" value="{p.xi}" /></q:loop>
</q:function>
<q:function name="manager_lineup" params="club">
<q:set name="any" value="{false}" />
<q:loop items="{club.players}" var="p"><q:if condition="{picked(p) != ''}"><q:set name="any" value="{true}" /></q:if></q:loop>
<q:if condition="{not any}"><q:call function="mark_picks" args="{club}" /></q:if>
<q:loop items="{club.players}" var="p">
<q:if condition="{picked(p) != '' and available(p)}"><q:set name="p.xi" value="{picked(p)}" /><q:set name="p.bench" value="{false}" />
<q:elseif condition="{p.xi != '' and picked(p) == ''}"><q:set name="p.xi" value="{''}" /></q:elseif>
</q:if>
</q:loop>
<q:call function="fill_lineup" args="{club}" />
</q:function>
<q:function name="auto_lineup" params="club">
<q:loop items="{club.players}" var="p"><q:set name="p.xi" value="{''}" /><q:set name="p.bench" value="{false}" /></q:loop>
<q:call function="fill_lineup" args="{club}" />
</q:function>
<!-- the eleven's open places filled, and the bench up to seven: who cannot play leaves first, and who is in a
place the formation does not have; each place by those who play it as their own first, then by those for
whom it is a second position, then by anyone (a genius of the midfield plays in the midfield, not in goal
because half of him beats the keeper). A manager's eleven stays as it is but for the gaps -->
<q:function name="fill_lineup" params="club">
<q:set name="n" value="{shape(club.formation)}" />
<q:set name="open" value="{['GK'] + ['DF', 'DF', 'DF', 'DF', 'DF'][0:n[0]] + ['MF', 'MF', 'MF', 'MF', 'MF'][0:n[1]] + ['FW', 'FW', 'FW'][0:n[2]]}" />
<q:loop items="{club.players}" var="p">
<q:if condition="{not available(p)}"><q:set name="p.xi" value="{''}" /><q:set name="p.bench" value="{false}" /></q:if>
</q:loop>
<!-- who keeps a place, the best first: with fewer places of a kind, the weakest of the eleven leaves -->
<q:set name="keys" value="{[]}" />
<q:loop var="k" from="0" to="{len(club.players) - 1}"><q:if condition="{club.players[k].xi != ''}"><q:set name="keys" value="{keys + [[eff(club.players[k], club.players[k].xi), 100 - k]]}" /></q:if></q:loop>
<q:loop items="{sort(keys, true)}" var="key">
<q:set name="keeps" value="{club.players[100 - key[1]]}" />
<q:if condition="{keeps.xi in open}"><q:set name="open" value="{without(open, keeps.xi)}" /><q:set name="keeps.bench" value="{false}" />
<q:else><q:set name="keeps.xi" value="{''}" /></q:else>
</q:if>
</q:loop>
<q:loop items="{[1.0, 0.8, 0.5]}" var="least">
<q:loop items="{open}" var="place">
<q:set name="top" value="{null}" />
<q:loop items="{club.players}" var="p">
<q:if condition="{p.xi == '' and available(p) and fit_for(p, place) >= least and (top == null or eff(p, place) > eff(top, place))}"><q:set name="top" value="{p}" /></q:if>
</q:loop>
<q:if condition="{top != null}"><q:set name="top.xi" value="{place}" /><q:set name="top.bench" value="{false}" /><q:set name="open" value="{without(open, place)}" /></q:if>
</q:loop>
</q:loop>
<q:set name="seats" value="{0}" />
<q:loop items="{club.players}" var="p"><q:if condition="{p.bench}"><q:set name="seats" value="{seats + 1}" /></q:if></q:loop>
<q:loop var="k" from="{seats + 1}" to="7">
<q:set name="top" value="{null}" />
<q:loop items="{club.players}" var="p">
<q:if condition="{p.xi == '' and not p.bench and available(p) and (top == null or p.strength > top.strength)}"><q:set name="top" value="{p}" /></q:if>
</q:loop>
<q:if condition="{top != null}"><q:set name="top.bench" value="{true}" /></q:if>
</q:loop>
</q:function>
<!-- who is on the pitch: the eleven, less who was sent off, carried off or taken off -->
<q:function name="on_pitch" params="club">
<q:set name="out" value="{[]}" />
<q:loop items="{club.players}" var="p"><q:if condition="{p.xi != '' and p.off == ''}"><q:set name="out" value="{out + [p]}" /></q:if></q:loop>
<q:return value="{out}" />
</q:function>
<q:function name="eleven" params="club">
<q:set name="out" value="{[]}" />
<q:loop items="{club.players}" var="p"><q:if condition="{p.xi != ''}"><q:set name="out" value="{out + [p]}" /></q:if></q:loop>
<q:return value="{out}" />
</q:function>
<!-- eleven who can play, one of them in goal -->
<q:function name="lineup_ok" params="club">
<q:set name="keepers" value="{0}" />
<q:set name="out" value="{0}" />
<q:loop items="{eleven(club)}" var="p">
<q:if condition="{p.xi == 'GK'}"><q:set name="keepers" value="{keepers + 1}" /></q:if>
<q:if condition="{not available(p)}"><q:set name="out" value="{out + 1}" /></q:if>
</q:loop>
<q:return value="{len(eleven(club)) == 11 and keepers == 1 and out == 0}" />
</q:function>
<!-- ===== Tactics (GAME_DESIGN v0.2: formations, styles and the counter-tactics) ===== -->
<!-- a formation's lean, attack and defence -->
<q:function name="lean" params="formation">
<q:return value="{ {'4-4-2': [1.0, 1.0], '4-3-3': [1.06, 0.96], '5-3-2': [0.95, 1.07], '4-5-1': [0.93, 1.04], '3-4-3': [1.1, 0.9], '5-4-1': [0.88, 1.1]}[formation] }" />
</q:function>
<!-- a style's change to attack and defence -->
<q:function name="mods" params="style">
<q:return value="{ {'Balanced': [0, 0], 'Attacking': [0.15, -0.1], 'All-out attack': [0.3, -0.2], 'Defensive': [-0.1, 0.15], 'Park the bus': [-0.2, 0.3], 'Counter-attack': [0, 0.05], 'Kamikaze': [0.5, -0.4]}[style] }" />
</q:function>
<!-- a style's family: attacking, defensive, counter-attacking, balanced -->
<q:function name="family" params="style">
<q:return value="{ {'Balanced': 'B', 'Attacking': 'A', 'All-out attack': 'A', 'Kamikaze': 'A', 'Defensive': 'D', 'Park the bus': 'D', 'Counter-attack': 'C'}[style] }" />
</q:function>
<!-- the counter-tactics: what one family of styles gains against another -->
<q:function name="edge" params="mine, theirs">
<q:return value="{ {'AA': 0, 'AD': 0.1, 'AC': -0.15, 'AB': 0.05, 'DA': -0.1, 'DD': 0, 'DC': 0.1, 'DB': 0, 'CA': 0.15, 'CD': -0.1, 'CC': 0, 'CB': 0.05, 'BA': -0.05, 'BD': 0, 'BC': -0.05, 'BB': 0}[family(mine) + family(theirs)] }" />
</q:function>
<!-- how fast a style makes a match, for both sides: a side that sits back makes fewer chances at both ends
(fewer goals, more draws); one that throws everyone forward makes more -->
<q:function name="tempo" params="style">
<q:return value="{ {'Balanced': 1.0, 'Attacking': 1.05, 'All-out attack': 1.1, 'Kamikaze': 1.18, 'Defensive': 0.88, 'Park the bus': 0.75, 'Counter-attack': 0.95}[style] }" />
</q:function>
<!-- the style a club tends to start with: of its last five matches, the family it started most with (of a tie,
the latest), as it last played it; Balanced before its first match -->
<q:function name="usual" params="club">
<q:if condition="{len(club.history) == 0}"><q:return value="{'Balanced'}" /></q:if>
<q:set name="fams" value="{[]}" />
<q:loop items="{club.history}" var="h"><q:set name="fams" value="{fams + [family(h.style)]}" /></q:loop>
<q:set name="top" value="{club.history[0].style}" />
<q:loop items="{club.history}" var="h"><q:if condition="{times_in(fams, family(h.style)) > times_in(fams, family(top))}"><q:set name="top" value="{h.style}" /></q:if></q:loop>
<q:return value="{top}" />
</q:function>
<!-- an objective's tactics against the style the opponent tends to start with: the start, and what to change
to when winning, drawing or losing after a minute (the first due of each, in order) -->
<q:function name="preset" params="club, objective, theirs">
<q:set name="club.objective" value="{objective}" />
<q:set name="fam" value="{family(theirs)}" />
<q:if condition="{objective == 'Win'}">
<q:set name="club.formation" value="{'4-3-3'}" /><q:set name="club.style" value="{'Balanced' if fam == 'C' else 'Attacking'}" />
<q:set name="club.reactions" value="{[ {'when': 'winning', 'after': 75, 'formation': '4-4-2', 'style': 'Balanced'}, {'when': 'drawing', 'after': 60, 'formation': '3-4-3', 'style': 'All-out attack'}, {'when': 'losing', 'after': 60, 'formation': '3-4-3', 'style': 'All-out attack'}, {'when': 'losing', 'after': 80, 'formation': '3-4-3', 'style': 'Kamikaze'} ]}" />
<q:elseif condition="{objective == 'Draw'}">
<q:set name="club.formation" value="{'4-5-1'}" /><q:set name="club.style" value="{'Counter-attack' if fam == 'A' else 'Defensive'}" />
<q:set name="club.reactions" value="{[ {'when': 'winning', 'after': 60, 'formation': '5-4-1', 'style': 'Park the bus'}, {'when': 'drawing', 'after': 70, 'formation': '5-4-1', 'style': 'Defensive'}, {'when': 'losing', 'after': 70, 'formation': '4-4-2', 'style': 'Balanced'}, {'when': 'losing', 'after': 82, 'formation': '4-3-3', 'style': 'Attacking'} ]}" />
</q:elseif>
<q:elseif condition="{objective == 'Avoid defeat'}">
<q:set name="club.formation" value="{'5-4-1'}" /><q:set name="club.style" value="{'Counter-attack' if fam == 'A' else ('Defensive' if fam == 'C' else 'Park the bus')}" />
<q:set name="club.reactions" value="{[ {'when': 'winning', 'after': 60, 'formation': '5-4-1', 'style': 'Park the bus'}, {'when': 'losing', 'after': 75, 'formation': '4-4-2', 'style': 'Attacking'}, {'when': 'losing', 'after': 85, 'formation': '3-4-3', 'style': 'Kamikaze'} ]}" />
</q:elseif>
<q:elseif condition="{objective == 'By hand'}">
<q:set name="club.formation" value="{'4-4-2'}" /><q:set name="club.style" value="{'Balanced'}" />
<q:set name="club.reactions" value="{[]}" />
</q:elseif>
</q:if>
</q:function>
<!-- a club's tactics for its next match: the computer's, and a manager's on Auto, by the opponent's strength; a manager's
objective from its start again (what the last match changed is undone); by hand as left. The computer picks
its eleven again (the rested come back, the tired rest); a manager's eleven stays, its gaps filled -->
<q:function name="ready" params="club, opponent, home">
<q:set name="auto" value="{club.manager > 0 and club.objective == 'Auto'}" />
<q:set name="club.plan" value="{auto_objective(club, opponent, home) if club.manager == 0 or auto else club.objective}" />
<!-- a manager's objective reads the style the opponent tends to start with; the computer plays its objective's
own, as Elifoot's did: it can be read, and beaten (when both sides read each other, the weaker side
counters the stronger every time, and wins one match in three) -->
<q:if condition="{club.plan != 'By hand'}"><q:call function="preset" args="{club}, {club.plan}, {usual(opponent) if club.manager > 0 else 'Balanced'}" /></q:if>
<q:if condition="{auto}"><q:set name="club.objective" value="{'Auto'}" /></q:if>
<q:if condition="{club.manager == 0}"><q:call function="auto_lineup" args="{club}" />
<q:else><q:call function="manager_lineup" args="{club}" /></q:else>
</q:if>
</q:function>
<!-- Auto: the objective a manager would pick against this opponent, here or away -->
<q:function name="auto_objective" params="club, opponent, home">
<q:set name="mine" value="{rating(club)}" />
<q:set name="theirs" value="{rating(opponent)}" />
<q:if condition="{mine > theirs * 1.08 or (home and mine > theirs * 0.97)}"><q:return value="{'Win'}" /></q:if>
<q:if condition="{mine < theirs * 0.85}"><q:return value="{'Avoid defeat'}" /></q:if>
<q:return value="{'Draw'}" />
</q:function>
<!-- how strong a club's eleven is, whatever its formation: attack and defence without the formation's lean (the
objective picked by it does not change the objective again) -->
<q:function name="rating" params="club">
<q:set name="pw" value="{power(club)}" />
<q:return value="{pw[0] / lean(club.formation)[0] + pw[1] / lean(club.formation)[1]}" />
</q:function>
<!-- a club's attack and defence as it lines up (in a match, as it stands on the pitch): the forwards and midfielders attack, the keeper, the defence and the midfield defend -->
<q:function name="power" params="club">
<q:set name="sums" value="{ {'GK': 0, 'DF': 0, 'MF': 0, 'FW': 0} }" />
<q:set name="counts" value="{ {'GK': 0, 'DF': 0, 'MF': 0, 'FW': 0} }" />
<q:set name="walls" value="{0}" />
<q:set name="leader" value="{false}" />
<q:set name="xi" value="{on_pitch(club)}" />
<q:loop items="{xi}" var="p">
<q:set name="sums" index="{p.xi}" value="{sums[p.xi] + eff(p, p.xi)}" />
<q:set name="counts" index="{p.xi}" value="{counts[p.xi] + 1}" />
<q:if condition="{'Wall' in p.traits and (p.xi == 'GK' or p.xi == 'DF')}"><q:set name="walls" value="{walls + 1}" /></q:if>
<q:if condition="{'Leader' in p.traits}"><q:set name="leader" value="{true}" /></q:if>
</q:loop>
<q:set name="avg" value="{ {'GK': sums['GK'] / max(1, counts['GK']), 'DF': sums['DF'] / max(1, counts['DF']), 'MF': sums['MF'] / max(1, counts['MF']), 'FW': sums['FW'] / max(1, counts['FW'])} }" />
<q:if condition="{counts['GK'] == 0}"><q:set name="avg" index="{'GK'}" value="{avg['DF'] * 0.5}" /></q:if> <!-- an outfield player in goal -->
<q:set name="att" value="{avg['FW'] * 0.55 + avg['MF'] * 0.45}" />
<q:set name="dfn" value="{(avg['DF'] * 0.5 + avg['GK'] * 0.3 + avg['MF'] * 0.2) * (1 + 0.06 * walls)}" />
<q:set name="lift" value="{(1.04 if leader else 1.0) * (1 - 0.08 * max(0, 11 - len(xi)))}" /> <!-- a man short: 8% weaker -->
<q:return value="{[att * lift * lean(club.formation)[0], dfn * lift * lean(club.formation)[1]]}" />
</q:function>
<!-- ===== The live round (GAME_DESIGN v0.2: the live round, every match in the same minute) ===== -->
<!-- the share of a ground that would come at $15: the table, the visitor, the form, a star in the eleven; a dearer
ticket keeps people away (at $30 two in five of them come, at $50 one in five). And the day: the weather, the
traffic, what else is on (`day`, 0.85 to 1.15 a match), so a price says about how many, never exactly -->
<q:function name="crowd_for" params="home, away_place, place, day">
<q:set name="want" value="{0.55 + 0.25 * (4.5 - place) / 4.5 + 0.15 * max(0, 4.5 - away_place) / 4.5 + 0.04 * times_in(home.form, 'W') - 0.04 * times_in(home.form, 'L')}" />
<q:set name="star" value="{false}" />
<q:loop items="{eleven(home)}" var="p"><q:if condition="{'Star' in p.traits}"><q:set name="star" value="{true}" /></q:if></q:loop>
<q:set name="want" value="{max(0.15, want + (0.12 if star else 0))}" />
<q:return value="{min(open_seats(home), round(home.capacity * want * day * (15 / home.ticket) ** 1.3))}" />
</q:function>
<!-- a side of a match: its club, attack and defence with its style; the counter-tactics lift or sink both; the
home side is lifted by its ground (4%) and by its crowd, up to a tenth more with the ground full (2.3: a full
ground was worth a third more, more than a division's difference in the cup) -->
<q:function name="side" params="club, other, home, fill">
<q:set name="mine" value="{power(club)}" />
<q:set name="m" value="{mods(club.style)}" />
<q:set name="e" value="{edge(club.style, other.style)}" />
<q:set name="h" value="{1.04 + 0.10 * fill if home else 1.0}" />
<q:return value="{ {'attack': mine[0] * (1 + m[0]) * (1 + e) * h, 'defense': mine[1] * (1 + m[1]) * (1 + e) * h, 'shots': 0, 'on': 0, 'press': 0, 'subs': 0, 'done': []} }" />
</q:function>
<!-- the round's matches as they kick off: the crowd, the eleven as it starts (a manager's comes back to it after
the match) -->
<q:function name="kickoff">
<q:set name="matches" value="{[]}" />
<q:set name="orders" value="{[table_order(1), table_order(2), table_order(3), table_order(4)]}" />
<q:loop items="{today}" var="g">
<q:set name="home" value="{clubs[g[0]]}" />
<q:set name="away" value="{clubs[g[1]]}" />
<!-- the tactics and the elevens were made ready after the last round (prepare_round), or by the manager since:
what the dashboard shows is what plays; only a manager's gaps are filled -->
<q:if condition="{home.manager > 0}"><q:call function="fill_lineup" args="{home}" /></q:if>
<q:if condition="{away.manager > 0}"><q:call function="fill_lineup" args="{away}" /></q:if>
<q:loop items="{home.players + away.players}" var="p"><q:set name="p.start" value="{p.xi}" /><q:set name="p.sat" value="{p.bench}" /><q:set name="p.off" value="{''}" /><q:set name="p.booked" value="{false}" /></q:loop>
<q:set name="order" value="{orders[home.div - 1]}" />
<!-- a cup's visitor of another division: by its division, the bigger the more come -->
<q:set name="away_place" value="{place_in(order, g[1]) if away.div == home.div else (0 if away.div < home.div else 9)}" />
<q:set name="crowd" value="{crowd_for(home, away_place, place_in(order, g[0]), random(0.85, 1.15) * (1.2 if today_kind != 'league' else 1))}" />
<q:set name="fill" value="{crowd / home.capacity}" />
<q:set name="matches" value="{matches + [ {'home': g[0], 'away': g[1], 'hg': 0, 'ag': 0, 'events': [], 'h': side(home, away, true, fill), 'a': side(away, home, false, fill), 'styles': [home.style, away.style], 'crowd': crowd, 'fill': fill, 'luck': [random(0.8, 1.2), random(0.8, 1.2)]} ]}" />
</q:loop>
<q:return value="{matches}" />
</q:function>
<!-- who scores: the forwards most, the midfield some, a defender rarely; a finisher half again, a genius thrice -->
<!-- the penalty taker: the manager's choice in Tactics (F2) while he is on the pitch, else who takes them best
(the strongest, a set-piece man +8, a finisher +5; never the keeper) -->
<!-- how many of the eleven are tired (form under 60): they play a tenth worse and more, and should rest -->
<q:function name="tired_in" params="club">
<q:set name="n" value="{0}" />
<q:loop items="{club.players}" var="q"><q:if condition="{q.xi != '' and q.form < 60}"><q:set name="n" value="{n + 1}" /></q:if></q:loop>
<q:return value="{n}" />
</q:function>
<q:function name="pen_skill" params="p">
<q:return value="{p.strength + (8 if 'Set pieces' in p.traits else 0) + (5 if 'Finisher' in p.traits else 0) + (10 if p.genius else 0)}" />
</q:function>
<q:function name="pen_chance" params="p">
<q:return value="{min(0.92, 0.55 + 0.006 * pen_skill(p))}" /> <!-- about three in four go in -->
</q:function>
<q:function name="takers" params="club">
<q:set name="keys" value="{[]}" />
<q:loop var="k" from="0" to="{len(club.players) - 1}">
<q:set name="q" value="{club.players[k]}" />
<q:if condition="{q.xi != '' and q.xi != 'GK' and q.off == ''}"><q:set name="keys" value="{keys + [[pen_skill(q), 100 - k]]}" /></q:if>
</q:loop>
<q:set name="out" value="{[]}" />
<q:loop items="{sort(keys, true)}" var="key"><q:set name="out" value="{out + [club.players[100 - key[1]]]}" /></q:loop>
<q:return value="{out}" />
</q:function>
<q:function name="taker_of" params="club">
<q:set name="line" value="{takers(club)}" />
<q:if condition="{'taker' in club}">
<q:loop items="{line}" var="q"><q:if condition="{q.name == club.taker}"><q:return value="{q}" /></q:if></q:loop>
</q:if>
<q:return value="{line[0] if len(line) > 0 else null}" /> <!-- null: no eleven yet -->
</q:function>
<!-- Tactics: the next taker of the eleven, by how well they take them; after the last, the computer's choice -->
<q:function name="next_taker" params="club">
<q:set name="line" value="{takers(club)}" />
<q:set name="now" value="{club.taker if 'taker' in club else ''}" />
<q:set name="at" value="{-1}" />
<q:loop var="k" from="0" to="{len(line) - 1}"><q:if condition="{line[k].name == now}"><q:set name="at" value="{k}" /></q:if></q:loop>
<q:set name="club.taker" value="{line[at + 1].name if at + 1 < len(line) else ''}" />
</q:function>
<q:function name="taker_text" params="club">
<q:set name="who" value="{taker_of(club)}" />
<q:if condition="{who == null}"><q:return value="{tr('Penalty taker:') + ' ' + tr('the best')}" /></q:if>
<q:return value="{tr('Penalty taker:') + ' ' + (who.name if 'taker' in club and club.taker != '' else tr('the best') + ' (' + who.name + ')')}" />
</q:function>
<q:function name="scorer" params="club">
<q:set name="xi" value="{on_pitch(club)}" />
<q:set name="weights" value="{[]}" />
<q:loop items="{xi}" var="p">
<q:set name="w" value="{ {'GK': 0, 'DF': 1, 'MF': 3, 'FW': 6}[p.xi] * p.strength / 30 * (1.5 if 'Finisher' in p.traits else 1) * (3 if p.genius else 1)}" />
<q:set name="weights" value="{weights + [w]}" />
</q:loop>
<q:return value="{xi[roll(weights)]}" />
</q:function>
<!-- a minute of a match: each side may make a chance, as many as the two styles make the match; a chance may be
a goal. Strength counts whole (2.3: it counted to the power of seven tenths, and a side much stronger hardly
won more): the better side wins more, not always (a home side weaker by 4 or more wins about one match in
five, one stronger by 4 or more about three in four). A booked player is more careful, but a second yellow is a red;
a knock takes a player off (a tired one gets hurt more), and the best of the bench for the place comes on
if three have not come on yet -->
<q:function name="play_minute" params="m, minute">
<q:set name="goals" value="{0}" />
<q:set name="pace_of" value="{tempo(clubs[m.home].style) * tempo(clubs[m.away].style)}" />
<q:loop items="{['h', 'a']}" var="s">
<q:set name="me_side" value="{m[s]}" />
<q:set name="them" value="{m['a' if s == 'h' else 'h']}" />
<q:set name="club" value="{clubs[m.home if s == 'h' else m.away]}" />
<q:set name="luck" value="{m.luck[0 if s == 'h' else 1]}" />
<q:set name="att" value="{me_side.attack}" />
<q:set name="dfn" value="{them.defense}" />
<q:set name="me_side.press" value="{me_side.press * 0.8}" /> <!-- the chances of the last minutes, the older ones fading -->
<q:if condition="{chance(0.31 * att / (att + dfn) * luck * pace_of)}">
<q:set name="me_side.shots" value="{me_side.shots + 1}" />
<q:set name="me_side.press" value="{me_side.press + 1}" />
<q:set name="pen" value="{chance(0.01)}" /> <!-- a foul in the box: about one match in four -->
<q:set name="p" value="{taker_of(club) if pen else null}" />
<q:set name="in_net" value="{chance(pen_chance(p)) if pen else chance(0.125 * sqrt(att / dfn))}" />
<q:if condition="{in_net or chance(0.6 if pen else 0.25)}"><q:set name="me_side.on" value="{me_side.on + 1}" /></q:if> <!-- on target: a goal, or the keeper saved it -->
<q:if condition="{in_net}">
<q:if condition="{not pen}"><q:set name="p" value="{scorer(club)}" /></q:if>
<q:set name="p.goals" value="{p.goals + 1}" />
<q:if condition="{s == 'h'}"><q:set name="m.hg" value="{m.hg + 1}" /><q:else><q:set name="m.ag" value="{m.ag + 1}" /></q:else></q:if>
<q:set name="m.events" value="{[ {'kind': 'goal', 'minute': minute, 'who': p.name, 'club': club.short, 'pen': pen} ] + m.events}" />
<q:set name="goals" value="{goals + 1}" />
<q:elseif condition="{pen}"><q:set name="m.events" value="{[ {'kind': 'miss', 'minute': minute, 'who': p.name, 'club': club.short} ] + m.events}" /></q:elseif>
</q:if>
</q:if>
<q:set name="xi" value="{on_pitch(club)}" />
<q:set name="card" value="{'yellow' if chance(0.02) else ('red' if chance(0.0002) else '')}" />
<q:if condition="{card == 'yellow'}">
<q:set name="weights" value="{[]}" />
<q:loop items="{xi}" var="q"><q:set name="weights" value="{weights + [0.5 if q.booked else 1]}" /></q:loop>
<q:set name="p" value="{xi[roll(weights)]}" />
<q:if condition="{p.booked}">
<q:set name="card" value="{'second'}" />
<q:set name="p.yellows" value="{p.yellows - 1}" /> <!-- the yellows of a red do not count towards a ban of three -->
<q:else>
<q:set name="p.booked" value="{true}" />
<q:set name="p.yellows" value="{p.yellows + 1}" />
<q:set name="m.events" value="{[ {'kind': 'card', 'minute': minute, 'who': p.name, 'club': club.short} ] + m.events}" />
</q:else>
</q:if>
</q:if>
<q:if condition="{card == 'red'}"><q:set name="p" value="{pick(xi)}" /></q:if>
<q:if condition="{card == 'red' or card == 'second'}">
<q:set name="p.off" value="{'red'}" />
<q:set name="p.reds" value="{p.reds + 1}" />
<q:set name="m.events" value="{[ {'kind': 'red', 'minute': minute, 'who': p.name, 'club': club.short} ] + m.events}" />
<q:call function="refresh" args="{m}, {s}" />
</q:if>
<q:set name="xi" value="{on_pitch(club)}" />
<q:if condition="{chance(0.0015 * len(xi) / 11)}">
<q:set name="weights" value="{[]}" />
<q:loop items="{xi}" var="q"><q:set name="weights" value="{weights + [1 + max(0, 80 - q.form) / 40]}" /></q:loop>
<q:set name="p" value="{xi[roll(weights)]}" />
<q:set name="p.off" value="{'injured'}" />
<q:set name="p.injured" value="{round(random(1, 4))}" />
<q:set name="m.events" value="{[ {'kind': 'knock', 'minute': minute, 'who': p.name, 'club': club.short} ] + m.events}" />
<q:set name="on" value="{best_on_bench(club, p.xi)}" />
<q:if condition="{on != null and m[s].subs < 3}"><q:call function="bring_on" args="{m}, {s}, {p}, {on}, {minute}" /></q:if>
<q:call function="refresh" args="{m}, {s}" />
</q:if>
</q:loop>
<q:return value="{goals}" />
</q:function>
<!-- a match in numbers: the shots (on target), the ball (by how the sides attack) and who is pressing (the chances
of the last minutes): Shots 7 (3) x 4 (1) Ball 54% x 46% Pressing: LON ▶▶ -->
<q:function name="stats_line" params="m">
<q:set name="ha" value="{m.h.attack ** 0.7}" />
<q:set name="aa" value="{m.a.attack ** 0.7}" />
<q:set name="ball" value="{round(100 * ha / (ha + aa))}" />
<q:set name="lead" value="{m.h.press - m.a.press}" />
<q:set name="who" value="{'=' if abs(lead) < 0.5 else (clubs[m.home].short + ' ' + repeat('▶', min(3, ceil(lead))) if lead > 0 else clubs[m.away].short + ' ' + repeat('◀', min(3, ceil(-lead))))}" />
<q:return value="{shots_line(m) + ' ' + tr('Ball') + ' ' + str(ball) + '% x ' + str(100 - ball) + '% ' + tr('Pressing:') + ' ' + who}" />
</q:function>
<q:function name="shots_line" params="m">
<q:return value="{tr('Shots') + ' ' + str(m.h.shots) + ' (' + str(m.h.on) + ') x ' + str(m.a.shots) + ' (' + str(m.a.on) + ')'}" />
</q:function>
<!-- the best of the bench for a place (null: no one left to come on) -->
<q:function name="best_on_bench" params="club, place">
<q:set name="top" value="{null}" />
<q:loop items="{club.players}" var="q">
<q:if condition="{q.bench and q.xi == '' and q.off == '' and available(q) and (top == null or eff(q, place) > eff(top, place))}"><q:set name="top" value="{q}" /></q:if>
</q:loop>
<q:return value="{top}" />
</q:function>
<!-- a substitution: who leaves stays off (taken off, or carried off), who comes on takes the place -->
<q:function name="bring_on" params="m, s, leaving, coming, minute">
<q:set name="club" value="{clubs[m.home if s == 'h' else m.away]}" />
<q:if condition="{leaving.off == ''}"><q:set name="leaving.off" value="{'subbed'}" /></q:if>
<q:set name="coming.xi" value="{leaving.xi}" />
<q:set name="coming.bench" value="{false}" />
<q:set name="m_side" value="{m[s]}" />
<q:set name="m_side.subs" value="{m_side.subs + 1}" />
<q:set name="m.events" value="{[ {'kind': 'sub', 'minute': minute, 'who': coming.name, 'out': leaving.name, 'club': club.short} ] + m.events}" />
</q:function>
<!-- a side's attack and defence again, as it stands now (a red, a substitution, a change of tactics) -->
<q:function name="refresh" params="m, s">
<q:set name="club" value="{clubs[m.home if s == 'h' else m.away]}" />
<q:set name="other" value="{clubs[m.away if s == 'h' else m.home]}" />
<q:set name="fresh" value="{side(club, other, s == 'h', m.fill)}" />
<q:set name="fresh.shots" value="{m[s].shots}" />
<q:set name="fresh.on" value="{m[s].on}" />
<q:set name="fresh.press" value="{m[s].press}" />
<q:set name="fresh.subs" value="{m[s].subs}" />
<q:set name="fresh.done" value="{m[s].done}" />
<q:set name="m" index="{s}" value="{fresh}" />
</q:function>
<!-- how a side stands: winning, drawing, losing -->
<q:function name="standing" params="m, s">
<q:set name="mine" value="{m.hg if s == 'h' else m.ag}" />
<q:set name="theirs" value="{m.ag if s == 'h' else m.hg}" />
<q:return value="{'winning' if mine > theirs else ('drawing' if mine == theirs else 'losing')}" />
</q:function>
<!-- a reaction due now: the first of the club's not done yet whose time and score have come -->
<q:function name="due" params="m, s, minute">
<q:set name="club" value="{clubs[m.home if s == 'h' else m.away]}" />
<q:loop var="k" from="0" to="{len(club.reactions) - 1}">
<q:set name="r" value="{club.reactions[k]}" />
<q:if condition="{minute >= r.after and r.when == standing(m, s) and not (k in m[s].done)}"><q:return value="{k}" /></q:if>
</q:loop>
<q:return value="{-1}" />
</q:function>
<!-- a change of tactics during a match: the side's attack and defence again -->
<q:function name="change" params="m, s, formation, style">
<q:set name="club" value="{clubs[m.home if s == 'h' else m.away]}" />
<q:set name="club.formation" value="{formation}" />
<q:set name="club.style" value="{style}" />
<q:call function="refresh" args="{m}, {s}" />
<q:set name="m.events" value="{[ {'kind': 'tactic', 'club': club.short, 'formation': formation, 'style': style} ] + m.events}" />
</q:function>
<!-- a line of a match's events, in the player's language -->
<q:function name="event_text" params="e">
<q:if condition="{e.kind == 'tactic'}"><q:return value="{'⚙ ' + e.club + ': ' + e.formation + ' ' + tr(e.style)}" /></q:if>
<q:set name="at" value="{fit(str(e.minute), 2, 'right') + '′ ' + e.who + ' (' + e.club + ')'}" />
<q:if condition="{e.kind == 'goal'}"><q:return value="{fit(tr('GOAL'), 5) + at + (' (' + tr('penalty') + ')' if 'pen' in e and e.pen else '')}" /></q:if>
<q:if condition="{e.kind == 'miss'}"><q:return value="{'✗ ' + at + ' ' + tr('missed a penalty')}" /></q:if>
<q:if condition="{e.kind == 'card'}"><q:return value="{'▮ ' + at}" /></q:if>
<q:if condition="{e.kind == 'red'}"><q:return value="{'▮▮ ' + at + ' ' + tr('sent off')}" /></q:if>
<q:if condition="{e.kind == 'sub'}"><q:return value="{'⇄ ' + at + ' ' + tr('for') + ' ' + e.out}" /></q:if>
<q:return value="{'✚ ' + at + ' ' + tr('injured')}" />
</q:function>
<!-- the colour of an event's line -->
<q:function name="event_color" params="e">
<q:return value="{ {'goal': '#ffff55', 'card': '#ffff55', 'red': '#ff5555', 'knock': '#ff5555', 'sub': '#55ff55', 'tactic': '#55ffff', 'miss': '#ff5555'}[e.kind] }" />
</q:function>
<!-- ===== After the round ===== -->
<!-- a division's table: its clubs by points, goal difference, goals -->
<q:function name="table_order" params="d">
<q:set name="keys" value="{[]}" />
<q:loop var="i" from="0" to="{len(clubs) - 1}">
<q:set name="c" value="{clubs[i]}" />
<q:if condition="{c.div == d}"><q:set name="keys" value="{keys + [[c.points, c.gf - c.ga, c.gf, 1000 - i]]}" /></q:if>
</q:loop>
<q:set name="out" value="{[]}" />
<q:loop items="{sort(keys, true)}" var="k"><q:set name="out" value="{out + [1000 - k[3]]}" /></q:loop>
<q:return value="{out}" />
</q:function>
<q:function name="position_of" params="club_index">
<q:return value="{place_in(table_order(clubs[club_index].div), club_index) + 1}" />
</q:function>
<!-- the round's results into the table, the players and the purses -->
<q:function name="finish_round" params="matches">
<q:set name="orders" value="{[table_order(1), table_order(2), table_order(3), table_order(4)]}" />
<q:loop items="{matches}" var="m">
<q:call function="rate_match" args="{m}" />
<q:call function="result" args="{clubs[m.home]}, {m.hg}, {m.ag}" />
<q:call function="result" args="{clubs[m.away]}, {m.ag}, {m.hg}" />
<q:set name="home" value="{clubs[m.home]}" />
<q:set name="away" value="{clubs[m.away]}" />
<q:set name="home.crowd" value="{m.crowd}" />
<q:set name="home.gate" value="{round(m.crowd * home.ticket * gate_share(home))}" />
<!-- a match wears the ground: half a point, half again with it full, a third again when the side lost -->
<q:set name="home.health" value="{max(0, home.health - 0.5 * (1.5 if m.fill >= 0.9 else 1) * (1.3 if m.hg < m.ag else 1))}" />
<q:set name="home.balance" value="{home.balance + home.gate}" />
<q:set name="away.gate" value="{0}" />
<q:set name="home.history" value="{([ {'vs': away.short, 'home': true, 'style': m.styles[0], 'gf': m.hg, 'ga': m.ag} ] + home.history)[0:5]}" />
<q:set name="away.history" value="{([ {'vs': home.short, 'home': false, 'style': m.styles[1], 'gf': m.ag, 'ga': m.hg} ] + away.history)[0:5]}" />
</q:loop>
<q:loop var="i" from="0" to="{len(clubs) - 1}">
<q:set name="c" value="{clubs[i]}" />
<q:set name="pay" value="{0}" />
<q:loop items="{c.players}" var="p">
<q:set name="pay" value="{pay + p.salary}" />
<q:set name="played" value="{p.xi != ''}" />
<q:if condition="{p.injured > 0 and p.off != 'injured'}"><q:set name="p.injured" value="{p.injured - 1}" /></q:if> <!-- hurt today: out from the next match -->
<q:if condition="{p.suspended > 0}"><q:set name="p.suspended" value="{p.suspended - 1}" /></q:if>
<q:if condition="{p.off == 'red'}"><q:set name="p.suspended" value="{1}" /></q:if>
<q:if condition="{p.yellows >= 3}"><q:set name="p.suspended" value="{1}" /><q:set name="p.yellows" value="{0}" /></q:if>
<q:if condition="{played}"><q:set name="p.played" value="{p.played + 1}" /><q:set name="p.apps" value="{p.apps + 1}" /></q:if>
<q:set name="p.form" value="{max(30, min(100, p.form + 10 + c.training - (12 if played else 0)))}" /> <!-- a week: everyone recovers (a point more a level of the training center), who played tires -->
<q:set name="p.xi" value="{p.start}" /> <!-- the eleven as it started: a manager's comes back to it -->
<q:set name="p.bench" value="{p.sat}" />
<q:set name="p.off" value="{''}" />
<q:set name="p.booked" value="{false}" />
</q:loop>
<q:set name="c.wages" value="{pay / 4}" /> <!-- a week of the month's wages -->
<q:set name="c.balance" value="{c.balance - c.wages}" />
<q:if condition="{matchday % 4 == 3}"> <!-- the month's end: TV, sponsorship, costs, by division -->
<q:set name="c.balance" value="{c.balance + tv_of(c) + sponsor_of(c, place_in(orders[c.div - 1], i)) - costs_of(c)}" />
<q:call function="pay_instalments" args="{c}" />
<q:call function="month_of" args="{i}" />
<q:call function="month_growth" args="{c}" />
</q:if>
</q:loop>
<q:set name="last" value="{matches}" />
<q:call function="papers" />
<q:call function="boards" />
<q:call function="market_round" />
<q:if condition="{matchday == 8}"><q:call function="midseason_offers" /></q:if>
<q:set name="matchday" value="{matchday + 1}" />
<q:call function="prepare_round" />
</q:function>
<!-- every club ready for the next date: its tactics as they will start, its eleven (the gaps filled) -->
<q:function name="prepare_round">
<q:if condition="{matchday < 18 or cup_due()}">
<q:loop items="{next_games()}" var="g">
<q:call function="ready" args="{clubs[g[0]]}, {clubs[g[1]]}, {true}" />
<q:call function="ready" args="{clubs[g[1]]}, {clubs[g[0]]}, {false}" />
</q:loop>
</q:if>
</q:function>
<!-- ===== The cups (decision 30): the Panda Cup, knock-out, drawn on screen; the Supercup ===== -->
<!-- a cup round after every third round of the league, the final after the last -->
<q:function name="cup_due">
<q:return value="{cup_round < 6 and matchday >= [3, 6, 9, 12, 15, 18][min(5, cup_round)]}" />
</q:function>
<q:function name="super_due">
<q:return value="{len(super_tie) == 2 and matchday == 0}" />
</q:function>
<q:function name="next_kind">
<q:return value="{'super' if super_due() else ('cup' if cup_due() else 'league')}" />
</q:function>
<!-- the games of the next date: the Supercup, the cup's round (drawn now if it was not), or the league's round -->
<q:function name="next_games">
<q:if condition="{super_due()}"><q:return value="{[super_tie]}" /></q:if>
<q:if condition="{cup_due()}">
<q:if condition="{len(cup_ties) == 0}"><q:call function="make_draw" /></q:if>
<q:return value="{cup_ties}" />
</q:if>
<q:return value="{fixtures[min(matchday, 17)]}" />
</q:function>
<q:function name="cup_round_name" params="k">
<q:return value="{tr(['Preliminary round', 'Round of 32', 'Round of 16', 'Quarter-finals', 'Semi-finals', 'Final', 'Final'][k])}" />
</q:function>
<q:function name="new_cup">
<q:set name="cup_round" value="{0}" />
<q:set name="cup_alive" value="{upto(len(clubs))}" />
<q:set name="cup_ties" value="{[]}" />
<q:set name="cup_winner" value="{-1}" />
<q:set name="cup_runner" value="{-1}" />
</q:function>
<!-- the draw: the preliminary round between the 4th division's ten and six of the 3rd (the others wait for the
round of 32), then all who are left; the club of the lower division at home, else the first drawn -->
<q:function name="make_draw">
<q:set name="pot" value="{cup_alive}" />
<q:if condition="{cup_round == 0}">
<q:set name="third" value="{in_div(3)}" />
<q:set name="pot" value="{in_div(4)}" />
<q:loop var="k" from="1" to="6"><q:set name="drawn" value="{pick(third)}" /><q:set name="third" value="{without(third, drawn)}" /><q:set name="pot" value="{pot + [drawn]}" /></q:loop>
</q:if>
<q:set name="ties" value="{[]}" />
<q:loop var="k" from="1" to="{floor(len(pot) / 2)}">
<q:set name="a" value="{pick(pot)}" />
<q:set name="pot" value="{without(pot, a)}" />
<q:set name="b" value="{pick(pot)}" />
<q:set name="pot" value="{without(pot, b)}" />
<q:set name="ties" value="{ties + [[b, a] if clubs[b].div > clubs[a].div else [a, b]]}" />
</q:loop>
<q:set name="cup_ties" value="{ties}" />
</q:function>
<!-- whether any manager's club plays one of these games -->
<q:function name="managed_in" params="games">
<q:loop items="{games}" var="g"><q:if condition="{clubs[g[0]].manager > 0 or clubs[g[1]].manager > 0}"><q:return value="{true}" /></q:if></q:loop>
<q:return value="{false}" />
</q:function>
<!-- the date about to be played, as the round scene reads it -->
<q:function name="start_date">
<q:set name="today_kind" value="{next_kind()}" />
<q:set name="today" value="{next_games()}" />
</q:function>
<!-- a date no manager plays, played at once: its matches minute by minute, as the live round would -->
<q:function name="play_unseen">
<q:call function="start_date" />
<q:call function="prepare_round" />
<q:set name="matches" value="{kickoff()}" />
<q:loop var="minute" from="1" to="90">
<q:loop items="{matches}" var="m"><q:call function="play_minute" args="{m}, {minute}" /></q:loop>
</q:loop>
<q:call function="finish_cup" args="{matches}" />
</q:function>
<!-- penalties: five each, then one each until one misses (three in four go in; the taker first, by his chance) -->
<q:function name="shootout" params="m">
<q:set name="h" value="{0}" />
<q:set name="a" value="{0}" />
<q:set name="first" value="{[pen_chance(taker_of(clubs[m.home])), pen_chance(taker_of(clubs[m.away]))]}" />
<q:loop var="k" from="1" to="5"><q:if condition="{chance(first[0] if k == 1 else 0.75)}"><q:set name="h" value="{h + 1}" /></q:if><q:if condition="{chance(first[1] if k == 1 else 0.75)}"><q:set name="a" value="{a + 1}" /></q:if></q:loop>
<q:loop var="k" from="1" to="20">
<q:if condition="{h == a}"><q:if condition="{chance(0.75)}"><q:set name="h" value="{h + 1}" /></q:if><q:if condition="{chance(0.75)}"><q:set name="a" value="{a + 1}" /></q:if></q:if>
</q:loop>
<q:if condition="{h == a}"><q:set name="h" value="{h + 1}" /></q:if>
<q:set name="m.pens" value="{[h, a]}" />
<q:set name="m.events" value="{[ {'kind': 'pens', 'minute': 90, 'who': str(h) + '-' + str(a), 'club': clubs[m.home if h > a else m.away].short} ] + m.events}" />
</q:function>
<!-- a cup date's results: who goes through (on penalties after a draw), the prize of the round won, the gates and
the squads as after any match (no table, no wages: a date in midweek); the final makes the winner -->
<q:function name="finish_cup" params="matches">
<q:set name="out" value="{[]}" />
<q:set name="won" value="{[]}" />
<q:loop items="{matches}" var="m">
<q:call function="rate_match" args="{m}" />
<q:if condition="{m.hg == m.ag}"><q:call function="shootout" args="{m}" /></q:if>
<q:set name="home_won" value="{m.hg > m.ag or (m.hg == m.ag and m.pens[0] > m.pens[1])}" />
<q:set name="winner" value="{m.home if home_won else m.away}" />
<q:set name="loser" value="{m.away if home_won else m.home}" />
<q:set name="won" value="{won + [winner]}" />
<q:set name="out" value="{out + [loser]}" />
<q:set name="home" value="{clubs[m.home]}" />
<q:set name="home.crowd" value="{m.crowd}" />
<q:set name="home.gate" value="{round(m.crowd * home.ticket * gate_share(home))}" />
<q:set name="home.balance" value="{home.balance + home.gate}" />
<q:set name="champ" value="{clubs[winner]}" />
<q:set name="champ.balance" value="{champ.balance + [20000, 40000, 80000, 150000, 300000, 1000000, 500000][cup_round if today_kind == 'cup' else 6]}" />
<q:loop items="{managers}" var="t"><q:if condition="{t.club == winner}"><q:set name="t.patience" value="{min(100, t.patience + 2)}" /></q:if></q:loop>
<q:loop items="{[[m.home, m.hg, m.ag], [m.away, m.ag, m.hg]]}" var="side">
<q:set name="mood" value="{ {'W': 6, 'D': 0, 'L': -6}['W' if side[0] == winner else 'L'] }" />
<q:loop items="{clubs[side[0]].players}" var="q"><q:set name="q.morale" value="{max(20, min(100, q.morale + mood))}" /></q:loop>
</q:loop>
</q:loop>
<q:loop items="{clubs}" var="c">
<q:loop items="{c.players}" var="q">
<q:set name="took_part" value="{q.xi != ''}" />
<q:if condition="{q.injured > 0 and q.off != 'injured'}"><q:set name="q.injured" value="{q.injured - 1}" /></q:if>
<q:if condition="{q.suspended > 0 and took_part}"><q:set name="q.suspended" value="{q.suspended - 1}" /></q:if>
<q:if condition="{q.off == 'red'}"><q:set name="q.suspended" value="{1}" /></q:if>
<q:if condition="{took_part}"><q:set name="q.form" value="{max(30, q.form - 8)}" /><q:set name="q.played" value="{q.played + 1}" /></q:if>
<q:set name="q.xi" value="{q.start}" />
<q:set name="q.bench" value="{q.sat}" />
<q:set name="q.off" value="{''}" />
<q:set name="q.booked" value="{false}" />
</q:loop>
</q:loop>
<q:set name="last" value="{matches}" />
<q:if condition="{today_kind == 'super'}">
<q:call function="said" args="{'Supercup:'}, {clubs[won[0]].name}" />
<q:set name="super_tie" value="{[]}" />
<q:else>
<q:if condition="{cup_round == 0}"><q:set name="cup_alive" value="{won + without_all(cup_alive, won + out)}" />
<q:else><q:set name="cup_alive" value="{won}" /></q:else>
</q:if>
<q:if condition="{cup_round == 5}">
<q:set name="cup_winner" value="{won[0]}" />
<q:set name="cup_runner" value="{out[0]}" />
<q:call function="said" args="{'The Panda Cup goes to'}, {clubs[won[0]].name}" />
<q:set name="cupholder" value="{clubs[won[0]]}" />
<q:set name="cupholder.titles" value="{cupholder.titles + 1}" />
<q:loop items="{managers}" var="t">
<q:if condition="{t.club == won[0]}">
<q:set name="t.career.cups" value="{(t.career.cups if 'cups' in t.career else 0) + 1}" />
<q:set name="t.patience" value="{min(100, t.patience + 15)}" />
<q:set name="st" value="{stint_of(t)}" />
<q:if condition="{st != null}"><q:set name="st.cups" value="{st.cups + 1}" /></q:if>
</q:if>
</q:loop>
<q:else><q:call function="said" args="{cup_round_name(cup_round) + ':'}, {join(names_of_clubs(out[0:4]), ', ') + (' ...' if len(out) > 4 else '') + ' ' + tr('out')}" /></q:else>
</q:if>
<q:set name="cup_round" value="{cup_round + 1}" />
<q:set name="cup_ties" value="{[]}" />
</q:else>
</q:if>
<q:call function="prepare_round" />
</q:function>
<q:function name="without_all" params="list, gone">
<q:set name="kept" value="{[]}" />
<q:loop items="{list}" var="x"><q:if condition="{not (x in gone)}"><q:set name="kept" value="{kept + [x]}" /></q:if></q:loop>
<q:return value="{kept}" />
</q:function>
<q:function name="names_of_clubs" params="indices">
<q:set name="names" value="{[]}" />
<q:loop items="{indices}" var="k"><q:set name="names" value="{names + [clubs[k].short]}" /></q:loop>
<q:return value="{names}" />
</q:function>
<!-- after a date's results: the jobs first, then a cup date no manager plays (at once), the draw of one a manager
plays, the season's end, or the next date -->
<q:function name="after_results">
<q:if condition="{len(jobs) > 0}"><qg:goto-scene name="job" /><q:return value="{false}" /></q:if>
<q:loop var="k" from="1" to="6">
<q:if condition="{(cup_due() and not managed_in(next_games())) or (super_due() and not managed_in(next_games()))}"><q:call function="play_unseen" /></q:if>
</q:loop>
<q:if condition="{cup_due()}"><qg:goto-scene name="draw" /><q:return value="{false}" /></q:if>
<q:if condition="{matchday >= 18}"><qg:goto-scene name="season-end" /><q:return value="{false}" /></q:if>
<qg:goto-scene name="dashboard" />
<q:return value="{true}" />
</q:function>
<q:function name="result" params="club, scored, conceded">
<q:set name="club.played" value="{club.played + 1}" />
<q:set name="club.gf" value="{club.gf + scored}" />
<q:set name="club.ga" value="{club.ga + conceded}" />
<q:set name="mark" value="{'W' if scored > conceded else ('D' if scored == conceded else 'L')}" />
<q:if condition="{mark == 'W'}"><q:set name="club.won" value="{club.won + 1}" /><q:set name="club.points" value="{club.points + 3}" /></q:if>
<q:if condition="{mark == 'D'}"><q:set name="club.drawn" value="{club.drawn + 1}" /><q:set name="club.points" value="{club.points + 1}" /></q:if>
<q:if condition="{mark == 'L'}"><q:set name="club.lost" value="{club.lost + 1}" /></q:if>
<q:set name="club.form" value="{(club.form + [mark])[max(0, len(club.form) - 4):]}" />
<q:loop items="{club.players}" var="p">
<!-- a good player left out is unhappy, unless he was tired or could not play -->
<q:set name="p.morale" value="{max(20, min(100, p.morale + {'W': 6, 'D': 1, 'L': -4}[mark] + (-3 if p.xi == '' and p.strength > 35 and p.form >= 70 and available(p) else 0)))}" />
<q:set name="p.morale" value="{round(p.morale + (70 - p.morale) * 0.15)}" /> <!-- a run, good or bad, fades -->
</q:loop>
</q:function>
<!-- ===== The ratings (the beta's ask: how each player took part): 3 to 10, every match ===== -->
<!-- a player's match so far, before the day's luck: 6; a win +0.5, a defeat -0.5; a goal +1, a missed penalty -1,
a yellow -0.3, a red -2; with no goal against a keeper +1 and a defender +0.5, and each goal after the first
-0.4 and -0.2; a forward whose side has not scored -0.3; who came on, half of it -->
<q:function name="rating_in" params="m, club, p">
<q:set name="home" value="{club.short == clubs[m.home].short}" />
<q:set name="gf" value="{m.hg if home else m.ag}" />
<q:set name="ga" value="{m.ag if home else m.hg}" />
<q:set name="r" value="{0.5 if gf > ga else (-0.5 if gf < ga else 0)}" />
<q:loop items="{m.events}" var="e">
<q:if condition="{e.club == club.short and 'who' in e and e.who == p.name}">
<q:set name="r" value="{r + (1.0 if e.kind == 'goal' else (-1.0 if e.kind == 'miss' else (-0.3 if e.kind == 'card' else (-2.0 if e.kind == 'red' else 0))))}" />
</q:if>
</q:loop>
<q:if condition="{p.xi == 'GK'}"><q:set name="r" value="{r + (1.0 if ga == 0 else -0.4 * (ga - 1))}" /></q:if>
<q:if condition="{p.xi == 'DF'}"><q:set name="r" value="{r + (0.5 if ga == 0 else -0.2 * (ga - 1))}" /></q:if>
<q:if condition="{p.xi == 'FW' and gf == 0}"><q:set name="r" value="{r - 0.3}" /></q:if>
<q:if condition="{p.start == ''}"><q:set name="r" value="{r / 2}" /></q:if>
<q:return value="{6 + r}" />
</q:function>
<!-- a match's ratings, with the day's luck (±0.7), into each player's (his last, the season's and the month's sums);
the man of the match is the best of them -->
<q:function name="rate_match" params="m">
<q:set name="top" value="{0}" />
<q:set name="m.motm" value="{''}" />
<q:set name="m.ratings" value="{[]}" />
<q:loop items="{[m.home, m.away]}" var="ci">
<q:set name="club" value="{clubs[ci]}" />
<q:loop items="{club.players}" var="p">
<q:if condition="{p.xi != ''}">
<q:set name="r" value="{max(3, min(10, round((rating_in(m, club, p) + random(-0.7, 0.7)) * 10) / 10))}" />
<q:set name="p.rating" value="{r}" />
<q:set name="p.rsum" value="{p.rsum + r}" />
<q:set name="p.rn" value="{p.rn + 1}" />
<q:set name="p.msum" value="{p.msum + r}" />
<q:set name="p.mn" value="{p.mn + 1}" />
<q:set name="m.ratings" value="{m.ratings + [ {'who': p.name, 'pos': p.xi, 'r': r, 'home': ci == m.home, 'on': p.start == ''} ]}" />
<q:if condition="{r > top}"><q:set name="top" value="{r}" /><q:set name="m.motm" value="{p.name + ' (' + club.short + ') ' + r1(r)}" /></q:if>
</q:if>
</q:loop>
</q:loop>
</q:function>
<!-- a rating as the screens write it: 7.0, 6.5 -->
<q:function name="r1" params="x">
<q:set name="n" value="{round(x * 10)}" />
<q:return value="{str(floor(n / 10)) + '.' + str(n % 10)}" />
</q:function>
<!-- a player's mean of the season ('-' before his first match) -->
<q:function name="mean_rating" params="p">
<q:return value="{p.rsum / p.rn if 'rn' in p and p.rn > 0 else 0}" />
</q:function>
<q:function name="mean_text" params="p">
<q:return value="{r1(mean_rating(p)) if mean_rating(p) > 0 else '-'}" />
</q:function>
<!-- the month's end (decision of 2.2): whose mean of the month (two matches at least) is 7 or more grows a point,
the club's training center lowering that bar (by 0.15 a level), and so does his being weaker than the eleven
(2.3, the beta's ask: the starting squads of the 4th never grew): 0.1 lower a point under the eleven's mean,
never under 6; 5 or less, he loses one. Who hardly played trains with the eleven (the beta's ask: the reserves
never got better): one weaker than the eleven's mean, not injured, grows a point one month in five, and one in
twenty more a level of the training center (nearly half the months at level 5); never past the eleven. The
arrow in the squad says it until the next month -->
<q:function name="month_growth" params="club">
<q:set name="up" value="{[]}" />
<q:set name="down" value="{[]}" />
<q:set name="trained" value="{[]}" />
<q:set name="bar" value="{7.0 - 0.15 * club.training}" />
<q:set name="level" value="{xi_level(club)}" />
<q:set name="drill" value="{0.2 + 0.05 * club.training}" />
<q:loop items="{club.players}" var="q">
<q:set name="q.trend" value="{0}" />
<q:if condition="{q.mn >= 2}">
<q:set name="avg" value="{q.msum / q.mn}" />
<q:if condition="{avg >= max(6.0, bar - 0.1 * max(0, level - q.strength)) and q.strength < 50}"><q:set name="q.strength" value="{q.strength + 1}" /><q:set name="q.trend" value="{1}" /><q:set name="q.grown" value="{q.grown + 1}" /><q:set name="up" value="{up + [q.name]}" /></q:if>
<q:if condition="{avg <= 5.0 and q.strength > 5}"><q:set name="q.strength" value="{q.strength - 1}" /><q:set name="q.trend" value="{-1}" /><q:set name="q.grown" value="{q.grown - 1}" /><q:set name="down" value="{down + [q.name]}" /></q:if>
</q:if>
<q:if condition="{q.mn < 2 and q.injured == 0 and q.strength + 1 <= level and chance(drill)}"><q:set name="q.strength" value="{q.strength + 1}" /><q:set name="q.trend" value="{1}" /><q:set name="q.grown" value="{q.grown + 1}" /><q:set name="trained" value="{trained + [q.name]}" /></q:if>
<q:set name="q.msum" value="{0}" />
<q:set name="q.mn" value="{0}" />
</q:loop>
<q:if condition="{club.manager > 0 and len(up) > 0}"><q:call function="said" args="{'Better this month (+1):'}, {join(up[0:5], ', ')}" /></q:if>
<q:if condition="{club.manager > 0 and len(trained) > 0}"><q:call function="said" args="{'Better in training (+1):'}, {join(trained[0:5], ', ')}" /></q:if>
<q:if condition="{club.manager > 0 and len(down) > 0}"><q:call function="said" args="{'Worse this month (-1):'}, {join(down[0:5], ', ')}" /></q:if>
</q:function>
<!-- the eleven's mean strength (the sixteen best's when there is no eleven yet) -->
<q:function name="xi_level" params="club">
<q:set name="xi" value="{eleven(club)}" />
<q:if condition="{len(xi) == 0}"><q:return value="{squad_strength(club)}" /></q:if>
<q:set name="sum" value="{0}" />
<q:loop items="{xi}" var="q"><q:set name="sum" value="{sum + q.strength}" /></q:loop>
<q:return value="{sum / len(xi)}" />
</q:function>
<!-- the papers: now and then a word on the hidden genius -->
<q:function name="papers">
<q:set name="gem" value="{null}" />
<q:set name="home_of" value="{null}" />
<q:loop items="{clubs}" var="c">
<q:if condition="{c.div >= 3}">
<q:loop items="{c.players}" var="p"><q:if condition="{p.genius}"><q:set name="gem" value="{p}" /><q:set name="home_of" value="{c}" /></q:if></q:loop>
</q:if>
</q:loop>
<q:if condition="{gem != null and matchday == 2}"><q:call function="said" args="{'They say a phenomenon plays in the lower divisions...'}, {''}" /></q:if>
<q:if condition="{gem != null and matchday == 6}"><q:call function="said" args="{ {'GK': 'Rumour: the hidden phenomenon would be a goalkeeper.', 'DF': 'Rumour: the hidden phenomenon would be a defender.', 'MF': 'Rumour: the hidden phenomenon would be a midfielder.', 'FW': 'Rumour: the hidden phenomenon would be a forward.'}[gem.pos] }, {''}" /></q:if>
<q:if condition="{gem != null and matchday == 11}"><q:call function="said" args="{'Revealed: the phenomenon plays for'}, {home_of.name + '!'}" /></q:if>
</q:function>
<!-- the season's end: its champions into the honours, two up and two down between each two divisions, and a new
season with the same squads, rested -->
<q:function name="new_season">
<q:set name="orders" value="{[table_order(1), table_order(2), table_order(3), table_order(4)]}" />
<q:set name="top" value="{top_scorers(1, 0)}" />
<q:set name="prized" value="{season_prizes(orders)}" />
<q:set name="honours" value="{[ {'season': season, 'champions': [clubs[orders[0][0]].name, clubs[orders[1][0]].name, clubs[orders[2][0]].name, clubs[orders[3][0]].name], 'cup': clubs[cup_winner].name if cup_winner >= 0 else '-', 'scorer': top[0].name + ' (' + top[0].club + ', ' + str(top[0].goals) + ')' if len(top) > 0 else '-', 'awards': prized} ] + honours}" />
<!-- the Supercup: the first division's champion against the cup's winner (its runner-up, when the champion won both) -->
<q:set name="super_tie" value="{[orders[0][0], cup_winner if cup_winner != orders[0][0] else cup_runner] if cup_winner >= 0 else []}" />
<q:call function="season_verdicts" args="{orders}" />
<q:loop var="d" from="1" to="4"><q:set name="champ" value="{clubs[orders[d - 1][0]]}" /><q:set name="champ.titles" value="{champ.titles + 1}" /></q:loop>
<q:call function="growth" />
<q:set name="was" value="{[]}" />
<q:loop items="{managers}" var="t"><q:set name="was" value="{was + [clubs[t.club].div]}" /></q:loop>
<!-- a club rescued from bankruptcy ends its division last: it goes down -->
<q:loop var="ci" from="0" to="{len(clubs) - 1}">
<q:set name="broke" value="{clubs[ci]}" />
<q:if condition="{broke.bankrupt}">
<q:set name="orders" index="{broke.div - 1}" value="{without(orders[broke.div - 1], ci) + [ci]}" />
<q:set name="broke.bankrupt" value="{false}" />
</q:if>
</q:loop>
<q:loop var="d" from="1" to="3">
<q:loop items="{[orders[d - 1][len(orders[d - 1]) - 2], orders[d - 1][len(orders[d - 1]) - 1]]}" var="down"><q:set name="moved" value="{clubs[down]}" /><q:set name="moved.div" value="{d + 1}" /></q:loop>
<q:loop items="{[orders[d][0], orders[d][1]]}" var="up"><q:set name="moved" value="{clubs[up]}" /><q:set name="moved.div" value="{d}" /></q:loop>
</q:loop>
<q:loop var="mi" from="0" to="{len(managers) - 1}">
<q:set name="mine" value="{clubs[managers[mi].club]}" />
<q:if condition="{mine.div < was[mi]}"><q:call function="said" args="{'Promoted to the'}, {div_name(mine.div) + ': ' + mine.name}" /></q:if>
<q:if condition="{mine.div > was[mi]}"><q:call function="said" args="{'Relegated to the'}, {div_name(mine.div) + ': ' + mine.name}" /></q:if>
</q:loop>
<q:set name="season" value="{season + 1}" />
<!-- the academies' youths, and new free agents up to forty -->
<q:set name="known" value="{names_in_world()}" />
<q:loop items="{clubs}" var="c">
<q:set name="kids" value="{youths(c, known)}" />
<q:loop items="{kids}" var="kid">
<q:set name="known" value="{known + [kid.name]}" />
<q:if condition="{c.manager > 0}"><q:call function="said" args="{'From the academy:'}, {kid.name + ' (' + tr(kid.pos) + ', ' + str(kid.strength) + ')'}" /></q:if>
</q:loop>
</q:loop>
<q:call function="free_agents_to" args="{40}, {known}" />
<q:loop items="{clubs}" var="c">
<q:loop items="{['points', 'played', 'won', 'drawn', 'lost', 'gf', 'ga']}" var="k"><q:set name="c" index="{k}" value="{0}" /></q:loop>
<q:set name="c.form" value="{[]}" />
<q:loop items="{c.players}" var="p"><q:set name="p.morale" value="{70}" /><q:set name="p.form" value="{100}" /><q:set name="p.goals" value="{0}" /><q:set name="p.yellows" value="{0}" /><q:set name="p.reds" value="{0}" /></q:loop>
</q:loop>
<q:call function="draw_offers" />
<q:call function="return_loans" />
<q:call function="make_fixtures" />
<q:call function="set_expectations" />
<q:call function="new_cup" />
<q:set name="matchday" value="{0}" />
<q:set name="champion" value="{-1}" />
<q:call function="prepare_round" />
<q:call function="said" args="{'The Panda League kicks off:'}, {str(season)}" />
</q:function>
<!-- ===== Screens ===== -->
<!-- The title: a new game, one of the three saved, the options. -->
<qg:scene name="title" width="640" height="400" background="#0000a8">
<q:set name="save_version" value="{0}" /> <!-- a save continued from here says its own (none: 2.1 or before) -->
<q:call function="send_reports" />
<qg:hud x="160" y="40" box="frame" font="assets/DejaVuSansMono.ttf" size="16">
<qg:text value="{' ☻ P A N D A F O O T ☻ '}" color="#ffff55" />
<qg:text value="{' one manager, one league, the ball '}" color="#55ffff" />
</qg:hud>
<qg:menu x="200" y="140" font="assets/DejaVuSansMono.ttf" size="14" box="frame">
<qg:button label="New game"><q:call function="reset_game" /><qg:goto-scene name="new-game" /></qg:button>
<qg:button label="{tr('Continue') + ' 1'}" if="{has_save(1)}"><qg:continue slot="1" /></qg:button>
<qg:button label="{tr('Continue') + ' 2'}" if="{has_save(2)}"><qg:continue slot="2" /></qg:button>
<qg:button label="{tr('Continue') + ' 3'}" if="{has_save(3)}"><qg:continue slot="3" /></qg:button>
<qg:button label="Options"><qg:goto-scene name="options" /></qg:button>
<qg:button label="{tr('What\'s new') + ' (v' + game_version() + ')'}"><qg:open-url href="{changelog_url()}" /></qg:button>
<qg:button label="Report a bug or suggest"><qg:open-overlay name="report" /></qg:button>
<qg:button label="Quit" if="{can_quit()}"><qg:quit /></qg:button>
</qg:menu>
<qg:hud position="bottom-right" font="assets/DejaVuSansMono.ttf" size="11">
<qg:text value="{'PandaFoot v' + game_version() + ' beta'}" color="#aaaaaa" />
</qg:hud>
</qg:scene>
<!-- A new game: the world's number, the save, the managers and their clubs. -->
<qg:scene name="new-game" width="640" height="400" background="#0000a8">
<q:set name="who" value="" type="string" />
<q:set name="taken" value="[]" type="array" />
<qg:hud x="16" y="12" font="assets/DejaVuSansMono.ttf" size="14">
<qg:text value="{'NEW GAME'}" color="#ffff55" />
<qg:text value="{tr('Managers:') + ' ' + str(len(managers)) + '/4'}" />
</qg:hud>
<!-- each manager is given a club, drawn as Elifoot draws it: a small club of the 4th division no one has -->
<q:function name="draw_club">
<q:set name="open" value="{[]}" />
<q:loop var="i" from="30" to="39"><q:if condition="{not (i in taken)}"><q:set name="open" value="{open + [i]}" /></q:if></q:loop>
<q:return value="{open[int((world * 31 + len(managers) * 7 + ticks()) % len(open))]}" />
</q:function>
<qg:menu x="16" y="60" font="assets/DejaVuSansMono.ttf" size="13" box="frame">
<qg:slider label="World no." bind="world" min="1" max="999" step="1" />
<qg:slider label="Save in slot" bind="slot" min="1" max="3" step="1" />
<qg:field bind="who" label="Manager's name:" max-length="14" />
<qg:button label="Draw the manager's club" if="{len(managers) < 4}">
<q:set name="club" value="{draw_club()}" />
<q:set name="managers" value="{managers + [ {'name': who if who != '' else tr('Manager') + ' ' + str(len(managers) + 1), 'club': club, 'patience': 50, 'career': new_career(club_names[club], 4)} ]}" />
<q:set name="taken" value="{taken + [club]}" />
<q:set name="who" value="" />
</qg:button>
</qg:menu>
<qg:hud x="16" y="210" font="assets/DejaVuSansMono.ttf" size="13" if="{len(managers) > 0}">
<qg:table source="{managers}" as="t"><qg:column value="{'☻ ' + fit(t.name, 15) + club_names[t.club]}" width="34" color="#ffff55" /></qg:table>
</qg:hud>
<qg:hud x="340" y="60" font="assets/DejaVuSansMono.ttf" size="13">
<qg:text value="{'Each manager is given a club,'}" color="#55ffff" />
<qg:text value="{'drawn among the small clubs'}" color="#55ffff" />
<qg:text value="{'of the 4th division.'}" color="#55ffff" />
</qg:hud>
<qg:menu x="16" y="330" font="assets/DejaVuSansMono.ttf" size="13">
<qg:button label="Start the league" if="{len(managers) > 0}"><qg:goto-scene name="world" /></qg:button>
<qg:button label="Back"><q:call function="reset_game" /><qg:goto-scene name="title" /></qg:button>
</qg:menu>
</qg:scene>
<!-- The world is made from its number: the same number, the same clubs and players. -->
<qg:scene name="world" width="640" height="400" background="#0000a8" seed="{world}">
<q:call function="new_world" />
<qg:timer after="1"><qg:save scene="dashboard" slot="{slot}" /><qg:goto-scene name="dashboard" /></qg:timer>
</qg:scene>
<!-- ===== The dashboard: the club at a glance, its eleven in view, the next match and who it is against ===== -->
<qg:scene name="dashboard" width="640" height="400" background="#0000a8" save="true">
<q:if condition="{save_version < 22 and len(clubs) == 40}"><q:call function="refit" /></q:if>
<q:call function="send_reports" />
<q:set name="g" value="{next_match(managers[turn].club)}" />
<q:set name="opp" value="{g[1] if g[0] == managers[turn].club else g[0]}" />
<qg:hud x="6" y="4" box="frame" font="assets/DejaVuSansMono.ttf" size="12">
<qg:text value="{'PANDAFOOT ' + fit(my_club().name, 16) + fit(managers[turn].name, 16) + tr('Round') + ' ' + str(matchday + 1) + '/18 ' + str(season)}" color="#ffff55" />
<qg:text value="{fit(tr('Balance:'), 9) + fit(money(my_club().balance), 14) + tr('Place:') + ' ' + str(position_of(managers[turn].club)) + 'º ' + div_name(my_club().div) + ' ' + tr('Form:') + ' ' + join(form_of(my_club()), ' ')}" />
<qg:text value="{fit(tr('Morale:'), 9) + blocks(team_morale(my_club()), 10) + ' ' + tr('Ground:') + ' ' + thousands(my_club().capacity, '.') + ' ' + tr('seats') + ' ' + tr('Board:') + ' ' + blocks(managers[turn].patience, 8) + ' ' + str(round(managers[turn].patience)) + '%'}" />
</qg:hud>
<qg:hud x="6" y="86" box="frame" font="assets/DejaVuSansMono.ttf" size="10">
<qg:text value="{tr('LINE-UP') + ' ' + my_club().formation + ' (' + str(len(eleven(my_club()))) + '/11)'}" color="#55ffff" />
<qg:table source="{lineup_rows(my_club())[0:16]}" as="p">
<qg:column label="Pos" value="{tr(p.xi) if p.xi != '' else tr('res')}" width="4" color="{'#ffffff' if p.xi != '' else '#aaaaaa'}" />
<qg:column label="Name" value="{p.name}" width="15" color="{'#ff55ff' if p.genius else ('#ffaa00' if award_of(p) != '' else ('#ffff55' if p.name in best_three(my_club()) else ('#ffffff' if p.xi != '' else '#aaaaaa')))}" />
<qg:column label="Str" value="{p.strength}" width="3" align="right" />
<qg:column label="Form" value="{p.form}" width="5" align="right" color="{form_color(p)}" />
<qg:column label="Fit" value="{state_of(p)}" width="3" color="{'#55ff55' if available(p) else '#ff5555'}" />
</qg:table>
</qg:hud>
<qg:hud x="250" y="86" box="frame" font="assets/DejaVuSansMono.ttf" size="11">
<qg:text value="{'═══ ' + tr('NEXT MATCH') + ' ═══'}" color="#55ffff" />
<qg:text value="{fit(clubs[g[0]].name + ' x ' + clubs[g[1]].name, 50)}" color="#ffffff" />
<qg:text value="{fit((div_name(my_club().div) + ', ' + tr('round') + ' ' + str(matchday + 1) if next_kind() == 'league' else (tr('PANDA CUP') + ', ' + cup_round_name(cup_round) if next_kind() == 'cup' else tr('SUPERCUP'))) + (' (' + tr('home' if g[0] == managers[turn].club else 'away') + ')' if g[0] != g[1] else ' - ' + tr('no match for you')), 50)}" />
<qg:text value="{fit(tr('Opponent:') + ' ' + str(position_of(opp)) + 'º ' + tr('Form:') + ' ' + join(form_of(clubs[opp]), ' ') + ' ' + tr('Top scorer:') + ' ' + club_top(clubs[opp]), 50)}" />
<qg:text value="{fit(tr('They tend to play:') + ' ' + tr(usual(clubs[opp])) + ' - ' + hint(my_club(), clubs[opp]), 50)}" />
<qg:text value="{fit(tr('Objective:') + ' ' + objective_text(my_club()) + ' ' + my_club().formation + ' ' + tr(my_club().style), 50)}" color="#ffff55" />
<qg:text value="{('⚠ ' + tr('The line-up is not complete (F1)')) if not lineup_ok(my_club()) else (('⚠ ' + str(tired_in(my_club())) + ' ' + tr('tired in the eleven: rest them (F1)')) if tired_in(my_club()) >= 2 else (('⚠ ' + tr('The board is losing its patience')) if managers[turn].patience < 20 else (('⚠ ' + crisis_text(my_club())) if crisis(my_club()) > 0 else (('⚠ ' + tr('The ground needs care (F5): at 60% it cannot grow')) if my_club().health <= 65 else (tr('The board expects:') + ' ' + str(my_club().expect) + 'º')))))}" color="{'#ff5555' if not lineup_ok(my_club()) or tired_in(my_club()) >= 2 or crisis(my_club()) > 0 or managers[turn].patience < 20 or my_club().health <= 65 else '#aaaaaa'}" />
</qg:hud>
<qg:hud x="250" y="238" box="frame" font="assets/DejaVuSansMono.ttf" size="11">
<qg:text value="{'═══ ' + tr('NEWS') + ' ═══'}" color="#55ffff" />
<qg:table source="{news[0:5]}" as="n"><qg:column value="{tr(n.text) + (' ' + n.arg if n.arg != '' else '')}" width="50" /></qg:table>
</qg:hud>
<qg:hud x="6" y="358" font="assets/DejaVuSansMono.ttf" size="10">
<qg:text value="{'[F1] ' + tr('Line-up') + ' [F2] ' + tr('Tactics') + ' [F3] ' + tr('Squad') + ' [F4] ' + tr('Players') + ' [F5] ' + tr('Finances') + ' [F6] ' + tr('Market') + ' [F7] ' + tr('Table') + ' [F8] ' + tr('History')}" color="#55ffff" />
<qg:text value="{'[ENTER] ' + tr('Ready: next manager' if turn < len(managers) - 1 else 'Play the round') + ' [ESC] ' + tr('Save and leave') + ' [F9] ' + tr('Report')}" color="#ffff55" />
</qg:hud>
<qg:hud position="bottom-right" font="assets/DejaVuSansMono.ttf" size="10">
<qg:text value="{'v' + game_version() + ' beta'}" color="#aaaaaa" />
</qg:hud>
<qg:on-input action="lineup"><qg:goto-scene name="lineup" /></qg:on-input>
<qg:on-input action="tactics"><qg:goto-scene name="tactics" /></qg:on-input>
<qg:on-input action="squad"><qg:goto-scene name="squad" /></qg:on-input>
<qg:on-input action="players"><qg:goto-scene name="players" /></qg:on-input>
<qg:on-input action="finances"><qg:goto-scene name="finances" /></qg:on-input>
<qg:on-input action="standings"><qg:goto-scene name="standings" /></qg:on-input>
<qg:on-input action="market"><qg:goto-scene name="market" /></qg:on-input>
<qg:on-input action="history"><qg:goto-scene name="history" /></qg:on-input>
<qg:on-input action="select">
<q:if condition="{not lineup_ok(my_club())}"><q:call function="fill_lineup" args="{my_club()}" /></q:if>
<q:if condition="{turn < len(managers) - 1}">
<q:set name="turn" value="{turn + 1}" />
<qg:goto-scene name="dashboard" />
<q:else>
<q:set name="turn" value="{0}" />
<q:call function="start_date" />
<qg:goto-scene name="round" />
</q:else>
</q:if>
</qg:on-input>
<qg:on-input action="cancel"><qg:save slot="{slot}" /><qg:goto-scene name="title" /></qg:on-input>
<!-- the season is over: back from the table (F7 on the last results) or from a save, the season's end, never
round 19 of 18 (it played the last match again, and again) -->
<qg:when condition="{matchday >= 18 and not cup_due()}"><qg:goto-scene name="season-end" /></qg:when>
<!-- a cup date no manager plays, reached by a way other than Enter on the results (the table, a save): at once -->
<qg:when condition="{(cup_due() or super_due()) and not managed_in(next_games())}"><q:call function="after_results" /></qg:when>
<qg:when condition="{len(clubs) != 40}"><qg:goto-scene name="old-save" /></qg:when>
</qg:scene>
<!-- a save of the first PandaFoot (one league of ten): this one cannot continue it -->
<qg:scene name="old-save" width="640" height="400" background="#0000a8">
<qg:hud position="center" box="frame" font="assets/DejaVuSansMono.ttf" size="14">
<qg:text value="{tr('This game was saved by the first PandaFoot')}" color="#ffff55" />
<qg:text value="{tr('(one league of ten clubs), which this one')}" />
<qg:text value="{tr('cannot continue. Start a new game: four')}" />
<qg:text value="{tr('divisions, from a small club of the 4th.')}" />
<qg:text value="{'[ENTER] ' + tr('To the title')}" color="#55ffff" />
</qg:hud>
<qg:on-input action="select"><q:call function="reset_game" /><qg:goto-scene name="title" /></qg:on-input>
</qg:scene>
<q:function name="next_match" params="club_index">
<q:loop items="{next_games()}" var="g">
<q:if condition="{g[0] == club_index or g[1] == club_index}"><q:return value="{g}" /></q:if>
</q:loop>
<q:return value="{[club_index, club_index]}" />
</q:function>
<!-- the eleven by place, the keeper first, then the bench (the dashboard shows five of it) -->
<q:function name="lineup_rows" params="club">
<q:set name="out" value="{[]}" />
<q:loop items="{['GK', 'DF', 'MF', 'FW']}" var="place">
<q:loop items="{club.players}" var="p"><q:if condition="{p.xi == place}"><q:set name="out" value="{out + [p]}" /></q:if></q:loop>
</q:loop>
<q:loop items="{club.players}" var="p"><q:if condition="{p.xi == '' and p.bench}"><q:set name="out" value="{out + [p]}" /></q:if></q:loop>
<q:return value="{out}" />
</q:function>
<!-- how fresh a player is, in colour: rested, tiring, tired -->
<q:function name="form_color" params="p">
<q:return value="{'#55ff55' if p.form >= 80 else ('#ffff55' if p.form >= 60 else '#ff5555')}" />
</q:function>
<!-- a manager's objective as the dashboard says it: Auto with what it picked for this match -->
<q:function name="objective_text" params="club">
<q:return value="{tr('Auto') + ' (' + tr(club.plan) + ')' if club.objective == 'Auto' else tr(club.objective)}" />
</q:function>
<!-- a club's best scorer of the season -->
<q:function name="club_top" params="club">
<q:set name="top" value="{null}" />
<q:loop items="{club.players}" var="p"><q:if condition="{p.goals > 0 and (top == null or p.goals > top.goals)}"><q:set name="top" value="{p}" /></q:if></q:loop>
<q:return value="{'-' if top == null else top.name + ' (' + str(top.goals) + ')'}" />
</q:function>
<!-- the last results, W D L, as the player's language writes them -->
<q:function name="form_of" params="club">
<q:set name="out" value="{[]}" />
<q:loop items="{club.form}" var="f"><q:set name="out" value="{out + [tr(f)]}" /></q:loop>
<q:return value="{out}" />
</q:function>
<q:function name="team_morale" params="club">
<q:set name="sum" value="{0}" />
<q:loop items="{club.players}" var="p"><q:set name="sum" value="{sum + p.morale}" /></q:loop>
<q:return value="{sum / len(club.players)}" />
</q:function>
<!-- the traits as marks: the same in every language, the legend below says them -->
<q:function name="marks" params="p">
<q:set name="out" value="{'◆' if p.genius else ''}" />
<q:loop items="{p.traits}" var="t">
<q:set name="out" value="{out + {'Ace': '★', 'Finisher': '►', 'Set pieces': '◎', 'Leader': '♛', 'Star': '☆', 'Wall': '▣'}[t]}" />
</q:loop>
<q:return value="{out}" />
</q:function>
<q:function name="positions" params="p">
<q:set name="out" value="{tr(p.pos)}" />
<q:loop items="{p.sec}" var="s"><q:set name="out" value="{out + '/' + tr(s)}" /></q:loop>
<q:return value="{out}" />
</q:function>
<!-- the last month's growth: ▲ a point more, ▼ a point less -->
<q:function name="trend_of" params="p">
<q:return value="{p.trend if 'trend' in p else 0}" />
</q:function>
<q:function name="trend_mark" params="p">
<q:return value="{'▲' if trend_of(p) > 0 else ('▼' if trend_of(p) < 0 else '')}" />
</q:function>
<!-- a rating in colour: green from 7, yellow from 6, red under 5.5 -->
<q:function name="rating_color" params="r">
<q:return value="{'#aaaaaa' if r <= 0 else ('#55ff55' if r >= 7 else ('#ffffff' if r >= 6 else ('#ffff55' if r >= 5.5 else '#ff5555')))}" />
</q:function>
<q:function name="state_of" params="p">
<q:return value="{('✚' + str(p.injured)) if p.injured > 0 else (('▮' + str(p.suspended)) if p.suspended > 0 else '✓')}" />
</q:function>
<!-- ===== F3: the squad ===== -->
<qg:scene name="squad" width="640" height="400" background="#0000a8">
<qg:hud x="8" y="6" box="frame" font="assets/DejaVuSansMono.ttf" size="11">
<qg:text value="{tr('SQUAD') + ' - ' + shown_club().name + ' (' + str(position_of(shown_index())) + 'º, ' + str(len(shown_club().players)) + ' ' + tr('players, wages') + ' ' + money(payroll(shown_club())) + tr('/month') + ')'}" color="#ffff55" />
<qg:table source="{shown_club().players}" as="p">
<qg:column label="#" value="{p.num}" width="2" align="right" />
<qg:column label="Name" value="{p.name}" width="16" color="{'#ffaa00' if award_of(p) != '' else ('#ffff55' if p.name in best_three(shown_club()) else '#ffffff')}" />
<qg:column label="Pos" value="{positions(p)}" width="9" />
<qg:column label="Str" value="{p.strength}" width="3" align="right" color="{'#ff55ff' if known_genius(p) else ('#ffff55' if p.strength >= 42 else '#ffffff')}" />
<qg:column value="{trend_mark(p)}" width="1" color="{'#55ff55' if trend_of(p) > 0 else '#ff5555'}" /> <!-- the last month: ▲ grew, ▼ fell -->
<qg:column label="Traits" value="{award_mark(p) + (marks(p) if known_genius(p) or not p.genius else marks(p)[1:])}" width="6" color="#ff55ff" />
<qg:column label="Mor" value="{blocks(p.morale, 4)}" width="4" color="{'#55ff55' if p.morale >= 70 else ('#ffff55' if p.morale >= 40 else '#ff5555')}" />
<qg:column label="Form" value="{p.form}" width="4" align="right" color="{form_color(p)}" />
<qg:column label="Gm" value="{p.apps}" width="3" align="right" />
<qg:column label="Gls" value="{p.goals}" width="3" align="right" />
<qg:column label="Avg" value="{mean_text(p)}" width="4" align="right" color="{rating_color(mean_rating(p))}" />
<qg:column label="Car" value="{p.total + p.goals}" width="4" align="right" color="#aaaaaa" /> <!-- the goals of a career: the seasons before and this one -->
<qg:column label="▮" value="{p.yellows}" width="2" align="right" color="#ffff55" />
<qg:column label="Fit" value="{state_of(p)}" width="3" color="{'#55ff55' if available(p) else '#ff5555'}" />
<qg:column label="Salary" value="{money(p.salary)}" width="9" align="right" />
</qg:table>
</qg:hud>
<qg:hud x="8" y="368" font="assets/DejaVuSansMono.ttf" size="11">
<qg:text value="{'◆ ' + tr('Genius') + ' ★ ' + tr('Ace') + ' ► ' + tr('Finisher') + ' ◎ ' + tr('Set pieces') + ' ♛ ' + tr('Leader') + ' ☆ ' + tr('Star') + ' ▣ ' + tr('Wall')}" color="#55ffff" />
<qg:text value="{'✪ ' + tr('Top scorer') + ' ✦ ' + tr('Best player') + ' ▲▼ ' + tr('the last month') + (' [B] ' + tr('Bid for a player') if viewing >= 0 and shown_club().manager == 0 else '') + ' [ESC] ' + tr('Back')}" color="#55ffff" />
</qg:hud>
<qg:on-input action="scout"><q:if condition="{viewing >= 0 and shown_club().manager == 0}"><qg:goto-scene name="scout" /></q:if></qg:on-input>
<qg:on-input action="lineup"><q:set name="viewing" value="{-1}" /><qg:goto-scene name="lineup" /></qg:on-input>
<qg:on-input action="tactics"><q:set name="viewing" value="{-1}" /><qg:goto-scene name="tactics" /></qg:on-input>
<qg:on-input action="finances"><q:set name="viewing" value="{-1}" /><qg:goto-scene name="finances" /></qg:on-input>
<qg:on-input action="standings"><q:set name="viewing" value="{-1}" /><qg:goto-scene name="standings" /></qg:on-input>
<qg:on-input action="cancel">
<q:if condition="{viewing >= 0}"><qg:goto-scene name="standings" />
<q:else><qg:goto-scene name="dashboard" /></q:else>
</q:if>
</qg:on-input>
</qg:scene>
<!-- the squad on screen: the club chosen on the table, else the manager's -->
<q:function name="shown_index">
<q:return value="{viewing if viewing >= 0 else managers[turn].club}" />
</q:function>
<q:function name="shown_club">
<q:return value="{clubs[shown_index()]}" />
</q:function>
<!-- a genius one knows of: the manager's own, and the big clubs' (the papers write about them); the one hidden in
a small club is a rumour until someone finds him -->
<q:function name="known_genius" params="p">
<q:return value="{p.genius and (shown_index() == managers[turn].club or shown_club().div == 1)}" />
</q:function>
<q:function name="payroll" params="club">
<q:set name="sum" value="{0}" />
<q:loop items="{club.players}" var="p"><q:set name="sum" value="{sum + p.salary}" /></q:loop>
<q:return value="{sum}" />
</q:function>
<!-- ===== F1: the line-up: choose a player to put them in the eleven, on the bench, or out ===== -->
<qg:scene name="lineup" width="640" height="400" background="#0000a8">
<qg:hud x="8" y="8" font="assets/DejaVuSansMono.ttf" size="12">
<qg:text value="{tr('LINE-UP') + ' - ' + my_club().name + ' ' + my_club().formation + ' ' + tr('in the eleven:') + ' ' + str(len(eleven(my_club()))) + '/11'}" color="#ffff55" />
<qg:text value="{' # ' + fit(tr('Name'), 18) + fit(tr('Pos'), 12) + fit(tr('Str'), 4) + fit(tr('Form'), 6) + fit(tr('Traits'), 7) + fit(tr('Fit'), 4) + tr('Plays')}" color="#55ffff" />
</qg:hud>
<qg:menu x="8" y="40" source="{my_club().players}" as="p" rows="18" font="assets/DejaVuSansMono.ttf" size="12">
<qg:button label="{fit(p.num, 3, 'right') + ' ' + fit(p.name, 18) + fit(positions(p), 12) + fit(p.strength, 3, 'right') + ' ' + fit(p.form, 5, 'right') + ' ' + fit(marks(p), 7) + fit(state_of(p), 4) + (tr('ELEVEN') + ' ' + tr(p.xi) if p.xi != '' else (tr('bench') if p.bench else ''))}">
<q:call function="toggle" args="{my_club()}, {p}" />
</qg:button>
</qg:menu>
<qg:hud x="8" y="356" font="assets/DejaVuSansMono.ttf" size="12">
<qg:text value="{tr('Attack') + ' ' + str(round(power(my_club())[0])) + ' ' + tr('Defence') + ' ' + str(round(power(my_club())[1])) + ('' if lineup_ok(my_club()) else ' ⚠ ' + tr('eleven fit to play, one of them in goal'))}" color="{'#ffffff' if lineup_ok(my_club()) else '#ff5555'}" />
<qg:text value="{'[ENTER] ' + tr('Eleven → bench → out') + ' [A] ' + tr('Automatic') + ' [F2] ' + tr('Tactics') + ' [ESC] ' + tr('Back')}" color="#55ffff" />
</qg:hud>
<qg:on-input action="auto"><q:call function="auto_lineup" args="{my_club()}" /><q:call function="mark_picks" args="{my_club()}" /></qg:on-input>
<qg:on-input action="tactics"><qg:goto-scene name="tactics" /></qg:on-input>
<qg:on-input action="squad"><qg:goto-scene name="squad" /></qg:on-input>
<qg:on-input action="cancel"><qg:goto-scene name="dashboard" /></qg:on-input>
</qg:scene>
<!-- a player chosen in the line-up: in the eleven, then on the bench, then out -->
<q:function name="toggle" params="club, p">
<q:if condition="{p.xi != ''}"><q:set name="p.xi" value="{''}" /><q:set name="p.bench" value="{true}" /><q:call function="mark_picks" args="{club}" /><q:return /></q:if>
<q:if condition="{p.bench}"><q:set name="p.bench" value="{false}" /><q:return /></q:if>
<q:if condition="{available(p) and len(eleven(club)) < 11}"><q:set name="p.xi" value="{open_place(club, p)}" /></q:if>
<q:call function="mark_picks" args="{club}" />
</q:function>
<!-- where a player goes into the eleven: their own place if it is open, a second one, else any open -->
<q:function name="open_place" params="club, p">
<q:set name="n" value="{shape(club.formation)}" />
<q:set name="wanted" value="{ {'GK': 1, 'DF': n[0], 'MF': n[1], 'FW': n[2]} }" />
<q:loop items="{eleven(club)}" var="q"><q:set name="wanted" index="{q.xi}" value="{wanted[q.xi] - 1}" /></q:loop>
<q:if condition="{wanted[p.pos] > 0}"><q:return value="{p.pos}" /></q:if>
<q:loop items="{p.sec}" var="s"><q:if condition="{wanted[s] > 0}"><q:return value="{s}" /></q:if></q:loop>
<q:loop items="{['DF', 'MF', 'FW', 'GK']}" var="s"><q:if condition="{wanted[s] > 0}"><q:return value="{s}" /></q:if></q:loop>
<q:return value="{p.pos}" />
</q:function>
<!-- ===== F2: tactics: the objective, or the formation and the style by hand; the opponent's last matches ===== -->
<qg:scene name="tactics" width="640" height="400" background="#0000a8">
<q:set name="g" value="{next_match(managers[turn].club)}" />
<q:set name="opponent" value="{clubs[g[1] if g[0] == managers[turn].club else g[0]]}" />
<qg:hud x="8" y="8" box="frame" font="assets/DejaVuSansMono.ttf" size="13">
<qg:text value="{tr('TACTICS') + ' - ' + my_club().name + ' x ' + opponent.name}" color="#ffff55" />
<qg:text value="{tr('They tend to play:') + ' ' + tr(usual(opponent)) + ' ' + hint(my_club(), opponent)}" color="#55ffff" />
</qg:hud>
<qg:menu x="8" y="82" font="assets/DejaVuSansMono.ttf" size="13" box="frame">
<qg:button label="{tr('Objective:') + ' ' + objective_text(my_club())}"><q:call function="next_objective" args="{my_club()}, {opponent}, {g[0] == managers[turn].club}" /></qg:button>
<qg:button label="{tr('Formation:') + ' ' + my_club().formation}"><q:call function="by_hand" args="{my_club()}, {after_in(formations, my_club().formation)}, {my_club().style}" /></qg:button>
<qg:button label="{tr('Style:') + ' ' + tr(my_club().style)}"><q:call function="by_hand" args="{my_club()}, {my_club().formation}, {after_in(styles, my_club().style)}" /></qg:button>
<qg:button label="{taker_text(my_club())}"><q:call function="next_taker" args="{my_club()}" /></qg:button>
<qg:button label="Back"><qg:goto-scene name="dashboard" /></qg:button>
</qg:menu>
<qg:hud x="300" y="82" box="frame" font="assets/DejaVuSansMono.ttf" size="11">
<qg:text value="{'═══ ' + tr('AUTOMATIC CHANGES') + ' ═══'}" color="#55ffff" />
<qg:table source="{my_club().reactions}" as="r">
<qg:column label="When" value="{tr(r.when)}" width="10" />
<qg:column label="after" value="{str(r.after) + '′'}" width="6" align="right" />
<qg:column label="change to" value="{r.formation + ' ' + tr(r.style)}" width="22" />
</qg:table>
</qg:hud>
<qg:hud x="8" y="222" box="frame" font="assets/DejaVuSansMono.ttf" size="12">
<qg:text value="{'═══ ' + opponent.name + ' - ' + tr('LAST MATCHES') + ' ═══'}" color="#55ffff" />
<qg:table source="{opponent.history}" as="h">
<qg:column value="{tr('vs') + ' ' + h.vs + ' (' + tr('home' if h.home else 'away') + ')'}" width="22" />
<qg:column value="{tr(h.style)}" width="17" />
<qg:column value="{str(h.gf) + '-' + str(h.ga) + ' ' + tr('W' if h.gf > h.ga else ('D' if h.gf == h.ga else 'L'))}" width="8" color="{'#55ff55' if h.gf > h.ga else ('#ffffff' if h.gf == h.ga else '#ff5555')}" />
</qg:table>
<qg:text value="{tendency(opponent)}" color="#ffff55" />
</qg:hud>
<qg:on-input action="lineup"><qg:goto-scene name="lineup" /></qg:on-input>
<qg:on-input action="cancel"><qg:goto-scene name="dashboard" /></qg:on-input>
</qg:scene>
<!-- the formation or the style by hand: the objective is the manager's own -->
<q:function name="by_hand" params="club, formation, style">
<q:set name="club.formation" value="{formation}" />
<q:set name="club.style" value="{style}" />
<q:set name="club.objective" value="{'By hand'}" />
<q:set name="club.plan" value="{'By hand'}" />
<q:set name="club.reactions" value="{[]}" />
<q:call function="fill_lineup" args="{club}" />
</q:function>
<q:function name="ticket" params="club, by">
<q:set name="club.ticket" value="{max(5, min(50, club.ticket + by))}" />
</q:function>
<!-- the next objective: its tactics against this opponent at once -->
<q:function name="next_objective" params="club, opponent, home">
<q:set name="club.objective" value="{after_in(objectives, club.objective)}" />
<q:if condition="{club.objective == 'By hand'}"><q:call function="preset" args="{club}, {'By hand'}, {usual(opponent)}" /><q:set name="club.plan" value="{'By hand'}" /></q:if>
<q:call function="ready" args="{club}, {opponent}, {home}" />
</q:function>
<!-- what the counter-tactics give against the opponent's usual style -->
<q:function name="hint" params="club, opponent">
<q:set name="e" value="{edge(club.style, usual(opponent))}" />
<q:if condition="{e > 0}"><q:return value="{tr('Tactical edge') + ' +' + str(round(e * 100)) + '%'}" /></q:if>
<q:if condition="{e < 0}"><q:return value="{tr('Tactical handicap') + ' ' + str(round(e * 100)) + '%'}" /></q:if>
<q:return value="{tr('No tactical edge')}" />
</q:function>
<!-- what a club started its last matches with, and the style that beats it -->
<q:function name="tendency" params="club">
<q:if condition="{len(club.history) == 0}"><q:return value="{tr('No matches yet.')}" /></q:if>
<q:set name="theirs" value="{usual(club)}" />
<q:set name="share" value="{0}" />
<q:loop items="{club.history}" var="h"><q:if condition="{family(h.style) == family(theirs)}"><q:set name="share" value="{share + 1}" /></q:if></q:loop>
<q:set name="beat" value="{ {'A': 'Counter-attack', 'D': 'Attacking', 'C': 'Defensive', 'B': 'Attacking'}[family(theirs)] }" />
<q:return value="{tr('Tendency:') + ' ' + tr(theirs) + ' (' + str(share) + '/' + str(len(club.history)) + ') ' + tr('Against it:') + ' ' + tr(beat) + ' +' + str(round(edge(beat, theirs) * 100)) + '%'}" />
</q:function>
<!-- ===== F7: the table, and the top scorers ===== -->
<qg:scene name="standings" width="640" height="400" background="#0000a8">
<!-- the division shown: the club's just looked at, else the manager's; Tab, the next -->
<q:set name="shown" value="{clubs[viewing].div if viewing >= 0 else my_club().div}" type="number" />
<qg:hud x="8" y="4" box="frame" font="assets/DejaVuSansMono.ttf" size="13">
<qg:text value="{tr('PANDA LEAGUE') + ' ' + str(season) + ' - ' + div_name(shown) + ' - ' + tr('Round') + ' ' + str(matchday) + '/18'}" color="#ffff55" />
</qg:hud>
<qg:hud x="26" y="40" font="assets/DejaVuSansMono.ttf" size="13">
<qg:text value="{fit(tr('Pos'), 4, 'right') + ' ' + fit(tr('Club'), 14) + fit(tr('P'), 3, 'right') + fit(tr('W'), 3, 'right') + fit(tr('D'), 3, 'right') + fit(tr('L'), 3, 'right') + fit(tr('GF'), 4, 'right') + fit(tr('GA'), 4, 'right') + fit(tr('GD'), 4, 'right') + fit(tr('Pts'), 5, 'right') + ' ' + tr('Form')}" color="#55ffff" />
</qg:hud>
<!-- a club's row, chosen with Enter: its squad (the managers' clubs are marked ►) -->
<qg:menu x="8" y="58" source="{table_order(shown)}" as="i" font="assets/DejaVuSansMono.ttf" size="13" box="frame">
<qg:button label="{table_row(i)}"><q:set name="viewing" value="{i}" /><qg:goto-scene name="squad" /></qg:button>
</qg:menu>
<qg:hud x="468" y="58" box="frame" font="assets/DejaVuSansMono.ttf" size="10">
<qg:text value="{'═ ' + tr('TOP SCORERS') + ' ═'}" color="#55ffff" />
<qg:table source="{top_scorers(10, shown)}" as="t">
<qg:column value="{t.goals}" width="3" align="right" color="#ffff55" />
<qg:column value="{t.name}" width="14" color="{'#ff55ff' if t.genius else '#ffffff'}" />
<qg:column value="{t.club}" width="4" />
</qg:table>
</qg:hud>
<qg:hud x="8" y="364" font="assets/DejaVuSansMono.ttf" size="13">
<qg:text value="{'► ' + tr('managers') + ' ↑↓ ' + tr('up and down') + ' [ENTER] ' + tr('Squad') + ' [TAB] ' + tr('Division') + ' [ESC] ' + tr('Back')}" color="#55ffff" />
</qg:hud>
<qg:on-input action="other"><q:set name="shown" value="{shown % 4 + 1}" /></qg:on-input>
<qg:on-input action="cancel"><qg:goto-scene name="dashboard" /></qg:on-input>
<qg:on-input action="squad"><qg:goto-scene name="squad" /></qg:on-input>
<qg:when condition="{viewing >= 0}"><q:set name="viewing" value="{-1}" /></qg:when> <!-- back from a club's squad -->
</qg:scene>
<q:function name="table_row" params="i">
<q:set name="c" value="{clubs[i]}" />
<q:set name="at" value="{position_of(i)}" />
<q:return value="{fit(str(at) + '.', 4, 'right') + ('↑' if c.div > 1 and at <= 2 else ('↓' if c.div < 4 and at >= 9 else ' ')) + ('►' if c.manager > 0 else ' ') + fit(c.name, 14) + fit(str(c.played), 3, 'right') + fit(str(c.won), 3, 'right') + fit(str(c.drawn), 3, 'right') + fit(str(c.lost), 3, 'right') + fit(str(c.gf), 4, 'right') + fit(str(c.ga), 4, 'right') + fit(str(c.gf - c.ga), 4, 'right') + fit(str(c.points), 5, 'right') + ' ' + join(form_of(c), ' ')}" />
</q:function>
<!-- the season's top scorers of a division (0: of all four), so many of them: the goals, then the order of the
clubs and squads -->
<q:function name="top_scorers" params="n, d">
<q:set name="keys" value="{[]}" />
<q:loop var="ci" from="0" to="{len(clubs) - 1}">
<q:if condition="{d == 0 or clubs[ci].div == d}">
<q:loop var="pk" from="0" to="{len(clubs[ci].players) - 1}">
<q:if condition="{clubs[ci].players[pk].goals > 0}"><q:set name="keys" value="{keys + [[clubs[ci].players[pk].goals, 100000 - ci * 100 - pk]]}" /></q:if>
</q:loop>
</q:if>
</q:loop>
<q:set name="out" value="{[]}" />
<q:loop items="{sort(keys, true)[0:n]}" var="key">
<q:set name="at" value="{int(100000 - key[1])}" />
<q:set name="his" value="{clubs[int(floor(at / 100))]}" />
<q:set name="who" value="{his.players[at % 100]}" />
<q:set name="out" value="{out + [ {'name': who.name, 'goals': who.goals, 'genius': who.genius, 'club': his.short} ]}" />
</q:loop>
<q:return value="{out}" />
</q:function>
<!-- ===== Bidding for a player of a computer's club: he goes on the list, an auction, the manager's bid first ===== -->
<qg:scene name="scout" width="640" height="400" background="#0000a8">
<q:set name="seller" value="{viewing}" type="number" />
<q:set name="note" value="" type="string" />
<q:set name="viewing" value="{-1}" />
<qg:hud x="8" y="4" box="frame" font="assets/DejaVuSansMono.ttf" size="12">
<qg:text value="{tr('BID FOR A PLAYER OF') + ' ' + clubs[seller].name + ' ' + tr('Balance:') + ' ' + money(my_club().balance)}" color="#ffff55" />
<qg:text value="{tr('He goes on the transfer list: the other clubs may bid too, for three rounds.')}" color="#55ffff" />
</qg:hud>
<qg:menu x="8" y="60" source="{upto(len(clubs[seller].players))}" as="k" rows="12" font="assets/DejaVuSansMono.ttf" size="12" box="frame">
<qg:button label="{scout_row(clubs[seller].players[k])}">
<q:set name="at" value="{open_auction(seller, clubs[seller].players[k])}" />
<q:if condition="{at >= 0}"><q:set name="bidding" value="{at}" /><qg:goto-scene name="bid" />
<q:else><q:set name="note" value="{clubs[seller].name + ' ' + tr('sells no one: it would have fewer than sixteen.')}" /></q:else>
</q:if>
</qg:button>
</qg:menu>
<qg:hud x="8" y="364" font="assets/DejaVuSansMono.ttf" size="12">
<qg:text value="{note}" color="#ff5555" />
<qg:text value="{'[ENTER] ' + tr('Bid') + ' [ESC] ' + tr('Back')}" color="#55ffff" />
</qg:hud>
<qg:on-input action="cancel"><q:set name="viewing" value="{seller}" /><qg:goto-scene name="squad" /></qg:on-input>
</qg:scene>
<q:function name="scout_row" params="p">
<q:return value="{fit(p.name, 17) + fit(positions(p), 11) + fit(str(p.strength), 3, 'right') + ' ' + fit(tr('Fee:') + ' ' + money(round(value_of(p) * 1.3 / 1000) * 1000), 22) + (tr('on the list') if listed(p.name) else '')}" />
</q:function>
<!-- a player of a computer's club on the list at a manager's request (its fee a third above his worth: it was not
selling); already there, his entry. -1: the club would be left with fewer than sixteen -->
<q:function name="open_auction" params="ci, p">
<q:loop var="k" from="0" to="{len(market) - 1}"><q:if condition="{market[k].who == p.name}"><q:return value="{k}" /></q:if></q:loop>
<q:if condition="{len(clubs[ci].players) <= 16}"><q:return value="{-1}" /></q:if>
<q:call function="list_player" args="{ci}, {p}, {value_of(p) * 1.3}" />
<q:call function="said" args="{'Up for auction:'}, {p.name + ' (' + clubs[ci].short + ')'}" />
<q:return value="{len(market) - 1}" />
</q:function>
<!-- ===== F4: every player of the world, to look at, compare and bid for (the beta's ask) ===== -->
<qg:scene name="players" width="640" height="400" background="#0000a8">
<q:set name="rows" value="{player_rows()}" type="array" />
<q:set name="note" value="" type="string" />
<qg:hud x="8" y="4" box="frame" font="assets/DejaVuSansMono.ttf" size="12">
<qg:text value="{tr('PLAYERS') + ' - ' + (tr('every position') if looking.pos == '' else tr(looking.pos)) + ', ' + (tr('every division') if looking.div == 0 else div_name(looking.div)) + ', ' + tr('by') + ' ' + tr(looking.by) + ' (' + str(len(rows)) + ')'}" color="#ffff55" />
</qg:hud>
<qg:hud x="25" y="32" font="assets/DejaVuSansMono.ttf" size="11">
<qg:text value="{fit(tr('Name'), 17) + fit(tr('Club'), 5) + fit(tr('Div'), 4) + fit(tr('Pos'), 9) + fit(tr('Str'), 4, 'right') + fit(tr('Gm'), 4, 'right') + fit(tr('Gls'), 4, 'right') + fit(tr('Avg'), 5, 'right') + fit(tr('Worth'), 13, 'right')}" color="#55ffff" />
</qg:hud>
<qg:menu x="8" y="48" source="{rows}" as="r" rows="16" font="assets/DejaVuSansMono.ttf" size="11" box="frame">
<qg:button label="{player_row(r)}">
<q:set name="note" value="{choose_player(r)}" />
<q:if condition="{note == ''}"><qg:goto-scene name="bid" /></q:if>
</qg:button>
</qg:menu>
<qg:hud x="8" y="352" font="assets/DejaVuSansMono.ttf" size="11">
<qg:text value="{note}" color="#ff5555" />
<qg:text value="{'✪ ' + tr('Top scorer') + ' ✦ ' + tr('Best player') + ' ✓ ' + tr('on the list') + ' Gm: ' + tr('matches') + ' Avg: ' + tr('mean rating')}" color="#aaaaaa" />
<qg:text value="{'[←→] ' + tr('Position') + ' [TAB] ' + tr('Division') + ' [O] ' + tr('Order') + ' [ENTER] ' + tr('Bid') + ' [ESC] ' + tr('Back')}" color="#55ffff" />
</qg:hud>
<qg:on-input action="left"><q:set name="looking.pos" value="{after_in(['', 'FW', 'MF', 'DF', 'GK'], looking.pos)}" /><q:set name="rows" value="{player_rows()}" /></qg:on-input>
<qg:on-input action="right"><q:set name="looking.pos" value="{after_in(['', 'GK', 'DF', 'MF', 'FW'], looking.pos)}" /><q:set name="rows" value="{player_rows()}" /></qg:on-input>
<qg:on-input action="other"><q:set name="looking.div" value="{(looking.div + 1) % 5}" /><q:set name="rows" value="{player_rows()}" /></qg:on-input>
<qg:on-input action="order"><q:set name="looking.by" value="{after_in(['strength', 'goals', 'rating', 'matches', 'worth'], looking.by)}" /><q:set name="rows" value="{player_rows()}" /></qg:on-input>
<qg:on-input action="cancel"><qg:goto-scene name="dashboard" /></qg:on-input>
</qg:scene>
<!-- the players F4 shows, as places [club, index] (club 40: the free agents), the first 120 by the order chosen -->
<q:function name="player_rows">
<q:set name="keys" value="{[]}" />
<q:loop var="ci" from="0" to="{len(clubs)}">
<q:set name="pool" value="{free_agents if ci == len(clubs) else clubs[ci].players}" />
<q:if condition="{looking.div == 0 or (ci < len(clubs) and clubs[ci].div == looking.div)}"> <!-- the free agents with every division only -->
<q:loop var="k" from="0" to="{len(pool) - 1}">
<q:set name="q" value="{pool[k]}" />
<q:if condition="{looking.pos == '' or q.pos == looking.pos}">
<q:set name="v" value="{q.strength if looking.by == 'strength' else (q.goals if looking.by == 'goals' else (mean_rating(q) if looking.by == 'rating' else (q.apps if looking.by == 'matches' else value_of(q))))}" />
<q:set name="keys" value="{keys + [[v, q.strength, 100000 - ci * 100 - k]]}" />
</q:if>
</q:loop>
</q:if>
</q:loop>
<q:set name="out" value="{[]}" />
<q:loop items="{sort(keys, true)[0:120]}" var="key">
<q:set name="at" value="{int(100000 - key[2])}" />
<q:set name="out" value="{out + [[int(floor(at / 100)), at % 100]]}" />
</q:loop>
<q:return value="{out}" />
</q:function>
<q:function name="row_player" params="r">
<q:return value="{free_agents[r[1]] if r[0] == len(clubs) else clubs[r[0]].players[r[1]]}" />
</q:function>
<q:function name="player_row" params="r">
<q:set name="q" value="{row_player(r)}" />
<q:set name="agent" value="{r[0] == len(clubs)}" />
<q:return value="{fit(award_mark(q) + q.name, 16) + ' ' + fit(tr('free') if agent else clubs[r[0]].short, 5) + fit('-' if agent else str(clubs[r[0]].div), 4) + fit(positions(q), 9) + fit(str(q.strength), 4, 'right') + fit(str(q.apps), 4, 'right') + fit(str(q.goals), 4, 'right') + fit(mean_text(q), 5, 'right') + fit(money(value_of(q)), 13, 'right') + (' ✓' if listed(q.name) else '') + (' ►' if not agent and r[0] == managers[turn].club else '')}" />
</q:function>
<!-- a player chosen on F4: his entry on the list to bid on ('' then; else why not): a free agent goes on it, a
computer's club's player is put up for auction; one's own is sold from F6 -->
<q:function name="choose_player" params="r">
<q:set name="q" value="{row_player(r)}" />
<q:loop var="k" from="0" to="{len(market) - 1}">
<q:if condition="{market[k].who == q.name and market[k].seller != managers[turn].club}"><q:set name="bidding" value="{k}" /><q:return value="{''}" /></q:if>
</q:loop>
<q:if condition="{r[0] == managers[turn].club}"><q:return value="{tr('Your own player: to sell him, F6 and then S.')}" /></q:if>
<q:if condition="{r[0] == len(clubs)}">
<q:call function="list_player" args="{-1}, {q}, {0}" />
<q:set name="bidding" value="{len(market) - 1}" />
<q:return value="{''}" />
</q:if>
<q:if condition="{clubs[r[0]].manager > 0}"><q:return value="{tr('A manager\'s player: only if he puts him on the list.')}" /></q:if>
<q:set name="at" value="{open_auction(r[0], q)}" />
<q:if condition="{at < 0}"><q:return value="{clubs[r[0]].name + ' ' + tr('sells no one: it would have fewer than sixteen.')}" /></q:if>
<q:set name="bidding" value="{at}" />
<q:return value="{''}" />
</q:function>
<!-- ===== F6: the transfer list ===== -->
<qg:scene name="market" width="640" height="400" background="#0000a8">
<qg:hud x="8" y="4" box="frame" font="assets/DejaVuSansMono.ttf" size="12">
<qg:text value="{tr('TRANSFER LIST') + ' - ' + my_club().name + ' ' + tr('Balance:') + ' ' + money(my_club().balance) + ' ' + str(len(my_club().players)) + ' ' + tr('players')}" color="#ffff55" />
</qg:hud>
<qg:hud x="25" y="46" font="assets/DejaVuSansMono.ttf" size="12">
<qg:text value="{fit(tr('Name'), 16) + fit(tr('Pos'), 4) + fit(tr('Str'), 4, 'right') + ' ' + fit(tr('Club'), 6) + fit(tr('Fee'), 13, 'right') + fit(tr('Wage'), 10, 'right') + fit(tr('Best bid'), 10, 'right') + fit(tr('Left'), 4, 'right')}" color="#55ffff" />
</qg:hud>
<qg:menu x="8" y="64" source="{upto(len(market))}" as="ei" rows="14" font="assets/DejaVuSansMono.ttf" size="12" box="frame">
<qg:button label="{market_row(market[ei])}"><q:set name="bidding" value="{ei}" /><qg:goto-scene name="bid" /></qg:button>
</qg:menu>
<qg:hud x="8" y="364" font="assets/DejaVuSansMono.ttf" size="12">
<qg:text value="{(tr('Nobody is on the list yet: it fills as the rounds go.') + ' ') if len(market) == 0 else ''}" color="#aaaaaa" />
<qg:text value="{'[ENTER] ' + tr('Bid') + ' [S] ' + tr('Sell a player') + ' ✓ ' + tr('your bid') + ' [ESC] ' + tr('Back')}" color="#55ffff" />
</qg:hud>
<qg:on-input action="sell"><qg:goto-scene name="sell" /></qg:on-input>
<qg:on-input action="cancel"><qg:goto-scene name="dashboard" /></qg:on-input>
</qg:scene>
<!-- a bid: the wage (a share of what he asks, 100% to 300%), paid at once or in instalments; his answer, as
likely as the design's sum says, with what weighed most; or, from a computer's club, a loan -->
<qg:scene name="bid" width="640" height="400" background="#0000a8">
<q:set name="pct" value="120" type="number" />
<q:set name="plan" value="cash" type="string" />
<q:set name="e" value="{market[bidding]}" />
<q:set name="p" value="{player_of(market[bidding])}" />
<qg:hud x="8" y="8" box="frame" font="assets/DejaVuSansMono.ttf" size="13">
<qg:text value="{p.name + ' ' + positions(p) + ' ' + tr('Str') + ' ' + str(p.strength) + ' ' + (marks(p) if not p.genius else marks(p)[1:]) + ' ' + tr('Form') + ' ' + str(p.form)}" color="#ffff55" />
<qg:text value="{(clubs[e.seller].name + ' (' + div_name(clubs[e.seller].div) + ')') if e.seller >= 0 else tr('A free agent')}" />
<qg:text value="{fit(tr('Fee:'), 22) + (money(e.fee) if e.fee > 0 else tr('none'))}" />
<qg:text value="{fit(tr('He asks a month:'), 22) + money(e.wage)}" />
<qg:text value="{fit(tr('Bids so far:'), 22) + str(len(e.bids)) + ' ' + tr('rounds left:') + ' ' + str(e.left)}" />
<qg:text value="{fit(tr('Your offer a month:'), 22) + money(round(e.wage * pct / 100)) + ' (' + str(pct) + '%)'}" color="#55ffff" />
<qg:text value="{fit(tr('Paid:'), 22) + (tr('all at once') if plan == 'cash' else tr('30% now, 7 months of 10%')) + ' [I]'}" color="#55ffff" />
<qg:text value="{fit(tr('He would come:'), 22) + str(acceptance(p, e.seller, managers[turn].club, round(e.wage * pct / 100)).chance) + '% (' + acceptance(p, e.seller, managers[turn].club, round(e.wage * pct / 100)).why + ')'}" color="#ffff55" />
<qg:text value="{('⚠ ' + tr('In crisis, the club cannot buy')) if crisis(my_club()) >= 2 else (('⚠ ' + tr('Not enough money for that')) if my_club().balance < (e.fee if plan == 'cash' else 0.3 * e.fee) else '')}" color="#ff5555" />
</qg:hud>
<qg:menu x="8" y="230" font="assets/DejaVuSansMono.ttf" size="13" box="frame">
<qg:slider label="Wage %" bind="pct" min="100" max="300" step="5" />
<qg:button label="Make the bid"><q:call function="place_bid" args="{market[bidding]}, {managers[turn].club}, {round(e.wage * pct / 100)}, {plan}" /><qg:goto-scene name="market" /></qg:button>
<qg:button label="{tr('Borrow him for the season') + ' (' + money(0.1 * e.fee) + ')'}" if="{e.seller >= 0 and clubs[e.seller].manager == 0 and my_club().balance >= 0.1 * e.fee}"><q:call function="borrow" args="{bidding}, {managers[turn].club}" /><qg:goto-scene name="market" /></qg:button>
<qg:button label="Back"><qg:goto-scene name="market" /></qg:button>
</qg:menu>
<qg:on-input action="plan"><q:set name="plan" value="{'parts' if plan == 'cash' else 'cash'}" /></qg:on-input>
<qg:on-input action="cancel"><qg:goto-scene name="market" /></qg:on-input>
</qg:scene>
<!-- selling: a player of one's own on the list at the price asked (a share of his worth: cheaper, more clubs bid) or
off it; sixteen stay at least. The bids are the other clubs' for three rounds; the best wage he takes, he goes -->
<qg:scene name="sell" width="640" height="400" background="#0000a8">
<qg:hud x="8" y="8" font="assets/DejaVuSansMono.ttf" size="12">
<qg:text value="{tr('SELL') + ' - ' + my_club().name + ' ' + tr('Enter: on the list, or off it')}" color="#ffff55" />
</qg:hud>
<qg:menu x="8" y="26" font="assets/DejaVuSansMono.ttf" size="12">
<qg:slider label="Asking price %" bind="asking" min="50" max="150" step="10" />
</qg:menu>
<qg:hud x="8" y="48" font="assets/DejaVuSansMono.ttf" size="12">
<qg:text value="{' # ' + fit(tr('Name'), 18) + fit(tr('Pos'), 12) + fit(tr('Str'), 3, 'right') + ' ' + fit(tr('Worth'), 12, 'right') + fit(tr('Asking'), 12, 'right') + ' ' + tr('Listed')}" color="#55ffff" />
</qg:hud>
<qg:menu x="8" y="66" source="{my_club().players}" as="p" rows="16" font="assets/DejaVuSansMono.ttf" size="12">
<qg:button label="{fit(p.num, 3, 'right') + ' ' + fit(p.name, 18) + fit(positions(p), 12) + fit(p.strength, 3, 'right') + ' ' + fit(money(value_of(p)), 12, 'right') + fit(money(listing_fee(p)), 12, 'right') + ' ' + (tr('ON THE LIST') + ' (' + str(bids_on(p.name)) + ' ' + tr('bids') + ')' if listed(p.name) else (tr('on loan') if p.loan >= 0 else ''))}">
<q:call function="toggle_listing" args="{p}" />
</qg:button>
</qg:menu>
<qg:hud x="8" y="364" font="assets/DejaVuSansMono.ttf" size="12">
<qg:text value="{'[ENTER] ' + tr('On the list / off it') + ' ' + tr('Cheaper: more clubs bid.') + ' [ESC] ' + tr('Back')}" color="#55ffff" />
</qg:hud>
<qg:on-input action="cancel"><qg:goto-scene name="market" /></qg:on-input>
</qg:scene>
<!-- what a player of one's own is asked for: listed, his fee; else the share of his worth on the slider -->
<q:function name="listing_fee" params="p">
<q:loop items="{market}" var="e"><q:if condition="{e.who == p.name}"><q:return value="{e.fee}" /></q:if></q:loop>
<q:return value="{round(value_of(p) * asking / 100 / 1000) * 1000}" />
</q:function>
<q:function name="bids_on" params="name">
<q:loop items="{market}" var="e"><q:if condition="{e.who == name}"><q:return value="{len(e.bids)}" /></q:if></q:loop>
<q:return value="{0}" />
</q:function>
<q:function name="toggle_listing" params="p">
<q:if condition="{listed(p.name)}">
<q:set name="kept" value="{[]}" />
<q:loop items="{market}" var="e"><q:if condition="{e.who != p.name}"><q:set name="kept" value="{kept + [e]}" /></q:if></q:loop>
<q:set name="market" value="{kept}" />
<q:elseif condition="{p.loan < 0 and len(my_club().players) > 16}"><q:call function="list_player" args="{managers[turn].club}, {p}, {value_of(p) * asking / 100}" /></q:elseif>
</q:if>
</q:function>
<!-- ===== F8: the history (decision 31) ===== -->
<qg:scene name="history" width="640" height="400" background="#0000a8">
<qg:hud x="8" y="6" box="frame" font="assets/DejaVuSansMono.ttf" size="11">
<qg:text value="{fit(tr('Season'), 6) + fit(div_name(1), 15) + fit(div_name(2), 15) + fit(div_name(3), 15) + fit(div_name(4), 15) + tr('PANDA CUP')}" color="#ffff55" />
<qg:text value="{tr('No season has ended yet.') if len(honours) == 0 else ''}" color="#aaaaaa" />
<qg:table source="{honours[0:6]}" as="h">
<qg:column value="{str(h.season)}" width="6" color="#55ffff" />
<qg:column value="{h.champions[0]}" width="15" />
<qg:column value="{h.champions[1]}" width="15" />
<qg:column value="{h.champions[2]}" width="15" />
<qg:column value="{h.champions[3]}" width="15" />
<qg:column value="{h.cup}" width="14" color="#55ff55" />
</qg:table>
</qg:hud>
<qg:hud x="8" y="166" font="assets/DejaVuSansMono.ttf" size="11">
<qg:text value="{'═══ ' + tr('THE MANAGERS') + ' ═══ [ENTER] ' + tr('the whole career')}" color="#ffff55" />
<qg:text value="{' ' + fit(tr('Name'), 13) + fit(tr('Seas'), 5, 'right') + fit(tr('Pts'), 6, 'right') + fit(tr('Pts/m'), 6, 'right') + fit(tr('Tit'), 4, 'right') + fit(tr('Cup'), 4, 'right') + fit('↑', 3, 'right') + fit('↓', 3, 'right') + fit(tr('Sack'), 5, 'right') + ' ' + tr('Clubs')}" color="#55ffff" />
</qg:hud>
<qg:menu x="8" y="198" source="{upto(len(managers_ranking()))}" as="k" rows="3" font="assets/DejaVuSansMono.ttf" size="11" box="frame">
<qg:button label="{ranking_row(managers_ranking()[k])}"><q:set name="seen_career" value="{k}" /><qg:goto-scene name="career" /></qg:button>
</qg:menu>
<qg:hud x="8" y="290" box="frame" font="assets/DejaVuSansMono.ttf" size="11">
<qg:text value="{'═══ ' + tr('TOP SCORERS OF ALL TIME') + ' ═══'}" color="#ffff55" />
<qg:table source="{all_time_scorers(4)}" as="t">
<qg:column value="{t.goals}" width="4" align="right" color="#ffff55" />
<qg:column value="{t.name}" width="18" color="{'#ff55ff' if t.genius else '#ffffff'}" />
<qg:column value="{t.club}" width="5" />
</qg:table>
</qg:hud>
<qg:hud x="480" y="300" font="assets/DejaVuSansMono.ttf" size="12">
<qg:text value="{'[ESC] ' + tr('Back')}" color="#55ffff" />
</qg:hud>
<qg:on-input action="cancel"><qg:goto-scene name="dashboard" /></qg:on-input>
</qg:scene>
<!-- the managers, in play and retired, by titles and cups, then points a match -->
<q:function name="managers_ranking">
<q:set name="all" value="{managers + retired}" />
<q:set name="keys" value="{[]}" />
<q:loop var="k" from="0" to="{len(all) - 1}"><q:set name="keys" value="{keys + [[all[k].career.titles + cups_of(all[k].career), all[k].career.points / max(1, all[k].career.matches), 100 - k]]}" /></q:loop>
<q:set name="out" value="{[]}" />
<q:loop items="{sort(keys, true)}" var="key"><q:set name="out" value="{out + [all[100 - key[2]]]}" /></q:loop>
<q:return value="{out}" />
</q:function>
<q:function name="cups_of" params="career">
<q:return value="{career.cups if 'cups' in career else 0}" />
</q:function>
<!-- a manager's line of the ranking: seasons, league points and a match, titles, cups, up, down, sacked, the clubs -->
<q:function name="ranking_row" params="r">
<q:set name="c" value="{r.career}" />
<q:return value="{fit(r.name, 13) + fit(str(c.seasons), 5, 'right') + fit(str(c.points), 6, 'right') + fit(r1(c.points / max(1, c.matches)), 6, 'right') + fit(str(c.titles), 4, 'right') + fit(str(cups_of(c)), 4, 'right') + fit(str(c.ups), 3, 'right') + fit(str(c.downs), 3, 'right') + fit(str(c.sacked), 5, 'right') + ' ' + fit(join(c.clubs, ', '), 30)}" />
</q:function>
<!-- ===== A manager's whole career: each club, its seasons, what was won there and how it ended ===== -->
<qg:scene name="career" width="640" height="400" background="#0000a8">
<q:set name="r" value="{managers_ranking()[max(0, seen_career)]}" />
<qg:hud x="8" y="8" box="frame" font="assets/DejaVuSansMono.ttf" size="12">
<qg:text value="{tr('CAREER') + ' - ' + r.name}" color="#ffff55" />
<qg:text value="{str(r.career.seasons) + ' ' + tr('seasons') + ', ' + str(r.career.matches) + ' ' + tr('matches') + ', ' + str(r.career.points) + ' ' + tr('points') + ' (' + r1(r.career.points / max(1, r.career.matches)) + ' ' + tr('a match') + ')'}" />
<qg:text value="{str(r.career.titles) + ' ' + tr('titles') + ', ' + str(cups_of(r.career)) + ' ' + tr('cups') + ', ' + str(r.career.ups) + ' ↑, ' + str(r.career.downs) + ' ↓, ' + str(r.career.sacked) + ' ' + tr('times sacked')}" />
</qg:hud>
<qg:hud x="8" y="80" box="frame" font="assets/DejaVuSansMono.ttf" size="12">
<qg:text value="{fit(tr('Club'), 15) + fit(tr('Seasons'), 12) + fit(tr('Div'), 6) + fit(tr('Tit'), 4, 'right') + fit(tr('Cup'), 4, 'right') + fit('↑', 3, 'right') + fit('↓', 3, 'right') + ' ' + tr('How it ended')}" color="#55ffff" />
<qg:table source="{r.career.stints if 'stints' in r.career else []}" as="st">
<qg:column value="{fit(st.club, 15) + fit(str(st.first) + '-' + str(st.last), 12) + fit(str(st.div) + 'ª', 6) + fit(str(st.titles), 4, 'right') + fit(str(st.cups), 4, 'right') + fit(str(st.ups), 3, 'right') + fit(str(st.downs), 3, 'right') + ' ' + stint_end(st)}" width="84" color="{'#ff5555' if st.how == 'sacked' else ('#ffff55' if st.how == '' else '#ffffff')}" />
</qg:table>
<qg:text value="{fit(tr('Clubs:') + ' ' + join(r.career.clubs, ', '), 84)}" color="#aaaaaa" />
</qg:hud>
<qg:hud x="8" y="372" font="assets/DejaVuSansMono.ttf" size="12">
<qg:text value="{'[ESC] ' + tr('Back')}" color="#55ffff" />
</qg:hud>
<qg:on-input action="cancel"><qg:goto-scene name="history" /></qg:on-input>
</qg:scene>
<q:function name="stint_end" params="st">
<q:return value="{ {'': tr('still there'), 'left': tr('left for another club'), 'sacked': tr('sacked'), 'retired': tr('ended the career')}[st.how] }" />
</q:function>
<!-- the goals of all time: the seasons ended and this one's -->
<q:function name="all_time_scorers" params="n">
<q:set name="keys" value="{[]}" />
<q:loop var="ci" from="0" to="{len(clubs) - 1}">
<q:loop var="pk" from="0" to="{len(clubs[ci].players) - 1}">
<q:set name="q" value="{clubs[ci].players[pk]}" />
<q:if condition="{q.total + q.goals > 0}"><q:set name="keys" value="{keys + [[q.total + q.goals, 100000 - ci * 100 - pk]]}" /></q:if>
</q:loop>
</q:loop>
<q:set name="out" value="{[]}" />
<q:loop items="{sort(keys, true)[0:n]}" var="key">
<q:set name="at" value="{int(100000 - key[1])}" />
<q:set name="his" value="{clubs[int(floor(at / 100))]}" />
<q:set name="who" value="{his.players[at % 100]}" />
<q:set name="out" value="{out + [ {'name': who.name, 'goals': key[0], 'genius': who.genius, 'club': his.short} ]}" />
</q:loop>
<q:return value="{out}" />
</q:function>
<!-- ===== F5: finances ===== -->
<qg:scene name="finances" width="640" height="400" background="#0000a8">
<q:set name="g" value="{next_match(managers[turn].club)}" />
<q:set name="opp" value="{g[1] if g[0] == managers[turn].club else g[0]}" />
<qg:hud x="8" y="8" box="frame" font="assets/DejaVuSansMono.ttf" size="12">
<qg:text value="{tr('FINANCES') + ' - ' + my_club().name}" color="#ffff55" />
<qg:text value="{fit(tr('Balance:'), 24) + fit(money(my_club().balance), 14, 'right')}" color="{'#55ff55' if my_club().balance >= 0 else '#ff5555'}" />
<qg:text value="{fit(tr('Wages a month:'), 24) + fit(money(payroll(my_club())), 14, 'right')}" />
<qg:text value="{fit(tr('Last gate:'), 24) + fit(money(my_club().gate), 14, 'right')}" />
<qg:text value="{fit(tr('TV a month:'), 24) + fit(money(tv_of(my_club())), 14, 'right')}" />
<qg:text value="{fit(tr('Sponsor a month:'), 24) + fit(money(sponsor_of(my_club(), position_of(managers[turn].club) - 1)), 14, 'right')}" />
<qg:text value="{fit(tr('Costs a month:'), 24) + fit(money(costs_of(my_club()) + instalments_of(my_club())), 14, 'right')}" />
<qg:text value="{fit(tr('Prizes won:'), 24) + fit(money(my_club().prizes if 'prizes' in my_club() else 0), 14, 'right')}" color="#ffaa00" />
<qg:text value="{fit(tr('Ticket:'), 24) + fit(money(my_club().ticket), 14, 'right')}" color="#ffff55" />
<qg:text value="{fit(tr('Crowd at this price:'), 24) + fit(thousands(crowd_for(my_club(), position_of(opp) - 1, position_of(managers[turn].club) - 1, 0.85), '.') + '-' + thousands(crowd_for(my_club(), position_of(opp) - 1, position_of(managers[turn].club) - 1, 1.15), '.'), 14, 'right')}" color="#ffff55" />
</qg:hud>
<qg:hud x="340" y="8" box="frame" font="assets/DejaVuSansMono.ttf" size="12">
<qg:text value="{tr('THE GROUND') + ': ' + thousands(my_club().capacity, '.') + ' ' + tr('seats')}" color="#ffff55" />
<qg:text value="{fit(tr('Health:'), 9) + blocks(my_club().health, 10) + ' ' + str(round(my_club().health)) + '%'}" color="{'#55ff55' if my_club().health >= 70 else ('#ffff55' if my_club().health > 60 else '#ff5555')}" />
<qg:text value="{health_word(my_club().health) + ', ' + tr('gate') + ' ' + str(round(gate_share(my_club()) * 100)) + '%'}" />
<qg:text value="{(tr('Works:') + ' +' + thousands(my_club().adding, '.') + ' ' + tr('seats') + ', ' + str(my_club().works) + ' ' + tr('rounds')) if my_club().works > 0 else tr('No works going on')}" />
<qg:text value="{('✓ ' + tr('It can grow: a third at once,')) if expand_block(my_club()) == '' else ('✗ ' + expand_block(my_club()))}" color="{'#55ff55' if expand_block(my_club()) == '' else '#ff5555'}" />
<qg:text value="{tr('the rest in seven months.') if expand_block(my_club()) == '' else crisis_text(my_club())}" color="{'#55ff55' if expand_block(my_club()) == '' else '#ff5555'}" />
<qg:text value="{tr('THE CLUB')}" color="#ffff55" />
<qg:text value="{tr('Training center:') + ' ' + tr('level') + ' ' + str(my_club().training) + '/5'}" />
<qg:text value="{tr('Youth academy:') + ' ' + tr('level') + ' ' + str(my_club().academy) + '/5'}" />
<qg:text value="{tr('They cost a month:') + ' ' + money(upkeep(my_club()))}" color="#aaaaaa" />
<qg:text value="{tr('Training: a reserve grows') + ' ' + str(10 + 5 * my_club().training) + '% ' + tr('of the months.')}" color="#55ffff" />
<qg:text value="{tr('Academy: youths every season.')}" color="#55ffff" />
</qg:hud>
<q:set name="note" value="" type="string" />
<qg:menu x="8" y="258" columns="2" font="assets/DejaVuSansMono.ttf" size="12">
<qg:button label="{tr('Ticket') + ' +$1'}"><q:call function="ticket" args="{my_club()}, {1}" /></qg:button>
<qg:button label="{tr('Ticket') + ' -$1'}"><q:call function="ticket" args="{my_club()}, {-1}" /></qg:button>
<qg:button label="{tr('Maintenance') + ' (' + money(maintenance_cost(my_club())) + ')'}" if="{my_club().health < 100}"><q:set name="note" value="{maintain(my_club())}" /></qg:button>
<qg:button label="{'+1.000 ' + tr('seats') + ' (' + money(expansion_cost(my_club(), false)) + ')'}" if="{my_club().works == 0}"><q:set name="note" value="{expand(my_club(), false)}" /></qg:button>
<qg:button label="{'+' + thousands(expansion_seats(my_club(), true), '.') + ' ' + tr('seats') + ' (' + money(expansion_cost(my_club(), true)) + ')'}" if="{my_club().works == 0 and expansion_seats(my_club(), true) > 1000}"><q:set name="note" value="{expand(my_club(), true)}" /></qg:button>
<qg:button label="{tr('Training center') + ' → ' + str(my_club().training + 1) + ' (' + money(training_cost(my_club())) + ')'}" if="{my_club().training < 5}"><q:set name="note" value="{build(my_club(), 'training')}" /></qg:button>
<qg:button label="{tr('Youth academy') + ' → ' + str(my_club().academy + 1) + ' (' + money(academy_cost(my_club())) + ')'}" if="{my_club().academy < 5}"><q:set name="note" value="{build(my_club(), 'academy')}" /></qg:button>
<qg:button label="Back"><qg:goto-scene name="dashboard" /></qg:button>
</qg:menu>
<qg:hud x="8" y="374" font="assets/DejaVuSansMono.ttf" size="12">
<qg:text value="{note if note != '' else tr('A dearer ticket keeps people away; a full ground lifts the side.')}" color="{'#ff5555' if note != '' else '#55ffff'}" />
</qg:hud>
<qg:on-input action="cancel"><qg:goto-scene name="dashboard" /></qg:on-input>
</qg:scene>
<!-- ===== The live round: every match of it in the same minute ===== -->
<!-- seeded by the world, the season and the round: the same game from the same world, and each round its own luck -->
<qg:scene name="round" width="640" height="400" background="#0000a8" seed="{world * 1000 + (season - 2026) * 40 + matchday * 2 + (0 if today_kind == 'league' else 1)}">
<q:set name="minute" value="0" type="number" />
<q:set name="clock" value="0" type="number" /> <!-- ticks towards the next minute -->
<q:set name="pace" value="2" type="number" /> <!-- 1x 2x 4x 8x 16x -->
<q:set name="flash" value="0" type="number" />
<q:set name="stopped" value="false" type="boolean" />
<q:set name="timeouts" value="[3, 3, 3, 3]" type="array" />
<q:set name="pending" value="[]" type="array" /> <!-- a manager's automatic change, three seconds to undo it: [match, side, reaction, formation, style before] -->
<q:set name="subbing" value="false" type="boolean" /> <!-- in a pause, F1: a substitution being chosen -->
<q:set name="leaving" value="-1" type="number" /> <!-- who comes off: an index of the squad, -1 not chosen yet -->
<q:set name="scored" value="0" type="number" />
<q:set name="live" value="{kickoff()}" type="array" /> <!-- the round's matches, as they go -->
<q:set name="before" value="{table_order(clubs[managers[0].club].div)}" type="array" /> <!-- the first manager's division's table as the round starts -->
<q:set name="focus" value="{match_of(managers[0].club)}" type="number" /> <!-- the match in front -->
<qg:timer after="1"><qg:play sound="whistle" /></qg:timer>
<!-- a minute every 30 ticks at 1x: the match in 45 seconds; 16x in under three -->
<qg:timer every="1">
<q:if condition="{not stopped and minute < 90}">
<q:set name="clock" value="{clock + pace}" />
<q:loop var="k" from="1" to="{floor(clock / 30)}">
<q:if condition="{minute < 90}">
<q:set name="minute" value="{minute + 1}" />
<q:loop items="{live}" var="m">
<q:set name="scored" value="{play_minute(m, minute)}" />
<q:if condition="{scored > 0}"><q:set name="flash" value="{90}" /><qg:play sound="goal" /></q:if>
</q:loop>
<q:call function="reactions" />
</q:if>
</q:loop>
<q:set name="clock" value="{clock % 30}" />
<q:if condition="{minute >= 90}">
<qg:play sound="final" />
<qg:after seconds="2"><q:if condition="{today_kind == 'league'}"><q:call function="finish_round" args="{live}" /><q:else><q:call function="finish_cup" args="{live}" /></q:else></q:if><qg:goto-scene name="results" /></qg:after>
</q:if>
</q:if>
<q:if condition="{flash > 0}"><q:set name="flash" value="{flash - 1}" /></q:if>
</qg:timer>
<qg:hud x="8" y="8" box="frame" font="assets/DejaVuSansMono.ttf" size="14" if="{len(live) > 0}">
<qg:text value="{clubs[live[focus].home].name + ' ' + str(live[focus].hg) + ' x ' + str(live[focus].ag) + ' ' + clubs[live[focus].away].name}" color="#ffff55" />
<qg:text value="{'[' + repeat('█', floor(minute / 4.5)) + repeat('░', 20 - floor(minute / 4.5)) + '] ' + str(minute) + '′ - ' + tr('first half' if minute <= 45 else 'second half') + ' ' + str(pace) + 'x ' + tr('Crowd:') + ' ' + thousands(live[focus].crowd, '.')}" />
<qg:text value="{stats_line(live[focus])}" color="#55ffff" />
</qg:hud>
<qg:hud x="8" y="100" box="frame" font="assets/DejaVuSansMono.ttf" size="13" if="{len(live) > 0 and not subbing}">
<qg:table source="{live[focus].events[0:8]}" as="e">
<qg:column value="{event_text(e)}" width="44" color="{event_color(e)}" />
</qg:table>
</qg:hud>
<qg:hud x="400" y="100" box="frame" font="assets/DejaVuSansMono.ttf" size="13" if="{len(live) > 0}">
<qg:text value="{'═══ ' + tr('LIVE ROUND') + (' ' + str(matchday + 1) + '/18' if today_kind == 'league' else '') + ' ═══'}" color="#55ffff" />
<qg:text value="{div_name(clubs[live[focus].home].div) if today_kind == 'league' else (tr('PANDA CUP') + ', ' + cup_round_name(cup_round) if today_kind == 'cup' else tr('SUPERCUP'))}" color="#55ffff" />
</qg:hud>
<!-- the round's matches of the division shown: a click (or Enter) puts one in front; ► the managers' clubs -->
<qg:menu x="400" y="162" source="{shown_matches()}" as="j" font="assets/DejaVuSansMono.ttf" size="13" box="frame" if="{len(live) > 0 and not subbing}">
<qg:button label="{match_row(live[j], j == focus)}"><q:set name="focus" value="{j}" /></qg:button>
</qg:menu>
<qg:hud x="400" y="318" font="assets/DejaVuSansMono.ttf" size="12" if="{len(live) > 0}">
<qg:text value="{live_move()}" color="#55ff55" />
</qg:hud>
<!-- the elevens of the match in front, with their ratings as it goes: the round waits under the panel -->
<qg:menu x="8" y="284" font="assets/DejaVuSansMono.ttf" size="12" box="frame" if="{len(live) > 0 and not subbing and len(pending) == 0}">
<qg:button label="{'[F3] ' + tr('The teams')}"><q:set name="peek" value="{live[focus]}" /><qg:open-overlay name="teams" /></qg:button>
</qg:menu>
<!-- the substitution, in a pause: who comes off, then who comes on -->
<qg:hud x="8" y="100" font="assets/DejaVuSansMono.ttf" size="12" if="{subbing}">
<qg:text value="{tr('Who comes off?') if leaving < 0 else tr('Who comes on for') + ' ' + me_in_focus().players[leaving].name + '?'}" color="#ffff55" />
</qg:hud>
<qg:menu x="8" y="120" source="{pitch_of_me()}" as="k" if="{subbing and leaving < 0}" font="assets/DejaVuSansMono.ttf" size="12" box="frame">
<qg:button label="{sub_label(k)}"><q:set name="leaving" value="{k}" /></qg:button>
</qg:menu>
<qg:menu x="8" y="120" source="{bench_of_me()}" as="k" if="{subbing and leaving >= 0}" font="assets/DejaVuSansMono.ttf" size="12" box="frame">
<qg:button label="{sub_label(k)}"><q:call function="substitute" args="{k}" /></qg:button>
</qg:menu>
<qg:hud position="center" size="40" font="assets/DejaVuSansMono.ttf" if="{flash > 60 and ticks() % 20 < 12}">
<qg:text value="{tr('GOOOOOAL!')}" color="#ffff55" />
</qg:hud>
<qg:hud x="8" y="264" box="frame" font="assets/DejaVuSansMono.ttf" size="13" if="{len(pending) > 0 and not subbing}">
<qg:text value="{'⚙ ' + tr('AUTOMATIC CHANGE') + ' - ' + pending_text()}" color="#ffff55" />
<qg:text value="{'[C] ' + tr('Undo') + ' (' + tr('three seconds to undo it') + ')'}" color="#55ffff" />
</qg:hud>
<qg:hud x="8" y="340" font="assets/DejaVuSansMono.ttf" size="13" if="{len(live) > 0}">
<qg:text value="{('■ ' + tr('PAUSED') + ' - ' if stopped else '') + '[' + tr('SPACE') + '] ' + (tr('Go on') if stopped else ((tr('Time-out') + ' (' + str(timeouts[turn_of_focus()]) + ')') if len(managers) > 1 and turn_of_focus() >= 0 else tr('Pause'))) + ' [+/-] ' + tr('Speed') + ' [TAB] ' + tr('Another division' if today_kind == 'league' else 'Another match') + ' [F3] ' + tr('The teams')}" color="#55ffff" />
<qg:text value="{('[F1] ' + tr('Substitution') + ' (' + str(subs_of_me()) + '/3) [F2] ' + tr('Tactics of') + ' ' + me_in_focus().name) if stopped and me_in_focus() != null else ''}" color="#ffff55" />
</qg:hud>
<qg:on-input action="faster"><q:set name="pace" value="{min(16, pace * 2)}" /></qg:on-input>
<qg:on-input action="slower"><q:set name="pace" value="{max(1, pace / 2)}" /></qg:on-input>
<qg:on-input action="other"><q:if condition="{not subbing}"><q:set name="focus" value="{next_division_match(live, focus) if today_kind == 'league' else (focus + 1) % len(live)}" /></q:if></qg:on-input>
<qg:on-input action="pause">
<q:if condition="{stopped}"><q:set name="stopped" value="{false}" /><q:set name="subbing" value="{false}" /><q:set name="leaving" value="{-1}" />
<q:elseif condition="{len(managers) == 1}"><q:set name="stopped" value="{true}" /></q:elseif>
<q:elseif condition="{turn_of_focus() >= 0 and timeouts[turn_of_focus()] > 0}">
<q:set name="timeouts" index="{turn_of_focus()}" value="{timeouts[turn_of_focus()] - 1}" />
<q:set name="stopped" value="{true}" />
<qg:after seconds="30" name="timeout"><q:set name="stopped" value="{false}" /><q:set name="subbing" value="{false}" /><q:set name="leaving" value="{-1}" /></qg:after>
</q:elseif>
</q:if>
</qg:on-input>
<qg:on-input action="lineup"><q:if condition="{stopped and me_in_focus() != null and subs_of_me() < 3}"><q:set name="subbing" value="{true}" /><q:set name="leaving" value="{-1}" /></q:if></qg:on-input>
<qg:on-input action="cancel"><q:set name="subbing" value="{false}" /><q:set name="leaving" value="{-1}" /></qg:on-input>
<qg:on-input action="tactics"><q:if condition="{stopped and me_in_focus() != null and not subbing}"><q:call function="next_style" /></q:if></qg:on-input>
<qg:on-input action="undo"><q:if condition="{len(pending) > 0}"><q:call function="undo_pending" /></q:if></qg:on-input>
<qg:on-input action="squad"><q:if condition="{len(live) > 0 and not subbing}"><q:set name="peek" value="{live[focus]}" /><qg:open-overlay name="teams" /></q:if></qg:on-input>
<!-- the reactions due this minute, at once: a manager's with three seconds to undo it -->
<q:function name="reactions">
<q:loop var="j" from="0" to="{len(live) - 1}">
<q:set name="m" value="{live[j]}" />
<q:loop items="{['h', 'a']}" var="s">
<q:set name="k" value="{due(m, s, minute)}" />
<q:if condition="{k >= 0}">
<q:set name="done" value="{m[s].done + [k]}" />
<q:set name="m_side" value="{m[s]}" />
<q:set name="m_side.done" value="{done}" />
<q:set name="club" value="{clubs[m.home if s == 'h' else m.away]}" />
<q:if condition="{club.manager > 0}">
<q:set name="pending" value="{[j, s, k, club.formation, club.style]}" />
<qg:after seconds="3" name="reaction"><q:set name="pending" value="{[]}" /></qg:after>
</q:if>
<q:call function="change" args="{m}, {s}, {club.reactions[k].formation}, {club.reactions[k].style}" />
</q:if>
</q:loop>
</q:loop>
</q:function>
<!-- the manager's side of the match in front: 'h' or 'a' -->
<q:function name="my_side">
<q:return value="{'h' if live[focus].home == managers[turn_of_focus()].club else 'a'}" />
</q:function>
<!-- in a pause, F2: the next style for the manager's side of the match in front -->
<q:function name="next_style">
<q:call function="change" args="{live[focus]}, {my_side()}, {me_in_focus().formation}, {after_in(styles, me_in_focus().style)}" />
</q:function>
<!-- the substitutions made by the manager's side of the match in front -->
<q:function name="subs_of_me">
<q:if condition="{me_in_focus() == null}"><q:return value="{0}" /></q:if>
<q:return value="{live[focus][my_side()].subs}" />
</q:function>
<!-- who of the manager's club is on the pitch, and who on the bench may come on: indices of the squad -->
<q:function name="pitch_of_me">
<q:set name="out" value="{[]}" />
<q:if condition="{me_in_focus() == null}"><q:return value="{out}" /></q:if>
<q:set name="squad" value="{me_in_focus().players}" />
<q:loop var="k" from="0" to="{len(squad) - 1}"><q:if condition="{squad[k].xi != '' and squad[k].off == ''}"><q:set name="out" value="{out + [k]}" /></q:if></q:loop>
<q:return value="{out}" />
</q:function>
<q:function name="bench_of_me">
<q:set name="out" value="{[]}" />
<q:if condition="{me_in_focus() == null}"><q:return value="{out}" /></q:if>
<q:set name="squad" value="{me_in_focus().players}" />
<q:loop var="k" from="0" to="{len(squad) - 1}"><q:if condition="{squad[k].bench and squad[k].xi == '' and squad[k].off == '' and available(squad[k])}"><q:set name="out" value="{out + [k]}" /></q:if></q:loop>
<q:return value="{out}" />
</q:function>
<q:function name="sub_label" params="k">
<q:set name="p" value="{me_in_focus().players[k]}" />
<q:return value="{fit(tr(p.xi) if p.xi != '' else tr(p.pos), 4) + fit(p.name, 17) + fit(p.strength, 3, 'right') + ' ' + tr('Form') + ' ' + fit(p.form, 3, 'right') + (' ▮' if p.booked else '')}" />
</q:function>
<!-- F1, then the two choices: the substitution made, the side as it stands now -->
<q:function name="substitute" params="k">
<q:set name="m" value="{live[focus]}" />
<q:set name="s" value="{my_side()}" />
<q:if condition="{m[s].subs < 3}">
<q:call function="bring_on" args="{m}, {s}, {me_in_focus().players[leaving]}, {me_in_focus().players[k]}, {minute}" />
<q:call function="refresh" args="{m}, {s}" />
</q:if>
<q:set name="subbing" value="{false}" />
<q:set name="leaving" value="{-1}" />
</q:function>
<!-- C: the automatic change undone, the tactics as they were -->
<q:function name="undo_pending">
<q:call function="change" args="{live[pending[0]]}, {pending[1]}, {pending[3]}, {pending[4]}" />
<q:set name="pending" value="{[]}" />
</q:function>
<q:function name="pending_text">
<q:set name="m" value="{live[pending[0]]}" />
<q:set name="club" value="{clubs[m.home if pending[1] == 'h' else m.away]}" />
<q:set name="r" value="{club.reactions[pending[2]]}" />
<q:return value="{club.short + ' ' + tr(r.when) + ', ' + str(r.after) + '′: ' + r.formation + ' ' + tr(r.style)}" />
</q:function>
<q:function name="match_of" params="club_index">
<q:loop var="k" from="0" to="{len(live) - 1}">
<q:if condition="{live[k].home == club_index or live[k].away == club_index}"><q:return value="{k}" /></q:if>
</q:loop>
<q:return value="{0}" />
</q:function>
<!-- the manager whose club plays the match in front (-1: none) -->
<q:function name="turn_of_focus">
<q:loop var="k" from="0" to="{len(managers) - 1}">
<q:if condition="{live[focus].home == managers[k].club or live[focus].away == managers[k].club}"><q:return value="{k}" /></q:if>
</q:loop>
<q:return value="{-1}" />
</q:function>
<q:function name="me_in_focus">
<q:if condition="{turn_of_focus() >= 0}"><q:return value="{clubs[managers[turn_of_focus()].club]}" /></q:if>
<q:return value="{null}" />
</q:function>
<!-- the round's matches of a division, as they go -->
<!-- the matches shown on the right, as places in the round: the division of the match in front (a cup's eight) -->
<q:function name="shown_matches">
<q:set name="out" value="{[]}" />
<q:loop var="j" from="0" to="{len(live) - 1}">
<q:if condition="{(today_kind == 'league' and clubs[live[j].home].div == clubs[live[focus].home].div) or (today_kind != 'league' and floor(j / 8) == floor(focus / 8))}"><q:set name="out" value="{out + [j]}" /></q:if>
</q:loop>
<q:return value="{out}" />
</q:function>
<!-- a match's row on the right: the clubs (► a manager's), the score, a goal just scored -->
<q:function name="match_row" params="m, front">
<q:return value="{('►' if clubs[m.home].manager > 0 else ' ') + fit(clubs[m.home].short, 4) + fit(str(m.hg) + ' x ' + str(m.ag), 6, 'right') + ' ' + fit(clubs[m.away].short, 4) + ('◄' if clubs[m.away].manager > 0 else ' ') + (' ' + tr('GOAL') + '!' if len(m.events) > 0 and m.events[0].kind == 'goal' and flash > 0 else (' ◆' if front else ''))}" />
</q:function>
<!-- the table as it would be now: the first manager's club, where it was and where it is -->
<q:function name="live_move">
<q:if condition="{today_kind != 'league'}"><q:return value="{''}" /></q:if>
<q:set name="keys" value="{[]}" />
<q:loop items="{in_div(clubs[managers[0].club].div)}" var="i">
<q:set name="c" value="{clubs[i]}" />
<q:set name="pts" value="{c.points}" />
<q:set name="gd" value="{c.gf - c.ga}" />
<q:loop items="{live}" var="m">
<q:if condition="{m.home == i}"><q:set name="pts" value="{pts + (3 if m.hg > m.ag else (1 if m.hg == m.ag else 0))}" /><q:set name="gd" value="{gd + m.hg - m.ag}" /></q:if>
<q:if condition="{m.away == i}"><q:set name="pts" value="{pts + (3 if m.ag > m.hg else (1 if m.hg == m.ag else 0))}" /><q:set name="gd" value="{gd + m.ag - m.hg}" /></q:if>
</q:loop>
<q:set name="keys" value="{keys + [[pts, gd, 1000 - i]]}" />
</q:loop>
<q:set name="now" value="{[]}" />
<q:loop items="{sort(keys, true)}" var="k"><q:set name="now" value="{now + [1000 - k[2]]}" /></q:loop>
<q:set name="mine" value="{managers[0].club}" />
<q:set name="was_at" value="{place_in(before, mine) + 1}" />
<q:set name="now_at" value="{place_in(now, mine) + 1}" />
<q:return value="{clubs[mine].short + ': ' + tr('place') + ' ' + str(was_at) + ' → ' + str(now_at) + ' ' + ('↑' if now_at < was_at else ('↓' if now_at > was_at else '='))}" />
</q:function>
</qg:scene>
<!-- ===== The cup's draw, a tie at a time ===== -->
<qg:scene name="draw" width="640" height="400" background="#0000a8" seed="{world * 7 + season * 13 + cup_round}">
<q:set name="seen" value="0" type="number" />
<q:set name="ties" value="{next_games()}" type="array" />
<qg:timer every="25"><q:if condition="{seen < len(ties)}"><q:set name="seen" value="{seen + 1}" /><qg:play sound="whistle" /></q:if></qg:timer>
<qg:hud x="8" y="6" box="frame" font="assets/DejaVuSansMono.ttf" size="14">
<qg:text value="{'⚽ ' + tr('PANDA CUP') + ' - ' + cup_round_name(cup_round) + ' - ' + tr('THE DRAW')}" color="#ffff55" />
</qg:hud>
<qg:hud x="8" y="48" box="frame" font="assets/DejaVuSansMono.ttf" size="12">
<qg:table source="{upto(min(8, len(ties)))}" as="k">
<qg:column value="{tie_line(ties, k, seen)}" width="38" color="{'#ffff55' if k < seen and managed_in([ties[k]]) else '#ffffff'}" />
</qg:table>
</qg:hud>
<qg:hud x="324" y="48" box="frame" font="assets/DejaVuSansMono.ttf" size="12" if="{len(ties) > 8}">
<qg:table source="{upto(len(ties) - 8)}" as="k">
<qg:column value="{tie_line(ties, k + 8, seen)}" width="38" color="{'#ffff55' if k + 8 < seen and managed_in([ties[k + 8]]) else '#ffffff'}" />
</qg:table>
</qg:hud>
<qg:hud x="8" y="330" font="assets/DejaVuSansMono.ttf" size="12">
<qg:text value="{tr('The other clubs enter in the round of 32.') if cup_round == 0 else (tr('The club of the lower division plays at home.'))}" color="#aaaaaa" />
<qg:text value="{'[ENTER] ' + (tr('Show them all') if seen < len(ties) else tr('To the dashboard'))}" color="#55ffff" />
</qg:hud>
<qg:on-input action="select">
<q:if condition="{seen < len(ties)}"><q:set name="seen" value="{len(ties)}" />
<q:else><qg:goto-scene name="dashboard" /></q:else>
</q:if>
</qg:on-input>
</qg:scene>
<q:function name="tie_line" params="ties, k, seen">
<q:if condition="{k >= seen}"><q:return value="{fit(str(k + 1) + '.', 4) + ' ? x ?'}" /></q:if>
<q:set name="h" value="{clubs[ties[k][0]]}" />
<q:set name="a" value="{clubs[ties[k][1]]}" />
<q:return value="{fit(str(k + 1) + '.', 4) + fit(h.name + ' (' + str(h.div) + ')', 17) + 'x ' + a.name + ' (' + str(a.div) + ')'}" />
</q:function>
<!-- ===== After the round: the results with their scorers, the manager's match, the purses ===== -->
<qg:scene name="results" width="640" height="400" background="#0000a8">
<q:set name="shown" value="{clubs[managers[0].club].div if today_kind == 'league' else 1 + floor(max(0, match_of_last(managers[0].club)) / 5)}" type="number" />
<q:set name="rated" value="false" type="boolean" />
<qg:hud x="8" y="6" box="frame" font="assets/DejaVuSansMono.ttf" size="11" if="{rated and my_last() != null}">
<qg:text value="{'═══ ' + tr('RATINGS') + ' ═══ ' + clubs[my_last().home].name + ' ' + str(my_last().hg) + ' x ' + str(my_last().ag) + ' ' + clubs[my_last().away].name}" color="#55ffff" />
<qg:table source="{rating_rows(my_last())}" as="row">
<qg:column value="{row[0]}" width="30" color="{row[1]}" />
<qg:column value="{row[2]}" width="4" color="#aaaaaa" />
<qg:column value="{row[3]}" width="30" color="{row[4]}" />
</qg:table>
</qg:hud>
<qg:hud x="8" y="6" box="frame" font="assets/DejaVuSansMono.ttf" size="11" if="{not rated or my_last() == null}">
<qg:text value="{('✓ ' + tr('ROUND') + ' ' + str(matchday) + ' ' + tr('PLAYED') + ' - ' + div_name(shown)) if today_kind == 'league' else ('✓ ' + (tr('PANDA CUP') + ', ' + cup_round_name(cup_round - 1) if today_kind == 'cup' else tr('SUPERCUP')) + (' (' + str(shown) + '/' + str(ceil(len(last) / 5)) + ')' if len(last) > 5 else ''))}" color="#ffff55" />
<qg:table source="{last_of(shown) if today_kind == 'league' else last[(shown - 1) * 5:shown * 5]}" as="m">
<qg:column value="{clubs[m.home].name}" width="13" align="right" color="{'#ffff55' if clubs[m.home].manager > 0 else '#ffffff'}" />
<qg:column value="{str(m.hg) + ' x ' + str(m.ag) + (' p' if 'pens' in m else '')}" width="7" align="right" />
<qg:column value="{' ' + clubs[m.away].name}" width="14" color="{'#ffff55' if clubs[m.away].manager > 0 else '#ffffff'}" />
<qg:column value="{scorers_line(m)}" width="42" color="#aaaaaa" />
</qg:table>
</qg:hud>
<qg:hud x="8" y="126" box="frame" font="assets/DejaVuSansMono.ttf" size="12" if="{my_last() != null and not rated}">
<qg:text value="{'═══ ' + tr('YOUR MATCH') + ' ═══ ' + clubs[my_last().home].name + ' ' + str(my_last().hg) + ' x ' + str(my_last().ag) + ' ' + clubs[my_last().away].name}" color="#55ffff" />
<qg:text value="{tr('Crowd:') + ' ' + thousands(my_last().crowd, '.') + ' (' + str(round(my_last().fill * 100)) + '%) ' + tr('Gate:') + ' ' + money(clubs[my_last().home].gate) + ' (' + clubs[my_last().home].short + ')'}" />
<qg:text value="{fit(tr('Man of the match:') + ' ' + man_of_match(my_last()), 52) + ' ' + shots_line(my_last())}" color="#ffff55" />
<qg:text value="{fit(tr('Booked:') + ' ' + names_of(my_last(), 'card') + ' ' + tr('Sent off:') + ' ' + names_of(my_last(), 'red'), 84)}" />
<qg:text value="{fit(tr('Injured:') + ' ' + names_of(my_last(), 'knock') + ' ' + tr('Came on:') + ' ' + names_of(my_last(), 'sub'), 84)}" />
<qg:text value="{fit(tr('Out of the next match:') + ' ' + out_next(clubs[managers[0].club]), 84)}" color="#ff5555" />
</qg:hud>
<qg:hud x="8" y="256" box="frame" font="assets/DejaVuSansMono.ttf" size="12">
<qg:text value="{'═══ ' + tr('THE MANAGERS') + ' ═══'}" color="#55ffff" />
<qg:table source="{managers}" as="t">
<qg:column value="{t.name}" width="14" />
<qg:column value="{clubs[t.club].name}" width="16" />
<qg:column value="{str(position_of(t.club)) + '.'}" width="5" align="right" />
<qg:column value="{'+' + money(clubs[t.club].gate) if clubs[t.club].gate > 0 else ''}" width="13" align="right" color="#55ff55" />
<qg:column value="{'-' + money(clubs[t.club].wages)}" width="12" align="right" color="#ff5555" />
<qg:column value="{money(clubs[t.club].balance)}" width="14" align="right" color="{'#55ff55' if clubs[t.club].balance >= 0 else '#ff5555'}" />
</qg:table>
</qg:hud>
<qg:hud x="8" y="364" font="assets/DejaVuSansMono.ttf" size="13">
<qg:text value="{'[ENTER] ' + tr('End of the season' if matchday >= 18 else 'Next round') + ' [F7] ' + tr('Table') + ' [TAB] ' + tr('Another division' if today_kind == 'league' else 'More matches') + (' [R] ' + tr('Ratings') if my_last() != null else '')}" color="#55ffff" />
</qg:hud>
<qg:on-input action="other"><q:set name="rated" value="{false}" /><q:set name="shown" value="{shown % (4 if today_kind == 'league' else max(1, ceil(len(last) / 5))) + 1}" /></qg:on-input>
<qg:on-input action="ratings"><q:set name="rated" value="{not rated}" /></qg:on-input>
<qg:on-input action="standings"><qg:goto-scene name="standings" /></qg:on-input>
<qg:hud x="8" y="318" box="frame" font="assets/DejaVuSansMono.ttf" size="12" if="{len(jobs) > 0 and jobs[0].kind == 'sacked'}">
<qg:text value="{'✖ ' + tr('SACKED') + ': ' + managers[jobs[0].who].name}" color="#ff5555" />
</qg:hud>
<qg:on-input action="select"><q:call function="after_results" /></qg:on-input>
</qg:scene>
<!-- TAB in a league round: the next division's match, a manager's if it has one, else its first -->
<q:function name="next_division_match" params="live, focus">
<q:set name="d" value="{clubs[live[focus].home].div % 4 + 1}" />
<q:set name="first" value="{-1}" />
<q:loop var="k" from="0" to="{len(live) - 1}">
<q:if condition="{clubs[live[k].home].div == d}">
<q:if condition="{clubs[live[k].home].manager > 0 or clubs[live[k].away].manager > 0}"><q:return value="{k}" /></q:if>
<q:if condition="{first < 0}"><q:set name="first" value="{k}" /></q:if>
</q:if>
</q:loop>
<q:return value="{first if first >= 0 else focus}" />
</q:function>
<!-- where a club's match is in the last date's list (-1: it did not play) -->
<q:function name="match_of_last" params="club_index">
<q:loop var="k" from="0" to="{len(last) - 1}"><q:if condition="{last[k].home == club_index or last[k].away == club_index}"><q:return value="{k}" /></q:if></q:loop>
<q:return value="{-1}" />
</q:function>
<!-- the last round's matches of a division -->
<q:function name="last_of" params="d">
<q:set name="out" value="{[]}" />
<q:loop items="{last}" var="m"><q:if condition="{clubs[m.home].div == d}"><q:set name="out" value="{out + [m]}" /></q:if></q:loop>
<q:return value="{out}" />
</q:function>
<!-- a match's scorers, the home side's and the away side's: Silva 12', Souza 70' / Lima 33' -->
<q:function name="scorers_line" params="m">
<q:set name="mine" value="{[]}" />
<q:set name="theirs" value="{[]}" />
<q:loop items="{m.events}" var="e">
<q:if condition="{e.kind == 'goal' and e.club == clubs[m.home].short}"><q:set name="mine" value="{[e.who + ' ' + str(e.minute) + '′'] + mine}" /></q:if>
<q:if condition="{e.kind == 'goal' and e.club == clubs[m.away].short}"><q:set name="theirs" value="{[e.who + ' ' + str(e.minute) + '′'] + theirs}" /></q:if>
</q:loop>
<q:if condition="{len(mine) + len(theirs) == 0}"><q:return value="{''}" /></q:if>
<q:return value="{join(mine, ', ') + ' / ' + join(theirs, ', ')}" />
</q:function>
<!-- a match's ratings as rows: [home's line, its colour, '', away's line, its colour], the eleven then who came on -->
<q:function name="rating_rows" params="m">
<q:if condition="{not ('ratings' in m)}"><q:return value="{[[tr('No ratings for this match.'), '#aaaaaa', '', '', '#aaaaaa']]}" /></q:if>
<q:set name="sides" value="{[[], []]}" />
<q:loop items="{m.ratings}" var="x">
<q:set name="at" value="{0 if x.home else 1}" />
<q:set name="sides" index="{at}" value="{sides[at] + [[fit(tr(x.pos), 3) + ' ' + fit(x.who + (' ⇄' if x.on else ''), 20) + fit(r1(x.r), 5, 'right'), rating_color(x.r)]]}" />
</q:loop>
<q:set name="out" value="{[]}" />
<q:loop var="k" from="0" to="{max(len(sides[0]), len(sides[1])) - 1}">
<q:set name="a" value="{sides[0][k] if k < len(sides[0]) else ['', '#ffffff']}" />
<q:set name="b" value="{sides[1][k] if k < len(sides[1]) else ['', '#ffffff']}" />
<q:set name="out" value="{out + [[a[0], a[1], '', b[0], b[1]]]}" />
</q:loop>
<q:return value="{out}" />
</q:function>
<!-- the first manager's match of the last round (null: none) -->
<q:function name="my_last">
<q:loop items="{last}" var="m"><q:if condition="{m.home == managers[0].club or m.away == managers[0].club}"><q:return value="{m}" /></q:if></q:loop>
<q:return value="{null}" />
</q:function>
<!-- the man of the match: the best rating (of a match of a save before 2.2, who scored most in it) -->
<q:function name="man_of_match" params="m">
<q:if condition="{'motm' in m and m.motm != ''}"><q:return value="{m.motm}" /></q:if>
<q:set name="names" value="{[]}" />
<q:loop items="{m.events}" var="e"><q:if condition="{e.kind == 'goal'}"><q:set name="names" value="{names + [e.who + ' (' + e.club + ')']}" /></q:if></q:loop>
<q:if condition="{len(names) == 0}"><q:return value="{tr('the keepers')}" /></q:if>
<q:set name="best" value="{names[len(names) - 1]}" />
<q:loop items="{names}" var="who"><q:if condition="{times_in(names, who) > times_in(names, best)}"><q:set name="best" value="{who}" /></q:if></q:loop>
<q:return value="{best + ', ' + str(times_in(names, best)) + ' ' + tr('goals' if times_in(names, best) > 1 else 'goal')}" />
</q:function>
<!-- the first manager's players in a match's events of a kind: the booked, the sent off, the injured, who came on -->
<q:function name="names_of" params="m, kind">
<q:set name="short" value="{clubs[managers[0].club].short}" />
<q:set name="out" value="{[]}" />
<q:loop items="{m.events}" var="e"><q:if condition="{e.kind == kind and e.club == short}"><q:set name="out" value="{[e.who] + out}" /></q:if></q:loop>
<q:return value="{'-' if len(out) == 0 else join(out, ', ')}" />
</q:function>
<!-- who cannot play the next match, and why: ▮ suspended, ✚ injured (for so many matches) -->
<q:function name="out_next" params="club">
<q:set name="out" value="{[]}" />
<q:loop items="{club.players}" var="p">
<q:if condition="{p.suspended > 0}"><q:set name="out" value="{out + [p.name + ' ▮']}" /></q:if>
<q:if condition="{p.injured > 0}"><q:set name="out" value="{out + [p.name + ' ✚' + str(p.injured)]}" /></q:if>
</q:loop>
<q:return value="{'-' if len(out) == 0 else join(out, ', ')}" />
</q:function>
<!-- ===== The season's end ===== -->
<qg:scene name="season-end" width="640" height="400" background="#0000a8">
<qg:hud x="8" y="6" box="frame" font="assets/DejaVuSansMono.ttf" size="12">
<qg:text value="{'♛ ' + tr('CHAMPIONS') + ' ' + str(season) + ' ♛ ' + tr('and the prizes of each division')}" color="#ffff55" />
<qg:table source="{[1, 2, 3, 4]}" as="d">
<qg:column value="{fit(div_name(d) + ':', 15) + fit(clubs[table_order(d)[0]].name, 14) + ' ' + money(prize_of(d, 'champion'))}" width="43" color="#ffffff" />
<qg:column value="{manager_of(table_order(d)[0])}" width="40" color="#ffff55" />
</qg:table>
<qg:text value="{'✪ ' + tr('Top scorer') + ' / ✦ ' + tr('Best player') + ' (' + tr('mean rating, ten matches at least') + ')'}" color="#ffaa00" />
<qg:table source="{[1, 2, 3, 4]}" as="d">
<qg:column value="{fit(div_name(d) + ':', 15) + '✪ ' + prize_line(scorer_of_div(d), 'goals')}" width="43" color="#ffaa00" />
<qg:column value="{'✦ ' + prize_line(best_of_div(d), 'rating')}" width="40" color="#ffaa00" />
</qg:table>
<qg:text value="{tr('Each wins its club') + ' ' + money(prize_of(1, 'scorer')) + ' (' + div_name(1) + ') ' + tr('to') + ' ' + money(prize_of(4, 'scorer')) + ' (' + div_name(4) + '), ' + tr('and is worth a third more next season.')}" color="#aaaaaa" />
</qg:hud>
<qg:hud x="8" y="206" box="frame" font="assets/DejaVuSansMono.ttf" size="12">
<qg:text value="{'═══ ' + tr('UP AND DOWN') + ' ═══'}" color="#55ffff" />
<qg:text value="{'↑ ' + tr('to the') + ' ' + div_name(1) + ': ' + clubs[table_order(2)[0]].name + ', ' + clubs[table_order(2)[1]].name + ' ↓ ' + clubs[table_order(1)[8]].name + ', ' + clubs[table_order(1)[9]].name}" />
<qg:text value="{'↑ ' + tr('to the') + ' ' + div_name(2) + ': ' + clubs[table_order(3)[0]].name + ', ' + clubs[table_order(3)[1]].name + ' ↓ ' + clubs[table_order(2)[8]].name + ', ' + clubs[table_order(2)[9]].name}" />
<qg:text value="{'↑ ' + tr('to the') + ' ' + div_name(3) + ': ' + clubs[table_order(4)[0]].name + ', ' + clubs[table_order(4)[1]].name + ' ↓ ' + clubs[table_order(3)[8]].name + ', ' + clubs[table_order(3)[9]].name}" />
<qg:text value="{fate(managers[0].club)}" color="#ffff55" />
</qg:hud>
<qg:menu position="bottom-center" font="assets/DejaVuSansMono.ttf" size="14">
<qg:button label="New season"><q:call function="new_season" /><q:call function="after_results" /></qg:button>
<qg:button label="To the title"><qg:save slot="{slot}" /><qg:goto-scene name="title" /></qg:button>
</qg:menu>
</qg:scene>
<!-- what waits for a manager: sacked, the clubs that would have them (or the end of the career); or a bigger
club's offer (or staying) -->
<qg:scene name="job" width="640" height="400" background="#0000a8">
<q:set name="j" value="{jobs[0]}" />
<q:set name="t" value="{managers[j.who]}" />
<qg:hud x="16" y="16" box="frame" font="assets/DejaVuSansMono.ttf" size="15">
<qg:text value="{('✖ ' + tr('SACKED')) if j.kind == 'sacked' else ('✉ ' + tr('AN OFFER'))}" color="{'#ff5555' if j.kind == 'sacked' else '#ffff55'}" />
<qg:text value="{t.name + ', ' + clubs[t.club].name}" color="#ffffff" />
<qg:text value="{tr('The board has lost its patience.') if j.kind == 'sacked' else tr('Your work has been noticed.')}" />
<qg:text value="{(tr('These clubs would have you:') if len(j.clubs) > 0 else tr('No club would have you now.')) if j.kind == 'sacked' else tr('These clubs would have you:')}" color="#55ffff" />
</qg:hud>
<qg:menu x="16" y="170" source="{j.clubs}" as="ci" font="assets/DejaVuSansMono.ttf" size="14" box="frame">
<qg:button label="{tr('Take') + ' ' + clubs[ci].name + ' (' + div_name(clubs[ci].div) + ', ' + tr('expects') + ' ' + str(clubs[ci].expect) + 'º)'}">
<q:call function="take_job" args="{j.who}, {ci}" /><q:call function="next_job" args="{true}" />
</qg:button>
</qg:menu>
<qg:menu x="16" y="300" font="assets/DejaVuSansMono.ttf" size="14">
<qg:button label="{tr('Stay at') + ' ' + clubs[t.club].name}" if="{j.kind == 'offer'}"><q:call function="next_job" args="{true}" /></qg:button>
<qg:button label="End the career" if="{j.kind == 'sacked'}"><q:call function="end_career" args="{j.who}" /><q:call function="next_job" args="{false}" /></qg:button>
</qg:menu>
</qg:scene>
<!-- the job dealt with (`done`: still the first of the list; a career ended took its jobs with it): the next one, or
the game goes on (or ends, with no manager left) -->
<q:function name="next_job" params="done">
<q:if condition="{done and len(jobs) > 0}"><q:set name="jobs" value="{jobs[1:]}" /></q:if>
<q:if condition="{len(managers) == 0}"><qg:goto-scene name="career-over" />
<q:else><q:call function="after_results" /></q:else>
</q:if>
</q:function>
<!-- no manager left: the careers, and to the title -->
<qg:scene name="career-over" width="640" height="400" background="#0000a8">
<qg:hud position="center" box="frame" font="assets/DejaVuSansMono.ttf" size="14">
<qg:text value="{'═══ ' + tr('THE END OF THE CAREER') + ' ═══'}" color="#ffff55" />
<qg:table source="{retired}" as="r">
<qg:column value="{r.name}" width="14" />
<qg:column value="{str(r.career.seasons) + ' ' + tr('seasons') + ', ' + str(r.career.titles) + ' ' + tr('titles') + ', ' + str(r.career.ups) + ' ↑'}" width="30" />
<qg:column value="{join(r.career.clubs, ', ')}" width="30" color="#aaaaaa" />
</qg:table>
<qg:text value="{'[ENTER] ' + tr('To the title')}" color="#55ffff" />
</qg:hud>
<qg:on-input action="select"><q:call function="reset_game" /><qg:goto-scene name="title" /></qg:on-input>
</qg:scene>
<!-- a club's season in a line: its place, and up, down or staying -->
<q:function name="fate" params="club_index">
<q:set name="c" value="{clubs[club_index]}" />
<q:set name="at" value="{position_of(club_index)}" />
<q:set name="move" value="{tr('promoted!') if c.div > 1 and at <= 2 else (tr('relegated.') if c.div < 4 and at >= 9 else tr('stays in the') + ' ' + div_name(c.div) + '.')}" />
<q:return value="{c.name + ': ' + str(at) + 'º ' + tr('of the') + ' ' + div_name(c.div) + ', ' + move}" />
</q:function>
<!-- a prize's line: who, his club, his goals or his mean rating ('-' none) -->
<q:function name="prize_line" params="found, what">
<q:if condition="{found == null}"><q:return value="{'-'}" /></q:if>
<q:return value="{found[1].name + ' (' + (clubs[found[0]].short if found[0] >= 0 else tr('free')) + ', ' + (str(found[1].goals) + ' ' + tr('goals') if what == 'goals' else r1(mean_rating(found[1]))) + ')'}" />
</q:function>
<q:function name="manager_of" params="club_index">
<q:loop items="{managers}" var="t"><q:if condition="{t.club == club_index}"><q:return value="{tr('Manager:') + ' ' + t.name}" /></q:if></q:loop>
<q:return value="{''}" />
</q:function>
<!-- ===== Options ===== -->
<qg:scene name="options" width="640" height="400" background="#0000a8">
<qg:menu position="center" font="assets/DejaVuSansMono.ttf" size="14" box="frame">
<qg:slider label="Sounds" bus="effects" />
<qg:button label="{tr('Language') + ': ' + language()}"><qg:next-language /></qg:button>
<qg:button label="{tr('Full screen') + ': ' + tr('yes' if fullscreen() else 'no')}"><qg:fullscreen /></qg:button>
<qg:button label="Back"><qg:goto-scene name="title" /></qg:button>
</qg:menu>
</qg:scene>
<!-- ===== Portuguese and Spanish ===== -->
<qg:language lang="pt" name="Português">
<qg:phrase text=" ☻ P A N D A F O O T ☻ " as=" ☻ P A N D A F O O T ☻ " />
<qg:phrase text=" one manager, one league, the ball " as=" um técnico, uma liga, a bola " />
<qg:phrase text="New game" as="Novo jogo" />
<qg:phrase text="Continue" as="Continuar" />
<qg:phrase text="Options" as="Opções" />
<qg:phrase text="Quit" as="Sair" />
<qg:phrase text="What's new" as="Novidades" />
<qg:phrase text="Report a bug or suggest" as="Relatar bug ou sugerir" />
<qg:phrase text="REPORT A BUG OR SUGGEST" as="RELATAR UM BUG OU SUGERIR" />
<qg:phrase text="Every report is read. Thank you!" as="Todo relato é lido. Obrigado!" />
<qg:phrase text="Kind:" as="Tipo:" />
<qg:phrase text="Bug" as="Bug" />
<qg:phrase text="Suggestion" as="Sugestão" />
<qg:phrase text="What happened, or your idea:" as="O que aconteceu, ou a sua ideia:" />
<qg:phrase text="Attach my game:" as="Anexar o meu jogo:" />
<qg:phrase text="Send" as="Enviar" />
<qg:phrase text="Where:" as="Onde:" />
<qg:phrase text="Write a few words first." as="Escreva algumas palavras antes." />
<qg:phrase text="Sending..." as="Enviando..." />
<qg:phrase text="Kept: it goes when the beta's server is ready." as="Guardado: vai quando o servidor do beta estiver pronto." />
<qg:phrase text="Sent: report no." as="Enviado: relato nº" />
<qg:phrase text="Thank you!" as="Obrigado!" />
<qg:phrase text="The report was not accepted:" as="O relato não foi aceito:" />
<qg:phrase text="Not sent now" as="Não enviado agora" />
<qg:phrase text="it is kept and goes later." as="fica guardado e vai depois." />
<qg:phrase text="Waiting to be sent:" as="Esperando envio:" />
<qg:phrase text="[!] or F9 opens this on any screen." as="[!] ou F9 abre isto em qualquer tela." />
<qg:phrase text="Something else" as="Outra coisa" />
<qg:phrase text="Report" as="Relatar" />
<qg:phrase text="NEW GAME" as="NOVO JOGO" />
<qg:phrase text="Managers:" as="Técnicos:" />
<qg:phrase text="World no." as="Mundo nº" />
<qg:phrase text="Save in slot" as="Salvar no jogo" />
<qg:phrase text="Manager's name:" as="Nome do técnico:" />
<qg:phrase text="Draw the manager's club" as="Sortear o clube do técnico" />
<qg:phrase text="Each manager is given a club," as="Cada técnico recebe um clube," />
<qg:phrase text="drawn among the small clubs" as="sorteado entre os clubes pequenos" />
<qg:phrase text="of the 4th division." as="da 4ª divisão." />
<qg:phrase text="big" as="grande" />
<qg:phrase text="middle" as="médio" />
<qg:phrase text="small" as="pequeno" />
<qg:phrase text="Manager" as="Técnico" />
<qg:phrase text="Start the league" as="Começar o campeonato" />
<qg:phrase text="Back" as="Voltar" />
<qg:phrase text="Round" as="Rodada" />
<qg:phrase text="Balance:" as="Saldo:" />
<qg:phrase text="Place:" as="Posição:" />
<qg:phrase text="place" as="posição" />
<qg:phrase text="Form:" as="Forma:" />
<qg:phrase text="Morale:" as="Moral:" />
<qg:phrase text="Ground:" as="Estádio:" />
<qg:phrase text="seats" as="lugares" />
<qg:phrase text="NEXT MATCH" as="PRÓXIMO JOGO" />
<qg:phrase text="Panda League, round" as="Campeonato Panda, rodada" />
<qg:phrase text="home" as="em casa" />
<qg:phrase text="away" as="fora" />
<qg:phrase text="Objective:" as="Objetivo:" />
<qg:phrase text="The line-up is not complete (F1)" as="A escalação não está completa (F1)" />
<qg:phrase text="NEWS" as="NOTÍCIAS" />
<qg:phrase text="Line-up" as="Escalação" />
<qg:phrase text="Tactics" as="Táticas" />
<qg:phrase text="Squad" as="Elenco" />
<qg:phrase text="Finances" as="Finanças" />
<qg:phrase text="Table" as="Classificação" />
<qg:phrase text="Ready: next manager" as="Pronto: próximo técnico" />
<qg:phrase text="Play the round" as="Jogar a rodada" />
<qg:phrase text="Save and leave" as="Salvar e sair" />
<qg:phrase text="The Panda League kicks off:" as="Começa o Campeonato Panda:" />
<qg:phrase text="They say a phenomenon plays in the lower divisions..." as="Dizem que um fenômeno joga nas divisões de baixo..." />
<qg:phrase text="Rumour: the hidden phenomenon would be a goalkeeper." as="Boato: o fenômeno escondido seria um goleiro." />
<qg:phrase text="Rumour: the hidden phenomenon would be a defender." as="Boato: o fenômeno escondido seria um defensor." />
<qg:phrase text="Rumour: the hidden phenomenon would be a midfielder." as="Boato: o fenômeno escondido seria um meia." />
<qg:phrase text="Rumour: the hidden phenomenon would be a forward." as="Boato: o fenômeno escondido seria um atacante." />
<qg:phrase text="Revealed: the phenomenon plays for" as="Revelado: o fenômeno joga no" />
<qg:phrase text="W" as="V" />
<qg:phrase text="D" as="E" />
<qg:phrase text="L" as="D" />
<qg:phrase text="GK" as="GOL" />
<qg:phrase text="DF" as="DEF" />
<qg:phrase text="MF" as="MEI" />
<qg:phrase text="FW" as="ATA" />
<qg:phrase text="SQUAD" as="ELENCO" />
<qg:phrase text="players, wages" as="jogadores, folha" />
<qg:phrase text="/month" as="/mês" />
<qg:phrase text="Name" as="Nome" />
<qg:phrase text="Pos" as="Pos" />
<qg:phrase text="Str" as="For" />
<qg:phrase text="Traits" as="DNA" />
<qg:phrase text="Morale" as="Moral" />
<qg:phrase text="Form" as="Forma" />
<qg:phrase text="Goals" as="Gols" />
<qg:phrase text="Career" as="Carreira" />
<qg:phrase text="Not enough money: it costs" as="Dinheiro insuficiente: custa" />
<qg:phrase text="The works going on must end first." as="As obras em andamento precisam terminar antes." />
<qg:phrase text="The ground needs care first (health over 60)." as="O estádio precisa de cuidados antes (saúde acima de 60)." />
<qg:phrase text="Not enough money: a third at once is" as="Dinheiro insuficiente: o terço à vista é" />
<qg:phrase text="Works begin:" as="Começam as obras:" />
<qg:phrase text="Works: a third at once, the rest in seven months." as="Obras: um terço à vista, o resto em sete meses." />
<qg:phrase text="penalty" as="pênalti" />
<qg:phrase text="missed a penalty" as="perdeu um pênalti" />
<qg:phrase text="Penalty taker:" as="Batedor de pênalti:" />
<qg:phrase text="the best" as="o melhor" />
<qg:phrase text="tired in the eleven: rest them (F1)" as="cansados entre os titulares: descanse-os (F1)" />
<qg:phrase text="Bid for a player" as="Fazer lance por um jogador" />
<qg:phrase text="BID FOR A PLAYER OF" as="LANCE POR UM JOGADOR DO" />
<qg:phrase text="He goes on the transfer list: the other clubs may bid too, for three rounds." as="Ele vai para a lista de transferências: os outros clubes também podem dar lances, por três rodadas." />
<qg:phrase text="sells no one: it would have fewer than sixteen." as="não vende ninguém: ficaria com menos de dezesseis." />
<qg:phrase text="on the list" as="na lista" />
<qg:phrase text="Up for auction:" as="Em leilão:" />
<qg:phrase text="Fit" as="Est" />
<qg:phrase text="Salary" as="Salário" />
<qg:phrase text="Genius" as="Gênio" />
<qg:phrase text="Ace" as="Craque" />
<qg:phrase text="Finisher" as="Finalizador" />
<qg:phrase text="Set pieces" as="Cobrador" />
<qg:phrase text="Leader" as="Líder" />
<qg:phrase text="Star" as="Estrela" />
<qg:phrase text="Wall" as="Muralha" />
<qg:phrase text="LINE-UP" as="ESCALAÇÃO" />
<qg:phrase text="in the eleven:" as="titulares:" />
<qg:phrase text="Plays" as="No time" />
<qg:phrase text="ELEVEN" as="TITULAR" />
<qg:phrase text="bench" as="banco" />
<qg:phrase text="Attack" as="Ataque" />
<qg:phrase text="Defence" as="Defesa" />
<qg:phrase text="eleven fit to play, one of them in goal" as="11 titulares em condições, um deles no gol" />
<qg:phrase text="Eleven → bench → out" as="Titular → banco → fora" />
<qg:phrase text="Automatic" as="Automática" />
<qg:phrase text="TACTICS" as="TÁTICAS" />
<qg:phrase text="They tend to play:" as="Costuma jogar:" />
<qg:phrase text="Formation:" as="Formação:" />
<qg:phrase text="Style:" as="Estilo:" />
<qg:phrase text="AUTOMATIC CHANGES" as="REAÇÕES AUTOMÁTICAS" />
<qg:phrase text="When" as="Se" />
<qg:phrase text="after" as="após" />
<qg:phrase text="change to" as="muda para" />
<qg:phrase text="winning" as="ganhando" />
<qg:phrase text="drawing" as="empatando" />
<qg:phrase text="losing" as="perdendo" />
<qg:phrase text="Balanced" as="Equilibrado" />
<qg:phrase text="Attacking" as="Ofensivo" />
<qg:phrase text="All-out attack" as="Muito ofensivo" />
<qg:phrase text="Defensive" as="Defensivo" />
<qg:phrase text="Park the bus" as="Muito defensivo" />
<qg:phrase text="Counter-attack" as="Contra-ataque" />
<qg:phrase text="Kamikaze" as="Kamikaze" />
<qg:phrase text="Win" as="Vitória" />
<qg:phrase text="Draw" as="Empate" />
<qg:phrase text="Avoid defeat" as="Não perder" />
<qg:phrase text="By hand" as="Decidir no jogo" />
<qg:phrase text="Auto" as="Auto" />
<qg:phrase text="Tactical edge" as="Vantagem tática" />
<qg:phrase text="Tactical handicap" as="Desvantagem tática" />
<qg:phrase text="No tactical edge" as="Sem vantagem tática" />
<qg:phrase text="PANDA LEAGUE" as="CAMPEONATO PANDA" />
<qg:phrase text="Club" as="Time" />
<qg:phrase text="P" as="J" />
<qg:phrase text="GF" as="GM" />
<qg:phrase text="GA" as="GS" />
<qg:phrase text="GD" as="SG" />
<qg:phrase text="Pts" as="Pts" />
<qg:phrase text="FINANCES" as="FINANÇAS" />
<qg:phrase text="Wages a month:" as="Folha salarial/mês:" />
<qg:phrase text="Wages a week:" as="Folha da semana:" />
<qg:phrase text="Last gate:" as="Última bilheteria:" />
<qg:phrase text="TV a month:" as="Cota de TV/mês:" />
<qg:phrase text="Costs a month:" as="Custos/mês:" />
<qg:phrase text="Ticket:" as="Ingresso:" />
<qg:phrase text="Ticket" as="Ingresso" />
<qg:phrase text="first half" as="1º tempo" />
<qg:phrase text="second half" as="2º tempo" />
<qg:phrase text="GOAL" as="GOL" />
<qg:phrase text="injured" as="lesionado" />
<qg:phrase text="LIVE ROUND" as="RODADA AO VIVO" />
<qg:phrase text="GOOOOOAL!" as="GOOOOOL!" />
<qg:phrase text="AUTOMATIC CHANGE" as="MUDANÇA TÁTICA AUTOMÁTICA" />
<qg:phrase text="Undo" as="Desfazer" />
<qg:phrase text="Keep" as="Manter" />
<qg:phrase text="three seconds to undo it" as="3 segundos para desfazer" />
<qg:phrase text="PAUSED" as="PAUSA" />
<qg:phrase text="SPACE" as="ESPAÇO" />
<qg:phrase text="Go on" as="Continuar" />
<qg:phrase text="Time-out" as="Pedir tempo" />
<qg:phrase text="Pause" as="Pausar" />
<qg:phrase text="Speed" as="Velocidade" />
<qg:phrase text="Another match" as="Outro jogo" />
<qg:phrase text="Tactics of" as="Táticas do" />
<qg:phrase text="ROUND" as="RODADA" />
<qg:phrase text="PLAYED" as="FINALIZADA" />
<qg:phrase text="THE MANAGERS" as="OS TÉCNICOS" />
<qg:phrase text="End of the season" as="Fim de temporada" />
<qg:phrase text="Next round" as="Próxima rodada" />
<qg:phrase text="CHAMPIONS" as="CAMPEÕES" />
<qg:phrase text="New season" as="Nova temporada" />
<qg:phrase text="To the title" as="Ao título" />
<qg:phrase text="Manager:" as="Técnico:" />
<qg:phrase text="Sounds" as="Sons" />
<qg:phrase text="Language" as="Idioma" />
<qg:phrase text="Full screen" as="Tela cheia" />
<qg:phrase text="yes" as="sim" />
<qg:phrase text="no" as="não" />
<qg:phrase text="(up to a quarter more at home)." as="(até um quarto a mais em casa)." />
<qg:phrase text="A dearer ticket keeps people away:" as="Ingresso mais caro afasta o público;" />
<qg:phrase text="a full ground lifts the side" as="estádio cheio empurra o time" />
<qg:phrase text="Against it:" as="Contra isso:" />
<qg:phrase text="Booked:" as="Amarelos:" />
<qg:phrase text="Came on:" as="Entraram:" />
<qg:phrase text="Crowd at this price:" as="Público com este ingresso:" />
<qg:phrase text="Crowd:" as="Público:" />
<qg:phrase text="Shots" as="Chutes" />
<qg:phrase text="Ball" as="Posse" />
<qg:phrase text="Pressing:" as="Pressão:" />
<qg:phrase text="Gate:" as="Renda:" />
<qg:phrase text="Injured:" as="Lesionados:" />
<qg:phrase text="LAST MATCHES" as="ÚLTIMOS JOGOS" />
<qg:phrase text="Last crowd:" as="Último público:" />
<qg:phrase text="Man of the match:" as="Destaque:" />
<qg:phrase text="No matches yet." as="Ainda sem jogos." />
<qg:phrase text="Opponent:" as="Adversário:" />
<qg:phrase text="Out of the next match:" as="Fora do próximo jogo:" />
<qg:phrase text="Sent off:" as="Expulsos:" />
<qg:phrase text="Substitution" as="Substituição" />
<qg:phrase text="TOP SCORERS" as="ARTILHARIA" />
<qg:phrase text="Tendency:" as="Tendência:" />
<qg:phrase text="Top scorer:" as="Artilheiro:" />
<qg:phrase text="Who comes off?" as="Quem sai?" />
<qg:phrase text="Who comes on for" as="Quem entra no lugar de" />
<qg:phrase text="YOUR MATCH" as="SEU JOGO" />
<qg:phrase text="for" as="no lugar de" />
<qg:phrase text="goal" as="gol" />
<qg:phrase text="goals" as="gols" />
<qg:phrase text="res" as="res" />
<qg:phrase text="sent off" as="expulso" />
<qg:phrase text="the keepers" as="os goleiros" />
<qg:phrase text="vs" as="x" />
<qg:phrase text="1st division" as="1ª divisão" />
<qg:phrase text="2nd division" as="2ª divisão" />
<qg:phrase text="3rd division" as="3ª divisão" />
<qg:phrase text="4th division" as="4ª divisão" />
<qg:phrase text="round" as="rodada" />
<qg:phrase text="Sponsor a month:" as="Patrocínio por mês:" />
<qg:phrase text="up and down" as="sobe e desce" />
<qg:phrase text="Another division" as="Outra divisão" />
<qg:phrase text="Promoted to the" as="Subiu para a" />
<qg:phrase text="Relegated to the" as="Caiu para a" />
<qg:phrase text="UP AND DOWN" as="SOBE E DESCE" />
<qg:phrase text="to the" as="para a" />
<qg:phrase text="promoted!" as="subiu!" />
<qg:phrase text="relegated." as="caiu." />
<qg:phrase text="stays in the" as="fica na" />
<qg:phrase text="of the" as="da" />
<qg:phrase text="This game was saved by the first PandaFoot" as="Este jogo foi salvo pelo primeiro PandaFoot" />
<qg:phrase text="(one league of ten clubs), which this one" as="(uma liga de dez times), que este não" />
<qg:phrase text="cannot continue. Start a new game: four" as="consegue continuar. Comece um jogo novo: quatro" />
<qg:phrase text="divisions, from a small club of the 4th." as="divisões, de um time pequeno da 4ª." />
<qg:phrase text="managers" as="técnicos" />
<qg:phrase text="Division" as="Divisão" />
<qg:phrase text="Board:" as="Diretoria:" />
<qg:phrase text="The board is losing its patience" as="A diretoria está perdendo a paciência" />
<qg:phrase text="The board expects:" as="A diretoria espera:" />
<qg:phrase text="Sacked by the board of" as="Demitido pela diretoria do" />
<qg:phrase text="New manager of" as="Novo técnico do" />
<qg:phrase text="SACKED" as="DEMITIDO" />
<qg:phrase text="AN OFFER" as="UMA PROPOSTA" />
<qg:phrase text="The board has lost its patience." as="A diretoria perdeu a paciência." />
<qg:phrase text="Your work has been noticed." as="O seu trabalho chamou a atenção." />
<qg:phrase text="These clubs would have you:" as="Estes clubes querem você:" />
<qg:phrase text="No club would have you now." as="Nenhum clube quer você agora." />
<qg:phrase text="Take" as="Assumir o" />
<qg:phrase text="expects" as="espera" />
<qg:phrase text="Stay at" as="Ficar no" />
<qg:phrase text="End the career" as="Encerrar a carreira" />
<qg:phrase text="THE END OF THE CAREER" as="FIM DA CARREIRA" />
<qg:phrase text="seasons" as="temporadas" />
<qg:phrase text="titles" as="títulos" />
<qg:phrase text="Higher division" as="Divisão superior" />
<qg:phrase text="Lower division" as="Divisão inferior" />
<qg:phrase text="Top of the table" as="Topo da tabela" />
<qg:phrase text="Bottom of the table" as="Lanterna" />
<qg:phrase text="The squad's morale" as="Moral do elenco" />
<qg:phrase text="A star wants more" as="Uma estrela quer mais" />
<qg:phrase text="free" as="livre" />
<qg:phrase text="No deal:" as="Sem acordo:" />
<qg:phrase text="Surprise! He took the long shot:" as="Zebra! Topou a proposta:" />
<qg:phrase text="Signed:" as="Contratado:" />
<qg:phrase text="On loan:" as="Emprestado:" />
<qg:phrase text="Market" as="Mercado" />
<qg:phrase text="TRANSFER LIST" as="LISTA DE TRANSFERÊNCIAS" />
<qg:phrase text="players" as="jogadores" />
<qg:phrase text="Fee" as="Passe" />
<qg:phrase text="Wage" as="Salário" />
<qg:phrase text="Best bid" as="Melhor" />
<qg:phrase text="Left" as="Rod." />
<qg:phrase text="Nobody is on the list yet: it fills as the rounds go." as="Ninguém na lista ainda: ela enche com as rodadas." />
<qg:phrase text="Bid" as="Lance" />
<qg:phrase text="Sell a player" as="Vender um jogador" />
<qg:phrase text="your bid" as="seu lance" />
<qg:phrase text="A free agent" as="Agente livre" />
<qg:phrase text="Fee:" as="Passe:" />
<qg:phrase text="none" as="nenhum" />
<qg:phrase text="He asks a month:" as="Pede por mês:" />
<qg:phrase text="Bids so far:" as="Lances até agora:" />
<qg:phrase text="rounds left:" as="rodadas restantes:" />
<qg:phrase text="Your offer a month:" as="Sua oferta por mês:" />
<qg:phrase text="Paid:" as="Pagamento:" />
<qg:phrase text="all at once" as="à vista" />
<qg:phrase text="30% now, 7 months of 10%" as="30% agora, 7 meses de 10%" />
<qg:phrase text="He would come:" as="Chance de topar:" />
<qg:phrase text="Not enough money for that" as="Não há dinheiro para isso" />
<qg:phrase text="Wage %" as="Salário %" />
<qg:phrase text="Make the bid" as="Dar o lance" />
<qg:phrase text="Borrow him for the season" as="Pegar emprestado na temporada" />
<qg:phrase text="SELL" as="VENDER" />
<qg:phrase text="Enter: on the list, or off it" as="Enter: põe na lista, ou tira" />
<qg:phrase text="Worth" as="Valor" />
<qg:phrase text="Listed" as="Na lista" />
<qg:phrase text="ON THE LIST" as="NA LISTA" />
<qg:phrase text="on loan" as="emprestado" />
<qg:phrase text="On the list / off it" as="Na lista / fora dela" />
<qg:phrase text="spotless" as="impecável" />
<qg:phrase text="needs care" as="precisa de atenção" />
<qg:phrase text="run down" as="degradado" />
<qg:phrase text="critical" as="crítico" />
<qg:phrase text="Watch the finances!" as="Atenção com as finanças!" />
<qg:phrase text="CRISIS: no buying" as="CRISE: sem compras" />
<qg:phrase text="GRAVE: points off, best sold" as="GRAVE: pontos a menos, o melhor à venda" />
<qg:phrase text="BANKRUPT" as="FALÊNCIA" />
<qg:phrase text="The new stand is open:" as="A arquibancada nova está aberta:" />
<qg:phrase text="Six points off for the debts:" as="Seis pontos a menos pelas dívidas:" />
<qg:phrase text="Bankrupt: rescued, and down a division:" as="Falência: socorrido, e uma divisão abaixo:" />
<qg:phrase text="A star is born:" as="Nasce uma estrela:" />
<qg:phrase text="Better this season:" as="Evoluíram na temporada:" />
<qg:phrase text="In crisis, the club cannot buy" as="Em crise, o clube não pode comprar" />
<qg:phrase text="THE GROUND" as="O ESTÁDIO" />
<qg:phrase text="Health:" as="Saúde:" />
<qg:phrase text="gate" as="renda" />
<qg:phrase text="Works:" as="Obras:" />
<qg:phrase text="rounds" as="rodadas" />
<qg:phrase text="No works going on" as="Nenhuma obra" />
<qg:phrase text="A dearer ticket keeps people away;" as="Ingresso caro afasta o público;" />
<qg:phrase text="Maintenance" as="Manutenção" />
<qg:phrase text="History" as="Histórico" />
<qg:phrase text="No season has ended yet." as="Nenhuma temporada terminou ainda." />
<qg:phrase text="Seasons" as="Temp." />
<qg:phrase text="Titles" as="Títulos" />
<qg:phrase text="Up" as="Sob." />
<qg:phrase text="Pts/match" as="Pts/jogo" />
<qg:phrase text="Clubs" as="Clubes" />
<qg:phrase text="TOP SCORERS OF ALL TIME" as="ARTILHEIROS DE TODOS OS TEMPOS" />
<qg:phrase text="Preliminary round" as="Fase preliminar" />
<qg:phrase text="Round of 32" as="16 avos de final" />
<qg:phrase text="Round of 16" as="Oitavas de final" />
<qg:phrase text="Quarter-finals" as="Quartas de final" />
<qg:phrase text="Semi-finals" as="Semifinal" />
<qg:phrase text="Final" as="Final" />
<qg:phrase text="PANDA CUP" as="COPA PANDA" />
<qg:phrase text="SUPERCUP" as="SUPERCOPA" />
<qg:phrase text="Supercup:" as="Supercopa:" />
<qg:phrase text="The Panda Cup goes to" as="A Copa Panda é do" />
<qg:phrase text="out" as="eliminados" />
<qg:phrase text="no match for you" as="sem jogo para você" />
<qg:phrase text="THE DRAW" as="O SORTEIO" />
<qg:phrase text="The other clubs enter in the round of 32." as="Os outros clubes entram nos 16 avos de final." />
<qg:phrase text="The club of the lower division plays at home." as="O clube da divisão mais baixa joga em casa." />
<qg:phrase text="Show them all" as="Mostrar todos" />
<qg:phrase text="To the dashboard" as="Para o painel" />
<qg:phrase text="Season" as="Temp." />
<qg:phrase text="More matches" as="Mais jogos" />
<!-- 2.2 -->
<qg:phrase text="A dearer ticket keeps people away; a full ground lifts the side." as="Ingresso caro afasta o público; estádio cheio empurra o time." />
<qg:phrase text="A manager's player: only if he puts him on the list." as="Jogador de um técnico: só se ele o puser na lista." />
<qg:phrase text="Academy: youths every season." as="Base: jovens a cada temporada." />
<qg:phrase text="Asking" as="Pedido" />
<qg:phrase text="Asking price %" as="Preço pedido %" />
<qg:phrase text="Avg" as="Méd" />
<qg:phrase text="Back to the round" as="Voltar à rodada" />
<qg:phrase text="Best player" as="Melhor jogador" />
<qg:phrase text="Better this month (+1):" as="Evoluíram no mês (+1):" />
<qg:phrase text="Worse this month (-1):" as="Caíram no mês (-1):" />
<qg:phrase text="Better in training (+1):" as="Evoluíram no treino (+1):" />
<qg:phrase text="CAREER" as="CARREIRA" />
<qg:phrase text="Car" as="Car" />
<qg:phrase text="Cheaper: more clubs bid." as="Mais barato: mais clubes dão lance." />
<qg:phrase text="Clubs:" as="Clubes:" />
<qg:phrase text="Cup" as="Copa" />
<qg:phrase text="Div" as="Div" />
<qg:phrase text="Each wins its club" as="Cada um rende ao clube" />
<qg:phrase text="Fm" as="Fm" />
<qg:phrase text="From the academy:" as="Da base:" />
<qg:phrase text="Gls" as="Gols" />
<qg:phrase text="Gm" as="Jg" />
<qg:phrase text="Health over 60 first: Maintenance." as="Saúde acima de 60 antes: Manutenção." />
<qg:phrase text="How it ended" as="Como terminou" />
<qg:phrase text="In crisis: no works." as="Em crise: sem obras." />
<qg:phrase text="It can grow: a third at once," as="Pode ampliar: um terço na hora," />
<qg:phrase text="the rest in seven months." as="o resto em sete meses." />
<qg:phrase text="It is at the top level already." as="Já está no nível máximo." />
<qg:phrase text="Mor" as="Mor" />
<qg:phrase text="No ratings for this match." as="Sem notas para este jogo." />
<qg:phrase text="Nt" as="Nt" />
<qg:phrase text="Order" as="Ordem" />
<qg:phrase text="PLAYERS" as="JOGADORES" />
<qg:phrase text="Players" as="Jogadores" />
<qg:phrase text="Position" as="Posição" />
<qg:phrase text="Prizes won:" as="Prêmios ganhos:" />
<qg:phrase text="Pts/m" as="Pts/j" />
<qg:phrase text="RATINGS" as="NOTAS" />
<qg:phrase text="Ratings" as="Notas" />
<qg:phrase text="Sack" as="Dem" />
<qg:phrase text="Seas" as="Temp" />
<qg:phrase text="Str: strength, Fm: form, Nt: the rating so far" as="For: força, Fm: forma, Nt: a nota até agora" />
<qg:phrase text="THE CLUB" as="O CLUBE" />
<qg:phrase text="The ground needs care (F5): at 60% it cannot grow" as="O estádio precisa de cuidado (F5): com 60% não amplia" />
<qg:phrase text="The ground needs care first (health over 60): Maintenance." as="O estádio precisa de cuidado antes (saúde acima de 60): Manutenção." />
<qg:phrase text="The teams" as="Os times" />
<qg:phrase text="They cost a month:" as="Custam por mês:" />
<qg:phrase text="Tit" as="Tít" />
<qg:phrase text="Top scorer" as="Artilheiro" />
<qg:phrase text="Training center" as="Centro de treinamento" />
<qg:phrase text="Training center:" as="Centro de treinamento:" />
<qg:phrase text="Training: a reserve grows" as="Treino: um reserva evolui em" />
<qg:phrase text="of the months." as="dos meses." />
<qg:phrase text="Youth academy" as="Categoria de base" />
<qg:phrase text="Youth academy:" as="Categoria de base:" />
<qg:phrase text="Your own player: to sell him, F6 and then S." as="Jogador seu: para vendê-lo, F6 e depois S." />
<qg:phrase text="a match" as="por jogo" />
<qg:phrase text="and is worth a third more next season." as="e vale um terço a mais na próxima temporada." />
<qg:phrase text="and the prizes of each division" as="e os prêmios de cada divisão" />
<qg:phrase text="bids" as="lances" />
<qg:phrase text="by" as="por" />
<qg:phrase text="came on" as="entrou" />
<qg:phrase text="cups" as="copas" />
<qg:phrase text="ended the career" as="encerrou a carreira" />
<qg:phrase text="every division" as="todas as divisões" />
<qg:phrase text="every position" as="todas as posições" />
<qg:phrase text="left for another club" as="saiu para outro clube" />
<qg:phrase text="level" as="nível" />
<qg:phrase text="matches" as="jogos" />
<qg:phrase text="mean rating" as="nota média" />
<qg:phrase text="mean rating, ten matches at least" as="nota média, dez jogos no mínimo" />
<qg:phrase text="points" as="pontos" />
<qg:phrase text="sacked" as="demitido" />
<qg:phrase text="still there" as="ainda lá" />
<qg:phrase text="the last month" as="o último mês" />
<qg:phrase text="the whole career" as="a carreira inteira" />
<qg:phrase text="times sacked" as="demissões" />
<qg:phrase text="to" as="a" />
<qg:phrase text="strength" as="força" />
<qg:phrase text="rating" as="nota" />
<qg:phrase text="worth" as="valor" />
<qg:phrase text="Prize for the title:" as="Prêmio pelo título:" />
<qg:phrase text="Prize for the runner-up:" as="Prêmio pelo vice:" />
<qg:phrase text="Prize for the top scorer:" as="Prêmio pelo artilheiro:" />
<qg:phrase text="Prize for the best player:" as="Prêmio pelo melhor jogador:" />
<qg:phrase text="Training center, level" as="Centro de treinamento, nível" />
<qg:phrase text="Youth academy, level" as="Categoria de base, nível" />
</qg:language>
<qg:language lang="es" name="Español">
<qg:phrase text=" ☻ P A N D A F O O T ☻ " as=" ☻ P A N D A F O O T ☻ " />
<qg:phrase text=" one manager, one league, the ball " as=" un técnico, una liga, la pelota " />
<qg:phrase text="New game" as="Nueva partida" />
<qg:phrase text="Continue" as="Continuar" />
<qg:phrase text="Options" as="Opciones" />
<qg:phrase text="Quit" as="Salir" />
<qg:phrase text="What's new" as="Novedades" />
<qg:phrase text="Report a bug or suggest" as="Reportar un error o sugerir" />
<qg:phrase text="REPORT A BUG OR SUGGEST" as="REPORTAR UN ERROR O SUGERIR" />
<qg:phrase text="Every report is read. Thank you!" as="Se lee cada reporte. ¡Gracias!" />
<qg:phrase text="Kind:" as="Tipo:" />
<qg:phrase text="Bug" as="Error" />
<qg:phrase text="Suggestion" as="Sugerencia" />
<qg:phrase text="What happened, or your idea:" as="Qué pasó, o tu idea:" />
<qg:phrase text="Attach my game:" as="Adjuntar mi partida:" />
<qg:phrase text="Send" as="Enviar" />
<qg:phrase text="Where:" as="Dónde:" />
<qg:phrase text="Write a few words first." as="Escribe unas palabras antes." />
<qg:phrase text="Sending..." as="Enviando..." />
<qg:phrase text="Kept: it goes when the beta's server is ready." as="Guardado: se envía cuando el servidor de la beta esté listo." />
<qg:phrase text="Sent: report no." as="Enviado: reporte n.º" />
<qg:phrase text="Thank you!" as="¡Gracias!" />
<qg:phrase text="The report was not accepted:" as="El reporte no fue aceptado:" />
<qg:phrase text="Not sent now" as="No enviado ahora" />
<qg:phrase text="it is kept and goes later." as="se guarda y se envía después." />
<qg:phrase text="Waiting to be sent:" as="Esperando envío:" />
<qg:phrase text="[!] or F9 opens this on any screen." as="[!] o F9 abre esto en cualquier pantalla." />
<qg:phrase text="Something else" as="Otra cosa" />
<qg:phrase text="Report" as="Reportar" />
<qg:phrase text="NEW GAME" as="NUEVA PARTIDA" />
<qg:phrase text="Managers:" as="Técnicos:" />
<qg:phrase text="World no." as="Mundo n.º" />
<qg:phrase text="Save in slot" as="Guardar en" />
<qg:phrase text="Manager's name:" as="Nombre del técnico:" />
<qg:phrase text="Draw the manager's club" as="Sortear el club del técnico" />
<qg:phrase text="Each manager is given a club," as="Cada técnico recibe un club," />
<qg:phrase text="drawn among the small clubs" as="sorteado entre los clubes pequeños" />
<qg:phrase text="of the 4th division." as="de la 4ª división." />
<qg:phrase text="big" as="grande" />
<qg:phrase text="middle" as="mediano" />
<qg:phrase text="small" as="pequeño" />
<qg:phrase text="Manager" as="Técnico" />
<qg:phrase text="Start the league" as="Empezar la liga" />
<qg:phrase text="Back" as="Volver" />
<qg:phrase text="Round" as="Jornada" />
<qg:phrase text="Balance:" as="Saldo:" />
<qg:phrase text="Place:" as="Puesto:" />
<qg:phrase text="place" as="puesto" />
<qg:phrase text="Form:" as="Racha:" />
<qg:phrase text="Morale:" as="Moral:" />
<qg:phrase text="Ground:" as="Estadio:" />
<qg:phrase text="seats" as="asientos" />
<qg:phrase text="NEXT MATCH" as="PRÓXIMO PARTIDO" />
<qg:phrase text="Panda League, round" as="Liga Panda, jornada" />
<qg:phrase text="home" as="en casa" />
<qg:phrase text="away" as="fuera" />
<qg:phrase text="Objective:" as="Objetivo:" />
<qg:phrase text="The line-up is not complete (F1)" as="La alineación no está completa (F1)" />
<qg:phrase text="NEWS" as="NOTICIAS" />
<qg:phrase text="Line-up" as="Alineación" />
<qg:phrase text="Tactics" as="Tácticas" />
<qg:phrase text="Squad" as="Plantilla" />
<qg:phrase text="Finances" as="Finanzas" />
<qg:phrase text="Table" as="Clasificación" />
<qg:phrase text="Ready: next manager" as="Listo: siguiente técnico" />
<qg:phrase text="Play the round" as="Jugar la jornada" />
<qg:phrase text="Save and leave" as="Guardar y salir" />
<qg:phrase text="The Panda League kicks off:" as="Empieza la Liga Panda:" />
<qg:phrase text="They say a phenomenon plays in the lower divisions..." as="Dicen que un fenómeno juega en las divisiones de abajo..." />
<qg:phrase text="Rumour: the hidden phenomenon would be a goalkeeper." as="Rumor: el fenómeno escondido sería un portero." />
<qg:phrase text="Rumour: the hidden phenomenon would be a defender." as="Rumor: el fenómeno escondido sería un defensa." />
<qg:phrase text="Rumour: the hidden phenomenon would be a midfielder." as="Rumor: el fenómeno escondido sería un centrocampista." />
<qg:phrase text="Rumour: the hidden phenomenon would be a forward." as="Rumor: el fenómeno escondido sería un delantero." />
<qg:phrase text="Revealed: the phenomenon plays for" as="Revelado: el fenómeno juega en" />
<qg:phrase text="W" as="G" />
<qg:phrase text="D" as="E" />
<qg:phrase text="L" as="P" />
<qg:phrase text="GK" as="POR" />
<qg:phrase text="DF" as="DEF" />
<qg:phrase text="MF" as="MED" />
<qg:phrase text="FW" as="DEL" />
<qg:phrase text="SQUAD" as="PLANTILLA" />
<qg:phrase text="players, wages" as="jugadores, sueldos" />
<qg:phrase text="/month" as="/mes" />
<qg:phrase text="Name" as="Nombre" />
<qg:phrase text="Pos" as="Pos" />
<qg:phrase text="Str" as="Fue" />
<qg:phrase text="Traits" as="ADN" />
<qg:phrase text="Morale" as="Moral" />
<qg:phrase text="Form" as="Forma" />
<qg:phrase text="Goals" as="Goles" />
<qg:phrase text="Career" as="Carrera" />
<qg:phrase text="Not enough money: it costs" as="Dinero insuficiente: cuesta" />
<qg:phrase text="The works going on must end first." as="Las obras en curso deben terminar antes." />
<qg:phrase text="The ground needs care first (health over 60)." as="El estadio necesita cuidados antes (salud por encima de 60)." />
<qg:phrase text="Not enough money: a third at once is" as="Dinero insuficiente: el tercio al contado es" />
<qg:phrase text="Works begin:" as="Empiezan las obras:" />
<qg:phrase text="Works: a third at once, the rest in seven months." as="Obras: un tercio al contado, el resto en siete meses." />
<qg:phrase text="penalty" as="penalti" />
<qg:phrase text="missed a penalty" as="falló un penalti" />
<qg:phrase text="Penalty taker:" as="Lanzador de penaltis:" />
<qg:phrase text="the best" as="el mejor" />
<qg:phrase text="tired in the eleven: rest them (F1)" as="cansados entre los titulares: haz que descansen (F1)" />
<qg:phrase text="Bid for a player" as="Pujar por un jugador" />
<qg:phrase text="BID FOR A PLAYER OF" as="PUJA POR UN JUGADOR DEL" />
<qg:phrase text="He goes on the transfer list: the other clubs may bid too, for three rounds." as="Pasa a la lista de traspasos: los otros clubes también pueden pujar, durante tres jornadas." />
<qg:phrase text="sells no one: it would have fewer than sixteen." as="no vende a nadie: se quedaría con menos de dieciséis." />
<qg:phrase text="on the list" as="en la lista" />
<qg:phrase text="Up for auction:" as="En subasta:" />
<qg:phrase text="Fit" as="Est" />
<qg:phrase text="Salary" as="Sueldo" />
<qg:phrase text="Genius" as="Genio" />
<qg:phrase text="Ace" as="Crack" />
<qg:phrase text="Finisher" as="Goleador" />
<qg:phrase text="Set pieces" as="Especialista" />
<qg:phrase text="Leader" as="Líder" />
<qg:phrase text="Star" as="Estrella" />
<qg:phrase text="Wall" as="Muralla" />
<qg:phrase text="LINE-UP" as="ALINEACIÓN" />
<qg:phrase text="in the eleven:" as="titulares:" />
<qg:phrase text="Plays" as="Juega" />
<qg:phrase text="ELEVEN" as="TITULAR" />
<qg:phrase text="bench" as="banquillo" />
<qg:phrase text="Attack" as="Ataque" />
<qg:phrase text="Defence" as="Defensa" />
<qg:phrase text="eleven fit to play, one of them in goal" as="11 titulares disponibles, uno de ellos en la portería" />
<qg:phrase text="Eleven → bench → out" as="Titular → banquillo → fuera" />
<qg:phrase text="Automatic" as="Automática" />
<qg:phrase text="TACTICS" as="TÁCTICAS" />
<qg:phrase text="They tend to play:" as="Suele jugar:" />
<qg:phrase text="Formation:" as="Formación:" />
<qg:phrase text="Style:" as="Estilo:" />
<qg:phrase text="AUTOMATIC CHANGES" as="CAMBIOS AUTOMÁTICOS" />
<qg:phrase text="When" as="Si va" />
<qg:phrase text="after" as="desde" />
<qg:phrase text="change to" as="pasa a" />
<qg:phrase text="winning" as="ganando" />
<qg:phrase text="drawing" as="empatando" />
<qg:phrase text="losing" as="perdiendo" />
<qg:phrase text="Balanced" as="Equilibrado" />
<qg:phrase text="Attacking" as="Ofensivo" />
<qg:phrase text="All-out attack" as="Muy ofensivo" />
<qg:phrase text="Defensive" as="Defensivo" />
<qg:phrase text="Park the bus" as="Muy defensivo" />
<qg:phrase text="Counter-attack" as="Contraataque" />
<qg:phrase text="Kamikaze" as="Kamikaze" />
<qg:phrase text="Win" as="Victoria" />
<qg:phrase text="Draw" as="Empate" />
<qg:phrase text="Avoid defeat" as="No perder" />
<qg:phrase text="By hand" as="Decidir en el partido" />
<qg:phrase text="Auto" as="Auto" />
<qg:phrase text="Tactical edge" as="Ventaja táctica" />
<qg:phrase text="Tactical handicap" as="Desventaja táctica" />
<qg:phrase text="No tactical edge" as="Sin ventaja táctica" />
<qg:phrase text="PANDA LEAGUE" as="LIGA PANDA" />
<qg:phrase text="Club" as="Club" />
<qg:phrase text="P" as="PJ" />
<qg:phrase text="GF" as="GF" />
<qg:phrase text="GA" as="GC" />
<qg:phrase text="GD" as="DG" />
<qg:phrase text="Pts" as="Pts" />
<qg:phrase text="FINANCES" as="FINANZAS" />
<qg:phrase text="Wages a month:" as="Sueldos al mes:" />
<qg:phrase text="Wages a week:" as="Sueldos a la semana:" />
<qg:phrase text="Last gate:" as="Última taquilla:" />
<qg:phrase text="TV a month:" as="TV al mes:" />
<qg:phrase text="Costs a month:" as="Gastos al mes:" />
<qg:phrase text="Ticket:" as="Entrada:" />
<qg:phrase text="Ticket" as="Entrada" />
<qg:phrase text="first half" as="primera parte" />
<qg:phrase text="second half" as="segunda parte" />
<qg:phrase text="GOAL" as="GOL" />
<qg:phrase text="injured" as="lesionado" />
<qg:phrase text="LIVE ROUND" as="JORNADA EN DIRECTO" />
<qg:phrase text="GOOOOOAL!" as="¡GOOOOOL!" />
<qg:phrase text="AUTOMATIC CHANGE" as="CAMBIO TÁCTICO AUTOMÁTICO" />
<qg:phrase text="Undo" as="Deshacer" />
<qg:phrase text="Keep" as="Mantener" />
<qg:phrase text="three seconds to undo it" as="3 segundos para deshacerlo" />
<qg:phrase text="PAUSED" as="PAUSA" />
<qg:phrase text="SPACE" as="ESPACIO" />
<qg:phrase text="Go on" as="Seguir" />
<qg:phrase text="Time-out" as="Pedir tiempo" />
<qg:phrase text="Pause" as="Pausar" />
<qg:phrase text="Speed" as="Velocidad" />
<qg:phrase text="Another match" as="Otro partido" />
<qg:phrase text="Tactics of" as="Tácticas de" />
<qg:phrase text="ROUND" as="JORNADA" />
<qg:phrase text="PLAYED" as="JUGADA" />
<qg:phrase text="THE MANAGERS" as="LOS TÉCNICOS" />
<qg:phrase text="End of the season" as="Fin de temporada" />
<qg:phrase text="Next round" as="Siguiente jornada" />
<qg:phrase text="CHAMPIONS" as="CAMPEONES" />
<qg:phrase text="New season" as="Nueva temporada" />
<qg:phrase text="To the title" as="Al título" />
<qg:phrase text="Manager:" as="Técnico:" />
<qg:phrase text="Sounds" as="Sonidos" />
<qg:phrase text="Language" as="Idioma" />
<qg:phrase text="Full screen" as="Pantalla completa" />
<qg:phrase text="yes" as="sí" />
<qg:phrase text="no" as="no" />
<qg:phrase text="(up to a quarter more at home)." as="(hasta un cuarto más en casa)." />
<qg:phrase text="A dearer ticket keeps people away:" as="Una entrada más cara aleja al público;" />
<qg:phrase text="a full ground lifts the side" as="un estadio lleno empuja al equipo" />
<qg:phrase text="Against it:" as="Contra eso:" />
<qg:phrase text="Booked:" as="Amonestados:" />
<qg:phrase text="Came on:" as="Entraron:" />
<qg:phrase text="Crowd at this price:" as="Público con esta entrada:" />
<qg:phrase text="Crowd:" as="Público:" />
<qg:phrase text="Shots" as="Tiros" />
<qg:phrase text="Ball" as="Posesión" />
<qg:phrase text="Pressing:" as="Presión:" />
<qg:phrase text="Gate:" as="Taquilla:" />
<qg:phrase text="Injured:" as="Lesionados:" />
<qg:phrase text="LAST MATCHES" as="ÚLTIMOS PARTIDOS" />
<qg:phrase text="Last crowd:" as="Último público:" />
<qg:phrase text="Man of the match:" as="Figura:" />
<qg:phrase text="No matches yet." as="Aún sin partidos." />
<qg:phrase text="Opponent:" as="Rival:" />
<qg:phrase text="Out of the next match:" as="Fuera del próximo partido:" />
<qg:phrase text="Sent off:" as="Expulsados:" />
<qg:phrase text="Substitution" as="Cambio" />
<qg:phrase text="TOP SCORERS" as="GOLEADORES" />
<qg:phrase text="Tendency:" as="Tendencia:" />
<qg:phrase text="Top scorer:" as="Goleador:" />
<qg:phrase text="Who comes off?" as="¿Quién sale?" />
<qg:phrase text="Who comes on for" as="¿Quién entra por" />
<qg:phrase text="YOUR MATCH" as="TU PARTIDO" />
<qg:phrase text="for" as="por" />
<qg:phrase text="goal" as="gol" />
<qg:phrase text="goals" as="goles" />
<qg:phrase text="res" as="sup" />
<qg:phrase text="sent off" as="expulsado" />
<qg:phrase text="the keepers" as="los porteros" />
<qg:phrase text="vs" as="vs" />
<qg:phrase text="1st division" as="1ª división" />
<qg:phrase text="2nd division" as="2ª división" />
<qg:phrase text="3rd division" as="3ª división" />
<qg:phrase text="4th division" as="4ª división" />
<qg:phrase text="round" as="jornada" />
<qg:phrase text="Sponsor a month:" as="Patrocinio al mes:" />
<qg:phrase text="up and down" as="ascenso y descenso" />
<qg:phrase text="Another division" as="Otra división" />
<qg:phrase text="Promoted to the" as="Ascendió a la" />
<qg:phrase text="Relegated to the" as="Descendió a la" />
<qg:phrase text="UP AND DOWN" as="ASCENSOS Y DESCENSOS" />
<qg:phrase text="to the" as="a la" />
<qg:phrase text="promoted!" as="¡ascendió!" />
<qg:phrase text="relegated." as="descendió." />
<qg:phrase text="stays in the" as="se queda en la" />
<qg:phrase text="of the" as="de la" />
<qg:phrase text="This game was saved by the first PandaFoot" as="Esta partida la guardó el primer PandaFoot" />
<qg:phrase text="(one league of ten clubs), which this one" as="(una liga de diez clubes), que este no" />
<qg:phrase text="cannot continue. Start a new game: four" as="puede continuar. Empieza una nueva: cuatro" />
<qg:phrase text="divisions, from a small club of the 4th." as="divisiones, desde un club pequeño de la 4ª." />
<qg:phrase text="managers" as="técnicos" />
<qg:phrase text="Division" as="División" />
<qg:phrase text="Board:" as="Directiva:" />
<qg:phrase text="The board is losing its patience" as="La directiva está perdiendo la paciencia" />
<qg:phrase text="The board expects:" as="La directiva espera:" />
<qg:phrase text="Sacked by the board of" as="Despedido por la directiva del" />
<qg:phrase text="New manager of" as="Nuevo técnico del" />
<qg:phrase text="SACKED" as="DESPEDIDO" />
<qg:phrase text="AN OFFER" as="UNA OFERTA" />
<qg:phrase text="The board has lost its patience." as="La directiva perdió la paciencia." />
<qg:phrase text="Your work has been noticed." as="Tu trabajo llamó la atención." />
<qg:phrase text="These clubs would have you:" as="Estos clubes te quieren:" />
<qg:phrase text="No club would have you now." as="Ningún club te quiere ahora." />
<qg:phrase text="Take" as="Dirigir al" />
<qg:phrase text="expects" as="espera" />
<qg:phrase text="Stay at" as="Quedarse en el" />
<qg:phrase text="End the career" as="Terminar la carrera" />
<qg:phrase text="THE END OF THE CAREER" as="FIN DE LA CARRERA" />
<qg:phrase text="seasons" as="temporadas" />
<qg:phrase text="titles" as="títulos" />
<qg:phrase text="Higher division" as="División superior" />
<qg:phrase text="Lower division" as="División inferior" />
<qg:phrase text="Top of the table" as="Arriba en la tabla" />
<qg:phrase text="Bottom of the table" as="Colista" />
<qg:phrase text="The squad's morale" as="Moral de la plantilla" />
<qg:phrase text="A star wants more" as="Una estrella quiere más" />
<qg:phrase text="free" as="libre" />
<qg:phrase text="No deal:" as="Sin acuerdo:" />
<qg:phrase text="Surprise! He took the long shot:" as="¡Sorpresa! Aceptó la oferta:" />
<qg:phrase text="Signed:" as="Fichado:" />
<qg:phrase text="On loan:" as="Cedido:" />
<qg:phrase text="Market" as="Mercado" />
<qg:phrase text="TRANSFER LIST" as="LISTA DE TRASPASOS" />
<qg:phrase text="players" as="jugadores" />
<qg:phrase text="Fee" as="Traspaso" />
<qg:phrase text="Wage" as="Salario" />
<qg:phrase text="Best bid" as="Mejor" />
<qg:phrase text="Left" as="Jor." />
<qg:phrase text="Nobody is on the list yet: it fills as the rounds go." as="Nadie en la lista aún: se llena con las jornadas." />
<qg:phrase text="Bid" as="Pujar" />
<qg:phrase text="Sell a player" as="Vender un jugador" />
<qg:phrase text="your bid" as="tu puja" />
<qg:phrase text="A free agent" as="Agente libre" />
<qg:phrase text="Fee:" as="Traspaso:" />
<qg:phrase text="none" as="ninguno" />
<qg:phrase text="He asks a month:" as="Pide al mes:" />
<qg:phrase text="Bids so far:" as="Pujas hasta ahora:" />
<qg:phrase text="rounds left:" as="jornadas restantes:" />
<qg:phrase text="Your offer a month:" as="Tu oferta al mes:" />
<qg:phrase text="Paid:" as="Pago:" />
<qg:phrase text="all at once" as="al contado" />
<qg:phrase text="30% now, 7 months of 10%" as="30% ahora, 7 meses de 10%" />
<qg:phrase text="He would come:" as="Probabilidad de aceptar:" />
<qg:phrase text="Not enough money for that" as="No hay dinero para eso" />
<qg:phrase text="Wage %" as="Salario %" />
<qg:phrase text="Make the bid" as="Hacer la puja" />
<qg:phrase text="Borrow him for the season" as="Cedido por la temporada" />
<qg:phrase text="SELL" as="VENDER" />
<qg:phrase text="Enter: on the list, or off it" as="Enter: a la lista, o fuera" />
<qg:phrase text="Worth" as="Valor" />
<qg:phrase text="Listed" as="En lista" />
<qg:phrase text="ON THE LIST" as="EN LA LISTA" />
<qg:phrase text="on loan" as="cedido" />
<qg:phrase text="On the list / off it" as="A la lista / fuera" />
<qg:phrase text="spotless" as="impecable" />
<qg:phrase text="needs care" as="necesita atención" />
<qg:phrase text="run down" as="deteriorado" />
<qg:phrase text="critical" as="crítico" />
<qg:phrase text="Watch the finances!" as="¡Cuidado con las finanzas!" />
<qg:phrase text="CRISIS: no buying" as="CRISIS: sin fichajes" />
<qg:phrase text="GRAVE: points off, best sold" as="GRAVE: puntos menos, el mejor en venta" />
<qg:phrase text="BANKRUPT" as="QUIEBRA" />
<qg:phrase text="The new stand is open:" as="La nueva grada está abierta:" />
<qg:phrase text="Six points off for the debts:" as="Seis puntos menos por las deudas:" />
<qg:phrase text="Bankrupt: rescued, and down a division:" as="Quiebra: rescatado, y una división abajo:" />
<qg:phrase text="A star is born:" as="Nace una estrella:" />
<qg:phrase text="Better this season:" as="Mejoraron en la temporada:" />
<qg:phrase text="In crisis, the club cannot buy" as="En crisis, el club no puede fichar" />
<qg:phrase text="THE GROUND" as="EL ESTADIO" />
<qg:phrase text="Health:" as="Salud:" />
<qg:phrase text="gate" as="taquilla" />
<qg:phrase text="Works:" as="Obras:" />
<qg:phrase text="rounds" as="jornadas" />
<qg:phrase text="No works going on" as="Ninguna obra" />
<qg:phrase text="A dearer ticket keeps people away;" as="Una entrada cara aleja al público;" />
<qg:phrase text="Maintenance" as="Mantenimiento" />
<qg:phrase text="History" as="Historia" />
<qg:phrase text="No season has ended yet." as="Ninguna temporada ha terminado aún." />
<qg:phrase text="Seasons" as="Temp." />
<qg:phrase text="Titles" as="Títulos" />
<qg:phrase text="Up" as="Asc." />
<qg:phrase text="Pts/match" as="Pts/partido" />
<qg:phrase text="Clubs" as="Clubes" />
<qg:phrase text="TOP SCORERS OF ALL TIME" as="GOLEADORES DE TODOS LOS TIEMPOS" />
<qg:phrase text="Preliminary round" as="Ronda previa" />
<qg:phrase text="Round of 32" as="Dieciseisavos" />
<qg:phrase text="Round of 16" as="Octavos de final" />
<qg:phrase text="Quarter-finals" as="Cuartos de final" />
<qg:phrase text="Semi-finals" as="Semifinales" />
<qg:phrase text="Final" as="Final" />
<qg:phrase text="PANDA CUP" as="COPA PANDA" />
<qg:phrase text="SUPERCUP" as="SUPERCOPA" />
<qg:phrase text="Supercup:" as="Supercopa:" />
<qg:phrase text="The Panda Cup goes to" as="La Copa Panda es para" />
<qg:phrase text="out" as="eliminados" />
<qg:phrase text="no match for you" as="sin partido para ti" />
<qg:phrase text="THE DRAW" as="EL SORTEO" />
<qg:phrase text="The other clubs enter in the round of 32." as="Los demás clubes entran en dieciseisavos." />
<qg:phrase text="The club of the lower division plays at home." as="El club de la división inferior juega en casa." />
<qg:phrase text="Show them all" as="Mostrar todos" />
<qg:phrase text="To the dashboard" as="Al panel" />
<qg:phrase text="Season" as="Temp." />
<qg:phrase text="More matches" as="Más partidos" />
<!-- 2.2 -->
<qg:phrase text="A dearer ticket keeps people away; a full ground lifts the side." as="Una entrada cara aleja al público; el estadio lleno empuja al equipo." />
<qg:phrase text="A manager's player: only if he puts him on the list." as="Jugador de un técnico: solo si él lo pone en la lista." />
<qg:phrase text="Academy: youths every season." as="Cantera: juveniles cada temporada." />
<qg:phrase text="Asking" as="Pedido" />
<qg:phrase text="Asking price %" as="Precio pedido %" />
<qg:phrase text="Avg" as="Med" />
<qg:phrase text="Back to the round" as="Volver a la jornada" />
<qg:phrase text="Best player" as="Mejor jugador" />
<qg:phrase text="Better this month (+1):" as="Mejoraron en el mes (+1):" />
<qg:phrase text="Worse this month (-1):" as="Empeoraron en el mes (-1):" />
<qg:phrase text="Better in training (+1):" as="Mejoraron en el entrenamiento (+1):" />
<qg:phrase text="CAREER" as="CARRERA" />
<qg:phrase text="Car" as="Car" />
<qg:phrase text="Cheaper: more clubs bid." as="Más barato: más clubes pujan." />
<qg:phrase text="Clubs:" as="Clubes:" />
<qg:phrase text="Cup" as="Copa" />
<qg:phrase text="Div" as="Div" />
<qg:phrase text="Each wins its club" as="Cada uno le da a su club" />
<qg:phrase text="Fm" as="Fm" />
<qg:phrase text="From the academy:" as="De la cantera:" />
<qg:phrase text="Gls" as="Gol" />
<qg:phrase text="Gm" as="PJ" />
<qg:phrase text="Health over 60 first: Maintenance." as="Salud por encima de 60 antes: Mantenimiento." />
<qg:phrase text="How it ended" as="Cómo terminó" />
<qg:phrase text="In crisis: no works." as="En crisis: sin obras." />
<qg:phrase text="It can grow: a third at once," as="Puede crecer: un tercio al contado," />
<qg:phrase text="the rest in seven months." as="el resto en siete meses." />
<qg:phrase text="It is at the top level already." as="Ya está en el nivel máximo." />
<qg:phrase text="Mor" as="Mor" />
<qg:phrase text="No ratings for this match." as="Sin notas para este partido." />
<qg:phrase text="Nt" as="Nt" />
<qg:phrase text="Order" as="Orden" />
<qg:phrase text="PLAYERS" as="JUGADORES" />
<qg:phrase text="Players" as="Jugadores" />
<qg:phrase text="Position" as="Posición" />
<qg:phrase text="Prizes won:" as="Premios ganados:" />
<qg:phrase text="Pts/m" as="Pts/p" />
<qg:phrase text="RATINGS" as="NOTAS" />
<qg:phrase text="Ratings" as="Notas" />
<qg:phrase text="Sack" as="Desp" />
<qg:phrase text="Seas" as="Temp" />
<qg:phrase text="Str: strength, Fm: form, Nt: the rating so far" as="Fue: fuerza, Fm: forma, Nt: la nota hasta ahora" />
<qg:phrase text="THE CLUB" as="EL CLUB" />
<qg:phrase text="The ground needs care (F5): at 60% it cannot grow" as="El estadio necesita cuidado (F5): con 60% no crece" />
<qg:phrase text="The ground needs care first (health over 60): Maintenance." as="El estadio necesita cuidado antes (salud por encima de 60): Mantenimiento." />
<qg:phrase text="The teams" as="Los equipos" />
<qg:phrase text="They cost a month:" as="Cuestan al mes:" />
<qg:phrase text="Tit" as="Tít" />
<qg:phrase text="Top scorer" as="Goleador" />
<qg:phrase text="Training center" as="Centro de entrenamiento" />
<qg:phrase text="Training center:" as="Centro de entrenamiento:" />
<qg:phrase text="Training: a reserve grows" as="Entreno: un suplente mejora" />
<qg:phrase text="of the months." as="de los meses." />
<qg:phrase text="Youth academy" as="Cantera" />
<qg:phrase text="Youth academy:" as="Cantera:" />
<qg:phrase text="Your own player: to sell him, F6 and then S." as="Jugador tuyo: para venderlo, F6 y luego S." />
<qg:phrase text="a match" as="por partido" />
<qg:phrase text="and is worth a third more next season." as="y vale un tercio más la próxima temporada." />
<qg:phrase text="and the prizes of each division" as="y los premios de cada división" />
<qg:phrase text="bids" as="pujas" />
<qg:phrase text="by" as="por" />
<qg:phrase text="came on" as="entró" />
<qg:phrase text="cups" as="copas" />
<qg:phrase text="ended the career" as="terminó la carrera" />
<qg:phrase text="every division" as="todas las divisiones" />
<qg:phrase text="every position" as="todas las posiciones" />
<qg:phrase text="left for another club" as="se fue a otro club" />
<qg:phrase text="level" as="nivel" />
<qg:phrase text="matches" as="partidos" />
<qg:phrase text="mean rating" as="nota media" />
<qg:phrase text="mean rating, ten matches at least" as="nota media, diez partidos como mínimo" />
<qg:phrase text="points" as="puntos" />
<qg:phrase text="sacked" as="despedido" />
<qg:phrase text="still there" as="sigue allí" />
<qg:phrase text="the last month" as="el último mes" />
<qg:phrase text="the whole career" as="la carrera entera" />
<qg:phrase text="times sacked" as="despidos" />
<qg:phrase text="to" as="a" />
<qg:phrase text="strength" as="fuerza" />
<qg:phrase text="rating" as="nota" />
<qg:phrase text="worth" as="valor" />
<qg:phrase text="Prize for the title:" as="Premio por el título:" />
<qg:phrase text="Prize for the runner-up:" as="Premio por el subcampeonato:" />
<qg:phrase text="Prize for the top scorer:" as="Premio por el goleador:" />
<qg:phrase text="Prize for the best player:" as="Premio por el mejor jugador:" />
<qg:phrase text="Training center, level" as="Centro de entrenamiento, nivel" />
<qg:phrase text="Youth academy, level" as="Cantera, nivel" />
</qg:language>
</q:application>RPG
projects/rpg/rpg.q — Godot's "JRPG" demo transcribed: its map in three tile layers, a slime that walks it cell by cell, a key and another slime that talk in a dialogue box, and a turn-based fight (projects/rpg/README.md).
<q:application id="rpg" type="game">
<!-- RPG: Godot's "JRPG" demo (godotengine/godot-demo-projects, 2d/role_playing_game,
MIT), transcribed. A slime walks a grassy map cell by cell, reads a key,
talks to another slime and fights it, turn by turn: Attack, Defend, Flee.
The art and the font are the demo's (assets/LICENSE.md), packed by
scripts/art/rpg.py; README.md says what is where. -->
<!-- How the last fight ended ('' before any), and the cell it was started from. -->
<q:set name="outcome" value="" />
<q:set name="back_col" value="3" type="number" />
<q:set name="back_row" value="4" type="number" />
<!-- Beyond the demo: what the player carries, and what it took from the map, by name. -->
<q:set name="keys" value="0" type="number" />
<q:set name="potions" value="0" type="number" />
<q:set name="taken" value="[]" type="array" />
<!-- The demo's ui_accept: Enter, Space, the joypad's A; and a click. -->
<qg:input action="select" keys="Enter, Space, MouseLeft, JoyA" />
<!-- Every tile of the three layers is a picture of its own; the Grid
layer's are the ones that stop a walker. -->
<!-- beyond the demo: the game in Portuguese and Spanish (a web page's ?lang=pt, the command line's q-lang, or the system's language) -->
<!-- on a phone: what the keys do, under a finger, in every scene -->
<qg:touch-stick x="140" y="580" size="200" />
<qg:language lang="pt" name="Português">
<qg:phrase text="Hey, it's a good time to have a JRPG fight, right?" as="Ei, é uma boa hora para uma luta de JRPG, não acha?" />
<qg:phrase text="Let me introduce myself, I'm your OPPONENT." as="Deixa eu me apresentar: sou o seu OPONENTE." />
<qg:phrase text="Enough talking. Let's fight!" as="Chega de conversa. Vamos lutar!" />
<qg:phrase text="Just a key..." as="Só uma chave..." />
<qg:phrase text="I'll keep it anyway." as="Vou guardar mesmo assim." />
<qg:phrase text="A potion! In a fight, it gives back 5 life." as="Uma poção! Numa luta, ela devolve 5 de vida." />
<qg:phrase text="Congratulations, you won!" as="Parabéns, você venceu!" />
<qg:phrase text="Aha! I won, maybe you can try again next time." as="Rá! Eu venci, quem sabe você tenta de novo outra vez." />
<qg:phrase text="UNKNOWN" as="DESCONHECIDO" />
<qg:phrase text="OPPONENT" as="OPONENTE" />
<qg:phrase text="PLAYER" as="JOGADOR" />
<qg:phrase text="Player" as="Jogador" />
<qg:phrase text="Opponent" as="Oponente" />
<qg:phrase text="Attack" as="Atacar" />
<qg:phrase text="Defend" as="Defender" />
<qg:phrase text="Potion" as="Poção" />
<qg:phrase text="Flee" as="Fugir" />
<qg:phrase text="Resume" as="Continuar" />
<qg:phrase text="Music" as="Música" />
<qg:phrase text="Full screen" as="Tela cheia" />
<qg:phrase text="on" as="sim" />
<qg:phrase text="off" as="não" />
<qg:phrase text="potion" as="poção" />
<qg:phrase text="potions" as="poções" />
<qg:phrase text="key" as="chave" />
<qg:phrase text="keys" as="chaves" />
<!-- the fight's banner, drawn in Portuguese -->
<qg:asset name="banner" src="assets/banner-pt.png" />
</qg:language>
<qg:language lang="es" name="Español">
<qg:phrase text="Hey, it's a good time to have a JRPG fight, right?" as="Oye, es un buen momento para una pelea de JRPG, ¿no crees?" />
<qg:phrase text="Let me introduce myself, I'm your OPPONENT." as="Déjame presentarme: soy tu OPONENTE." />
<qg:phrase text="Enough talking. Let's fight!" as="Basta de charla. ¡A pelear!" />
<qg:phrase text="Just a key..." as="Solo una llave..." />
<qg:phrase text="I'll keep it anyway." as="Me la quedo de todos modos." />
<qg:phrase text="A potion! In a fight, it gives back 5 life." as="¡Una poción! En una pelea, devuelve 5 de vida." />
<qg:phrase text="Congratulations, you won!" as="¡Felicidades, ganaste!" />
<qg:phrase text="Aha! I won, maybe you can try again next time." as="¡Ajá! Gané, tal vez puedas intentarlo de nuevo la próxima vez." />
<qg:phrase text="UNKNOWN" as="DESCONOCIDO" />
<qg:phrase text="OPPONENT" as="OPONENTE" />
<qg:phrase text="PLAYER" as="JUGADOR" />
<qg:phrase text="Player" as="Jugador" />
<qg:phrase text="Opponent" as="Oponente" />
<qg:phrase text="Attack" as="Atacar" />
<qg:phrase text="Defend" as="Defender" />
<qg:phrase text="Potion" as="Poción" />
<qg:phrase text="Flee" as="Huir" />
<qg:phrase text="Resume" as="Continuar" />
<qg:phrase text="Music" as="Música" />
<qg:phrase text="Full screen" as="Pantalla completa" />
<qg:phrase text="on" as="sí" />
<qg:phrase text="off" as="no" />
<qg:phrase text="potion" as="poción" />
<qg:phrase text="potions" as="pociones" />
<qg:phrase text="key" as="llave" />
<qg:phrase text="keys" as="llaves" />
<qg:asset name="banner" src="assets/banner-es.png" />
</qg:language>
<!-- Russian: letters Montserrat has not, so a font that has them (Xolonium, assets/LICENSE.md),
and three plural forms: 1 зелье, 2 зелья, 5 зелий -->
<qg:language lang="ru" name="Русский" font="assets/Xolonium-Regular.ttf">
<qg:phrase text="Hey, it's a good time to have a JRPG fight, right?" as="Эй, самое время для JRPG-драки, правда?" />
<qg:phrase text="Let me introduce myself, I'm your OPPONENT." as="Позволь представиться: я твой ПРОТИВНИК." />
<qg:phrase text="Enough talking. Let's fight!" as="Хватит болтать. В бой!" />
<qg:phrase text="Just a key..." as="Просто ключ..." />
<qg:phrase text="I'll keep it anyway." as="Всё равно возьму." />
<qg:phrase text="A potion! In a fight, it gives back 5 life." as="Зелье! В бою оно возвращает 5 жизни." />
<qg:phrase text="Congratulations, you won!" as="Поздравляю, ты победил!" />
<qg:phrase text="Aha! I won, maybe you can try again next time." as="Ага! Я победил. Может, в следующий раз получится." />
<qg:phrase text="UNKNOWN" as="НЕЗНАКОМЕЦ" />
<qg:phrase text="OPPONENT" as="ПРОТИВНИК" />
<qg:phrase text="PLAYER" as="ИГРОК" />
<qg:phrase text="Player" as="Игрок" />
<qg:phrase text="Opponent" as="Противник" />
<qg:phrase text="Attack" as="Атака" />
<qg:phrase text="Defend" as="Защита" />
<qg:phrase text="Potion" as="Зелье" />
<qg:phrase text="Flee" as="Бежать" />
<qg:phrase text="Resume" as="Продолжить" />
<qg:phrase text="Music" as="Музыка" />
<qg:phrase text="Full screen" as="Полный экран" />
<qg:phrase text="on" as="вкл" />
<qg:phrase text="off" as="выкл" />
<qg:phrase text="potion" as="зелье" />
<qg:phrase text="potions" as="зелий" one="зелье" few="зелья" />
<qg:phrase text="key" as="ключ" />
<qg:phrase text="keys" as="ключей" one="ключ" few="ключа" />
<qg:asset name="banner" src="assets/banner-ru.png" />
</qg:language>
<qg:tileset name="world" src="assets/tiles.png" tile="64" />
<qg:spritesheet name="pawns" src="assets/pawns.png" tile="64" />
<qg:spritesheet name="fighters" src="assets/fighters.png" tile="128" />
<qg:spritesheet name="arena" src="assets/arena.png" tile="1280x720" />
<!-- beyond the demo: a frame for the dialogue box that stretches to it (its 16-pixel edges stay as drawn),
and "FIGHT!" as the fight starts, drawn in each language (scripts/art/rpg_extra.py, CC0) -->
<qg:spritesheet name="frame" src="assets/frame.png" tile="48" slice="16" />
<qg:spritesheet name="banner" src="assets/banner-en.png" tile="480x120" />
<qg:prefab name="Banner" tag="banner" sheet="banner" hitbox="480x120" />
<!-- beyond the demo: music of our own (scripts/art/rpg_music.py, CC0) -->
<qg:sound bus="music" name="meadow" src="assets/meadow.ogg" loop="true" />
<qg:sound bus="music" name="clash" src="assets/clash.ogg" loop="true" />
<qg:prefab name="Opponent" tag="opponent" sheet="pawns" frame="2" hitbox="48x48" />
<qg:prefab name="Key" tag="key" sheet="pawns" frame="3" hitbox="48x48" />
<qg:prefab name="Potion" tag="potion" sheet="pawns" frame="4" hitbox="48x48" />
<!-- ===== Exploration: grid_movement/exploration.tscn ===== -->
<qg:scene name="exploration" width="1280" height="720" background="#62a033">
<qg:music sound="meadow" fade="0.6" volume="-4" />
<qg:tilemap tileset="world">
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0
</qg:tilemap>
<qg:tilemap tileset="world">
0,0,0,0,0,0,0,0,0,0,0,2,3,3,3,4,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,5,6,6,6,7,0,0,0,0
0,0,8,9,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,11,12,4,0,0,13,13,13,13,10,0,0,0,0,0,0,0,0
0,0,11,12,14,0,0,15,15,15,15,14,0,0,0,0,0,0,0,0
0,0,5,16,7,0,0,6,6,6,6,17,0,0,0,8,13,13,12,0
0,0,0,0,0,0,0,0,0,0,0,18,13,19,0,20,12,12,12,0
0,0,0,0,0,0,0,0,0,0,0,20,21,14,0,22,12,12,12,12
0,0,0,0,0,0,0,0,0,0,0,5,23,17,0,24,23,23,12,12
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</qg:tilemap>
<qg:tilemap tileset="world" collision="true">
25,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,27,28
29,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,30,0,31,28
29,0,0,0,0,0,32,33,33,33,34,0,0,0,0,0,0,0,31,28
29,0,0,0,0,0,31,25,26,26,35,0,0,0,0,0,0,32,36,28
29,0,0,0,0,0,31,29,0,0,0,0,0,0,0,0,0,37,38,28
29,0,0,0,0,0,31,39,33,33,34,0,0,0,0,0,0,40,27,28
29,41,0,0,0,0,40,42,26,26,35,0,0,0,0,0,0,0,31,28
43,44,34,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,28
28,28,29,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,36,28
28,28,43,44,44,44,44,34,0,0,0,0,0,0,0,0,0,37,38,28
28,28,28,28,28,28,28,43,44,44,44,44,44,44,44,44,44,36,38,28
</qg:tilemap>
<qg:instance prefab="Key" name="key" x="544" y="288" if="{'key' not in taken}" />
<qg:instance prefab="Potion" name="potion-1" x="96" y="96" if="{'potion-1' not in taken}" />
<qg:instance prefab="Potion" name="potion-2" x="928" y="608" if="{'potion-2' not in taken}" />
<qg:instance prefab="Opponent" x="800" y="480" />
<qg:character id="player" controller="grid" diagonal="true" step-frames="15"
sheet="pawns" frame="0" x="224" y="288" hitbox="48x48">
<qg:animation name="idle" frames="0" fps="1" />
<qg:animation name="walk" frames="0" fps="1" />
<qg:animation name="bump" frames="1" fps="1" />
<qg:on-collision with="key">
<q:set name="taken" value="{taken + [other.name]}" />
<q:set name="keys" value="{keys + 1}" />
<qg:destroy target="other" />
<qg:say dialogue="key" />
</qg:on-collision>
<qg:on-collision with="potion">
<q:set name="taken" value="{taken + [other.name]}" />
<q:set name="potions" value="{potions + 1}" />
<qg:destroy target="other" />
<qg:say dialogue="potion" />
</qg:on-collision>
<qg:on-collision with="opponent">
<q:set name="back_col" value="{me.col}" />
<q:set name="back_row" value="{me.row}" />
<qg:say dialogue="npc" />
</qg:on-collision>
</qg:character>
<!-- dialogue/dialogue_data/*.json -->
<qg:dialogue name="npc" font="assets/montserrat_extra_bold.otf" size="28" box="frame">
<qg:line who="UNKNOWN" text="Hey, it's a good time to have a JRPG fight, right?" />
<qg:line who="UNKNOWN" text="Let me introduce myself, I'm your OPPONENT." />
<qg:line who="OPPONENT" text="Enough talking. Let's fight!" />
<qg:on-end>
<qg:goto-scene name="combat" fade="0.4" />
</qg:on-end>
</qg:dialogue>
<qg:dialogue name="key" font="assets/montserrat_extra_bold.otf" size="28" box="frame">
<qg:line who="PLAYER" text="Just a key..." />
<qg:line who="PLAYER" text="I'll keep it anyway." />
</qg:dialogue>
<qg:dialogue name="potion" font="assets/montserrat_extra_bold.otf" size="28" box="frame">
<qg:line who="PLAYER" text="A potion! In a fight, it gives back 5 life." />
</qg:dialogue>
<!-- What the player carries. -->
<!-- beyond the demo: "1 potion", "2 potions", in the player's language and its plural forms -->
<qg:hud position="top-left" font="assets/montserrat_extra_bold.otf" size="24">
<qg:text value="{str(potions) + ' ' + tr_n('potion', 'potions', potions)}" />
<qg:text value="{str(keys) + ' ' + tr_n('key', 'keys', keys)}" />
</qg:hud>
<qg:dialogue name="won" font="assets/montserrat_extra_bold.otf" size="28" box="frame">
<qg:line who="OPPONENT" text="Congratulations, you won!" />
<qg:on-end><q:set name="outcome" value="" /></qg:on-end>
</qg:dialogue>
<qg:dialogue name="lost" font="assets/montserrat_extra_bold.otf" size="28" box="frame">
<qg:line who="OPPONENT" text="Aha! I won, maybe you can try again next time." />
<qg:on-end><q:set name="outcome" value="" /></qg:on-end>
</qg:dialogue>
<!-- beyond the demo: Escape (or the joypad's B) pauses, with the options; not while someone talks -->
<qg:on-input action="cancel">
<q:if condition="{not talking()}">
<q:if condition="{paused()}"><qg:resume /><q:else><qg:pause /></q:else></q:if>
</q:if>
</qg:on-input>
<qg:menu if="{paused() and not talking()}" size="32">
<qg:button label="Resume"><qg:resume /></qg:button>
<qg:slider label="Music" bus="music" />
<qg:button label="{tr('Full screen') + ': ' + (tr('on') if fullscreen() else tr('off'))}"><qg:fullscreen /></qg:button>
</qg:menu>
<!-- Back from a fight: where it started, and what the opponent says of it. -->
<q:if condition="{outcome != ''}">
<qg:put target="player" x="{back_col * 64 + 32}" y="{back_row * 64 + 32}" />
<q:if condition="{outcome == 'won'}"><qg:say dialogue="won" /></q:if>
<q:if condition="{outcome == 'lost'}"><qg:say dialogue="lost" /></q:if>
</q:if>
</qg:scene>
<!-- ===== Combat: combat/combat.tscn, turn_queue.gd, combatant.gd, health.gd ===== -->
<qg:scene name="combat" width="1280" height="720" background="#4a4a4a">
<qg:music sound="clash" fade="0.4" volume="-6" />
<!-- player.tscn: 10 life, 2 damage, 1 defense, 1 base armor;
opponent.tscn: 7 life, 3 damage, 1 defense, no armor. -->
<q:set name="life" value="10" type="number" />
<q:set name="armor" value="1" type="number" />
<q:set name="foe_life" value="7" type="number" />
<q:set name="foe_armor" value="0" type="number" />
<q:set name="turn" value="player" />
<q:set name="wait" value="0" type="number" /> <!-- the opponent's Timer: 0.25 s -->
<!-- beyond the demo: a fighter just hit makes a face for half a second -->
<q:set name="hurt" value="0" type="number" />
<q:set name="foe_hurt" value="0" type="number" />
<!-- health.gd: a hit takes the damage less the armor. -->
<q:function name="end" params="result">
<q:set name="outcome" value="{result}" />
<qg:goto-scene name="exploration" fade="0.4" />
</q:function>
<q:function name="foes_turn">
<q:set name="turn" value="opponent" />
<q:if condition="{foe_armor >= 0 + 1}"><q:set name="foe_armor" value="0" /></q:if>
<q:set name="wait" value="15" />
</q:function>
<qg:sprite sheet="arena" x="640" y="360" />
<qg:sprite sheet="fighters" frame="4" x="440" y="470" />
<qg:sprite sheet="fighters" frame="4" x="840" y="470" />
<qg:sprite sheet="fighters" frame="{1 if hurt > 0 else 0}" x="440" y="420" />
<qg:sprite sheet="fighters" frame="{3 if foe_hurt > 0 else 2}" x="840" y="420" />
<!-- beyond the demo: FIGHT! (in the player's language) as the scene fades in, and gone a second later -->
<qg:instance prefab="Banner" name="banner" x="640" y="250" />
<qg:timer after="60">
<qg:tween target="banner" property="alpha" to="0" seconds="0.5">
<qg:on-end><qg:destroy target="me" /></qg:on-end>
</qg:tween>
</qg:timer>
<qg:hud position="top-left" font="assets/montserrat_extra_bold.otf" size="32">
<qg:text value="{'Player'}" />
<qg:bar bind="life" max="10" width="320" height="20" color="#d81b60" />
</qg:hud>
<qg:hud position="top-right" font="assets/montserrat_extra_bold.otf" size="32">
<qg:text value="{'Opponent'}" />
<qg:bar bind="foe_life" max="7" width="320" height="20" color="#d81b60" />
</qg:hud>
<!-- ui.gd: the buttons work on the player's turn; beyond the demo, in the dialogues' frame. -->
<qg:menu position="bottom-center" font="assets/montserrat_extra_bold.otf" size="36" box="frame"
if="{turn == 'player'}">
<qg:button label="Attack">
<q:set name="foe_life" value="{foe_life - 2 + foe_armor}" />
<q:set name="foe_hurt" value="30" />
<qg:shake frames="10" strength="6" />
<q:if condition="{foe_life <= 0}"><q:call function="end" args="'won'" /></q:if>
<q:if condition="{foe_life > 0}"><q:call function="foes_turn" /></q:if>
</qg:button>
<qg:button label="Defend">
<q:set name="armor" value="{armor + 1}" />
<q:call function="foes_turn" />
</qg:button>
<!-- beyond the demo: a potion gives back 5 life (health.gd's heal, up to the most) -->
<qg:button label="Potion" if="{potions > 0}">
<q:set name="potions" value="{potions - 1}" />
<q:set name="life" value="{min(10, life + 5)}" />
<q:call function="foes_turn" />
</qg:button>
<qg:button label="Flee">
<q:call function="end" args="'lost'" />
</qg:button>
</qg:menu>
<!-- opponent.gd: a quarter of a second into its turn, it attacks. -->
<qg:timer every="1">
<q:if condition="{hurt > 0}"><q:set name="hurt" value="{hurt - 1}" /></q:if>
<q:if condition="{foe_hurt > 0}"><q:set name="foe_hurt" value="{foe_hurt - 1}" /></q:if>
<q:if condition="{turn == 'opponent' and wait > 0}">
<q:set name="wait" value="{wait - 1}" />
<q:if condition="{wait == 0}">
<q:set name="life" value="{life - 3 + armor}" />
<q:set name="hurt" value="30" />
<qg:shake frames="10" strength="6" />
<q:if condition="{life <= 0}"><q:call function="end" args="'lost'" /></q:if>
<q:if condition="{life > 0}">
<q:set name="turn" value="player" />
<q:if condition="{armor >= 1 + 1}"><q:set name="armor" value="1" /></q:if>
</q:if>
</q:if>
</q:if>
</qg:timer>
</qg:scene>
</q:application>