From b4b4166566acfd68a737144400b0c69c4f6587a3 Mon Sep 17 00:00:00 2001 From: Dmitry Fedotov Date: Mon, 7 Apr 2025 02:04:18 +0300 Subject: [PATCH] basically working version --- engine/engine.rs | 60 ++++++++++++++++++----- game/entity.rs | 125 +++++++++++++++++++++++++++++++---------------- game/mod.rs | 2 +- player/human.rs | 69 +++++++++++++++----------- player/mod.rs | 3 +- 5 files changed, 175 insertions(+), 84 deletions(-) diff --git a/engine/engine.rs b/engine/engine.rs index dc19b6b..b6b74de 100644 --- a/engine/engine.rs +++ b/engine/engine.rs @@ -1,38 +1,72 @@ -use crate::game; -use crate::player; +use crate::game::{Board, Cell}; +use crate::player::{Player, PlayerConsole}; pub struct Engine { - players: Vec, + players: Vec>, turn: usize, - board: game::Board, + board: Board, + x: usize, } impl Engine { pub fn new() -> Engine { - let p1 = player::PlayerConsole::new("P1", game::CELL_X); - let p2 = player::PlayerConsole::new("P2", game::CELL_O); + // TODO: accept players as args + let p1 = PlayerConsole::new("Gopher"); + let p2 = PlayerConsole::new("Rustacean"); - let board = game::Board::new(); + let board = Board::new(); Engine { - players: vec![p1, p2], + players: vec![Box::new(p1), Box::new(p2)], turn: 0, board: board, + x: 0, } } pub fn run(&mut self) -> Result<(), Box> { loop { + // setup new game for players + self.players[self.x].start_new_game(Cell::X)?; + self.players[(self.x + 1) % 2].start_new_game(Cell::O)?; + + // reset board + self.board.reset(); + self.turn = self.x; + + // run game + self.run_single_game()?; + + // switch sides + self.x = (self.x + 1) % 2 + } + } + + fn run_single_game(&mut self) -> Result<(), Box> { + loop { + println!("{}", self.board); + + // request move from player, fail if there is error let m = self.players[self.turn].request_move(&self.board)?; - let ok = self.board.put(m); - if !ok { + if !self.board.is_valid_move(&m) { + println!("invalid move"); continue; } - let winner = self.board.has_winner(); - if winner != game::CELL_EMPTY { - println!("the winner is {}", self.players[self.turn].name()); + // apply move + self.board.apply(m)?; + + // check if there is a winner + if let Some(_winner) = self.board.has_winner() { + println!("{} wins!", self.players[self.turn].name()); + println!("{}", self.board); + break; + } + + if !self.board.valid_moves_available() { + println!("It's a draw!"); + println!("{}", self.board); break; } diff --git a/game/entity.rs b/game/entity.rs index 403c559..6eb9520 100644 --- a/game/entity.rs +++ b/game/entity.rs @@ -1,16 +1,17 @@ #[derive(Clone, Copy, PartialEq)] -pub struct Cell(u8); -pub const CELL_EMPTY: Cell = Cell(0); -pub const CELL_X: Cell = Cell(1); -pub const CELL_O: Cell = Cell(2); +pub enum Cell { + Empty, + X, + O, +} impl std::fmt::Display for Cell { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - let mut s: &str = " "; - if *self == CELL_X { - s = "X"; - } else if *self == CELL_O { - s = "O"; + let s: &str; + match *self { + Cell::X => s = "X", + Cell::O => s = "O", + Cell::Empty => s = " ", } write!(f, "{}", s) @@ -20,7 +21,7 @@ impl std::fmt::Display for Cell { pub struct Move { pub x: usize, pub y: usize, - pub c: Cell, + pub piece: Cell, } pub struct Board { @@ -30,51 +31,83 @@ pub struct Board { impl Board { pub fn new() -> Board { - return Board { - cells: vec![CELL_EMPTY; 9], - next: CELL_X, - }; + Board { + cells: vec![Cell::Empty; 9], + next: Cell::X, + } } pub fn reset(&mut self) { - self.cells = vec![CELL_EMPTY; 9]; - self.next = CELL_X; + self.cells = vec![Cell::Empty; 9]; + self.next = Cell::X; } - pub fn put(&mut self, m: Move) -> bool { + pub fn apply(&mut self, m: Move) -> Result<(), &str> { let i = m.x * 1 + m.y * 3; - if self.cells[i] != CELL_EMPTY { - return false; + + if !self.is_valid_move(&m) { + return Err("invalid move"); } - self.cells[i] = m.c; + self.cells[i] = m.piece; - return true; + match self.next { + Cell::X => self.next = Cell::O, + Cell::O => self.next = Cell::X, + _ => {} + } + + Ok(()) } - pub fn has_winner(&self) -> Cell { + pub fn is_valid_move(&self, m: &Move) -> bool { + let i = m.x * 1 + m.y * 3; + + m.piece == self.next && self.cells[i] == Cell::Empty + } + + pub fn valid_moves_available(&self) -> bool { + // check for draw condition + let mut empty: usize = 0; + for i in 0..self.cells.len() { + if self.cells[i] == Cell::Empty { + empty += 1; + } + } + + empty > 0 + } + + pub fn has_winner(&self) -> Option { // rows and cols for i in 0..3 { - if self.cells[i] == self.cells[i + 3] && self.cells[i] == self.cells[i + 6] { - return self.cells[i]; - } else if self.cells[i * 3] == self.cells[i * 3 + 1] - && self.cells[i * 3] == self.cells[i * 3 + 2] + if (self.cells[i] != Cell::Empty) + && (self.cells[i] == self.cells[i + 3] && self.cells[i] == self.cells[i + 6]) { - return self.cells[i * 3]; + return Some(self.cells[i]); + } else if (self.cells[i * 3] != Cell::Empty) + && (self.cells[i * 3] == self.cells[i * 3 + 1] + && self.cells[i * 3] == self.cells[i * 3 + 2]) + { + return Some(self.cells[i * 3]); } } // diagonals - if self.cells[0] == self.cells[4] && self.cells[0] == self.cells[8] { - return self.cells[0]; + if (self.cells[0] != Cell::Empty) + && (self.cells[0] == self.cells[4] && self.cells[0] == self.cells[8]) + { + return Some(self.cells[0]); } - if self.cells[2] == self.cells[4] && self.cells[2] == self.cells[6] { - return self.cells[2]; + if (self.cells[2] != Cell::Empty) + && (self.cells[2] == self.cells[4] && self.cells[2] == self.cells[6]) + { + return Some(self.cells[2]); } // no winner - return CELL_EMPTY; + return None; } } @@ -82,26 +115,36 @@ impl std::fmt::Display for Board { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { let mut s = String::new(); - s.push_str("┌─┬─┬─┐\n"); + s.push_str(" 1 2 3 \n"); + + s.push_str(" ┌───┬───┬───┐\n"); for i in 0..3 { + match i { + 0 => s.push_str("a "), + 1 => s.push_str("b "), + 2 => s.push_str("c "), + _ => {} + } s.push_str(&format!( - "│{}│{}│{}│\n", + "│ {} │ {} │ {} │\n", self.cells[i * 3], self.cells[i * 3 + 1], self.cells[i * 3 + 2] )); if i < 2 { - s.push_str("├─┼─┼─┤\n"); + s.push_str(" ├───┼───┼───┤\n"); } } - s.push_str("└─┴─┴─┘\n"); + s.push_str(" └───┴───┴───┘\n"); - let w = self.has_winner(); - if w != CELL_EMPTY { - s.push_str(&format!("The winner is {w}")); - } else { - s.push_str(&format!("Next: {}", self.next)); + match self.has_winner() { + Some(w) => s.push_str(&format!("The winner is {w}")), + None => match self.valid_moves_available() { + true => s.push_str(&format!("Next: {}", self.next)), + false => s.push_str("DRAW!"), + }, } + write!(f, "{s}") } } diff --git a/game/mod.rs b/game/mod.rs index 9a71726..9e9b2e1 100644 --- a/game/mod.rs +++ b/game/mod.rs @@ -1,2 +1,2 @@ mod entity; -pub use entity::{Board, Cell, Move, CELL_EMPTY, CELL_O, CELL_X}; +pub use entity::{Board, Cell, Move}; diff --git a/player/human.rs b/player/human.rs index 14f99a6..70b6e5d 100644 --- a/player/human.rs +++ b/player/human.rs @@ -1,4 +1,4 @@ -use crate::game::{Board, Cell, Move, CELL_EMPTY, CELL_O, CELL_X}; +use crate::game::{Board, Cell, Move}; use crate::player::Player; use std::io; @@ -8,10 +8,10 @@ pub struct PlayerConsole { } impl PlayerConsole { - pub fn new(name: &str, p: Cell) -> PlayerConsole { + pub fn new(name: &str) -> impl Player { PlayerConsole { name: name.to_owned(), - piece: p, + piece: Cell::Empty, } } @@ -19,44 +19,63 @@ impl PlayerConsole { return self.name.as_str(); } - pub fn request_move(&self, b: &Board) -> Result> { + pub fn start_new_game(&mut self, p: Cell) -> Result<(), Box> { + self.piece = p; + println!("{}, you are now playing {}", self.name, p); + + Ok(()) + } + + pub fn request_move(&self, _: &Board) -> Result> { let mut x: usize = 0; let mut y: usize = 0; loop { - println!("{}", b); - println!("player {}, it is your turn", self.name); + println!("{}, it is your turn", self.name); - print!("x: "); let mut s = String::new(); - io::stdin().read_line(&mut s).expect("could not read input"); - if let Ok(x_) = s.parse::() { - x = x_; - } else { - continue; - }; + io::stdin().read_line(&mut s)?; - print!("y: "); - let mut s = String::new(); - io::stdin().read_line(&mut s).expect("could not read input"); - if let Ok(y_) = s.parse::() { - y = y_; - } else { + s = s.trim().into(); + + if s == "" { + return Err("player has surrendered".into()); + } else if s.len() != 2 { + println!("invalid input. type board coordinates as a1, b2 etc..."); continue; - }; + } + + for c in s.chars() { + match c { + 'a' => y = 0, + 'b' => y = 1, + 'c' => y = 2, + '1' => x = 0, + '2' => x = 1, + '3' => x = 2, + _ => { + println!("invalid input: {}", c); + continue; + } + } + } break; } return Ok(Move { x: x, - y: x, - c: self.piece, + y: y, + piece: self.piece, }); } } impl Player for PlayerConsole { + fn start_new_game(&mut self, p: Cell) -> Result<(), Box> { + return self.start_new_game(p); + } + fn request_move(&self, b: &Board) -> Result> { self.request_move(b) } @@ -65,9 +84,3 @@ impl Player for PlayerConsole { self.name() } } - -impl std::fmt::Display for PlayerConsole { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "Player: {}, piece: {}", self.name, self.piece) - } -} diff --git a/player/mod.rs b/player/mod.rs index c5a3ac8..3d47eea 100644 --- a/player/mod.rs +++ b/player/mod.rs @@ -1,9 +1,10 @@ mod human; -use crate::game::{Board, Move}; +use crate::game::{Board, Cell, Move}; pub use human::PlayerConsole; pub trait Player { + fn start_new_game(&mut self, p: Cell) -> Result<(), Box>; fn request_move(&self, b: &Board) -> Result>; fn name(&self) -> &str; }