| 1 | """Run with .venvs/gomoku/bin/python -m unittest discover -s games/gomoku/tests.""" |
| 2 | import importlib.util |
| 3 | import pathlib |
| 4 | import queue |
| 5 | import time |
| 6 | import sys |
| 7 | import unittest |
| 8 | |
| 9 | ROOT = pathlib.Path(__file__).resolve().parents[1] |
| 10 | sys.path.insert(0, str(ROOT / 'referee')) |
| 11 | from gomoku import run_of, Brain, match |
| 12 | |
| 13 | |
| 14 | def load(name, path): |
| 15 | spec = importlib.util.spec_from_file_location(name, path) |
| 16 | module = importlib.util.module_from_spec(spec) |
| 17 | spec.loader.exec_module(module) |
| 18 | return module |
| 19 | |
| 20 | |
| 21 | class StandardRules(unittest.TestCase): |
| 22 | def test_any_axis_exact_five_beats_a_crossing_overline(self): |
| 23 | board = {(x, 7): 0 for x in range(4, 10)} |
| 24 | board.update({(7, y): 0 for y in range(5, 10)}) |
| 25 | n, line = run_of(board, 7, 7, 0) |
| 26 | self.assertEqual(n, 5) |
| 27 | self.assertEqual(line, [(7, y) for y in range(5, 10)]) |
| 28 | |
| 29 | def test_overline_alone_is_not_a_win(self): |
| 30 | n, _ = run_of({(x, 7): 1 for x in range(4, 10)}, 7, 7, 1) |
| 31 | self.assertEqual(n, 6) |
| 32 | |
| 33 | def test_room_preserves_own_stones_from_string_seat_ids(self): |
| 34 | room = load('live_gomoku_room', ROOT / 'referee/room.py') |
| 35 | seat = room.Seat(str(ROOT / 'field/threats')) |
| 36 | try: |
| 37 | |
| 38 | stones = [[3, 3, '0'], [3, 7, '1']] |
| 39 | stones += [[x, 3, '1'] for x in range(4, 8)] |
| 40 | stones += [[x, 7, '0'] for x in range(4, 8)] |
| 41 | move = seat.ask({'seat': '1', 'moves': stones}) |
| 42 | self.assertEqual(move, '8,3') |
| 43 | finally: |
| 44 | seat.close() |
| 45 | |
| 46 | def test_broken_four_and_overline_are_distinguished(self): |
| 47 | bot = load('tactical_gomoku', ROOT / 'field/threats/bot.py') |
| 48 | bot.board.update({(x, 7): 1 for x in (3, 4, 6, 7, 8)}) |
| 49 | bot.board.update({(x, 4): 1 for x in (3, 4, 6, 7)}) |
| 50 | self.assertLess(bot.pattern((5, 7), 1), bot.WIN) |
| 51 | self.assertEqual(bot.pattern((5, 4), 1), bot.WIN) |
| 52 | self.assertEqual(bot.move(), (5, 4)) |
| 53 | |
| 54 | def test_forced_defence(self): |
| 55 | bot = load('defending_gomoku', ROOT / 'field/threats/bot.py') |
| 56 | bot.board.update({(3, 7): 1, **{(x, 7): 2 for x in range(4, 8)}}) |
| 57 | self.assertEqual(bot.move(), (8, 7)) |
| 58 | |
| 59 | def test_crashed_brain_forfeits_without_crashing_referee(self): |
| 60 | dead = ([sys.executable, '-c', 'print("OK", flush=True)'], str(ROOT)) |
| 61 | live = ([sys.executable, str(ROOT / 'field/neighbour/bot.py')], str(ROOT)) |
| 62 | replay = match([dead, live], [], turn_ms=100) |
| 63 | self.assertEqual(replay['winner'], 1) |
| 64 | self.assertIn(replay['reason'], ('did not start', 'out of time or no move')) |
| 65 | |
| 66 | def test_debug_output_cannot_extend_the_clock(self): |
| 67 | code = 'while True: print("DEBUG thinking", flush=True)' |
| 68 | brain = Brain([sys.executable, '-c', code], str(ROOT)) |
| 69 | started = time.monotonic() |
| 70 | try: |
| 71 | with self.assertRaises(queue.Empty): |
| 72 | brain.reply(0.03) |
| 73 | self.assertLess(time.monotonic() - started, 0.5) |
| 74 | finally: |
| 75 | brain.p.kill(); brain.p.wait() |
| 76 | brain.p.stdin.close(); brain.p.stdout.close() |
| 77 | |
| 78 | def test_suggest_protocol_is_acknowledged(self): |
| 79 | code = 'import sys; print("SUGGEST 7,7", flush=True); print(sys.stdin.readline().split()[1], flush=True)' |
| 80 | brain = Brain([sys.executable, '-c', code], str(ROOT)) |
| 81 | try: |
| 82 | self.assertEqual(brain.reply(1), '7,7') |
| 83 | finally: |
| 84 | brain.p.wait(1) |
| 85 | brain.p.stdin.close(); brain.p.stdout.close() |
| 86 | |
| 87 | def test_board_replaces_previous_position_and_restart_replies(self): |
| 88 | for relative in ('field/threats/bot.py', 'field/neighbour/bot.py', 'routes/bot.py'): |
| 89 | with self.subTest(relative=relative): |
| 90 | brain = Brain([sys.executable, str(ROOT / relative)], str(ROOT)) |
| 91 | try: |
| 92 | brain.send('START 15') |
| 93 | self.assertEqual(brain.reply(1), 'OK') |
| 94 | brain.send('BOARD', '7,7,2', 'DONE') |
| 95 | self.assertNotEqual(brain.reply(1), '7,7') |
| 96 | brain.send('BOARD', 'DONE') |
| 97 | self.assertEqual(brain.reply(1), '7,7') |
| 98 | brain.send('RESTART') |
| 99 | self.assertEqual(brain.reply(1), 'OK') |
| 100 | finally: |
| 101 | brain.send('END') |
| 102 | brain.p.wait(1) |
| 103 | brain.p.stdin.close() |
| 104 | brain.p.stdout.close() |
| 105 | |
| 106 | |
| 107 | if __name__ == '__main__': |
| 108 | unittest.main() |