def test_re_cache_cleared(self): pattern = "^(..+?)\1+$" key = (str, pattern, 0) re.compile(pattern) self.assertIn(key, re._cache) run_game(game, get_next_move_1, get_next_move_1) self.assertNotIn(key, re._cache)
def test_exception(self): # X | . | . # --|---|-- # . | . | . # --|---|-- # . | . | . result = run_game(game, get_next_move_1, get_next_move_9) self.assertEqual(result.result_type, ResultType.EXCEPTION) self.assertEqual(result.score, 1) self.assertEqual(result.move_list, [0]) self.assertIn("KeyError: 123", result.traceback)
def test_invalid_state(self): # X | . | . # --|---|-- # . | . | . # --|---|-- # . | . | . result = run_game(game, get_next_move_1, get_next_move_11) expected_result = Result( result_type=ResultType.INVALID_STATE, score=1, move_list=[0], traceback=None, invalid_move=None, ) self.assertEqual(result, expected_result)
def test_timeout_when_opcode_limit_not_set(self): # X | O | X # --|---|-- # O | X | O # --|---|-- # X | . | . result = run_game(game, get_next_move_1, get_next_move_10) expected_result = Result( result_type=ResultType.COMPLETE, score=1, move_list=[0, 1, 2, 3, 4, 5, 6], traceback=None, invalid_move=None, ) self.assertEqual(result, expected_result)
def test_all_params(self): # X | O | X # --|---|-- # O | X | O # --|---|-- # X | . | . result = run_game(game, get_next_move_7, get_next_move_7) expected_result = Result( result_type=ResultType.COMPLETE, score=1, move_list=[0, 1, 2, 3, 4, 5, 6], traceback=None, invalid_move=None, ) self.assertEqual(result, expected_result)
def test_draw(self): # X | X | O # --|---|-- # O | O | X # --|---|-- # X | X | O result = run_game(game, get_next_move_3, get_next_move_4) expected_result = Result( result_type=ResultType.COMPLETE, score=0, move_list=[0, 2, 1, 3, 5, 4, 6, 8, 7], traceback=None, invalid_move=None, ) self.assertEqual(result, expected_result)
def test_bot2_wins(self): # . | X | O # --|---|-- # X | O | X # --|---|-- # O | . | . result = run_game(game, get_next_move_2, get_next_move_2) expected_result = Result( result_type=ResultType.COMPLETE, score=-1, move_list=[1, 2, 3, 4, 5, 6], traceback=None, invalid_move=None, ) self.assertEqual(result, expected_result)
def test_timeout(self): # X | . | . # --|---|-- # . | . | . # --|---|-- # . | . | . result = run_game(game, get_next_move_1, get_next_move_10, opcode_limit=1000) expected_result = Result( result_type=ResultType.TIMEOUT, score=1, move_list=[0], traceback=None, invalid_move=None, ) self.assertEqual(result, expected_result)
def play_game(bot1_id, bot2_id): if (Game.objects.filter( bot1_id=bot1_id, bot2_id=bot2_id).count() == settings.BOTANY_NUM_ROUNDS): return # TODO: run game in subprocess and ensure environment variables are not accessible game = loader.load_module_from_dotted_path(settings.BOTANY_GAME_MODULE) bot1 = Bot.objects.get(id=bot1_id) bot2 = Bot.objects.get(id=bot2_id) if bot1.is_inactive or bot2.is_inactive: return mod1 = loader.create_module_from_str("mod1", bot1.code) mod2 = loader.create_module_from_str("mod2", bot2.code) return runner.run_game( game, mod1.get_next_move, mod2.get_next_move, opcode_limit=settings.BOTANY_OPCODE_LIMIT, )
def tournament(path1, path2, pathn, full_output, num_rounds, opcode_limit): game = utils.load_game_module() paths = [path1, path2] + list(pathn) bots = [] for path in paths: mod = utils.create_bot_module("mod", path) bots.append({ "path": path, "fn": mod.get_next_move, "num_played": 0, "num_wins": 0, "num_draws": 0, "num_losses": 0, "score": 0, }) if num_rounds is None: num_rounds = utils.get_setting("botany_num_rounds") if opcode_limit is None: opcode_limit = utils.get_setting("botany_opcode_limit") if opcode_limit == 0: opcode_limit = None if not tracer.opcode_limit_supported: print("Opcode limiting not supported in this version of Python") print() opcode_limit = None for bot1 in bots: for bot2 in bots: if bot1 == bot2: continue if full_output: print(f"{bot1['path']} vs {bot2['path']}") for ix in range(num_rounds): result = runner.run_game(game, bot1["fn"], bot2["fn"], opcode_limit=opcode_limit) bot1["num_played"] += 1 bot2["num_played"] += 1 if result.score == 1: bot1["num_wins"] += 1 bot1["score"] += 1 bot2["num_losses"] += 1 bot2["score"] -= 1 winning_bot = "bot1" losing_bot = "bot2" elif result.score == 0: bot1["num_draws"] += 1 bot2["num_draws"] += 1 winning_bot = None losing_bot = None elif result.score == -1: bot1["num_losses"] += 1 bot1["score"] -= 1 bot2["num_wins"] += 1 bot2["score"] += 1 winning_bot = "bot2" losing_bot = "bot1" else: assert False if winning_bot is None: result_summary = "game drawn" else: result_summary = f"{winning_bot} wins" if result.result_type == runner.ResultType.INVALID_MOVE: result_extra = f"{losing_bot} made an invalid move" elif result.result_type == runner.ResultType.EXCEPTION: result_extra = f"{losing_bot} raised an exception" elif result.result_type == runner.ResultType.TIMEOUT: result_extra = f"{losing_bot} exceeded the opcode limit" elif result.result_type == runner.ResultType.INVALID_STATE: result_extra = f"{losing_bot} returned an invalid state" else: assert result.result_type == runner.ResultType.COMPLETE result_extra = None if full_output: items = [ str(ix).rjust(len(str(num_rounds))), result_summary.ljust(10), "".join(str(move) for move in result.move_list), ] if result_extra: items.append(result_extra) print(" " + " ".join(items)) if full_output: print() if full_output: print() max_path_width = max(len(bot["path"]) for bot in bots) max_col_width = len(str(num_rounds * len(bots) * len(bots))) def sortkey(bot): return [-bot["score"], bot["num_played"], -bot["num_wins"]] bots = sorted(bots, key=sortkey) row_items = [ "Bot".center(max_path_width), "P".center(max_col_width), "W".center(max_col_width), "D".center(max_col_width), "L".center(max_col_width), "Score", ] header_row = " | ".join(row_items) print(header_row) dividing_row = header_row.replace("|", "+") dividing_row = re.sub("[^+]", "-", dividing_row) print(dividing_row) for bot in bots: row_items = [ bot["path"].ljust(max_path_width), str(bot["num_played"]).rjust(max_col_width), str(bot["num_wins"]).rjust(max_col_width), str(bot["num_draws"]).rjust(max_col_width), str(bot["num_losses"]).rjust(max_col_width), str(bot["score"]).rjust(max_col_width), ] print(" | ".join(row_items))
def play(path1, path2, opcode_limit): game = utils.load_game_module() def get_next_move_human(board): available_moves = game.available_moves(board) while True: col = input("> ") try: col = int(col) except ValueError: continue if col not in available_moves: continue return col def wrap_bot_fn(fn): param_list = runner.get_param_list(fn) def wrapped(board, move_list, token, state): input() all_args = { "board": board, "move_list": move_list, "token": token, "state": state, } args = { param: value for param, value in all_args.items() if param in param_list } return fn(**args) return wrapped if opcode_limit is None: opcode_limit = utils.get_setting("botany_opcode_limit") if opcode_limit == 0: opcode_limit = None if not tracer.opcode_limit_supported: print("Opcode limiting not supported in this version of Python") print() opcode_limit = None if path1 == "human": fn1 = get_next_move_human else: mod1 = utils.create_bot_module("mod1", path1) fn1 = wrap_bot_fn(mod1.get_next_move) if path2 == "human": fn2 = get_next_move_human else: mod2 = utils.create_bot_module("mod2", path2) fn2 = wrap_bot_fn(mod2.get_next_move) result = runner.run_game(game, fn1, fn2, opcode_limit=opcode_limit, display_board=True) if result.score == 1: winning_bot = f"bot1 ({path1})" losing_bot = f"bot2 ({path2})" elif result.score == 0: winning_bot = None losing_bot = None elif result.score == -1: winning_bot = f"bot2 ({path2})" losing_bot = f"bot1 ({path1})" else: assert False if result.result_type == runner.ResultType.INVALID_MOVE: print(f"{losing_bot} made an invalid move: {result.invalid_move}") elif result.result_type == runner.ResultType.EXCEPTION: print(f"{losing_bot} raised an exception:") print(result.traceback) elif result.result_type == runner.ResultType.TIMEOUT: print(f"{losing_bot} exceeded the opcode limit") elif result.result_type == runner.ResultType.INVALID_STATE: print(f"{losing_bot} returned an invalid state") else: assert result.result_type == runner.ResultType.COMPLETE if winning_bot is None: print("game drawn") else: print(f"{winning_bot} wins") print()