"""Run with .venvs/gomoku/bin/python -m unittest discover -s games/gomoku/tests.""" import importlib.util import pathlib import queue import time import sys import unittest ROOT = pathlib.Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT / 'referee')) from gomoku import run_of, Brain, match def load(name, path): spec = importlib.util.spec_from_file_location(name, path) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) return module class StandardRules(unittest.TestCase): def test_any_axis_exact_five_beats_a_crossing_overline(self): board = {(x, 7): 0 for x in range(4, 10)} board.update({(7, y): 0 for y in range(5, 10)}) n, line = run_of(board, 7, 7, 0) self.assertEqual(n, 5) self.assertEqual(line, [(7, y) for y in range(5, 10)]) def test_overline_alone_is_not_a_win(self): n, _ = run_of({(x, 7): 1 for x in range(4, 10)}, 7, 7, 1) self.assertEqual(n, 6) def test_room_preserves_own_stones_from_string_seat_ids(self): room = load('live_gomoku_room', ROOT / 'referee/room.py') seat = room.Seat(str(ROOT / 'field/threats')) try: # Win on y=3 instead of defending the opposing four nearer the centre. stones = [[3, 3, '0'], [3, 7, '1']] stones += [[x, 3, '1'] for x in range(4, 8)] stones += [[x, 7, '0'] for x in range(4, 8)] move = seat.ask({'seat': '1', 'moves': stones}) self.assertEqual(move, '8,3') finally: seat.close() def test_broken_four_and_overline_are_distinguished(self): bot = load('tactical_gomoku', ROOT / 'field/threats/bot.py') bot.board.update({(x, 7): 1 for x in (3, 4, 6, 7, 8)}) bot.board.update({(x, 4): 1 for x in (3, 4, 6, 7)}) self.assertLess(bot.pattern((5, 7), 1), bot.WIN) self.assertEqual(bot.pattern((5, 4), 1), bot.WIN) self.assertEqual(bot.move(), (5, 4)) def test_forced_defence(self): bot = load('defending_gomoku', ROOT / 'field/threats/bot.py') bot.board.update({(3, 7): 1, **{(x, 7): 2 for x in range(4, 8)}}) self.assertEqual(bot.move(), (8, 7)) def test_crashed_brain_forfeits_without_crashing_referee(self): dead = ([sys.executable, '-c', 'print("OK", flush=True)'], str(ROOT)) live = ([sys.executable, str(ROOT / 'field/neighbour/bot.py')], str(ROOT)) replay = match([dead, live], [], turn_ms=100) self.assertEqual(replay['winner'], 1) self.assertIn(replay['reason'], ('did not start', 'out of time or no move')) def test_debug_output_cannot_extend_the_clock(self): code = 'while True: print("DEBUG thinking", flush=True)' brain = Brain([sys.executable, '-c', code], str(ROOT)) started = time.monotonic() try: with self.assertRaises(queue.Empty): brain.reply(0.03) self.assertLess(time.monotonic() - started, 0.5) finally: brain.p.kill(); brain.p.wait() brain.p.stdin.close(); brain.p.stdout.close() def test_suggest_protocol_is_acknowledged(self): code = 'import sys; print("SUGGEST 7,7", flush=True); print(sys.stdin.readline().split()[1], flush=True)' brain = Brain([sys.executable, '-c', code], str(ROOT)) try: self.assertEqual(brain.reply(1), '7,7') finally: brain.p.wait(1) brain.p.stdin.close(); brain.p.stdout.close() def test_board_replaces_previous_position_and_restart_replies(self): for relative in ('field/threats/bot.py', 'field/neighbour/bot.py', 'routes/bot.py'): with self.subTest(relative=relative): brain = Brain([sys.executable, str(ROOT / relative)], str(ROOT)) try: brain.send('START 15') self.assertEqual(brain.reply(1), 'OK') brain.send('BOARD', '7,7,2', 'DONE') self.assertNotEqual(brain.reply(1), '7,7') brain.send('BOARD', 'DONE') self.assertEqual(brain.reply(1), '7,7') brain.send('RESTART') self.assertEqual(brain.reply(1), 'OK') finally: brain.send('END') brain.p.wait(1) brain.p.stdin.close() brain.p.stdout.close() if __name__ == '__main__': unittest.main()