From fa6f0b05200e4be50df53e5fa70bf5e6ed5a8427 Mon Sep 17 00:00:00 2001 From: Christophe Parent Date: Fri, 27 Oct 2023 22:41:25 -0700 Subject: [PATCH 1/2] Play in Terminal --- .gitignore | 15 ---- Cargo.lock | 7 ++ Cargo.toml | 4 + README.md | 62 ++++++++++++++- demo.png | Bin 0 -> 9763 bytes src/main.rs | 223 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 294 insertions(+), 17 deletions(-) create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 demo.png create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore index 3ca43ae..2f7896d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,16 +1 @@ -# ---> Rust -# Generated by Cargo -# will have compiled files and executables -debug/ target/ - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html -Cargo.lock - -# These are backup files generated by rustfmt -**/*.rs.bk - -# MSVC Windows builds of rustc generate these, which store debugging information -*.pdb - diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..fb57717 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "oox" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..cc896ad --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,4 @@ +[package] +name = "oox" +version = "0.1.0" +edition = "2021" diff --git a/README.md b/README.md index cef1eea..5876c66 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,61 @@ -# oox ++++ +title = "Tmp" +date = 2023-10-29 +draft = false ++++ +# OOX -A Tic‐tac‐toe game \ No newline at end of file +The name ”OOX” is a mutually recursive acronym: “OOX” stands for “**O**OX **O**r **X**OO”, while “XOO” stands for “**X**OO **O**r **O**OX”. Other than that this is your typical [Tic‐tac‐toe game](https://en.wikipedia.org/wiki/Tic-tac-toe), used as a training ground to explore the development of games in Rust. + +![OOX in action](/demo.png) + +## Usage + +### Requirements + +Rust must be installed on your system; the oldest supported version is `1.63.0`. No other dependencies are required. + +### Installing + +Clone this repository locally: + +```shell +$ git clone https://forge.thatspaceandtime.org/ooxie/oox.git +$ cd oox +``` + +### Building + +To build the binary, execute: + +```shell +$ cargo build --release +``` + +### Running + +To run the game, either execute (this will also build if need be): + +```shell +$ cargo run --release +``` + +or: + +```shell +$ ./target/release/oox +``` + +### Playing + +SETUP: This game is for two players. Player 1 plays with marks noted ”X” and Player 2 with marks noted ”O”. The board consists of 3 rows and 3 columns, and is empty at the start of the game. + +GOAL: Each player tries to align three of their marks on the board. + +GAMEPLAY: Each player takes their turn alternatively, starting with Player 1. Each turn a player must place one mark on one of the free spaces of the board; they do this by entering the coordinates of the desired space (for example ”a1”). + +END OF GAME: If one player succeeds in aligning three of their marks, either horizontally, vertically, or diagonally, they win the game. If no player is able to do so by the time the board is full, the game is declared a draw. + +## License + +The source code of this project is licensed under a [GNU General Public License v3.0](/LICENSE). diff --git a/demo.png b/demo.png new file mode 100644 index 0000000000000000000000000000000000000000..5c4784efcdb0f1d8124089cdb5b774bd809ecadb GIT binary patch literal 9763 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 V42Jj2{OukBl;qT9ze-y~{x3z3_00eP literal 0 HcmV?d00001 diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..326d540 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,223 @@ +use std::collections::HashMap; +use std::error::Error; +use std::fmt::Display; +use std::fmt::Formatter; +use std::fmt::Result as FmtResult; +use std::fmt::Write; +use std::io::stdin; +use std::process; + +const BOARD_SIZE: u32 = 3; +const FIRST_COLUMN_LABEL: u32 = 0x61; // LATIN SMALL LETTER A in UTF-8 +const LAST_COLUMN_LABEL: u32 = FIRST_COLUMN_LABEL + BOARD_SIZE - 1; +const FIRST_ROW_LABEL: u32 = 0x31; // DIGIT ONE in UTF-8 +const LAST_ROW_LABEL: u32 = FIRST_ROW_LABEL + BOARD_SIZE - 1; + +enum Player { + P1, + P2, +} + +impl Player { + fn next(&mut self) { + *self = match self { + Player::P1 => Player::P2, + Player::P2 => Player::P1, + }; + } +} + +impl Display for Player { + fn fmt(&self, formatter: &mut Formatter) -> FmtResult { + write!( + formatter, + "{}", + match self { + Player::P1 => "1", + Player::P2 => "2", + } + ) + } +} + +struct Coordinates { + row: u32, + column: u32, +} + +#[derive(Clone, Copy, PartialEq)] +enum Mark { + X, + O, +} + +enum Status { + SpaceAlreadyTaken, + InProgress, + Won, + Draw, +} + +fn draw(board: &[Option]) -> FmtResult { + println!(); + for row_index in 0..BOARD_SIZE as usize { + let mut row = (BOARD_SIZE as usize - row_index).to_string(); + for column_index in 0..BOARD_SIZE as usize { + write!( + row, + " {}", + match board[row_index * BOARD_SIZE as usize + column_index] { + Some(mark) => match mark { + Mark::X => "X", + Mark::O => "O", + }, + None => "•", + } + )?; + } + println!("{}", row); + } + let mut footer_row = String::from(" "); + for x in FIRST_COLUMN_LABEL..=LAST_COLUMN_LABEL { + write!(footer_row, " {}", char::from_u32(x).unwrap_or(' '))?; + } + println!("{}\n", footer_row); + Ok(()) +} + +fn get_input() -> Result { + let mut input = String::new(); + if let Err(error) = stdin().read_line(&mut input) { + return Err(error.to_string()); + } + let mut chars = input.trim().chars(); + let coordinates = Coordinates { + column: match chars.next() { + Some(x) if x as u32 >= FIRST_COLUMN_LABEL && x as u32 <= LAST_COLUMN_LABEL => { + x as u32 - FIRST_COLUMN_LABEL + } + _ => return Err("That input is invalid.".to_string()), + }, + row: match chars.next() { + Some(x) if x as u32 >= FIRST_ROW_LABEL && x as u32 <= LAST_ROW_LABEL => { + BOARD_SIZE as u32 - 1 - (x as u32 - FIRST_ROW_LABEL) + } + _ => return Err("That input is invalid.".to_string()), + }, + }; + match chars.next() { + Some(_) => Err("That input is invalid.".to_string()), + _ => Ok(coordinates), + } +} + +fn process_logic( + current_player: &Player, + board: &mut [Option], + lines: &mut HashMap)>, + input_coordinates: Coordinates, +) -> Result { + let current_index = + match usize::try_from(input_coordinates.row * BOARD_SIZE + input_coordinates.column) { + Ok(x) => x, + Err(error) => return Err(error.to_string()), + }; + if board[current_index] != None { + return Ok(Status::SpaceAlreadyTaken); + } + let current_mark = match current_player { + Player::P1 => Mark::X, + Player::P2 => Mark::O, + }; + board[current_index] = Some(current_mark); + + // The matching lines are the lines where the current space is located. + let mut matching_lines = vec![input_coordinates.row, BOARD_SIZE + input_coordinates.column]; + if input_coordinates.row == input_coordinates.column { + matching_lines.push(BOARD_SIZE * 2); + } + if input_coordinates.row + input_coordinates.column == BOARD_SIZE - 1 { + matching_lines.push(BOARD_SIZE * 2 + 1); + } + for line in matching_lines { + if let Some((number_free_spaces, mark_option)) = lines.get(&line) { + // A line with two different marks is removed from the hash map. + if mark_option != &None && mark_option != &Some(current_mark) { + lines.remove(&line); + // Otherwise, a line with more than one free space is filled with the current mark. + } else if number_free_spaces > &1 { + lines.insert(line, (number_free_spaces - 1, Some(current_mark))); + // Otherwise, ie. when a line only has one free space, + // that last free space is filled and the game is won. + } else { + return Ok(Status::Won); + } + }; + } + // The game is a draw if the board is entirely filled up and there is no winner. + if board.iter().all(|x| x.is_some()) { + return Ok(Status::Draw); + } + Ok(Status::InProgress) +} + +fn main() -> Result<(), Box> { + println!("OOX"); + println!("A Tic‐tac‐toe game"); + + let mut current_player = Player::P1; + let mut board = [None; (BOARD_SIZE * BOARD_SIZE) as usize]; + // Lines are rows, columns, and diagonals that can be a win. + // A hash map is used to keep track of what lines are still up for grabs and worth checking. + // The keys are 0-indexed integers, starting with the rows in order, followed by the columns + // in order, followed by the top-left-to-bottom-right and top-right-to-bottom-left diagonals. + let mut lines = (0..BOARD_SIZE * 2 + 2) + .map(|i| (i, (BOARD_SIZE, None))) + .collect::)>>(); + + loop { + if let Err(error) = draw(&board) { + println!("{}", error); + process::exit(1); + } + + println!("Waiting for Player {}...", current_player); + let input_coordinates = match get_input() { + Ok(x) => x, + Err(error) => { + println!("{}", error); + continue; + } + }; + + match process_logic(¤t_player, &mut board, &mut lines, input_coordinates) { + Ok(status) => match status { + Status::SpaceAlreadyTaken => println!("That space is already taken."), + Status::InProgress => current_player.next(), + Status::Won => { + if let Err(error) = draw(&board) { + println!("{}", error); + process::exit(1); + } + println!("Player {} won the game!", current_player); + break; + } + Status::Draw => { + if let Err(error) = draw(&board) { + println!("{}", error); + process::exit(1); + } + println!("The game ended in a draw!"); + break; + } + }, + Err(error) => { + println!("{}", error); + process::exit(1); + } + }; + } + + println!("\nGAME OVER"); + Ok(()) +} From bedcb4b69dde2fcfe2d67f30ffd2cff5f990e989 Mon Sep 17 00:00:00 2001 From: Christophe Parent Date: Fri, 27 Oct 2023 23:07:17 -0700 Subject: [PATCH 2/2] Clean Readme file --- README.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/README.md b/README.md index 5876c66..2661a89 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,3 @@ -+++ -title = "Tmp" -date = 2023-10-29 -draft = false -+++ # OOX The name ”OOX” is a mutually recursive acronym: “OOX” stands for “**O**OX **O**r **X**OO”, while “XOO” stands for “**X**OO **O**r **O**OX”. Other than that this is your typical [Tic‐tac‐toe game](https://en.wikipedia.org/wiki/Tic-tac-toe), used as a training ground to explore the development of games in Rust.