From 096069b8ec7cae1e17e86090518b303d3c3ef429 Mon Sep 17 00:00:00 2001 From: "estelle.tamalet" Date: Wed, 22 Oct 2025 13:47:02 +0200 Subject: [PATCH 1/6] Initialize Tic-Tac-Toe game Signed-off-by: estelle.tamalet --- topics/tic-tac-toe/Cargo.toml | 7 +++ topics/tic-tac-toe/src/game.rs | 89 ++++++++++++++++++++++++++++++++++ topics/tic-tac-toe/src/main.rs | 14 ++++++ 3 files changed, 110 insertions(+) create mode 100644 topics/tic-tac-toe/Cargo.toml create mode 100644 topics/tic-tac-toe/src/game.rs create mode 100644 topics/tic-tac-toe/src/main.rs diff --git a/topics/tic-tac-toe/Cargo.toml b/topics/tic-tac-toe/Cargo.toml new file mode 100644 index 0000000..f7e718c --- /dev/null +++ b/topics/tic-tac-toe/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "tic-tac-toe" +version = "0.1.0" +edition = "2021" + +[dependencies] + diff --git a/topics/tic-tac-toe/src/game.rs b/topics/tic-tac-toe/src/game.rs new file mode 100644 index 0000000..4d8030e --- /dev/null +++ b/topics/tic-tac-toe/src/game.rs @@ -0,0 +1,89 @@ +const SIZE: usize = 3; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Cell { + Empty, + X, + O, +} + +pub struct TicTacToe { + board: [[Cell; SIZE]; SIZE], +} + +impl TicTacToe { + pub fn new() -> Self { + TicTacToe { + board: [[Cell::Empty; SIZE]; SIZE], + } + } + + pub fn display(&self) { + println!(); + for (i, row) in self.board.iter().enumerate() { + for (j, cell) in row.iter().enumerate() { + let symbol = match cell { + Cell::Empty => ".", + Cell::X => "X", + Cell::O => "O", + }; + print!("{}", symbol); + if j < SIZE - 1 { + print!(" | "); + } + } + println!(); + if i < SIZE - 1 { + println!("---------"); + } + } + println!(); + } + + pub fn place(&mut self, row: usize, col: usize, player: Cell) -> bool { + if row < SIZE && col < SIZE && self.board[row][col] == Cell::Empty { + self.board[row][col] = player; + true + } else { + false + } + } + + pub fn is_full(&self) -> bool { + self.board.iter().all(|row| row.iter().all(|&cell| cell != Cell::Empty)) + } + + pub fn check_winner(&self) -> Option { + // Vérifier les lignes et colonnes + for i in 0..SIZE { + if self.board[i][0] != Cell::Empty + && self.board[i][0] == self.board[i][1] + && self.board[i][1] == self.board[i][2] + { + return Some(self.board[i][0]); + } + if self.board[0][i] != Cell::Empty + && self.board[0][i] == self.board[1][i] + && self.board[1][i] == self.board[2][i] + { + return Some(self.board[0][i]); + } + } + + // Vérifier les diagonales + if self.board[0][0] != Cell::Empty + && self.board[0][0] == self.board[1][1] + && self.board[1][1] == self.board[2][2] + { + return Some(self.board[0][0]); + } + if self.board[0][2] != Cell::Empty + && self.board[0][2] == self.board[1][1] + && self.board[1][1] == self.board[2][0] + { + return Some(self.board[0][2]); + } + + None + } +} diff --git a/topics/tic-tac-toe/src/main.rs b/topics/tic-tac-toe/src/main.rs new file mode 100644 index 0000000..980b767 --- /dev/null +++ b/topics/tic-tac-toe/src/main.rs @@ -0,0 +1,14 @@ +mod game; + +use game::{TicTacToe, Cell}; + +fn main() { + let mut game = TicTacToe::new(); + println!("=== Tic-Tac-Toe ==="); + game.display(); + + // Exemple de jeu + game.place(0, 0, Cell::X); + game.place(1, 1, Cell::O); + game.display(); +} From 70c704a86ebc9f4d3ed49a0d7b998cafce3e45fb Mon Sep 17 00:00:00 2001 From: "estelle.tamalet" Date: Wed, 22 Oct 2025 13:56:51 +0200 Subject: [PATCH 2/6] Add game loop and player input Signed-off-by: estelle.tamalet --- topics/tic-tac-toe/src/main.rs | 78 ++++++++++++++++++++++++++++++++-- 1 file changed, 74 insertions(+), 4 deletions(-) diff --git a/topics/tic-tac-toe/src/main.rs b/topics/tic-tac-toe/src/main.rs index 980b767..0536701 100644 --- a/topics/tic-tac-toe/src/main.rs +++ b/topics/tic-tac-toe/src/main.rs @@ -1,14 +1,84 @@ mod game; use game::{TicTacToe, Cell}; +use std::io::{self, Write}; fn main() { let mut game = TicTacToe::new(); + let mut current_player = Cell::X; + println!("=== Tic-Tac-Toe ==="); - game.display(); + println!("Joueur X commence !"); + println!("Entrez les coordonnées comme : ligne colonne (1-3)"); + println!("Exemple : 1 1 pour le coin supérieur gauche\n"); + + loop { + game.display(); + + // Vérifier s'il y a un gagnant + if let Some(winner) = game.check_winner() { + println!("Le joueur {:?} a gagné !", winner); + break; + } + + // Vérifier si la grille est pleine + if game.is_full() { + println!("Match nul ! La grille est pleine."); + break; + } + + // Demander au joueur actuel de jouer + println!("Joueur {:?}, entrez votre coup (ligne colonne) :", current_player); + print!("> "); + io::stdout().flush().unwrap(); + + let mut input = String::new(); + io::stdin().read_line(&mut input).unwrap(); + + // Parser l'entrée + let coords: Vec<&str> = input.trim().split_whitespace().collect(); + if coords.len() != 2 { + println!("❌ Entrée invalide ! Utilisez le format : ligne colonne (ex: 1 1)"); + continue; + } + + let row = match coords[0].parse::() { + Ok(r) if r >= 1 && r <= 3 => r - 1, + Ok(_) => { + println!("Ligne invalide ! Utilisez un nombre entre 1 et 3."); + continue; + } + Err(_) => { + println!("Ligne invalide !"); + continue; + } + }; + + let col = match coords[1].parse::() { + Ok(c) if c >= 1 && c <= 3 => c - 1, + Ok(_) => { + println!("Colonne invalide ! Utilisez un nombre entre 1 et 3."); + continue; + } + Err(_) => { + println!("Colonne invalide !"); + continue; + } + }; + + // Tenter de placer le symbole + if game.place(row, col, current_player) { + // Changer de joueur + current_player = match current_player { + Cell::X => Cell::O, + Cell::O => Cell::X, + Cell::Empty => Cell::X, + }; + } else { + println!("Coup invalide ! La case est déjà occupée ou hors limites."); + } + } - // Exemple de jeu - game.place(0, 0, Cell::X); - game.place(1, 1, Cell::O); game.display(); + println!("\nFin de la partie"); } From 830be2af933b229c6e397acf2926b7af268f4bf2 Mon Sep 17 00:00:00 2001 From: "estelle.tamalet" Date: Wed, 22 Oct 2025 21:05:24 +0200 Subject: [PATCH 3/6] Add game player vs computer mode Signed-off-by: estelle.tamalet --- topics/tic-tac-toe/Cargo.toml | 1 + topics/tic-tac-toe/src/game.rs | 12 +++ topics/tic-tac-toe/src/main.rs | 136 ++++++++++++++++++++++++++++++--- 3 files changed, 140 insertions(+), 9 deletions(-) diff --git a/topics/tic-tac-toe/Cargo.toml b/topics/tic-tac-toe/Cargo.toml index f7e718c..adc8d14 100644 --- a/topics/tic-tac-toe/Cargo.toml +++ b/topics/tic-tac-toe/Cargo.toml @@ -4,4 +4,5 @@ version = "0.1.0" edition = "2021" [dependencies] +rand = "0.8" diff --git a/topics/tic-tac-toe/src/game.rs b/topics/tic-tac-toe/src/game.rs index 4d8030e..4783f02 100644 --- a/topics/tic-tac-toe/src/game.rs +++ b/topics/tic-tac-toe/src/game.rs @@ -86,4 +86,16 @@ impl TicTacToe { None } + + pub fn get_empty_cells(&self) -> Vec<(usize, usize)> { + let mut empty_cells = Vec::new(); + for i in 0..SIZE { + for j in 0..SIZE { + if self.board[i][j] == Cell::Empty { + empty_cells.push((i, j)); + } + } + } + empty_cells + } } diff --git a/topics/tic-tac-toe/src/main.rs b/topics/tic-tac-toe/src/main.rs index 0536701..fbb587b 100644 --- a/topics/tic-tac-toe/src/main.rs +++ b/topics/tic-tac-toe/src/main.rs @@ -1,13 +1,35 @@ mod game; use game::{TicTacToe, Cell}; +use rand::Rng; use std::io::{self, Write}; fn main() { + println!("=== Tic-Tac-Toe ==="); + println!("Choisissez le mode de jeu :"); + println!("1. Deux joueurs"); + println!("2. Contre l'ordinateur"); + print!("> "); + io::stdout().flush().unwrap(); + + let mut mode_input = String::new(); + io::stdin().read_line(&mut mode_input).unwrap(); + let mode = mode_input.trim(); + + match mode { + "1" => play_two_players(), + "2" => play_against_computer(), + _ => { + println!("Mode invalide ! Relancez le jeu."); + } + } +} + +fn play_two_players() { let mut game = TicTacToe::new(); let mut current_player = Cell::X; - println!("=== Tic-Tac-Toe ==="); + println!("\n=== Mode Deux Joueurs ==="); println!("Joueur X commence !"); println!("Entrez les coordonnées comme : ligne colonne (1-3)"); println!("Exemple : 1 1 pour le coin supérieur gauche\n"); @@ -15,19 +37,16 @@ fn main() { loop { game.display(); - // Vérifier s'il y a un gagnant if let Some(winner) = game.check_winner() { println!("Le joueur {:?} a gagné !", winner); break; } - // Vérifier si la grille est pleine if game.is_full() { println!("Match nul ! La grille est pleine."); break; } - // Demander au joueur actuel de jouer println!("Joueur {:?}, entrez votre coup (ligne colonne) :", current_player); print!("> "); io::stdout().flush().unwrap(); @@ -35,10 +54,9 @@ fn main() { let mut input = String::new(); io::stdin().read_line(&mut input).unwrap(); - // Parser l'entrée let coords: Vec<&str> = input.trim().split_whitespace().collect(); if coords.len() != 2 { - println!("❌ Entrée invalide ! Utilisez le format : ligne colonne (ex: 1 1)"); + println!("Entrée invalide ! Utilisez le format : ligne colonne (ex: 1 1)"); continue; } @@ -66,9 +84,7 @@ fn main() { } }; - // Tenter de placer le symbole if game.place(row, col, current_player) { - // Changer de joueur current_player = match current_player { Cell::X => Cell::O, Cell::O => Cell::X, @@ -80,5 +96,107 @@ fn main() { } game.display(); - println!("\nFin de la partie"); + println!("\nMerci d'avoir joué !"); +} + +fn play_against_computer() { + let mut game = TicTacToe::new(); + let mut rng = rand::thread_rng(); + + println!("\n=== Mode Contre l'Ordinateur ==="); + println!("Vous êtes X, l'ordinateur est O"); + println!("Entrez les coordonnées comme : ligne colonne (1-3)"); + println!("Exemple : 1 1 pour le coin supérieur gauche\n"); + + loop { + game.display(); + + // Vérifier victoire ou match nul + if let Some(winner) = game.check_winner() { + match winner { + Cell::X => println!("Vous avez gagné !"), + Cell::O => println!("L'ordinateur a gagné !"), + _ => {} + } + break; + } + + if game.is_full() { + println!("Match nul ! La grille est pleine."); + break; + } + + // Tour du joueur humain (X) + println!("Votre tour (X), entrez votre coup (ligne colonne) :"); + print!("> "); + io::stdout().flush().unwrap(); + + let mut input = String::new(); + io::stdin().read_line(&mut input).unwrap(); + + let coords: Vec<&str> = input.trim().split_whitespace().collect(); + if coords.len() != 2 { + println!("Entrée invalide ! Utilisez le format : ligne colonne (ex: 1 1)"); + continue; + } + + let row = match coords[0].parse::() { + Ok(r) if r >= 1 && r <= 3 => r - 1, + Ok(_) => { + println!("Ligne invalide ! Utilisez un nombre entre 1 et 3."); + continue; + } + Err(_) => { + println!("Ligne invalide !"); + continue; + } + }; + + let col = match coords[1].parse::() { + Ok(c) if c >= 1 && c <= 3 => c - 1, + Ok(_) => { + println!("Colonne invalide ! Utilisez un nombre entre 1 et 3."); + continue; + } + Err(_) => { + println!("❌ Colonne invalide !"); + continue; + } + }; + + if !game.place(row, col, Cell::X) { + println!("Coup invalide ! La case est déjà occupée."); + continue; + } + + // Vérifier victoire après coup du joueur + if let Some(winner) = game.check_winner() { + game.display(); + if winner == Cell::X { + println!("Vous avez gagné !"); + } + break; + } + + if game.is_full() { + game.display(); + println!("Match nul ! La grille est pleine."); + break; + } + + // Tour de l'ordinateur (O) + println!("\nL'ordinateur réfléchit..."); + std::thread::sleep(std::time::Duration::from_millis(800)); + + let empty_cells = game.get_empty_cells(); + if !empty_cells.is_empty() { + let random_index = rng.gen_range(0..empty_cells.len()); + let (comp_row, comp_col) = empty_cells[random_index]; + game.place(comp_row, comp_col, Cell::O); + println!("L'ordinateur joue en ({}, {})", comp_row + 1, comp_col + 1); + } + } + + game.display(); + println!("\nMerci d'avoir joué !"); } From 93ad581b83d5e3e4812d2bb636d51700e1512028 Mon Sep 17 00:00:00 2001 From: "estelle.tamalet" Date: Wed, 22 Oct 2025 22:10:54 +0200 Subject: [PATCH 4/6] Add Minimax AI mode Signed-off-by: estelle.tamalet --- topics/tic-tac-toe/src/game.rs | 70 ++++++++++++++++++++++ topics/tic-tac-toe/src/main.rs | 103 ++++++++++++++++++++++++++++++++- 2 files changed, 172 insertions(+), 1 deletion(-) diff --git a/topics/tic-tac-toe/src/game.rs b/topics/tic-tac-toe/src/game.rs index 4783f02..05f0a38 100644 --- a/topics/tic-tac-toe/src/game.rs +++ b/topics/tic-tac-toe/src/game.rs @@ -98,4 +98,74 @@ impl TicTacToe { } empty_cells } + + pub fn unplace(&mut self, row: usize, col: usize) { + if row < SIZE && col < SIZE { + self.board[row][col] = Cell::Empty; + } + } + + pub fn evaluate(&self) -> i32 { + if let Some(winner) = self.check_winner() { + match winner { + Cell::O => 10, // L'ordinateur (O) gagne + Cell::X => -10, // Le joueur humain (X) gagne + Cell::Empty => 0, + } + } else { + 0 // Match nul ou jeu non terminé + } + } + + pub fn minimax(&mut self, is_maximizing: bool) -> i32 { + // Vérifier si le jeu est terminé + let score = self.evaluate(); + if score == 10 || score == -10 { + return score; + } + + if self.is_full() { + return 0; // Match nul + } + + if is_maximizing { + // Tour de l'ordinateur (O) - maximiser le score + let mut best_score = i32::MIN; + for (row, col) in self.get_empty_cells() { + self.board[row][col] = Cell::O; + let score = self.minimax(false); + self.board[row][col] = Cell::Empty; + best_score = best_score.max(score); + } + best_score + } else { + // Tour du joueur (X) - minimiser le score + let mut best_score = i32::MAX; + for (row, col) in self.get_empty_cells() { + self.board[row][col] = Cell::X; + let score = self.minimax(true); + self.board[row][col] = Cell::Empty; + best_score = best_score.min(score); + } + best_score + } + } + + pub fn find_best_move(&mut self) -> Option<(usize, usize)> { + let mut best_score = i32::MIN; + let mut best_move = None; + + for (row, col) in self.get_empty_cells() { + self.board[row][col] = Cell::O; + let score = self.minimax(false); + self.board[row][col] = Cell::Empty; + + if score > best_score { + best_score = score; + best_move = Some((row, col)); + } + } + + best_move + } } diff --git a/topics/tic-tac-toe/src/main.rs b/topics/tic-tac-toe/src/main.rs index fbb587b..a06ce60 100644 --- a/topics/tic-tac-toe/src/main.rs +++ b/topics/tic-tac-toe/src/main.rs @@ -8,7 +8,8 @@ fn main() { println!("=== Tic-Tac-Toe ==="); println!("Choisissez le mode de jeu :"); println!("1. Deux joueurs"); - println!("2. Contre l'ordinateur"); + println!("2. Contre l'ordinateur (aléatoire)"); + println!("3. Contre l'ordinateur (Minimax - imbattable)"); print!("> "); io::stdout().flush().unwrap(); @@ -19,6 +20,7 @@ fn main() { match mode { "1" => play_two_players(), "2" => play_against_computer(), + "3" => play_against_minimax(), _ => { println!("Mode invalide ! Relancez le jeu."); } @@ -200,3 +202,102 @@ fn play_against_computer() { game.display(); println!("\nMerci d'avoir joué !"); } + +fn play_against_minimax() { + let mut game = TicTacToe::new(); + + println!("\n=== Mode Contre l'Ordinateur (Minimax) ==="); + println!("Vous êtes X, l'ordinateur est O"); + println!("L'ordinateur utilise l'algorithme Minimax - il est imbattable !"); + println!("Entrez les coordonnées comme : ligne colonne (1-3)"); + println!("Exemple : 1 1 pour le coin supérieur gauche\n"); + + loop { + game.display(); + + // Vérifier victoire ou match nul + if let Some(winner) = game.check_winner() { + match winner { + Cell::X => println!("Vous avez gagné ! Incroyable !"), + Cell::O => println!("L'ordinateur a gagné !"), + _ => {} + } + break; + } + + if game.is_full() { + println!("Match nul ! Bien joué !"); + break; + } + + // Tour du joueur humain (X) + println!("Votre tour (X), entrez votre coup (ligne colonne) :"); + print!("> "); + io::stdout().flush().unwrap(); + + let mut input = String::new(); + io::stdin().read_line(&mut input).unwrap(); + + let coords: Vec<&str> = input.trim().split_whitespace().collect(); + if coords.len() != 2 { + println!("Entrée invalide ! Utilisez le format : ligne colonne (ex: 1 1)"); + continue; + } + + let row = match coords[0].parse::() { + Ok(r) if r >= 1 && r <= 3 => r - 1, + Ok(_) => { + println!("Ligne invalide ! Utilisez un nombre entre 1 et 3."); + continue; + } + Err(_) => { + println!("Ligne invalide !"); + continue; + } + }; + + let col = match coords[1].parse::() { + Ok(c) if c >= 1 && c <= 3 => c - 1, + Ok(_) => { + println!("Colonne invalide ! Utilisez un nombre entre 1 et 3."); + continue; + } + Err(_) => { + println!("Colonne invalide !"); + continue; + } + }; + + if !game.place(row, col, Cell::X) { + println!("Coup invalide ! La case est déjà occupée."); + continue; + } + + // Vérifier victoire après coup du joueur + if let Some(winner) = game.check_winner() { + game.display(); + if winner == Cell::X { + println!("Vous avez gagné ! Incroyable !"); + } + break; + } + + if game.is_full() { + game.display(); + println!("Match nul ! Bien joué !"); + break; + } + + // Tour de l'ordinateur (O) avec Minimax + println!("\nL'ordinateur calcule le meilleur coup (Minimax)..."); + std::thread::sleep(std::time::Duration::from_millis(500)); + + if let Some((comp_row, comp_col)) = game.find_best_move() { + game.place(comp_row, comp_col, Cell::O); + println!("L'ordinateur joue en ({}, {})", comp_row + 1, comp_col + 1); + } + } + + game.display(); + println!("\nMerci d'avoir joué !"); +} From de634d7b4cf13595bde30d27c02c740e8d8462d4 Mon Sep 17 00:00:00 2001 From: "estelle.tamalet" Date: Wed, 22 Oct 2025 22:27:32 +0200 Subject: [PATCH 5/6] Add architecture documentation Signed-off-by: estelle.tamalet --- topics/tic-tac-toe/docs/architecture.md | 275 ++++++++++++++++++++++++ 1 file changed, 275 insertions(+) create mode 100644 topics/tic-tac-toe/docs/architecture.md diff --git a/topics/tic-tac-toe/docs/architecture.md b/topics/tic-tac-toe/docs/architecture.md new file mode 100644 index 0000000..1444542 --- /dev/null +++ b/topics/tic-tac-toe/docs/architecture.md @@ -0,0 +1,275 @@ +# Tic-Tac-Toe Architecture Documentation + +## Project Definition + +This is a command-line Tic-Tac-Toe game implementation written in Rust. It provides an interactive game experience where players can either compete against each other or play against a computer opponent on a classic 3x3 grid. + +### Goals +The main goals of this project are: +- Provide an intuitive and user-friendly command-line interface for playing Tic-Tac-Toe +- Offer three game modes: player vs player, player vs random AI, and player vs intelligent AI +- Implement two AI opponents: one that plays randomly and one using the Minimax algorithm +- Demonstrate the Minimax algorithm for perfect play in a zero-sum game +- Implement game logic with proper validation and win detection +- Demonstrate clean code architecture with separation of concerns + +## Components and Modules + +The project is structured into two main modules: + +### 1. Game Module (`src/game.rs`) +This module contains the core game logic and data structures. + +**Key Components:** + +- **`Cell` enum**: Represents the state of a single cell on the board + - `Empty`: Unoccupied cell + - `X`: Cell occupied by player X + - `O`: Cell occupied by player O + +- **`TicTacToe` struct**: The main game state container + - Stores the 3x3 board as a 2D array of `Cell` values + - Encapsulates all game logic + +**Methods:** + +- `new()`: Creates a new game with an empty board +- `display()`: Renders the current board state to the console +- `place(row, col, player)`: Attempts to place a player's symbol at the specified position +- `is_full()`: Checks if the board is completely filled +- `check_winner()`: Analyzes the board to detect if a player has won +- `get_empty_cells()`: Returns a vector of all available (empty) cell coordinates +- `unplace(row, col)`: Removes a symbol from a cell (utility function) +- `evaluate()`: Evaluates the current board state and returns a score (+10 for O win, -10 for X win, 0 for draw) +- `minimax(is_maximizing)`: Recursive implementation of the Minimax algorithm to find optimal moves +- `find_best_move()`: Uses Minimax to determine the best possible move for the computer + +### 2. Main Module (`src/main.rs`) +This module handles user interaction and game flow. + +**Responsibilities:** + +- Presents a menu to choose between three game modes (player vs player, player vs random AI, or player vs Minimax AI) +- Manages the game loop for all three modes +- Handles user input and validation +- Converts user coordinates (1-3) to internal array indices (0-2) +- Implements two computer opponent strategies: random move selection and Minimax algorithm +- Alternates between players (or player and computer) +- Displays game status and results + +**Game Modes:** + +- **Two Players Mode** (`play_two_players()`): Traditional gameplay where two human players alternate turns +- **Random Computer Mode** (`play_against_computer()`): Single player mode where the human plays as X and the computer plays as O, selecting random moves from available cells (easy difficulty) +- **Minimax Computer Mode** (`play_against_minimax()`): Single player mode where the computer uses the Minimax algorithm to play optimally, making it unbeatable (impossible difficulty) + +### Architecture Justification + +The separation between game logic (`game.rs`) and user interface (`main.rs`) follows the **separation of concerns** principle: + +1. **Maintainability**: Game logic can be modified without affecting the UI, and vice versa +2. **Testability**: The game module can be unit tested independently +3. **Extensibility**: Easy to add new interfaces (GUI, web) or AI players without modifying core logic +4. **Reusability**: The game module can be used in different contexts + +This modular approach makes the codebase clean, organized, and ready for future enhancements. + +## The Minimax Algorithm + +### Overview + +The Minimax algorithm is a decision-making algorithm used in game theory and artificial intelligence. It's particularly effective for two-player zero-sum games like Tic-Tac-Toe, where one player's gain is the other player's loss. + +### How It Works + +The algorithm explores all possible game states recursively and assigns scores to terminal states (win, lose, or draw). It then propagates these scores back up the game tree to determine the best move. + +**Scoring System:** +- +10: Computer (O) wins +- -10: Human player (X) wins +- 0: Draw or non-terminal state + +**Algorithm Steps:** + +1. **Base Cases**: If the game is over (win, lose, or draw), return the evaluation score +2. **Maximizing Player (Computer - O)**: Try all possible moves and choose the one with the highest score +3. **Minimizing Player (Human - X)**: Try all possible moves and choose the one with the lowest score +4. **Recursion**: For each possible move, simulate it and recursively call Minimax for the opponent's turn +5. **Backtracking**: After exploring a move, undo it and try the next one + +### Implementation Details + +The implementation consists of three key functions: + +1. **`evaluate()`**: Evaluates the current board state + - Returns +10 if O wins + - Returns -10 if X wins + - Returns 0 for draw or ongoing game + +2. **`minimax(is_maximizing)`**: The core recursive algorithm + - Takes a boolean indicating whether it's the maximizing player's turn + - Explores all possible moves recursively + - Returns the best score achievable from the current state + +3. **`find_best_move()`**: Entry point for the AI + - Tests all available moves + - Calls `minimax()` for each move + - Returns the move with the highest score + +### Why It's Unbeatable + +For a game as simple as Tic-Tac-Toe (3x3 grid with ~255,168 possible games), the Minimax algorithm can explore the entire game tree in milliseconds. This means: + +- The computer always knows the outcome of every possible move sequence +- It will never make a mistake +- Best case for the human player: Draw (if they also play perfectly) +- If the human makes any mistake, the computer will exploit it and win + +## Usage + +### Prerequisites +- Rust toolchain installed (rustc, cargo) + +### Running the Game + +1. Navigate to the project directory: +```bash +cd topics/tic-tac-toe +``` + +2. Run the game: +```bash +cargo run +``` + +### How to Play + +1. The game starts with a menu asking you to choose a game mode: + - **Mode 1**: Two players (human vs human) + - **Mode 2**: Against the computer with random moves (easy - beatable) + - **Mode 3**: Against the computer with Minimax algorithm (impossible - unbeatable) +2. Enter `1`, `2`, or `3` to select your preferred mode +3. In two-player mode, players X and O alternate turns +4. In computer modes, you play as X and the computer plays as O +5. Enter coordinates as two numbers separated by a space: `row column` +6. Valid coordinates range from 1 to 3 for both row and column +7. In Mode 2, the computer selects moves randomly from available cells +8. In Mode 3, the computer uses Minimax to always play the optimal move + +### Examples + +**Grid Numbering:** +``` +(1,1) | (1,2) | (1,3) +--------- +(2,1) | (2,2) | (2,3) +--------- +(3,1) | (3,2) | (3,3) +``` + +**Sample Game Session (Minimax Mode):** +``` +=== Tic-Tac-Toe === +Choisissez le mode de jeu : +1. Deux joueurs +2. Contre l'ordinateur (aléatoire) +3. Contre l'ordinateur (Minimax - imbattable) +> 3 + +=== Mode Contre l'Ordinateur (Minimax) === +Vous êtes X, l'ordinateur est O +L'ordinateur utilise l'algorithme Minimax - il est imbattable ! +Entrez les coordonnées comme : ligne colonne (1-3) +Exemple : 1 1 pour le coin supérieur gauche + +. . . +--------- +. . . +--------- +. . . + +Votre tour (X), entrez votre coup (ligne colonne) : +> 2 2 + +L'ordinateur calcule le meilleur coup (Minimax)... +L'ordinateur joue en (1, 1) + +O . . +--------- +. X . +--------- +. . . + +... (game continues) +``` + +### Common Commands + +- **Place a move**: Enter `row column` (e.g., `1 1` for top-left corner) +- **Invalid moves**: The game will display an error message and allow you to try again + +### Win Conditions + +The game automatically detects when: +- A player gets three symbols in a row (horizontally, vertically, or diagonally) +- The board is full with no winner (draw) + +### Example Winning Scenarios + +**Horizontal Win:** +``` +X X X <- Player X wins +--------- +O O . +--------- +. . . +``` + +**Vertical Win:** +``` +X O . +--------- +X O . <- Player O wins (middle column) +--------- +X . . +``` + +**Diagonal Win:** +``` +X . O +--------- +. X O <- Player X wins (top-left to bottom-right) +--------- +O . X +``` + +## Future Enhancements + +Potential improvements to the project: +- Implement alpha-beta pruning to optimize Minimax performance +- Add difficulty levels (easy, medium, hard) with varying AI strategies +- Support for different board sizes (4x4, 5x5) +- Save/load game state functionality +- Network multiplayer support +- Graphical user interface (GUI) using a framework like egui or iced +- Game statistics tracking (wins, losses, draws) +- Undo/redo move functionality +- Time limits per move +- Tournament mode with multiple rounds + +## Dependencies + +The project uses the following external dependencies: + +- **`rand`** (version 0.8): Used for random number generation in the random AI opponent mode + +## Performance Notes + +- The random AI opponent has instant move selection (O(1) after getting empty cells) +- The Minimax AI explores the entire game tree but remains fast due to the small state space of Tic-Tac-Toe +- Typical Minimax computation time: < 100ms even in early game states +- No optimization techniques (like alpha-beta pruning) are currently implemented, but performance is already excellent for this game size + +## Conclusion + +This Tic-Tac-Toe implementation demonstrates fundamental concepts in game AI, including the Minimax algorithm for perfect play. The modular architecture allows for easy extension and modification, making it an excellent foundation for learning about game theory and AI decision-making algorithms. From de79bcb4251beeda0248c18debe66b9cf6e4d435 Mon Sep 17 00:00:00 2001 From: "estelle.tamalet" Date: Mon, 27 Oct 2025 19:56:28 +0100 Subject: [PATCH 6/6] Refactor architecture documentation file Signed-off-by: estelle.tamalet --- topics/tic-tac-toe/docs/architecture.md | 28 +------------------------ 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/topics/tic-tac-toe/docs/architecture.md b/topics/tic-tac-toe/docs/architecture.md index 1444542..ad4ab55 100644 --- a/topics/tic-tac-toe/docs/architecture.md +++ b/topics/tic-tac-toe/docs/architecture.md @@ -11,7 +11,6 @@ The main goals of this project are: - Implement two AI opponents: one that plays randomly and one using the Minimax algorithm - Demonstrate the Minimax algorithm for perfect play in a zero-sum game - Implement game logic with proper validation and win detection -- Demonstrate clean code architecture with separation of concerns ## Components and Modules @@ -243,33 +242,8 @@ X . O O . X ``` -## Future Enhancements - -Potential improvements to the project: -- Implement alpha-beta pruning to optimize Minimax performance -- Add difficulty levels (easy, medium, hard) with varying AI strategies -- Support for different board sizes (4x4, 5x5) -- Save/load game state functionality -- Network multiplayer support -- Graphical user interface (GUI) using a framework like egui or iced -- Game statistics tracking (wins, losses, draws) -- Undo/redo move functionality -- Time limits per move -- Tournament mode with multiple rounds - ## Dependencies The project uses the following external dependencies: -- **`rand`** (version 0.8): Used for random number generation in the random AI opponent mode - -## Performance Notes - -- The random AI opponent has instant move selection (O(1) after getting empty cells) -- The Minimax AI explores the entire game tree but remains fast due to the small state space of Tic-Tac-Toe -- Typical Minimax computation time: < 100ms even in early game states -- No optimization techniques (like alpha-beta pruning) are currently implemented, but performance is already excellent for this game size - -## Conclusion - -This Tic-Tac-Toe implementation demonstrates fundamental concepts in game AI, including the Minimax algorithm for perfect play. The modular architecture allows for easy extension and modification, making it an excellent foundation for learning about game theory and AI decision-making algorithms. +- **`rand`** (version 0.8): Used for random number generation in the random AI opponent mode \ No newline at end of file