How the arena works
The game: Paper.io, 4 players
You start with a 3×3 square of territory in one corner of a 40×40 board. Every tick, all four bots move one cell at the same time.
- Trails. Outside your territory you leave a trail behind you. Step back onto your own territory and the trail, plus everything it enclosed, becomes yours.
- Cutting. If anyone steps onto a trail, the trail's owner dies. That includes your own trail: crossing it kills you.
- Collisions. Two heads on the same cell: both die. Driving off the board: you die.
- Death. A dead player's territory and trail vanish immediately.
- Reversing (e.g. LEFT while moving RIGHT) is ignored; you keep going straight. Before your first move you face the centre of the board.
- End. After 300 ticks or when one player is left. Survivors rank by territory, then the eliminated rank by how long they lasted.
- Timeouts.
get_move()has 1 second of wall-clock time per tick. Go over once and your bot is frozen for the rest of that game: it keeps driving straight. Crashes inget_move()also count as "keep going straight" (the error shows in your console).
Rounds, points and the finals
Each team starts with 100 points. Roughly every 20 minutes the admin opens a round: a menu of games appears, each seating four bots. You pick one game per round, keep editing until the round locks, and then every game is played with the code you had saved at lock time. Empty seats are filled by house bots, which never earn points.
Points are the currency and the ranking: you spend them to enter higher-stakes tables and you earn them by placing well. When regular rounds end, points decide the seeds for the bracketed finals (seed 1 is the leader). Finals matches are four-player; the top two from each match advance until one bot remains.
| Game | Entry | Payout |
|---|---|---|
| Scrimmage Free to enter, nothing to win. Use it to test your bot against real teams instead of house bots. |
free | 1st +0 · 2nd +0 · 3rd +0 · 4th +0 |
| Casual Costs nothing. Everyone walks away with something; the winner gets the most. |
free | 1st +12 · 2nd +6 · 3rd +3 · 4th +1 |
| Ranked Entry fee of 10 points. First place earns 40, second 15, third 5. Fourth loses the fee. |
10 | 1st +40 · 2nd +15 · 3rd +5 · 4th +0 |
| High Stakes Entry fee of 30 points. First place earns 120, second 40, everyone else loses their fee. |
30 | 1st +120 · 2nd +40 · 3rd +0 · 4th +0 |
| Double or Nothing Entry fee of 25 points. The winner gets 50 back; nobody else gets anything. |
25 | returns 2× / 0× / 0× / 0× of your stake |
| Jeopardy You choose the wager (at least 5 points, up to everything you have). First place triples it, second place keeps it, third and fourth lose it. |
wager ≥ 5 | returns 3× / 1× / 0× / 0× of your stake |
| Winner Takes All Entry fee of 15 points from every team. The top finishing team takes 100% of the pot. House bots never win the pot. |
15 | pot split 100% / 0% / 0% / 0% (teams only) |
Entry fees are deducted when you join and refunded if you leave before the round locks. "Returns ×2" means the winner receives twice the stake back (net +1×).
Writing your bot
Define a class Bot with a get_move(self, state) method that returns "UP", "DOWN", "LEFT" or "RIGHT". __init__ runs once per game, so store memory on self. print() goes to your console. Plain Python 3 only (no pip packages); random, math, collections etc. are fine.
class Bot:
name = "Lawnmower"
def __init__(self):
self.turns = 0
def get_move(self, state):
me = state.me
for direction in state.legal_moves():
r, c = state.next_cell(me.row, me.col, direction)
if state.is_safe(r, c):
return direction
return "UP"
GameState reference
| Member | Meaning |
|---|---|
| state.me | Your Player |
| state.opponents | Living opponents (Player list) |
| state.players | All four players, including dead ones |
| state.rows, state.cols | Board size (40 × 40) |
| state.tick, state.max_ticks, state.ticks_left | Clock |
| state.grid[r][c] | Owner id of a cell, 0 if unclaimed |
| state.trails[r][c] | Id of the player whose trail is on the cell, 0 if none |
| state.owner(r, c) | Same as grid, but returns None off the board |
| state.trail_owner(r, c) | Same as trails, None off the board |
| state.is_mine(r, c) | True if you own the cell |
| state.in_my_territory() | True if your head is on your territory |
| state.is_safe(r, c) | On the board and no trail on it |
| state.in_bounds(r, c) | Cell is on the board |
| state.legal_moves() | Directions that stay on the board and are not a reversal |
| state.next_cell(r, c, direction) | The cell one step away |
| state.neighbors(r, c) | [(direction, r, c), ...] for in-bounds neighbours |
| state.distance(r1, c1, r2, c2) | Manhattan distance |
| state.cells_owned_by(id) / state.my_cells() | List of (r, c) |
| state.nearest_my_cell(r=None, c=None) | Closest cell you own (to your head by default) |
| state.direction_toward(r, c) | A legal direction that gets you closer |
Player
| id, name | Id 1–4 (matches grid values) and display name |
| row, col, position | Head position; position is (row, col) |
| direction | Last direction moved, None before the first tick |
| alive, death_tick | Status |
| territory | Cells owned |
| trail, trail_length | Current trail cells, oldest first |
| kills | Opponents this player has cut |
Globals available without importing: DIRECTIONS, DELTAS (direction → (dr, dc)), OPPOSITE. Coordinates are (row, col); row 0 is the top, col 0 is the left. UP is row − 1.