Welcome to FirstBattle

A coding championship where your API fights for gold.

Important rule: you are free to use AI tools (ChatGPT, Claude, Copilot, etc.) to help write your code. However, your bot must not call an AI service at runtime to decide its moves or allocations. The strategy must be coded in your program — no sending game state to an LLM and asking it what to do next.

What is this?

FirstBattle is a live, long-running game where your HTTP API controls a character on a grid. Your character collects gold from mines, fights other players, and tries to climb the leaderboard. The game runs continuously on the server — even when you close your browser. You watch the live board, think, improve your code, and deploy. Better strategy = more gold.

Quick Start

  1. Build an HTTP server (any language) that handles two POST endpoints: /move and /allocate
  2. Deploy it so it's publicly reachable (Replit, Render, a VPS, etc.)
  3. Give your base URL to the game admin (e.g. https://my-bot.example.com)
  4. Watch your character on the live game board and iterate!

Game World

Movement

Combat

Death & Rewards

When a character dies:

  1. Death penalty — the victim loses a portion of their gold as a penalty. This gold is destroyed (removed from the game, not given to anyone).
  2. Gold theft — the killer steals a percentage of the victim's remaining gold.
  3. Kill bounty — the killer receives a bonus bounty (generated gold, not taken from the victim).
  4. Leader bounty — if the victim is the richest player (the "leader"), the kill bounty is significantly increased. The leader is marked with a crown ♚ on the game board.
  5. If multiple killers are adjacent, theft and bounty are split equally between them.
  6. The victim respawns immediately at a random location with full HP.

The bottom line: killing pays well, but dying is costly. Choose your fights wisely.

Tax

Periodically, players with gold above the average are taxed on the excess. This keeps the economy from running away and gives trailing players a chance to catch up. Tax deductions are shown in the leaderboard.

Suspension & Inactivity

Your bot must stay online and respond correctly. If your API fails, you will be suspended:

Keep your bot running and healthy! Fix errors quickly to avoid losing your spot.

Attributes

You have 100 allocation points to distribute across 4 attributes. The /allocate endpoint is called once per minute, and your new allocation takes effect immediately.

Every attribute has a base value — even with 0 allocation, you still have some HP, attack, defense, and vision. Your computed stats (after scaling) are sent to you in every /move request in the stats field, so you always know your actual values.

Vision determines what you can see. Mines and enemies within your vision radius appear in the visibleMines and visibleEnemies arrays. You cannot see anything beyond your vision radius.

API Reference

POST /move — called every round (2–5 seconds)

The game server POSTs your bot's current state. You must return a movement decision within 2 seconds.

Request body (JSON): { "round": 142, "gridWidth": 30, "gridHeight": 20, "totalMines": 4, "mineGoldBase": 50, "moveCost": 1.56, "position": { "x": 12, "y": 8 }, "hp": 63, "maxHp": 350, "gold": 340, "stats": { "hp": 350, "attack": 35, "defense": 30, "vision": 17 }, "visibleMines": [ { "id": 3, "x": 14, "y": 9, "gold": 75 }, { "id": 7, "x": 10, "y": 6, "gold": 120 } ], "visibleEnemies": [ { "playerId": 2, "x": 15, "y": 8, "hp": 40, "gold": 210 } ] } Response body (JSON): { "direction": "right" } Fields: round — current game round number gridWidth — board width (x: 0 to gridWidth-1) gridHeight — board height (y: 0 to gridHeight-1) totalMines — number of mines currently on the board mineGoldBase — base gold value for mines (actual mine gold varies) moveCost — gold cost per step this round position — your {x, y} on the grid (0-indexed) hp / maxHp — your current and maximum HP gold — your current gold (can be negative) stats — your computed stats (from your allocation) visibleMines — mines within your vision radius visibleEnemies — enemies within your vision radius (playerId, position, hp, gold) Response fields: direction — "up" | "down" | "left" | "right" Notes: • If your bot times out (>2s) or returns invalid JSON, a random direction is chosen for you. • If you are in combat, /move is NOT called (you can't move). • "up" = y-1, "down" = y+1, "left" = x-1, "right" = x+1.
POST /allocate — called once per minute

Redistribute your 100 attribute points. The new allocation takes effect immediately (stats are recomputed).

Request body (JSON): { "round": 300, "gold": 540, "currentAllocation": { "hp": 25, "attack": 30, "defense": 20, "vision": 25 } } Response body (JSON): { "hp": 25, "attack": 30, "defense": 15, "vision": 30 } Rules: • Values must sum to exactly 100. • Each value must be an integer ≥ 0. • If the response is invalid or times out (>3s), your current allocation is kept unchanged.

Minimal Bot Example (Node.js)

Copy this, deploy it, and give the URL to the admin. It's a simple greedy miner that goes for the richest visible mine and flees when low on HP.

server.js var http = require('http'); http.createServer(function (req, res) { var chunks = []; req.on('data', function (c) { chunks.push(c); }); req.on('end', function () { var body = JSON.parse(Buffer.concat(chunks).toString()); var result; if (req.url === '/move') { result = decideMove(body); } else if (req.url === '/allocate') { result = { hp: 20, attack: 15, defense: 15, vision: 50 }; } else { res.writeHead(404); return res.end(); } var out = JSON.stringify(result); res.writeHead(200, { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(out) }); res.end(out); }); }).listen(process.env.PORT || 3000); function decideMove(state) { // Flee if low HP and enemies nearby if (state.hp < state.maxHp * 0.3 && state.visibleEnemies.length > 0) { var enemy = state.visibleEnemies[0]; return flee(state.position, enemy); } // Go to richest visible mine var mines = state.visibleMines .sort(function (a, b) { return b.gold - a.gold; }); if (mines.length > 0) { return moveToward(state.position, mines[0]); } // Wander randomly var dirs = ['up', 'down', 'left', 'right']; return { direction: dirs[Math.floor(Math.random() * 4)] }; } function moveToward(pos, target) { var dx = target.x - pos.x; var dy = target.y - pos.y; if (Math.abs(dx) >= Math.abs(dy)) { return { direction: dx > 0 ? 'right' : 'left' }; } return { direction: dy > 0 ? 'down' : 'up' }; } function flee(pos, enemy) { var dx = pos.x - enemy.x; var dy = pos.y - enemy.y; if (Math.abs(dx) >= Math.abs(dy)) { return { direction: dx >= 0 ? 'right' : 'left' }; } return { direction: dy >= 0 ? 'down' : 'up' }; }

Strategy Tips