From 3a00e077d8143a02ed01a7dade7666c60ffbcb3c 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 | 43 +++++++++++------ game/entity.rs | 117 ++++++++++++++++++++++++++++++++--------------- game/mod.rs | 2 +- player/human.rs | 56 ++++++++++++----------- 4 files changed, 139 insertions(+), 79 deletions(-) diff --git a/engine/engine.rs b/engine/engine.rs index dc19b6b..f4c8c5d 100644 --- a/engine/engine.rs +++ b/engine/engine.rs @@ -1,21 +1,22 @@ -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, } 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("P1", Cell::CellX); + let p2 = PlayerConsole::new("P2", Cell::CellO); - 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, } @@ -23,16 +24,32 @@ impl Engine { pub fn run(&mut self) -> Result<(), Box> { loop { + self.board.reset(); + match self.run_single_game() { + Ok(_) => {} + Err(e) => break Err(e), + } + + // TODO: switch sides + } + } + + fn run_single_game(&mut self) -> Result<(), Box> { + loop { + println!("{}", self.board); + 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()); + self.board.put(m)?; + + if let Some(_winner) = self.board.has_winner() { + println!("{}", self.board); + println!("The winner is: {}", self.players[self.turn].name()); break; } diff --git a/game/entity.rs b/game/entity.rs index 403c559..99b9107 100644 --- a/game/entity.rs +++ b/game/entity.rs @@ -1,16 +1,22 @@ #[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 struct Cell(u8); +//pub const Cell::CellEmpty: Cell = Cell(0); +//pub const Cell::CellX: Cell = Cell(1); +//pub const Cell::CellO: Cell = Cell(2); + +pub enum Cell { + CellEmpty, + CellX, + CellO, +} 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 mut s: &str = ""; + match *self { + Cell::CellX => s = "X", + Cell::CellO => s = "O", + Cell::CellEmpty => s = " ", } write!(f, "{}", s) @@ -20,7 +26,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 { @@ -31,50 +37,78 @@ pub struct Board { impl Board { pub fn new() -> Board { return Board { - cells: vec![CELL_EMPTY; 9], - next: CELL_X, + cells: vec![Cell::CellEmpty; 9], + next: Cell::CellX, }; } pub fn reset(&mut self) { - self.cells = vec![CELL_EMPTY; 9]; - self.next = CELL_X; + self.cells = vec![Cell::CellEmpty; 9]; + self.next = Cell::CellX; } - pub fn put(&mut self, m: Move) -> bool { + pub fn put(&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::CellX => self.next = Cell::CellO, + Cell::CellO => self.next = Cell::CellX, + _ => {} + } + + 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::CellEmpty + } + + 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] == self.cells[i + 3] && self.cells[i] == self.cells[i + 6]) + && (self.cells[i] != Cell::CellEmpty + && self.cells[i + 3] != Cell::CellEmpty + && self.cells[i + 6] != Cell::CellEmpty) { - return self.cells[i * 3]; + return Some(self.cells[i]); + } else if (self.cells[i * 3] == self.cells[i * 3 + 1] + && self.cells[i * 3] == self.cells[i * 3 + 2]) + && (self.cells[i * 3] != Cell::CellEmpty + && self.cells[i * 3 + 1] != Cell::CellEmpty + && self.cells[i * 3 + 2] != Cell::CellEmpty) + { + 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] == self.cells[4] && self.cells[0] == self.cells[8]) + && (self.cells[0] != Cell::CellEmpty + && self.cells[4] != Cell::CellEmpty + && self.cells[8] != Cell::CellEmpty) + { + 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] == self.cells[4] && self.cells[2] == self.cells[6]) + && (self.cells[2] != Cell::CellEmpty + && self.cells[4] != Cell::CellEmpty + && self.cells[6] != Cell::CellEmpty) + { + return Some(self.cells[2]); } // no winner - return CELL_EMPTY; + return None; } } @@ -82,8 +116,16 @@ 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", self.cells[i * 3], @@ -91,17 +133,16 @@ impl std::fmt::Display for Board { 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 => s.push_str(&format!("Next: {}", self.next)), } + 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..e354a34 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,7 +8,7 @@ pub struct PlayerConsole { } impl PlayerConsole { - pub fn new(name: &str, p: Cell) -> PlayerConsole { + pub fn new(name: &str, p: Cell) -> impl Player { PlayerConsole { name: name.to_owned(), piece: p, @@ -19,39 +19,47 @@ impl PlayerConsole { return self.name.as_str(); } - pub fn request_move(&self, b: &Board) -> Result> { + 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, }); } } @@ -65,9 +73,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) - } -}