🐷 Pig Arena

Write a bot

Your bot does two things in a loop: ask the server if it is your turn, and answer with roll or hold. The server rolls all the dice. You never roll anything yourself.

   ┌─────────────────────────────┐
   │  GET /decision?token=TOKEN  │ ── "waiting"? ask again
   └──────────────┬──────────────┘
                  │ a decision
   ┌──────────────▼──────────────┐
   │  POST /move  roll | hold    │ ── tells you what you rolled
   └──────────────┬──────────────┘
                  └──── repeat ────┘

1. Get a token

Do this once. The token is your bot's password — anyone who has it can play your games for you, so do not paste it into the group chat.

curl -X POST https://pig.ides.club/register \
     -H 'Content-Type: application/json' \
     -d '{"name": "ada"}'
{"ok": true, "id": "ada", "token": "PASTE_YOUR_TOKEN"}

Or just use the Play tab, which registers you and remembers the token.

2. Ask for a decision

GET /decision?token=TOKEN

Most of the time it is not your turn, and you get this:

{"status": "waiting"}

When it is your turn:

{
  "status": "decide",
  "decision_id": "g17-t9-d3",
  "game_id": "g17",
  "my_score": 41,
  "opponent_score": 55,
  "turn_total": 14,
  "rolls_this_turn": [4, 6, 4],
  "target": 100,
  "deadline_ms": 5000
}
fieldmeans
decision_idsend this back with your move. It is different every time
my_scorepoints you have already banked
opponent_scorepoints they have banked
turn_totalpoints riding on this turn. You lose these if you roll a 1
rolls_this_turnwhat you have rolled so far this turn
targetpoints needed to win, normally 100
deadline_msmilliseconds left to answer this decision

Add &wait=10s and the server holds the connection open until there is something to tell you, instead of saying waiting straight away. That is kinder than asking over and over, but either way works.

3. Send your move

curl -X POST https://pig.ides.club/move \
     -H 'Content-Type: application/json' \
     -d '{"token": "TOKEN",
          "decision_id": "g17-t9-d3",
          "move": "roll"}'

move is exactly "roll" or "hold". The reply tells you what happened:

{
  "ok": true,
  "rolled": 5,
  "bust": false,
  "turn_total": 19,
  "turn_over": false,
  "my_score": 41,
  "opponent_score": 55,
  "game_over": false
}
fieldmeans
rolledthe die you just rolled. Absent if you held
busttrue if you rolled a 1 and lost the turn total
turn_overtrue if your turn ended, by busting or holding
game_overtrue when someone reached the target. winner says who

If something is wrong you get {"ok": false, "error": "..."} with an HTTP error code. The usual causes are a decision_id that has expired, one you already answered, or one from a different game.

Rules your bot has to live with

  • One decision at a time. If you are in six games at once you are asked about them one at a time, in the order they came in. Answer the one you are given and the next one appears.
  • Answer before deadline_ms. Too slow counts as a hold. The clock only starts when you actually pick the decision up, so waiting in a queue never costs you time.
  • Three misses in a row and you forfeit that game. Answering anything resets the count.
  • The decision_id must match. You cannot answer twice, answer late, or answer a decision that was never yours.

No JSON? Use CSV

Add &format=csv to any route and you get a header row plus data rows, which is easier in some languages — and in bash.

GET /decision?token=TOKEN&format=csv

status,decision_id,game_id,my_score,opponent_score,turn_total,rolls_this_turn,target,deadline_ms
decide,g17-t9-d3,g17,41,55,14,4|6|4,100,5000

rolls_this_turn is joined with | so it does not break the columns. A waiting reply is just status then waiting. Moves work over GET too, so this is a legal move:

curl -X POST 'https://pig.ides.club/move?token=TOKEN&decision_id=g17-t9-d3&move=hold&format=csv'

ok,rolled,bust,turn_total,turn_over,my_score,opponent_score,game_over,winner
true,0,false,19,true,60,55,false,

Practice against a robot

Start a game whenever you like — you do not have to wait for a cousin.

curl -X POST 'https://pig.ides.club/practice?token=TOKEN&bot=house'

Opponents: loading.... Add &first=bot to let it go first. Then poll /decision exactly as you would in a real game.

A whole bot, to copy

Python

import time, json, urllib.request

SERVER = "https://pig.ides.club"
TOKEN  = "PASTE_YOUR_TOKEN"

def call(path, data=None):
    body = json.dumps(data).encode() if data else None
    req = urllib.request.Request(SERVER + path, body,
                                 {"Content-Type": "application/json"})
    return json.load(urllib.request.urlopen(req))

def think(d):
    """Return "roll" or "hold". Change THIS."""
    if d["my_score"] + d["turn_total"] >= d["target"]:
        return "hold"                      # take the win
    return "hold" if d["turn_total"] >= 20 else "roll"

while True:
    d = call("/decision?token=" + TOKEN + "&wait=10s")
    if d["status"] != "decide":
        continue
    r = call("/move", {"token": TOKEN,
                       "decision_id": d["decision_id"],
                       "move": think(d)})
    if r.get("game_over"):
        print("game over:", r["winner"], "wins")

Everything interesting is in think(). That is the only part worth changing, and it is the whole tournament.

Every route

routewhat it does
POST /register{"name"} → your token
GET /decision?token= → a decision or waiting
POST /move{"token","decision_id","move"} → what happened
POST /practice?token=&bot= → start a game against a robot
GET /botsthe robots you can practice against
GET /gamesevery game. ?player=ada to filter
GET /games/{id}one game, every roll of it
GET /leaderboardwho is winning