| 1 | """A live room (docs/games.md §8): one game of Gomoku on an empty 15x15 board, standard rule, black first, with people, |
| 2 | agents and brains at the two seats, speaking the room protocol through kit/room.py. A brain (a route's or a field |
| 3 | folder's, as in the checks) is told the whole position by BOARD before each move, 1 s a move; a seat on auto, a person |
| 4 | out of time and a brain that fails are played by the field's threats brain. At the end the game is left in replays/ in |
| 5 | the practice replays' format with a matches.json entry, so it lists as a match. |
| 6 | |
| 7 | SS_ROOM='{"seats": {"0": {"kind": "human", "name": "ada"}, "1": {"kind": "bot", "field": "threats"}}}' \ |
| 8 | python3 -I referee/room.py |
| 9 | |
| 10 | Messages (a point is "x,y", 0-based, x across): |
| 11 | event {type: start, size, seats} |
| 12 | request {to, version, obs: {seat, colour, size, moves: [[x, y, seat]], last}, legal: every empty point} |
| 13 | event {type: move, seat, x, y} |
| 14 | event {type: over, winner, reason, five}, then end {result, scores} |
| 15 | """ |
| 16 | import os, queue, sys |
| 17 | |
| 18 | ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) |
| 19 | sys.path[:0] = [os.path.join(ROOT, 'kit'), os.path.join(ROOT, 'referee')] |
| 20 | from room import Room, Stop |
| 21 | import league |
| 22 | from gomoku import Brain, base, run_of |
| 23 | from play import brain |
| 24 | |
| 25 | SIZE, TURN = 15, 1.0 |
| 26 | COLOUR = {'0': 'black', '1': 'white'} |
| 27 | |
| 28 | |
| 29 | class Seat: |
| 30 | """A brain at a seat: started once, told the position by BOARD for each move (1 its own stones, 2 the other's).""" |
| 31 | def __init__(self, folder): |
| 32 | self.b = Brain(*brain(folder)) |
| 33 | self.dead = False |
| 34 | try: |
| 35 | self.b.send(f'START {SIZE}', f'INFO timeout_turn {int(TURN * 1000)}', 'INFO timeout_match 0', 'INFO rule 1') |
| 36 | if not self.b.reply(5).upper().startswith('OK'): |
| 37 | raise ValueError('did not start') |
| 38 | except Exception: |
| 39 | self.close() |
| 40 | raise |
| 41 | |
| 42 | def ask(self, msg): |
| 43 | if self.dead: |
| 44 | return None |
| 45 | me = int(msg['seat']) |
| 46 | try: |
| 47 | self.b.send('BOARD', *[f'{x},{y},{1 if int(s) == me else 2}' for x, y, s in msg['moves']], 'DONE') |
| 48 | return self.b.reply(TURN).replace(' ', '') |
| 49 | except (queue.Empty, OSError): |
| 50 | |
| 51 | |
| 52 | self.close() |
| 53 | return None |
| 54 | |
| 55 | def close(self): |
| 56 | if self.dead: |
| 57 | return |
| 58 | self.dead = True |
| 59 | try: |
| 60 | self.b.send('END') |
| 61 | self.b.p.wait(1) |
| 62 | except Exception: |
| 63 | self.b.p.kill() |
| 64 | self.b.p.wait() |
| 65 | finally: |
| 66 | try: |
| 67 | self.b.p.stdin.close() |
| 68 | except OSError: |
| 69 | pass |
| 70 | self.b.p.stdout.close() |
| 71 | |
| 72 | |
| 73 | def main(): |
| 74 | room = Room(Seat, field=os.path.join(ROOT, 'field')) |
| 75 | names = [room.label(s) for s in '01'] |
| 76 | stand_in = room._bot({'field': 'threats'}) |
| 77 | board, moves, out = {}, [], {'winner': None, 'reason': 'board full', 'five': None} |
| 78 | room.event({'type': 'start', 'size': SIZE, 'rule': 'standard-exact-five', 'seats': {s: {'name': names[int(s)], 'kind': room.kind(s), 'colour': COLOUR[s]} for s in '01'}}) |
| 79 | |
| 80 | def auto(obs, legal): |
| 81 | move = stand_in.ask(obs) if stand_in else None |
| 82 | return move if move in legal else min(legal, key=lambda p: abs(int(p.split(',')[0]) - 7) + abs(int(p.split(',')[1]) - 7)) |
| 83 | |
| 84 | side, stopped = 0, False |
| 85 | try: |
| 86 | while len(board) < SIZE * SIZE: |
| 87 | s = str(side) |
| 88 | legal = [f'{x},{y}' for y in range(SIZE) for x in range(SIZE) if (x, y) not in board] |
| 89 | obs = {'seat': s, 'colour': COLOUR[s], 'size': SIZE, 'rule': 'standard-exact-five', 'moves': [m[:2] + [m[3]] for m in moves], 'last': moves[-1][:2] if moves else None} |
| 90 | move = room.ask(s, obs, legal, auto=lambda: auto(obs, legal), bot=obs) |
| 91 | x, y = map(int, move.split(',')) |
| 92 | board[(x, y)] = side |
| 93 | moves.append([x, y, 0, s]) |
| 94 | room.event({'type': 'move', 'seat': s, 'x': x, 'y': y}) |
| 95 | n, line = run_of(board, x, y, side) |
| 96 | if n == 5: |
| 97 | out = {'winner': side, 'reason': 'five', 'five': line} |
| 98 | break |
| 99 | side = 1 - side |
| 100 | except Stop: |
| 101 | stopped, out = True, {'winner': None, 'reason': 'stopped', 'five': None} |
| 102 | |
| 103 | w = out['winner'] |
| 104 | result = 'stopped' if stopped else 'a draw' if w is None else f'{names[w]} ({COLOUR[str(w)]}) wins' |
| 105 | room.event({'type': 'over', 'winner': None if w is None else str(w), 'reason': out['reason'], 'five': out['five']}) |
| 106 | replay = {**base(SIZE, [], [m[:3] for m in moves]), **out, 'players': names} |
| 107 | league.Replays().add('room.json', replay, f'Room: {names[0]} (black) vs {names[1]} (white)', names, |
| 108 | f'{result} in {len(moves)} moves') |
| 109 | room.end(result, {s: 0.0 if stopped else 0.5 if w is None else float(w == int(s)) for s in '01'}) |
| 110 | room.close() |
| 111 | |
| 112 | |
| 113 | if __name__ == '__main__': |
| 114 | main() |