diff --git a/tp3/README.md b/tp3/README.md index 3cd003ecd11bc69a427fd599f37e3cd3f64710c7..29a10913f17d4205dccaafa1ec395ddbec622426 100644 --- a/tp3/README.md +++ b/tp3/README.md @@ -305,5 +305,54 @@ impl<L, R> Either<L, R> { ``` ## 2.2. With trait bounds ```rs +use std::io::{BufRead, BufReader, BufWriter, Read, Write}; + +struct IOEncoder<E, I, O> { + encoder: E, + input: I, + output: O, +} + +impl<E, I, O> IOEncoder<E, I, O> +where + E: Encoder + Decoder, + I: Read, + O: Write, +{ + /// Creates a new IOEncoder + fn new(encoder: E, input: I, output: O) -> Self { + Self { + encoder, + input, + output, + } + } + + /// Process this encoders input by encoding each line of the input and saving the encoded version to + /// the ouput + fn encode(&mut self) -> Result<(), std::io::Error> { + let reader = BufReader::new(&mut self.input); + let mut writer = BufWriter::new(&mut self.output); + for line in reader.lines() { + let line = line?; + let encoded = self.encoder.encode(&line); + writeln!(writer, "{}", encoded)?; + } + Ok(()) + } + + /// Process this encoders input by decoding each line of the input and saving the decoded version to + /// the ouput + fn decode(&mut self) -> Result<(), std::io::Error> { + let reader = BufReader::new(&mut self.input); + let mut writer = BufWriter::new(&mut self.output); + for line in reader.lines() { + let line = line?; + let decoded = self.encoder.decode(&line); + writeln!(writer, "{}", decoded)?; + } + Ok(()) + } +} ``` -# 3. Long exercise proposal \ No newline at end of file +# 3. Long exercise proposal diff --git a/tp3/src/main.rs b/tp3/src/main.rs index b3c38188684df729166a704e2d968ef514d1c290..5730ec99d100f448cf4174b83d0aa050a71dad60 100644 --- a/tp3/src/main.rs +++ b/tp3/src/main.rs @@ -331,7 +331,208 @@ where // ========================================================= -// =============== 2. Long exercise proposal =============== +// =============== 3. Long exercise proposal =============== +trait Game { + type Move; + type Player: std::fmt::Debug; + + fn current_player(&self) -> Self::Player; + fn is_over(&self) -> bool; + fn make_move(&mut self, mv: Self::Move) -> Result<(), String>; + fn display(&self); + fn parse_move(input: &str) -> Result<Self::Move, String>; +} + +// Tic Tac Toe +#[derive(Clone, Copy, Debug, PartialEq)] +enum TicTacToePlayer { + X, + O, +} + +struct TicTacToe { + board: [Option<TicTacToePlayer>; 9], + current_player: TicTacToePlayer, +} + +impl TicTacToe { + fn new() -> Self { + Self { + board: [None; 9], + current_player: TicTacToePlayer::X, + } + } +} + +impl Game for TicTacToe { + type Move = (usize, usize); + type Player = TicTacToePlayer; + + fn current_player(&self) -> Self::Player { + self.current_player + } + fn is_over(&self) -> bool { + let b = &self.board; + // Check rows. + for i in 0..3 { + if let Some(p) = b[i * 3] { + if b[i * 3 + 1] == Some(p) && b[i * 3 + 2] == Some(p) { + return true; + } + } + } + // Check columns. + for j in 0..3 { + if let Some(p) = b[j] { + if b[j + 3] == Some(p) && b[j + 6] == Some(p) { + return true; + } + } + } + // Check diagonals. + if let Some(p) = b[0] { + if b[4] == Some(p) && b[8] == Some(p) { + return true; + } + } + if let Some(p) = b[2] { + if b[4] == Some(p) && b[6] == Some(p) { + return true; + } + } + // Game is over if board is full. + if b.iter().all(|&cell| cell.is_some()) { + return true; + } + false + } + fn make_move(&mut self, mv: Self::Move) -> Result<(), String> { + let (row, col) = mv; + if row >= 3 || col >= 3 { + return Err("Move out of bounds".to_string()); + } + let idx = row * 3 + col; + if self.board[idx].is_some() { + return Err("Cell already occupied".to_string()); + } + self.board[idx] = Some(self.current_player); + // Switch player. + self.current_player = match self.current_player { + TicTacToePlayer::X => TicTacToePlayer::O, + TicTacToePlayer::O => TicTacToePlayer::X, + }; + Ok(()) + } + fn display(&self) { + println!("Current board:"); + for row in 0..3 { + for col in 0..3 { + let idx = row * 3 + col; + let symbol = match self.board[idx] { + Some(TicTacToePlayer::X) => "X", + Some(TicTacToePlayer::O) => "O", + None => ".", + }; + print!("{} ", symbol); + } + println!(); + } + } + fn parse_move(input: &str) -> Result<Self::Move, String> { + // Expect two numbers separated by whitespace (e.g., "0 1") + let parts: Vec<&str> = input.split_whitespace().collect(); + if parts.len() != 2 { + return Err("Please enter two numbers separated by space".to_string()); + } + let row = parts[0] + .parse::<usize>() + .map_err(|_| "Invalid row".to_string())?; + let col = parts[1] + .parse::<usize>() + .map_err(|_| "Invalid column".to_string())?; + Ok((row, col)) + } +} + +// Jeu de Nim +#[derive(Clone, Copy, Debug, PartialEq)] +enum NimGamePlayer { + First, + Second, +} + +struct NimGame { + stones: u32, + current_player: NimGamePlayer, +} + +impl NimGame { + fn new(stones: u32) -> Self { + Self { + stones: stones, + current_player: NimGamePlayer::First, + } + } +} + +impl Game for NimGame { + type Move = u32; + type Player = NimGamePlayer; + + fn current_player(&self) -> Self::Player { + self.current_player + } + fn is_over(&self) -> bool { + self.stones == 0 + } + fn make_move(&mut self, mv: Self::Move) -> Result<(), String> { + if mv < 1 || mv > 2 { + return Err("You can only remove 1 or 2 stones".to_string()); + } + if mv > self.stones { + return Err("Not enough stones remaining".to_string()); + } + self.stones -= mv; + self.current_player = match self.current_player { + NimGamePlayer::First => NimGamePlayer::Second, + NimGamePlayer::Second => NimGamePlayer::First, + }; + Ok(()) + } + fn display(&self) { + println!("Stones remaining: {}", self.stones); + } + fn parse_move(input: &str) -> Result<Self::Move, String> { + input + .trim() + .parse::<u32>() + .map_err(|_| "Invalid number".to_string()) + } +} + +// Plays a game that implements the `Game` trait. +fn play_game<G: Game>(mut game: G) { + while !game.is_over() { + game.display(); + print!("Player {:?}, enter your move: ", game.current_player()); + std::io::stdout().flush().unwrap(); + let mut input = String::new(); + std::io::stdin() + .read_line(&mut input) + .expect("Failed to read input"); + match G::parse_move(input.trim()) { + Ok(mv) => { + if let Err(e) = game.make_move(mv) { + println!("Error: {}", e); + } + } + Err(e) => println!("Invalid move: {}", e), + } + } + println!("Game over!"); + game.display(); +} + // ========================================================= fn main() { @@ -361,7 +562,7 @@ fn main() { );*/ // 2.1. Without trait bounds - let want_a_string = true; + /*let want_a_string = true; let want_b_string = !want_a_string; let a = if want_a_string { Either::Left("A") @@ -381,7 +582,31 @@ fn main() { println!("It is a number: {}", a.right().unwrap()); } println!("{}", b.as_ref().right().unwrap()); - println!("{}", b.left().unwrap_or("this is no string :/")); + println!("{}", b.left().unwrap_or("this is no string :/"));*/ + + // 3. Long exercise proposal + println!("\n--- Game engine demo ---"); + let mut choice = String::new(); + println!("\n--- Select game ---"); + println!("1: Tic Tac Toe"); + println!("2: Nim Game"); + let mut game_choice = String::new(); + std::io::stdin().read_line(&mut game_choice).unwrap(); + match game_choice.trim() { + "1" => { + let game = TicTacToe::new(); + play_game(game); + } + "2" => { + println!("Enter initial number of stones:"); + let mut stones_str = String::new(); + std::io::stdin().read_line(&mut stones_str).unwrap(); + let stones = stones_str.trim().parse::<u32>().unwrap_or(10); + let game = NimGame::new(stones); + play_game(game); + } + _ => println!("Invalid game choice"), + } } // ====================== Tests ======================