{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Install kaggle-environments","metadata":{}},{"cell_type":"code","source":"# 1. Enable Internet in the Kernel (Settings side pane)\n\n# 2. Curl cache may need purged if v0.1.6 cannot be found (uncomment if needed). \n# !curl -X PURGE https://pypi.org/simple/kaggle-environments\n\n# ConnectX environment was defined in v0.1.6\n!pip install 'kaggle-environments==1.2.1'","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2022-07-30T03:45:33.747387Z","iopub.execute_input":"2022-07-30T03:45:33.748005Z","iopub.status.idle":"2022-07-30T03:45:39.667755Z","shell.execute_reply.started":"2022-07-30T03:45:33.747954Z","shell.execute_reply":"2022-07-30T03:45:39.666753Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Create ConnectX Environment","metadata":{}},{"cell_type":"code","source":"from kaggle_environments import evaluate, make, utils, agent\n\nenv = make(\"connectx\", debug=True)\nenv.render()","metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","execution":{"iopub.status.busy":"2022-07-30T03:46:58.134358Z","iopub.execute_input":"2022-07-30T03:46:58.134829Z","iopub.status.idle":"2022-07-30T03:46:58.149512Z","shell.execute_reply.started":"2022-07-30T03:46:58.134788Z","shell.execute_reply":"2022-07-30T03:46:58.148787Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Create an Agent\n\nTo create the submission, an agent function should be fully encapsulated (no external dependencies).  \n\nWhen your agent is being evaluated against others, it will not have access to the Kaggle docker image.  Only the following can be imported: Python Standard Library Modules, gym, numpy, scipy, pytorch (1.3.1, cpu only), and more may be added later.\n\n","metadata":{}},{"cell_type":"code","source":"def my_agent_(observation, configuration):\n    # Use a simple opening book for the first move\n    if observation.board == [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]:\n        return 3, -1\n    elif observation.board == [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0]:\n        return 1, -1\n    elif observation.board == [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0]:\n        return 2, -1\n    elif observation.board == [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0]:\n        return 3, -1\n    elif observation.board == [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0]:\n        return 3, -1\n    elif observation.board == [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0]:\n        return 4, -1\n    elif observation.board == [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0]:\n        return 4, -1\n    elif observation.board == [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]:\n        return 5, -1\n    import math\n    import time\n    import random\n    END_TIME = time.time() + 7.25\n    VALUES = {0: \"    \", 1: \"\\033[31m ◉  \\033[0m\", 2: \"\\033[93m ◉  \\033[0m\"}\n    \n    class Game:\n        def __init__(self, size=(6, 7), connect_size=4, position=None, turn=True, winner=None, terminal=False, moves=None):\n            if position is None:\n                self.position = (((0,) * size[0],) * size[1])\n            else:\n                self.position = position\n            if moves is None:\n                self.moves = []\n            else:\n                self.moves = moves\n            self.turn, self.winner, self.terminal = turn, winner, terminal\n            self.size, self.connect_size = size, connect_size\n\n        @staticmethod\n        def visualize_board(board):\n            board_rows = [[] for _ in range(board.size[0])]\n            for col in range(len(board.position)):\n                for square in range(len(board.position[col])):\n                    board_rows[square].append(board.position[col][square])\n            numbers_y, numbers_x = list(reversed(list(map(lambda x: x + 1, range(board.size[0]))))), list(reversed(list(map(lambda x: x + 1, range(board.size[1])))))\n            max_length_y, max_length_x = len(str(numbers_y[0])), len(str(numbers_x[0]))\n            # The top border (e.g. ┌———┬————┬————┬————┬————┐)\n            result = \"┌——\" + (\"—\" * max_length_y)\n            for _ in range(len(numbers_x)):\n                result += \"┬————\"\n            result += \"┐\\n\"\n            # Each rank, from top to bottom except the bottommost rank\n            for index in range(len(numbers_y) - 1):\n                result += f\"│ {numbers_y[index]}{' ' * (max_length_y - len(str(numbers_y[index])))} │\"  # The rank number\n                # Add the square cells\n                for square in board_rows[index]:\n                    result += VALUES[square] + \"│\"\n                result += \"\\n\"\n                # Add the horizontal separator\n                result += f\"┝ {' ' * max_length_y} ┽\"\n                for _ in range(len(board_rows[index]) - 1):\n                    result += \"————┼\"\n                result += \"————┤\\n\"\n            # Add the final rank with a special character\n            result += f\"│ 1{' ' * (max_length_y - 1)} │\"\n            for square in board_rows[-1]:\n                result += VALUES[square] + \"│\"\n            result += \"\\n\"\n            result += f\"├——{'—' * max_length_y}┼\"\n            for _ in range(len(board_rows[index]) - 1):\n                result += \"————╁\"\n            result += \"————┤\\n\"\n            # Add the file numbers\n            result += f\"│  {' ' * max_length_y}│\"\n            for number in numbers_x[::-1]:\n                result += f\" {' ' * (max_length_x - len(str(number)))}{number} {' ' if number != numbers_x[0] else ''}{' ' if max_length_x == 1 else ''}\"  # Append the file number with the correct spacing (on the last number, remove a space at the end for the border)\n            result += f\"│\\n└—{'—' * max_length_y}—┴\"\n            for _ in range(len(numbers_x) - 1):\n                result += \"————┸\"\n            result += \"————┘\"\n            return result\n\n        @staticmethod\n        def has_player_won(position):\n            for color in [1, 2]:\n                if position.get_n_in_a_row(position, color, position.connect_size):\n                    return color\n            return None\n\n        @staticmethod\n        def is_tie(board):\n            \"\"\"Returns True if the game is tied, False otherwise.\"\"\"\n            for column in board.position:\n                if column[0] == 0:\n                    return False\n            return True\n\n        @staticmethod\n        def make_move(board, index):\n            position = board.position\n            for i in range(len(board.position[index - 1]))[::-1]:\n                if not board.position[index - 1][i]:\n                    position = position[:index - 1] + (position[index - 1][:i] + (board.turn,) + position[index - 1][i + 1:],) + position[index:]\n                    turn = 3 - board.turn\n                    # winner/is_terminal variables: re-write functions here\n                    winner = Game.has_player_won(Game(position=position))\n                    terminal = (winner is not None) or Game.is_tie(Game(position=position))\n                    break\n            return Game(position=position, turn=turn, winner=winner, terminal=terminal, moves=board.moves + [index])\n\n        @staticmethod\n        def get_n_in_a_row(board, player, n):\n            \"\"\"Returns the number of n-in-a-rows for the given player.\"\"\"\n            # TODO: (maybe) add a variable for occupied columns\n            result = 0\n            # Check vertical lines\n            for column in board.position:\n                for square in range(0, board.size[0] - n + 1):  # step parameter of range function should be (n - 1) (optimization)?\n                    for i in column[square:square + n]:\n                        if i != player:\n                            break\n                    else:\n                        result += 1\n\n            # Check horizontal lines\n            for col_index in range(board.size[1] - n + 1):\n                for square_index in range(board.size[0]):\n                    for i in board.position[col_index:col_index + n]:\n                        if i[square_index] != player:\n                            break\n                    else:\n                        result += 1\n\n            # Check diagonal lines\n            for column_index in range(board.size[1] - n + 1):\n                for row_index in range(board.size[0] - n + 1):\n                    for x in range(n):\n                        if board.position[column_index + x][row_index + x] != player:\n                            break\n                    else:\n                        result += 1\n\n                for row_index in range(n - 1, board.size[0]):\n                    for x in range(n):\n                        if board.position[column_index + x][row_index - x] != player:\n                            break\n                    else:\n                        result += 1\n            return result\n\n        @staticmethod\n        def legal_moves(board):\n            moves = []\n            for column_index in range(len(board.position)):\n                if not board.position[column_index][0]:\n                    moves.append(column_index + 1)\n            return moves\n\n\n    class MonteCarlo:\n        def __init__(self):\n            self.outcomes = {}\n            self.visits = {}\n            self.tree = {}\n\n        @staticmethod\n        def make_legal_moves(board):\n            if board.terminal:\n                return set()\n            return {board.make_move(board, i) for i in board.legal_moves(board)}\n\n        @staticmethod\n        def make_random_move(board):\n            if board.terminal:\n                return None\n            return board.make_move(board, random.choice(board.legal_moves(board)))\n\n        @staticmethod\n        def evaluate(board):\n            if bool(board.turn - 1) == board.winner:\n                return 0\n            return 0.5\n\n        def choose(self, node):\n            if node not in self.tree:\n                return MonteCarlo.make_random_move(node)\n\n            def score(n):\n                if self.visits[n] == 0:\n                    return float(\"-inf\")\n                return self.outcomes[n] / self.visits[n]\n\n            return max(self.tree[node], key=score)\n\n        def rollout(self, node):\n            path = self.select(node)\n            leaf = path[-1]\n            self.expand(leaf)\n            outcome = self.simulate(leaf)\n            self.backpropagate(path, outcome)\n\n        def select(self, node, path=None):\n            if path is None:\n                path = []\n            path.append(node)\n            if node not in self.tree or not self.tree[node]:\n                return path\n            unexplored = self.tree[node] - self.tree.keys()\n            if unexplored:\n                path.append(unexplored.pop())\n                return path\n            return self.select(self.uct(node), path)\n\n        def expand(self, node):\n            if node not in self.tree:\n                self.tree[node] = MonteCarlo.make_legal_moves(node)\n\n        @staticmethod\n        def simulate(node, invert=True):\n            if node.terminal:\n                outcome = MonteCarlo.evaluate(node)\n                if invert:\n                    return 1 - outcome\n                return outcome\n            return MonteCarlo.simulate(MonteCarlo.make_random_move(node), not invert)\n\n        def backpropagate(self, path, outcome):\n            for node in path[::-1]:\n                if node in self.visits:\n                    self.visits[node] += 1\n                else:\n                    self.visits[node] = 1\n                if node in self.outcomes:\n                    self.outcomes[node] += outcome\n                else:\n                    self.outcomes[node] = outcome\n                outcome = 1 - outcome\n\n        def uct(self, node):\n            log = math.log(self.visits[node])\n            return max(self.tree[node], key=lambda n: (self.outcomes[n] / self.visits[n]) + (2 * math.sqrt(log / self.visits[n])))\n\n    position = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]\n    index = 0\n    position1 = [[]]\n    for i in observation[\"board\"]:\n        if index >= configuration[\"columns\"]:\n            index = 0\n            position1.append([])\n        position1[-1].append(i)\n        index += 1\n    index = 0\n    for i in range(len(position1)):\n        for x in range(len(position1[i])):\n            position[x][i] = position1[i][x]\n    for i in range(len(position)):\n        position[i] = tuple(position[i])\n    position = tuple(position)\n    game = Game(position=position, size=(configuration[\"rows\"], configuration[\"columns\"]), connect_size=configuration[\"inarow\"], turn=observation[\"mark\"])\n    mcts = MonteCarlo()\n    rollouts = \"inf (while time is left)\"\n    if observation[\"remainingOverageTime\"] > 22:\n        while True:\n            if time.time() >= END_TIME:\n                break\n            mcts.rollout(game)\n    elif observation[\"remainingOverageTime\"] > 15:\n        for i in range(2500):\n            mcts.rollout(game)\n        rollouts = 2500\n    elif observation[\"remainingOverageTime\"] > 10:\n        for i in range(2000):\n            mcts.rollout(game)\n        rollouts = 2000\n    elif observation[\"remainingOverageTime\"] < 5:\n        import random\n        return [random.choice(list(range(configuration[\"columns\"]))), rollouts, True]\n    else:\n        rollouts = 1500\n        for i in range(1500):\n            mcts.rollout(game)\n    return [mcts.choose(game).moves[-1] - 1, rollouts, False]\n\n\ndef my_agent(observation, configuration):\n    print(\"INFO position \" + str(observation[\"board\"]))\n    if \"remainingOverageTime\" in observation:\n        print(\"INFO remaining_time \" + str(observation[\"remainingOverageTime\"]))\n    observation[\"remainingOverageTime\"] = 15\n    agent_string = my_agent_(observation, configuration)\n    print(\"INFO rollouts \" + str(agent_string[1]) + \"\\nINFO move \" + str(agent_string[0]))\n    if agent_string[1] == -1:\n        print(\"INFO opening\")\n        return agent_string[0]\n    if agent_string[2]:\n        print(\"WARN remaining_time < 5s, moves will be random\")\n    return agent_string[0]\n    ","metadata":{"execution":{"iopub.status.busy":"2022-07-30T03:50:21.731740Z","iopub.execute_input":"2022-07-30T03:50:21.732052Z","iopub.status.idle":"2022-07-30T03:50:21.840144Z","shell.execute_reply.started":"2022-07-30T03:50:21.731997Z","shell.execute_reply":"2022-07-30T03:50:21.839338Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Test your Agent","metadata":{}},{"cell_type":"code","source":"# env.reset()\n# env.run([my_agent, \"negamax\"])\n# env.render(mode=\"ipython\", width=1000, height=900)","metadata":{"execution":{"iopub.status.busy":"2022-07-30T03:50:23.059411Z","iopub.execute_input":"2022-07-30T03:50:23.059764Z","iopub.status.idle":"2022-07-30T03:50:50.678489Z","shell.execute_reply.started":"2022-07-30T03:50:23.059700Z","shell.execute_reply":"2022-07-30T03:50:50.677547Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Debug/Train your Agent","metadata":{}},{"cell_type":"code","source":"# # Play as first position against random agent.\n# trainer = env.train([None, \"random\"])\n\n# observation = trainer.reset()\n\n# while not env.done:\n#     my_action = my_agent(observation, env.configuration)\n#     print(\"My Action\", my_action)\n#     observation, reward, done, info = trainer.step(my_action)\n#     env.render(mode=\"ipython\", width=100, height=90, header=False, controls=False)\n# env.render()","metadata":{"execution":{"iopub.status.busy":"2022-07-30T00:13:45.555721Z","iopub.execute_input":"2022-07-30T00:13:45.556151Z","iopub.status.idle":"2022-07-30T00:14:22.464788Z","shell.execute_reply.started":"2022-07-30T00:13:45.556090Z","shell.execute_reply":"2022-07-30T00:14:22.463794Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Evaluate your Agent","metadata":{}},{"cell_type":"code","source":"# def mean_reward(rewards):\n#     return sum(r[0] for r in rewards) / float(len(rewards))\n\n# print(evaluate(\"connectx\", [my_agent, \"random\"], num_episodes=10))\n# print(evaluate(\"connectx\", [my_agent, \"negamax\"], num_episodes=10))\n\n# Run multiple episodes to estimate its performance.\n# print(\"My Agent vs Random Agent:\", mean_reward(evaluate(\"connectx\", [agent, \"random\"], num_episodes=10)))\n# print(\"My Agent vs Negamax Agent:\", mean_reward(evaluate(\"connectx\", [agent, \"negamax\"], num_episodes=10)))","metadata":{"execution":{"iopub.status.busy":"2022-07-30T03:51:28.092445Z","iopub.execute_input":"2022-07-30T03:51:28.092763Z","iopub.status.idle":"2022-07-30T03:51:52.392343Z","shell.execute_reply.started":"2022-07-30T03:51:28.092722Z","shell.execute_reply":"2022-07-30T03:51:52.391426Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Play your Agent\nClick on any column to place a checker there (\"manually select action\").","metadata":{}},{"cell_type":"code","source":"# \"None\" represents which agent you'll manually play as (first or second player).\nenv.play([my_agent, None], width=1000, height=900)","metadata":{"execution":{"iopub.status.busy":"2022-07-30T03:52:57.211805Z","iopub.execute_input":"2022-07-30T03:52:57.212439Z","iopub.status.idle":"2022-07-30T03:52:57.238184Z","shell.execute_reply.started":"2022-07-30T03:52:57.212367Z","shell.execute_reply":"2022-07-30T03:52:57.237186Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Write Submission File\n\n","metadata":{}},{"cell_type":"code","source":"import inspect\nimport os\n\ndef write_agent_to_file(function, file):\n    with open(file, \"a\" if os.path.exists(file) else \"w\") as f:\n        f.write(inspect.getsource(function))\n        print(function, \"written to\", file)\n\nwrite_agent_to_file(my_agent, \"submission.py\")","metadata":{"execution":{"iopub.status.busy":"2022-07-30T01:12:43.194423Z","iopub.execute_input":"2022-07-30T01:12:43.194997Z","iopub.status.idle":"2022-07-30T01:12:43.207491Z","shell.execute_reply.started":"2022-07-30T01:12:43.194939Z","shell.execute_reply":"2022-07-30T01:12:43.206586Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Validate Submission\nPlay your submission against itself.  This is the first episode the competition will run to weed out erroneous agents.\n\nWhy validate? This roughly verifies that your submission is fully encapsulated and can be run remotely.","metadata":{}},{"cell_type":"code","source":"# Note: Stdout replacement is a temporary workaround.\nimport sys\nout = sys.stdout\nsubmission = utils.read_file(\"/kaggle/working/submission.py\")\nagent = agent.get_last_callable(submission)\nsys.stdout = out\n\nenv = make(\"connectx\", debug=True)\nenv.run([my_agent, my_agent])\nprint(\"Success!\" if env.state[0].status == env.state[1].status == \"DONE\" else \"Failed...\")","metadata":{"execution":{"iopub.status.busy":"2022-07-30T01:12:05.219689Z","iopub.execute_input":"2022-07-30T01:12:05.219997Z","iopub.status.idle":"2022-07-30T01:12:05.236884Z","shell.execute_reply.started":"2022-07-30T01:12:05.219950Z","shell.execute_reply":"2022-07-30T01:12:05.235686Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Submit to Competition\n\n1. Commit this kernel.\n2. View the commited version.\n3. Go to \"Data\" section and find submission.py file.\n4. Click \"Submit to Competition\"\n5. Go to [My Submissions](https://kaggle.com/c/connectx/submissions) to view your score and episodes being played.","metadata":{}}]}