From 5426da7d078445310da125b40a0997f19a050e18 Mon Sep 17 00:00:00 2001 From: Dmitry Fedotov Date: Sun, 4 Jan 2026 17:45:57 +0300 Subject: [PATCH] refactor, add lobby --- engine/engine.rs | 67 ++++-------------------- game/entity.rs | 57 ++++---------------- lobby/lobby.rs | 23 ++++++++ lobby/mod.rs | 2 + main.rs | 5 +- player/human.rs | 134 +++++++++++++++++++++++++++++++++++++---------- player/mod.rs | 2 +- 7 files changed, 153 insertions(+), 137 deletions(-) create mode 100644 lobby/lobby.rs create mode 100644 lobby/mod.rs diff --git a/engine/engine.rs b/engine/engine.rs index b498331..a2af1c3 100644 --- a/engine/engine.rs +++ b/engine/engine.rs @@ -1,7 +1,5 @@ -use std::io; - use crate::game::{Board, Cell}; -use crate::player::{Player, PlayerConsole}; +use crate::player::Player; pub struct Engine { players: Vec>, @@ -11,15 +9,10 @@ pub struct Engine { } impl Engine { - pub fn new() -> Engine { - // TODO: accept players as args - let p1 = PlayerConsole::new("Gopher"); - let p2 = PlayerConsole::new("Rustacean"); - + pub fn new(p1: Box, p2: Box) -> Engine { let board = Board::new(); - Engine { - players: vec![Box::new(p1), Box::new(p2)], + players: vec![p1, p2], turn: 0, board: board, x: 0, @@ -41,32 +34,16 @@ impl Engine { // switch sides self.x = (self.x + 1) % 2; - - match request_input("Press Enter to continue or \"q\" to quit.") - .as_str() - .trim() - { - "q" => { - return Ok(()); - } - _ => { - continue; - } - } } } fn run_single_game(&mut self) -> Result<(), Box> { loop { - cls(); - home(); - println!("{}", self.board); - // request move from player, fail if there is error let m = self.players[self.turn].request_move(&self.board)?; + let next = (self.turn + 1) % 2; if !self.board.is_valid_move(&m) { - println!("invalid move"); continue; } @@ -75,46 +52,20 @@ impl Engine { // check if there is a winner if let Some(_winner) = self.board.has_winner() { - cls(); - home(); - println!("{} wins!", self.players[self.turn].name()); - println!("{}", self.board); - request_input("Press Enter to continue..."); + self.players[self.turn].message("You win!")?; + self.players[next].message("You loose!")?; break; } if !self.board.valid_moves_available() { - cls(); - home(); - println!("It's a draw!"); - println!("{}", self.board); - request_input("press Enter to continue"); + self.players[self.turn].message("It's a draw!")?; + self.players[next].message("It's a draw!")?; break; } - self.turn = (self.turn + 1) % 2 + self.turn = next } Ok(()) } } - -fn cls() { - print!("{}[2J", 27 as char); -} - -fn home() { - print!("{}[H", 27 as char) -} - -fn request_input(prompt: &str) -> String { - println!("{}", prompt); - - let mut s = String::new(); - match io::stdin().read_line(&mut s) { - Ok(_) => {} - Err(_) => {} - } - - s -} diff --git a/game/entity.rs b/game/entity.rs index 9bd1a31..7db7e00 100644 --- a/game/entity.rs +++ b/game/entity.rs @@ -5,19 +5,6 @@ pub enum Cell { O, } -impl std::fmt::Display for Cell { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - let s: &str; - match *self { - Cell::X => s = "X", - Cell::O => s = "O", - Cell::Empty => s = " ", - } - - write!(f, "{}", s) - } -} - pub struct Move { pub x: usize, pub y: usize, @@ -60,6 +47,10 @@ impl Board { } pub fn is_valid_move(&self, m: &Move) -> bool { + if m.x > 2 || m.y > 2 { + return false; + } + let i = m.x * 1 + m.y * 3; m.piece == self.next && self.cells[i] == Cell::Empty @@ -107,42 +98,12 @@ impl Board { // no winner return None; } -} -impl std::fmt::Display for Board { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - let mut s = String::new(); + pub fn cells(&self) -> Vec { + self.cells.clone() + } - 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], - self.cells[i * 3 + 1], - self.cells[i * 3 + 2] - )); - if i < 2 { - s.push_str(" ├───┼───┼───┤\n"); - } - } - s.push_str(" └───┴───┴───┘\n"); - - 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}") + pub fn next_move(&self) -> Cell { + self.next } } diff --git a/lobby/lobby.rs b/lobby/lobby.rs new file mode 100644 index 0000000..f9cfa65 --- /dev/null +++ b/lobby/lobby.rs @@ -0,0 +1,23 @@ +use crate::engine::Engine; +use crate::player::PlayerConsole; + +pub struct Lobby {} + +impl Lobby { + pub fn new() -> Lobby { + Lobby {} + } + + pub fn run(self) -> Result<(), Box> { + loop { + let p1 = Box::new(PlayerConsole::new("Gopher")); + let p2 = Box::new(PlayerConsole::new("Rustacean")); + let mut engine = Engine::new(p1, p2); + + match engine.run() { + Ok(_) => continue, + Err(e) => return Err(e), + }; + } + } +} diff --git a/lobby/mod.rs b/lobby/mod.rs new file mode 100644 index 0000000..9657221 --- /dev/null +++ b/lobby/mod.rs @@ -0,0 +1,2 @@ +mod lobby; +pub use lobby::Lobby; diff --git a/main.rs b/main.rs index 09e6f53..ccd0167 100644 --- a/main.rs +++ b/main.rs @@ -1,11 +1,12 @@ mod engine; mod game; +mod lobby; mod player; fn main() { - let mut engine = engine::Engine::new(); + let lobby = lobby::Lobby::new(); - match engine.run() { + match lobby.run() { Ok(_) => println!("game over"), Err(e) => println!("{}", e), }; diff --git a/player/human.rs b/player/human.rs index 70b6e5d..a49dab8 100644 --- a/player/human.rs +++ b/player/human.rs @@ -2,6 +2,59 @@ use crate::game::{Board, Cell, Move}; use crate::player::Player; use std::io; +impl std::fmt::Display for Cell { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let s: &str; + match *self { + Cell::X => s = "X", + Cell::O => s = "O", + Cell::Empty => s = " ", + } + + write!(f, "{}", s) + } +} + +impl std::fmt::Display for Board { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let mut s = String::new(); + + let cells = self.cells(); + + 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", + cells[i * 3], + cells[i * 3 + 1], + cells[i * 3 + 2] + )); + if i < 2 { + s.push_str(" ├───┼───┼───┤\n"); + } + } + s.push_str(" └───┴───┴───┘\n"); + + 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_move())), + false => s.push_str("DRAW!"), + }, + } + + write!(f, "{s}") + } +} + pub struct PlayerConsole { name: String, piece: Cell, @@ -15,10 +68,6 @@ impl PlayerConsole { } } - pub fn name(&self) -> &str { - return self.name.as_str(); - } - pub fn start_new_game(&mut self, p: Cell) -> Result<(), Box> { self.piece = p; println!("{}, you are now playing {}", self.name, p); @@ -26,33 +75,35 @@ impl PlayerConsole { Ok(()) } - pub fn request_move(&self, _: &Board) -> Result> { - let mut x: usize = 0; - let mut y: usize = 0; + pub fn request_move(&self, board: &Board) -> Result> { + let mut m: Move = Move { + x: 0, + y: 0, + piece: self.piece, + }; loop { - println!("{}, it is your turn", self.name); + cls(); + home(); + println!("{}", board); - let mut s = String::new(); - io::stdin().read_line(&mut s)?; + let s: String = request_input("Your turn (or \"q\" to quit):").trim().into(); - s = s.trim().into(); - - if s == "" { + if s == "q" { return Err("player has surrendered".into()); } else if s.len() != 2 { - println!("invalid input. type board coordinates as a1, b2 etc..."); + _ = request_input("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, + 'a' => m.y = 0, + 'b' => m.y = 1, + 'c' => m.y = 2, + '1' => m.x = 0, + '2' => m.x = 1, + '3' => m.x = 2, _ => { println!("invalid input: {}", c); continue; @@ -60,27 +111,54 @@ impl PlayerConsole { } } + if !board.is_valid_move(&m) { + _ = request_input("Invalid move. Press Enter and try again..."); + continue; + } + break; } - return Ok(Move { - x: x, - y: y, - piece: self.piece, - }); + return Ok(m); } + + pub fn message(&self, msg: &str) -> Result<(), Box> { + let s = &format!("Message from game master: \"{}\"", msg); + _ = request_input(s); + Ok(()) + } +} + +fn cls() { + print!("{}[2J", 27 as char); +} + +fn home() { + print!("{}[H", 27 as char) +} + +fn request_input(prompt: &str) -> String { + println!("{}", prompt); + + let mut s = String::new(); + match io::stdin().read_line(&mut s) { + Ok(_) => {} + Err(_) => {} + } + + s } impl Player for PlayerConsole { fn start_new_game(&mut self, p: Cell) -> Result<(), Box> { - return self.start_new_game(p); + self.start_new_game(p) } fn request_move(&self, b: &Board) -> Result> { self.request_move(b) } - fn name(&self) -> &str { - self.name() + fn message(&self, msg: &str) -> Result<(), Box> { + self.message(msg) } } diff --git a/player/mod.rs b/player/mod.rs index 3d47eea..20cf255 100644 --- a/player/mod.rs +++ b/player/mod.rs @@ -6,5 +6,5 @@ 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; + fn message(&self, msg: &str) -> Result<(), Box>; }