From e84d6c60546ce7986d0446379917e2a57b776925 Mon Sep 17 00:00:00 2001 From: Dmitry Fedotov Date: Sun, 6 Apr 2025 18:37:49 +0300 Subject: [PATCH] add engine --- engine/engine.rs | 44 +++++++++++++++++++ engine/mod.rs | 3 ++ game/entity.rs | 107 +++++++++++++++++++++++++++++++++++++++++++++++ game/mod.rs | 4 +- game/ttt.rs | 29 ------------- main.rs | 10 ++++- player/human.rs | 73 ++++++++++++++++++++++++++++++++ player/mod.rs | 9 ++++ 8 files changed, 246 insertions(+), 33 deletions(-) create mode 100644 engine/engine.rs create mode 100644 engine/mod.rs create mode 100644 game/entity.rs delete mode 100644 game/ttt.rs create mode 100644 player/human.rs create mode 100644 player/mod.rs diff --git a/engine/engine.rs b/engine/engine.rs new file mode 100644 index 0000000..dc19b6b --- /dev/null +++ b/engine/engine.rs @@ -0,0 +1,44 @@ +use crate::game; +use crate::player; + +pub struct Engine { + players: Vec, + turn: usize, + board: game::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); + + let board = game::Board::new(); + + Engine { + players: vec![p1, p2], + turn: 0, + board: board, + } + } + + pub fn run(&mut self) -> Result<(), Box> { + loop { + let m = self.players[self.turn].request_move(&self.board)?; + + let ok = self.board.put(m); + if !ok { + continue; + } + + let winner = self.board.has_winner(); + if winner != game::CELL_EMPTY { + println!("the winner is {}", self.players[self.turn].name()); + break; + } + + self.turn = (self.turn + 1) % 2 + } + + Ok(()) + } +} diff --git a/engine/mod.rs b/engine/mod.rs new file mode 100644 index 0000000..ded3cc8 --- /dev/null +++ b/engine/mod.rs @@ -0,0 +1,3 @@ +mod engine; + +pub use engine::Engine; diff --git a/game/entity.rs b/game/entity.rs new file mode 100644 index 0000000..403c559 --- /dev/null +++ b/game/entity.rs @@ -0,0 +1,107 @@ +#[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); + +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"; + } + + write!(f, "{}", s) + } +} + +pub struct Move { + pub x: usize, + pub y: usize, + pub c: Cell, +} + +pub struct Board { + cells: Vec, + next: Cell, +} + +impl Board { + pub fn new() -> Board { + return Board { + cells: vec![CELL_EMPTY; 9], + next: CELL_X, + }; + } + + pub fn reset(&mut self) { + self.cells = vec![CELL_EMPTY; 9]; + self.next = CELL_X; + } + + pub fn put(&mut self, m: Move) -> bool { + let i = m.x * 1 + m.y * 3; + if self.cells[i] != CELL_EMPTY { + return false; + } + + self.cells[i] = m.c; + + return true; + } + + pub fn has_winner(&self) -> Cell { + // 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] + { + return 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[2] == self.cells[4] && self.cells[2] == self.cells[6] { + return self.cells[2]; + } + + // no winner + return CELL_EMPTY; + } +} + +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"); + for i in 0..3 { + s.push_str(&format!( + "│{}│{}│{}│\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"); + + 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)); + } + write!(f, "{s}") + } +} diff --git a/game/mod.rs b/game/mod.rs index 08d847e..9a71726 100644 --- a/game/mod.rs +++ b/game/mod.rs @@ -1,2 +1,2 @@ -mod ttt; -pub use ttt::{Board, Cell}; +mod entity; +pub use entity::{Board, Cell, Move, CELL_EMPTY, CELL_O, CELL_X}; diff --git a/game/ttt.rs b/game/ttt.rs deleted file mode 100644 index 1b7b915..0000000 --- a/game/ttt.rs +++ /dev/null @@ -1,29 +0,0 @@ -use std::io::Empty; - -pub enum Cell { - Empty, - X, - O, -} - -pub struct Board { - cells: Vec, - next: Cell, -} - -impl Board { - pub fn new() -> Board { - return Board { - cells: Vec::new(), - next: Cell::X, - }; - } - - pub fn put(&self, x: usize, y: usize, _: Cell) -> bool { - return false; - } - - pub fn winner(self) -> Cell { - return Cell::Empty; - } -} diff --git a/main.rs b/main.rs index 0ca312d..09e6f53 100644 --- a/main.rs +++ b/main.rs @@ -1,6 +1,12 @@ +mod engine; mod game; +mod player; fn main() { - let b = game::Board::new(); - let c = game::Cell::X; + let mut engine = engine::Engine::new(); + + match engine.run() { + Ok(_) => println!("game over"), + Err(e) => println!("{}", e), + }; } diff --git a/player/human.rs b/player/human.rs new file mode 100644 index 0000000..14f99a6 --- /dev/null +++ b/player/human.rs @@ -0,0 +1,73 @@ +use crate::game::{Board, Cell, Move, CELL_EMPTY, CELL_O, CELL_X}; +use crate::player::Player; +use std::io; + +pub struct PlayerConsole { + name: String, + piece: Cell, +} + +impl PlayerConsole { + pub fn new(name: &str, p: Cell) -> PlayerConsole { + PlayerConsole { + name: name.to_owned(), + piece: p, + } + } + + pub fn name(&self) -> &str { + return self.name.as_str(); + } + + pub fn request_move(&self, b: &Board) -> Result> { + let mut x: usize = 0; + let mut y: usize = 0; + + loop { + println!("{}", b); + println!("player {}, 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; + }; + + 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 { + continue; + }; + + break; + } + + return Ok(Move { + x: x, + y: x, + c: self.piece, + }); + } +} + +impl Player for PlayerConsole { + fn request_move(&self, b: &Board) -> Result> { + self.request_move(b) + } + + fn name(&self) -> &str { + 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 new file mode 100644 index 0000000..c5a3ac8 --- /dev/null +++ b/player/mod.rs @@ -0,0 +1,9 @@ +mod human; +use crate::game::{Board, Move}; + +pub use human::PlayerConsole; + +pub trait Player { + fn request_move(&self, b: &Board) -> Result>; + fn name(&self) -> &str; +}