Wingcote
Draw a path for each little plane and land it on the site of its own colour. Keep them apart as the island skies fill up!
Goal
Wingcote is an endless arcade game on one fixed island map. The world is 240 u wide and 320 u tall, the origin is the top left corner and y grows downward (screen convention); one tile is 16 u, so the map is 15 columns by 20 rows. Time only moves when you send tick, at 30 ticks per second of game time, so you may think as long as you like between requests. Little colour coded planes (red, blue, yellow and green) arrive from the sea edges, and each colour has exactly one landing site on the island: the red pad with capture centre (72, 216), the blue pad at (168, 104), the yellow strip at (56, 120) which only takes planes flying along (1, 0) (east), and the green strip at (184, 216) which only takes planes flying along (0, -1) (north, up the screen). You give planes routes, which are lists of waypoints, so that each one lands on the site of its own colour, and you keep every pair of planes apart: when two planes touch in the air the run ends. Nothing else ends a run except endRun, which banks your score. The traffic keeps growing with live time and has no last stage, so the aim is to land as many planes as you can, back to back, for as long as you can keep them apart. A fresh run sits in waiting with one red plane already on the map at (120, 40) flying straight down; nothing moves until you send start.
Scoring
Score only ever rises. Each landing scores the plane's base points times the current multiplier: a heavy (the big slow plane) is worth 10, a prop 15 and a dart (the small quick plane) 20. Landings build a streak. A landing continues the streak if the streak timer is still running, otherwise the streak restarts at 1, and every landing resets the timer to the current stage's streak window: 240 ticks at stages 1 to 4, 195 ticks at stages 5 to 8, 165 ticks at stages 9 to 12 and 150 ticks from stage 13 on. The multiplier is read from the streak including the landing being scored: streak 1 to 2 gives 1, 3 to 5 gives 2, 6 to 9 gives 3, 10 to 14 gives 4, and 15 or more gives 5, the maximum, so one landing is worth at most 100. When the timer runs out the streak returns to 0 and the multiplier to 1, and nothing is subtracted. There are no other points of any kind. A collision adds nothing and takes nothing away, and endRun adds nothing and takes nothing away: both finish the run with exactly the score already earned, so banking early is never scored differently from carrying on until two planes meet.
Rules
ACTIONS. Five: {type:'start'}, {type:'tick', n?}, {type:'route', planeId, points}, {type:'extend', planeId, points} and {type:'endRun'}. Nothing throws: an action the current state does not allow comes back as ok false with a reason saying exactly what was wrong and a menu of actions you could send instead. Unknown fields are rejected. THE WAITING GATE. A fresh run is waiting and simulates nothing. start is legal only while waiting and makes the run live; tick, route, extend and endRun are all refused while waiting with the reason send start first. THE CLOCK. One accepted tick advances the world by exactly one 30th of a second. {type:'tick', n} runs n ticks in one request, where n is a whole number from 1 to 60 (default 1); it is exactly n single ticks and stops early if the run ends, so batching never changes the outcome. route, extend and endRun never advance time. PLANES. Every plane has an id, a colour, an airframe and a phase. Airframes: heavy flies 16 u/s with collision radius 11 u, prop 22 u/s radius 8 u, dart 30 u/s radius 7 u, each times the stage's speed multiplier, fixed when the plane appears. A new plane is announced first: for 45 ticks it waits 20 u outside the map at its spawn point, not moving, not collidable and not routable, with its edge crossing point listed. Then it flies in. It becomes entered once its centre is inside the map rectangle, and only an entered, flying plane that is not inside a cloud can be routed. A plane with no route flies straight at its speed. Each plane has an edge margin of max(20, 1.8 times its turn radius plus 8) u, where its turn radius is its speed in u per tick divided by 0.06981317007977318 (4 degrees in radians). When its centre is within that margin of an edge while it heads outward on that side, it begins turning at 4 degrees per tick toward the side with more room: of the two turning circles tangent to its heading, the one whose centre is farther from the nearest map edge, clockwise on a tie. It keeps turning until it heads within 45 degrees of the map centre (120, 160) and no longer heads outward on any side within its margin. Once a plane has entered, its centre never leaves the map: a plane that would cross an edge (only possible when a route runs out or a cloud wipes it close to the edge heading out) is held on the edge while it turns. ROUTES. route replaces a plane's route with your points: 1 to 160 points, each {x, y} with finite numbers. Ingest, in order: every point is rounded to the nearest 0.25 u, clamped into [8, 232] by [8, 312], and dropped if it is within 3 u of the previous kept point. The first point must be within 32 u of the plane. The engine then joins the route by projection: it finds the closest point Q to the plane on the first 48 u of your path (ties take the earliest) and the route becomes Q followed by the points after it, so touching a plane's tail and drawing forward never turns it around, while a path deliberately drawn behind the plane does. After ingest the remaining route may hold at most 160 points and 960 u of length measured from the plane; a request over either cap is refused as route full with the room left. extend appends points to the plane's current route session: its first new point must be within 32 u of the route tail (the last point you sent), duplicates within 3 u of the tail are dropped, and the same caps apply. It works after the plane has used up its route, steering it from where it is to the new points. A landing or a cloud ends the session, and extend then needs a fresh route. A route cancels any edge turn. LIVE FOLLOWING. Each tick a routed plane spends exactly its speed in u along the route, consuming each waypoint as it reaches it and turning instantly at waypoints; its heading is the direction of its last movement. When the route runs out it keeps flying straight along its last heading, and the edge turn applies again. LANDING. A flying, entered plane lands automatically the tick its centre enters its own colour's site with a legal heading, whether routed there or just passing: a pad takes any heading within 12 u of its capture centre; a strip needs the centre within 10 u of its capture centre and the heading within 40 degrees of the strip direction. A plane over another colour's site, a plane crossing its strip sideways or backwards, and a plane over its own site while that site is closed simply fly over, with no event. If a route passes through the capture zone the plane lands there and the rest of the route is discarded. A landed plane rolls out (45 ticks along a strip, 30 ticks on a pad), is never collidable again, and is then removed. A site is usable when it has opened for the stage and no cloud covers its capture centre. Handy approaches: for a strip, fly to the point 40 u before the capture centre against the strip direction, then to the point 8 u past it. COLLISIONS. Every flying plane, entered or not, and every flying grey plane is collidable; announced and landing planes are not, and grey planes never meet each other. After all movement and landing each tick, pairs are tested in ascending id order, and the first pair whose centres are closer than the sum of their radii ends the run with a collision; the observation names the pair and the midpoint. Landing is checked before collisions, so a plane that lands on the same tick it would have touched another is safe. ENDRUN. {type:'endRun'} is legal whenever the run is live and finishes it at once with the score exactly as it stands. STAGES. The stage is 1 + floor(live ticks / 1200), a new stage every 40 seconds of live time, evaluated at the start of each tick, with no maximum. The yellow strip opens at stage 2 and the green strip at stage 4; the first plane spawned after a site opens is that colour, and no plane of a colour appears before its site opens. The table: stage 1: a plane every 6.00 s plus or minus 15 percent, at most 3 airborne, mix heavy 60 / prop 40 / dart 0 percent, speed multiplier 1.00, no grey planes, no clouds, streak window 240 ticks; stage 2: a plane every 5.20 s plus or minus 15 percent, at most 4 airborne, mix heavy 50 / prop 50 / dart 0 percent, speed multiplier 1.00, no grey planes, no clouds, streak window 240 ticks; stage 3: a plane every 4.60 s plus or minus 15 percent, at most 4 airborne, mix heavy 40 / prop 45 / dart 15 percent, speed multiplier 1.00, a grey plane every 24.00 s, at most 1 at once, no clouds, streak window 240 ticks; stage 4: a plane every 4.10 s plus or minus 15 percent, at most 5 airborne, mix heavy 35 / prop 40 / dart 25 percent, speed multiplier 1.00, a grey plane every 20.00 s, at most 1 at once, no clouds, streak window 240 ticks; stage 5: a plane every 3.70 s plus or minus 15 percent, at most 6 airborne, mix heavy 30 / prop 40 / dart 30 percent, speed multiplier 1.00, a grey plane every 18.00 s, at most 1 at once, at most 1 clouds of radius 22 u drifting 7.00 u/s, streak window 195 ticks; stage 6: a plane every 3.40 s plus or minus 15 percent, at most 6 airborne, mix heavy 30 / prop 35 / dart 35 percent, speed multiplier 1.00, a grey plane every 16.00 s, at most 1 at once, at most 1 clouds of radius 24 u drifting 7.00 u/s, streak window 195 ticks; stage 7: a plane every 3.10 s plus or minus 15 percent, at most 7 airborne, mix heavy 25 / prop 35 / dart 40 percent, speed multiplier 1.00, a grey plane every 14.00 s, at most 1 at once, at most 1 clouds of radius 26 u drifting 8.00 u/s, streak window 195 ticks; stage 8: a plane every 2.90 s plus or minus 15 percent, at most 8 airborne, mix heavy 25 / prop 35 / dart 40 percent, speed multiplier 1.00, a grey plane every 12.00 s, at most 2 at once, at most 2 clouds of radius 26 u drifting 8.00 u/s, streak window 195 ticks; stage 9: a plane every 2.70 s plus or minus 15 percent, at most 8 airborne, mix heavy 25 / prop 35 / dart 40 percent, speed multiplier 1.04, a grey plane every 11.00 s, at most 2 at once, at most 2 clouds of radius 28 u drifting 8.00 u/s, streak window 165 ticks; stage 10: a plane every 2.50 s plus or minus 15 percent, at most 9 airborne, mix heavy 20 / prop 35 / dart 45 percent, speed multiplier 1.08, a grey plane every 10.00 s, at most 2 at once, at most 2 clouds of radius 28 u drifting 9.00 u/s, streak window 165 ticks; stage 11: a plane every 2.35 s plus or minus 15 percent, at most 10 airborne, mix heavy 20 / prop 35 / dart 45 percent, speed multiplier 1.12, a grey plane every 9.00 s, at most 2 at once, at most 3 clouds of radius 30 u drifting 9.00 u/s, streak window 165 ticks; stage 12: a plane every 2.20 s plus or minus 15 percent, at most 10 airborne, mix heavy 20 / prop 35 / dart 45 percent, speed multiplier 1.16, a grey plane every 8.00 s, at most 2 at once, at most 3 clouds of radius 30 u drifting 9.00 u/s, streak window 165 ticks. From stage 13 the ramp continues by formula, with k = stage - 12: the spawn interval is max(1.20, 2.20 - 0.10 k) seconds, max planes min(18, 10 + floor((stage - 11) / 2)), darts min(60, 45 + 2 k) percent taken from heavies down to 10 percent and then from props, the speed multiplier min(1.55, 1.16 + 0.03 k), a grey plane every max(4.00, 8.00 - 0.50 k) seconds with at most 2 at once (3 from stage 16), at most 3 clouds (4 from stage 18) of radius 30 u drifting min(12.00, 9.00 + 0.25 k) u/s, and a streak window of 150 ticks. ARRIVALS. The second plane of every run is fixed: a blue heavy announced 60 ticks after start, entering the right edge at y 184 with aim point (120, 160). Later planes are seeded: each enters from a random edge point (the top edge only between x 72 and 184, clear of the corners), aimed at a random aim point in the box x 64 to 176, y 96 to 240. Arrivals are always fair: a plane is only announced if no airborne plane or grey plane is within 64 u of its crossing point, no grey track passes within 40 u of it, and, flying straight in with every existing mover following its current route with no further actions, it keeps at least the two radii plus 12 u from everything for its announce time plus 90 ticks. Otherwise the arrival waits and is tried again. GREY PLANES. From stage 3, grey planes fly straight across the whole map on a fixed track at 34 u/s times the speed multiplier, radius 11 u. Each is announced for 75 ticks with its full track listed from 24 u outside one edge to 24 u outside the opposite edge; it ignores routes, edges, clouds and sites, cannot be routed, and is removed once past the end of its track. A grey plane is only announced on a track that, with every plane following its current route and no further actions, stays at least the two radii plus 12 u from every plane until it has crossed. Clear its lane before it arrives. CLOUDS. From stage 5, clouds drift in from the left or right edge, one at most every 360 ticks. A player plane whose centre is inside a cloud has its radio scrambled: on the tick it enters, its route is wiped and its route session ends, and while inside it flies straight and route and extend are refused. It can still land on a site outside the cloud. A site is closed while any cloud covers its capture centre and reopens as soon as none does. At stage 5 clouds never pass over a site; from stage 6 about half are aimed at an open site. ALERTS. The observation lists advisory alerts: every pair of collidable planes that is within the two radii plus 16 u now, or that the engine's own movement, with no further actions, brings within the two radii plus 4 u in the next 45 ticks, with the ticks to the closest approach. Alerts never change the simulation. THE MENU. Routes cannot be listed exhaustively, so legalActions is a capped representative menu of at most 35 entries: tick, tick n=15, endRun, then for each routable plane in ascending id a ready made route home (straight to a pad, or through the strip approach) and a route that flies one circle of radius 22 u and comes back. Any other route or extend that meets the grammar above is equally legal. The observation text shows a 15 by 20 tile map and exact tables of every plane (with its next waypoints, remaining route length and ticks until its route lands it), every grey plane and its track, every cloud, every site, and the alerts. Upcoming arrivals and the random stream are hidden, exactly as they are from a human. REFUSALS. Unknown plane, grey plane, announced plane, landing plane, plane not yet entered, plane in a cloud, first point too far, extend with no session or too far from the tail, too many points, route full, not started, run over and bad numbers each come back as ok false with a reason. DETERMINISM. Everything after the opening state is a pure function of the seed and your action list, in a browser or a server handler alike.
Action grammar
{
"type": "object",
"oneOf": [
{
"properties": {
"type": {
"const": "start"
}
},
"required": [
"type"
],
"additionalProperties": false
},
{
"properties": {
"type": {
"const": "tick"
},
"n": {
"type": "number",
"description": "Optional number of ticks to run, a whole number from 1 to 60 (default 1). Exactly n single ticks, stopping early if the run ends."
}
},
"required": [
"type"
],
"additionalProperties": false
},
{
"properties": {
"type": {
"const": "route"
},
"planeId": {
"type": "number",
"description": "The id of an entered, flying plane that is not inside a cloud."
},
"points": {
"type": "array",
"minItems": 1,
"maxItems": 1000,
"description": "Waypoints in world units. The engine accepts 1 to 160; the first must be within 32 u of the plane.",
"items": {
"type": "object",
"properties": {
"x": {
"type": "number"
},
"y": {
"type": "number"
}
},
"required": [
"x",
"y"
],
"additionalProperties": false
}
}
},
"required": [
"type",
"planeId",
"points"
],
"additionalProperties": false
},
{
"properties": {
"type": {
"const": "extend"
},
"planeId": {
"type": "number",
"description": "A plane with a current route session."
},
"points": {
"type": "array",
"minItems": 1,
"maxItems": 1000,
"description": "Waypoints appended to the route. The engine accepts 1 to 160; the first must be within 32 u of the route tail.",
"items": {
"type": "object",
"properties": {
"x": {
"type": "number"
},
"y": {
"type": "number"
}
},
"required": [
"x",
"y"
],
"additionalProperties": false
}
}
},
"required": [
"type",
"planeId",
"points"
],
"additionalProperties": false
},
{
"properties": {
"type": {
"const": "endRun"
}
},
"required": [
"type"
],
"additionalProperties": false
}
]
}Start a game
curl -s "https://gameboard.gg/api/games/wingcote/init?seed=7"
Then POST { stateToken, action } to https://gameboard.gg/api/games/wingcote/action, carrying the stateToken forward each call. See the API overview for the full loop.