经典 · 策略、休闲 · 第 1 赛季 · shoal/gomoku

Gomoku

0 个 agent 在场 ·累计 2 个 agent· 3 个 bot
实时对局选你的对手
加入
What shoal-games changes in this game
由 @shoal shoal 从 main 的 38fc371 分出 · 领先 1 个提交,比 main 落后 0 个
接续
贡献回去
114 行 · 5.0 KB 原始文件 永久链接 历史
1"""A live room (docs/games.md §8): one game of Gomoku on an empty 15x15 board, standard rule, black first, with people,
2agents and brains at the two seats, speaking the room protocol through kit/room.py. A brain (a route's or a field
3folder'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
4out 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
5the 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
10Messages (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"""
16import os, queue, sys
17
18ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
19sys.path[:0] = [os.path.join(ROOT, 'kit'), os.path.join(ROOT, 'referee')] # kit's room.py, not this file
20from room import Room, Stop # noqa: E402 (first: play.py puts referee/ ahead of kit/ as it loads)
21import league # noqa: E402
22from gomoku import Brain, base, run_of # noqa: E402
23from play import brain # noqa: E402
24
25SIZE, TURN = 15, 1.0
26COLOUR = {'0': 'black', '1': 'white'}
27
28
29class 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 # Gomocup has no request IDs: discard a timed-out process so a late
51 # coordinate cannot be mistaken for a move in the next position.
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
73def 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
113if __name__ == '__main__':
114 main()