{ "cells": [ { "cell_type": "markdown", "id": "3cea2306-445c-412c-8cda-d9571aa65ae2", "metadata": {}, "source": [ "#
Practical Session 2 : Partizan games - The minimax Algorithm" ] }, { "cell_type": "markdown", "id": "43cc2b0c-5ccb-4f7b-97b2-070d48527b0f", "metadata": {}, "source": [ "The objective of this second practical session is to implement the minimax algorithm for a quite simple (but still complicated enough to not be able to go through the whole game tree) game : Connect 4. " ] }, { "cell_type": "markdown", "id": "1ac0be46-39ed-49ad-b6a0-63fc0e6852fc", "metadata": {}, "source": [ "You will find below Python functions that implement the logic of the Connect 4 games, including the following functions:\n", "- `__init__()`: Initializes an empty 6 × 7 board.\n", "- `set_piece(player, column)`: Places a player's piece in the chosen column.\n", "- `print_board()`: Displays the current board.\n", "- `is_legal_move(column)`: Checks whether a move in the column is possible.\n", " `_check_line(self, player, row, col, delta_row, delta_col)`: Checks if player has four consecutive pieces from (`row`, `col`), going in the direction (`delta_row`, `delta_col`).\n", "- `check_winner(player)`: Checks whether the player has connected four pieces." ] }, { "cell_type": "code", "execution_count": 1, "id": "58c73abc-9141-49e0-8788-090e8f9813ab", "metadata": {}, "outputs": [], "source": [ "class Logic:\n", " def __init__ (self):\n", " self._board = [ [ 0 for i in range(0, 7) ] for i in range(0, 6) ]\n", "\n", " def set_piece (self, player, column):\n", " for i in range(6):\n", " if self._board[i][column] == 0:\n", " self._board[i][column] = player\n", " return\n", "\n", " def print_board(self):\n", " for row in reversed(self._board):\n", " print(row)\n", " \n", " def is_legal_move (self, column):\n", " for i in range(6):\n", " if self._board[i][column] == 0:\n", " return True\n", " return False\n", " \n", " def _check_line(self, player, row, col, delta_row, delta_col):\n", " count = 0\n", " for i in range(4):\n", " r = row + i * delta_row\n", " c = col + i * delta_col\n", " if 0 <= r < 6 and 0 <= c < 7 and self._board[r][c] == player:\n", " count += 1\n", " else:\n", " break\n", " return count == 4\n", " \n", " def check_winner(self, player):\n", " for row in range(6):\n", " for col in range(7):\n", " if self._check_line(player, row, col, 1, 0) or \\\n", " self._check_line(player, row, col, 0, 1) or \\\n", " self._check_line(player, row, col, 1, 1) or \\\n", " self._check_line(player, row, col, 1, -1):\n", " return True\n", " return False" ] }, { "cell_type": "markdown", "id": "539b786b-70ce-43f3-a5cf-440e8b1b7177", "metadata": {}, "source": [ "## Exercicse - Let's add the player.\n", "\n", "Below you will find some classes implementing Human and Random players. You will have to add the Minimax player. To do so, you need to:\n", "- choose a good evaluation function of a grid. You can use the static grid of scores given by\n", "$$\n", "\\begin{pmatrix}\n", "3 & 4 & 5 & 7 & 5 & 4 & 3 \\\\\n", "4 &6 & 8 & 10 &8 &6 &4 \\\\\n", "5 & 8 & 11 & 13 &11 &8 &5 \\\\\n", "5 & 8 & 11 &13 &11 &8 &5 \\\\\n", "4 &6 &8 &10 &8 &6 &4\\\\\n", "3 &4 &5 &7 &5 &4 &3\n", "\\end{pmatrix} $$\n", "- implement the minimax function that recursively evaluates the position of a game using the minimax principle.\n", "- write down the function that finds the move giving this score." ] }, { "cell_type": "code", "execution_count": 2, "id": "fc3ac1e0-0c35-4205-b9e9-d3eaa9915eb1", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "pygame 2.1.2 (SDL 2.0.20, Python 3.10.12)\n", "Hello from the pygame community. https://www.pygame.org/contribute.html\n" ] } ], "source": [ "import random\n", "import copy\n", "import math\n", "import time\n", "import pygame\n", "from pygame.locals import MOUSEBUTTONDOWN, QUIT\n", "\n", "class Player:\n", " def __init__(self, player_id):\n", " self.player_id = player_id\n", "\n", "class Human(Player):\n", " def play_move(self, logic):\n", " while True:\n", " for event in pygame.event.get():\n", " if event.type == MOUSEBUTTONDOWN:\n", " x, y = event.pos\n", " column = x // 100\n", " if 0 <= column < 7 and logic.is_legal_move(column):\n", " return column\n", " if event.type == QUIT:\n", " pygame.quit()\n", " return None\n", "\n", " pygame.time.wait(10)\n", "\n", "\n", "class Random(Player):\n", " def play_move(self, logic):\n", " legal_moves = [c for c in range(7) if logic.is_legal_move(c)]\n", " if not legal_moves:\n", " return None\n", " return random.choice(legal_moves)\n", "\n", "\n", "class Minimax(Player):\n", " def __init__(self, player_id, depth=5): #YOU CAN CHANGE THE DEPTH OF THE ALGORITHM HERE\n", " super().__init__(player_id)\n", " self.depth = depth\n", "\n", " def evaluate_board(self, logic):\n", " scores = [\n", " [3, 4, 5, 7, 5, 4, 3],\n", " [4, 6, 8, 10, 8, 6, 4],\n", " [5, 8, 11, 13, 11, 8, 5],\n", " [5, 8, 11, 13, 11, 8, 5],\n", " [4, 6, 8, 10, 8, 6, 4],\n", " [3, 4, 5, 7, 5, 4, 3]\n", " ]\n", " next_player = 2 if self.player_id == 1 else 1\n", " \"\"\" TODO : Implement the evaluation function that uses the grid above. \"\"\"\n", " \n", " return 0\n", "\n", " def minimax(self, logic, depth, player):\n", " valid_moves = [col for col in range(7) if logic.is_legal_move(col)]\n", " is_terminal = (logic.check_winner(1) or logic.check_winner(2) or not valid_moves)\n", "\n", " \"\"\" TODO : Implement the minimax algorithm to compute the evaluation of the current grid. \"\"\"\n", "\n", " return 0\n", "\n", " def play_move(self, logic):\n", " legal_moves = [col for col in range(7) if logic.is_legal_move(col)]\n", "\n", " if not legal_moves:\n", " return None\n", "\n", " \"\"\" TODO: Implement the function that determines the best move, that is the one that gives the highest score\n", " from the previous minimax function. \"\"\"\n", " return 0" ] }, { "cell_type": "markdown", "id": "1c40df43-0546-4a5d-8ec2-3b8cbb9486fd", "metadata": {}, "source": [ "### Graphic Interface \n", "\n", "In order to play the game nicely, we introduce a graphic interface using the Pygame library." ] }, { "cell_type": "code", "execution_count": 3, "id": "6278e90a-5705-48a7-965b-048ada7a8465", "metadata": {}, "outputs": [], "source": [ "import pygame\n", "from pygame.locals import QUIT, MOUSEBUTTONDOWN\n", "from time import sleep\n", "\n", "BLUE = (0, 51, 153)\n", "YELLOW = (255, 204, 0)\n", "WHITE = (255, 255, 255)\n", "RED = (255, 0, 0)\n", "BLACK = (0, 0, 0)\n", "\n", "WINDOW_SIZE = 700\n", "\n", "def draw_board(screen, board):\n", " screen.fill(BLUE)\n", " for row in range(6):\n", " for col in range(7):\n", " pygame.draw.rect(screen, BLUE, (col * 100, row * 100 + 100, 100, 100))\n", " pygame.draw.circle(screen, BLACK, (col * 100 + 50, row * 100 + 150), 45)\n", "\n", " for row in range(6):\n", " for col in range(7):\n", " if board[row][col] == 1:\n", " pygame.draw.circle(screen, YELLOW, (col * 100 + 50, (5 - row) * 100 + 150), 45)\n", " elif board[row][col] == 2:\n", " pygame.draw.circle(screen, RED, (col * 100 + 50, (5 - row) * 100 + 150), 45)\n", "\n", " pygame.display.update()\n", "\n", "\n", "def display_message(screen, message):\n", " font = pygame.font.Font(None, 75)\n", " text = font.render(message, True, WHITE)\n", " screen.blit(text, (100, 10))\n", " pygame.display.update()\n", "\n", "def draw_menu(screen, p1_type, p2_type):\n", " font = pygame.font.Font(None, 40)\n", " screen.fill(BLUE)\n", "\n", " screen.blit(font.render(\"Player 1\", True, WHITE), (50, 50))\n", " screen.blit(font.render(\"Player 2\", True, WHITE), (400, 50))\n", "\n", " player_types = [\"Human\", \"Random\", \"Minimax\"]\n", "\n", " def draw_options(x, selected):\n", " for i, name in enumerate(player_types):\n", " y = 150 + i * 100\n", " pygame.draw.rect(screen, BLACK, (x, y, 30, 30))\n", " if selected == name:\n", " pygame.draw.rect(screen, WHITE, (x + 3, y + 3, 24, 24))\n", " screen.blit(font.render(name, True, WHITE), (x + 50, y - 10))\n", "\n", " draw_options(50, p1_type)\n", " draw_options(400, p2_type)\n", "\n", " pygame.draw.rect(screen, BLACK, (250, 650, 200, 50))\n", " screen.blit(font.render(\"Start\", True, WHITE), (300, 660))\n", " pygame.display.update()\n", "\n", "\n", "def choose_players(screen):\n", " p1_type = \"Human\"\n", " p2_type = \"Human\"\n", " selecting = True\n", "\n", " while selecting:\n", " draw_menu(screen, p1_type, p2_type)\n", "\n", " for event in pygame.event.get():\n", " if event.type == QUIT:\n", " pygame.quit()\n", " return None, None\n", " if event.type == MOUSEBUTTONDOWN:\n", " x, y = event.pos\n", " if 50 <= x <= 80:\n", " if 150 <= y <= 180:\n", " p1_type = \"Human\"\n", " elif 250 <= y <= 280:\n", " p1_type = \"Random\"\n", " elif 350 <= y <= 380:\n", " p1_type = \"Minimax\"\n", " if 400 <= x <= 430:\n", " if 150 <= y <= 180:\n", " p2_type = \"Human\"\n", " elif 250 <= y <= 280:\n", " p2_type = \"Random\"\n", " elif 350 <= y <= 380:\n", " p2_type = \"Minimax\"\n", " if 250 <= x <= 450 and 650 <= y <= 700:\n", " selecting = False\n", "\n", " return p1_type, p2_type\n", "\n", "\n", "def pygame_main(logic):\n", " pygame.init()\n", " screen = pygame.display.set_mode((WINDOW_SIZE, WINDOW_SIZE))\n", " pygame.display.set_caption(\"Connect 4\")\n", "\n", " while True:\n", " p1_type, p2_type = choose_players(screen)\n", " if p1_type is None:\n", " break\n", "\n", " if p1_type == \"Human\":\n", " player1 = Human(1)\n", " elif p1_type == \"Random\":\n", " player1 = Random(1)\n", " else: \n", " player1 = Minimax(1)\n", "\n", " if p2_type == \"Human\":\n", " player2 = Human(2)\n", " elif p2_type == \"Random\":\n", " player2 = Random(2)\n", " else: \n", " player2 = Minimax(2)\n", "\n", " logic.__init__()\n", " draw_board(screen, logic._board)\n", "\n", " turn = 1\n", " game_over = False\n", "\n", " while not game_over:\n", " current_player = player1 if turn == 1 else player2\n", "\n", " for event in pygame.event.get():\n", " if event.type == QUIT:\n", " pygame.quit()\n", " return\n", "\n", " col = current_player.play_move(logic)\n", "\n", " if logic.is_legal_move(col):\n", " logic.set_piece(turn, col)\n", " draw_board(screen, logic._board)\n", "\n", " if logic.check_winner(turn):\n", " display_message(screen, f\"Player {turn} wins!\")\n", " pygame.time.wait(3000)\n", " game_over = True\n", " continue\n", "\n", " turn = 1 if turn == 2 else 2\n", "\n", " pygame.time.wait(3000)\n", "\n", " pygame.quit()" ] }, { "cell_type": "markdown", "id": "263f502a-5242-4c00-9978-1ed4f410db0a", "metadata": {}, "source": [ "### Let's now try !" ] }, { "cell_type": "code", "execution_count": 4, "id": "af2f6d1b-e1af-44c7-b7c4-d435450bf77d", "metadata": {}, "outputs": [], "source": [ "import threading\n", "\n", "logic = Logic()\n", "threading.Thread(target=pygame_main,args=(logic,),daemon=True).start()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.12" } }, "nbformat": 4, "nbformat_minor": 5 }