first tic-tac-toe implementation
This commit is contained in:
commit
adfa99149e
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/target
|
||||
tic-tac-toe
|
||||
7
Cargo.lock
generated
Normal file
7
Cargo.lock
generated
Normal file
@ -0,0 +1,7 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "tic-tac-toe"
|
||||
version = "0.1.0"
|
||||
8
Cargo.toml
Normal file
8
Cargo.toml
Normal file
@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "tic-tac-toe"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
13
README.md
Normal file
13
README.md
Normal file
@ -0,0 +1,13 @@
|
||||
# Tic-Tac-Toe
|
||||
|
||||
A simple Rust implementation of the famous game: **Tic-Tac-Toe**
|
||||
|
||||
## Build
|
||||
```bash
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
## Run
|
||||
```bash
|
||||
./tic-tac-toe
|
||||
```
|
||||
332
src/main.rs
Normal file
332
src/main.rs
Normal file
@ -0,0 +1,332 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
/// Represents the `item` on the TTT board (X or O)
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
enum Item {
|
||||
X,
|
||||
O,
|
||||
Empty,
|
||||
}
|
||||
|
||||
impl Into<String> for Item {
|
||||
fn into(self) -> String {
|
||||
match self {
|
||||
Self::X => "X".to_string(),
|
||||
Self::O => "O".to_string(),
|
||||
Self::Empty => "_".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for Item {
|
||||
type Error = String;
|
||||
fn try_from(item: String) -> Result<Self, Self::Error> {
|
||||
match &item[..] {
|
||||
"X" | "x" => Ok(Self::X),
|
||||
"O" | "o" => Ok(Self::O),
|
||||
_ => Err("unknown item, retry!".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Two `GameType` availables:
|
||||
/// * TwoPlayers: no bot
|
||||
/// * Bot: player against the bot
|
||||
enum GameType {
|
||||
TwoPlayers,
|
||||
Bot,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl From<String> for GameType {
|
||||
fn from(game_type: String) -> Self {
|
||||
match &game_type[..] {
|
||||
"0" => Self::TwoPlayers,
|
||||
"1" => Self::Bot,
|
||||
_ => Self::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// In order to visualize the board game
|
||||
trait Displayer {
|
||||
fn show(&self);
|
||||
}
|
||||
|
||||
/// Represents the **Tic-Tac-Toe** board game
|
||||
struct Board {
|
||||
// board array is reprensented as follow:
|
||||
// [ 0, 1, 2 ]
|
||||
// [ 3, 4, 5 ]
|
||||
// [ 6, 7, 8 ]
|
||||
items: [Item; 9],
|
||||
}
|
||||
|
||||
impl Board {
|
||||
fn new() -> Board {
|
||||
Board {
|
||||
items: [Item::Empty; 9],
|
||||
}
|
||||
}
|
||||
|
||||
fn base_display(group_items: Vec<String>) {
|
||||
// output: X | _ | O
|
||||
println!(
|
||||
"{}",
|
||||
format!(
|
||||
"{:<5} {} {:<2}|{:<2} {} {:<2}|{:<2} {}",
|
||||
"", group_items[0], "", "", group_items[1], "", "", group_items[2]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/// checks whether the board is item's full (quit the game)
|
||||
fn is_full(&self) -> bool {
|
||||
for item in self.items {
|
||||
match item {
|
||||
Item::Empty => return false,
|
||||
_ => continue,
|
||||
};
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn set_item(&mut self, item: Item, pos: String) -> Result<usize, &str> {
|
||||
let idx = if let Ok(v) = usize::from_str(&pos) {
|
||||
v
|
||||
} else {
|
||||
return Err("unable to parse the input, retry!");
|
||||
};
|
||||
if idx == 0 || idx > 9 {
|
||||
return Err("can't be greater than 8");
|
||||
}
|
||||
match self.items[idx - 1] {
|
||||
Item::X => Err("already set, choose another one!"),
|
||||
Item::O => Err("already set, choose another one!"),
|
||||
Item::Empty => {
|
||||
self.items[idx - 1] = item;
|
||||
return Ok(idx - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_first_line(&self, item: Item) -> bool {
|
||||
self.items[0] == item && self.items[1] == item && self.items[2] == item
|
||||
}
|
||||
|
||||
fn check_second_line(&self, item: Item) -> bool {
|
||||
self.items[3] == item && self.items[4] == item && self.items[5] == item
|
||||
}
|
||||
|
||||
fn check_last_line(&self, item: Item) -> bool {
|
||||
self.items[6] == item && self.items[7] == item && self.items[8] == item
|
||||
}
|
||||
|
||||
fn check_first_col(&self, item: Item) -> bool {
|
||||
self.items[0] == item && self.items[3] == item && self.items[6] == item
|
||||
}
|
||||
|
||||
fn check_second_col(&self, item: Item) -> bool {
|
||||
self.items[1] == item && self.items[4] == item && self.items[7] == item
|
||||
}
|
||||
|
||||
fn check_last_col(&self, item: Item) -> bool {
|
||||
self.items[2] == item && self.items[5] == item && self.items[8] == item
|
||||
}
|
||||
|
||||
fn check_first_diag(&self, item: Item) -> bool {
|
||||
self.items[0] == item && self.items[4] == item && self.items[8] == item
|
||||
}
|
||||
|
||||
fn check_last_diag(&self, item: Item) -> bool {
|
||||
self.items[2] == item && self.items[4] == item && self.items[6] == item
|
||||
}
|
||||
|
||||
/// instead of scanning all the board, checks input "win" combinaison
|
||||
fn check_win(&self, item: Item, idx: usize) -> bool {
|
||||
match idx {
|
||||
0 => {
|
||||
self.check_first_col(item)
|
||||
|| self.check_first_line(item)
|
||||
|| self.check_first_diag(item)
|
||||
}
|
||||
1 => self.check_first_line(item) || self.check_second_col(item),
|
||||
2 => {
|
||||
self.check_first_line(item)
|
||||
|| self.check_last_col(item)
|
||||
|| self.check_last_diag(item)
|
||||
}
|
||||
3 => self.check_first_col(item) || self.check_second_line(item),
|
||||
4 => {
|
||||
self.check_second_col(item)
|
||||
|| self.check_second_line(item)
|
||||
|| self.check_last_diag(item)
|
||||
|| self.check_first_diag(item)
|
||||
}
|
||||
5 => self.check_last_col(item) || self.check_second_line(item),
|
||||
6 => {
|
||||
self.check_first_col(item)
|
||||
|| self.check_last_line(item)
|
||||
|| self.check_last_diag(item)
|
||||
}
|
||||
7 => self.check_last_line(item) || self.check_second_col(item),
|
||||
8 => {
|
||||
self.check_last_line(item)
|
||||
|| self.check_last_col(item)
|
||||
|| self.check_first_diag(item)
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Displayer for Board {
|
||||
/// loops over board items, groups items by 3 (multi dim array representation) and display it
|
||||
/// also displays board index number (empty item) for easy use
|
||||
fn show(&self) {
|
||||
println!("\n");
|
||||
println!("{:<3}{:-<23}", "", "");
|
||||
|
||||
let mut group_items: Vec<String> = vec![];
|
||||
let items: Vec<String> = self
|
||||
.items
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, x)| {
|
||||
if x != &Item::Empty {
|
||||
Into::<String>::into(*x)
|
||||
} else {
|
||||
format!("{}", idx + 1)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
for item in items {
|
||||
group_items.push(item.clone());
|
||||
if group_items.len() != 3 {
|
||||
continue;
|
||||
}
|
||||
Board::base_display(group_items.clone());
|
||||
group_items.clear();
|
||||
println!("{:<3}{:-<23}", "", "");
|
||||
}
|
||||
|
||||
println!("\n");
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
struct TicTacToe {
|
||||
board: Board,
|
||||
current_item: Item,
|
||||
game_type: GameType,
|
||||
turn: u8,
|
||||
}
|
||||
|
||||
impl Default for TicTacToe {
|
||||
fn default() -> Self {
|
||||
TicTacToe {
|
||||
board: Board::new(),
|
||||
game_type: GameType::Bot,
|
||||
current_item: Item::Empty,
|
||||
turn: 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TicTacToe {
|
||||
fn get_user_input(message: &str) -> String {
|
||||
info(message.to_string());
|
||||
let mut input = String::new();
|
||||
std::io::stdin()
|
||||
.read_line(&mut input)
|
||||
.expect("IO error: unable to read the stdin input");
|
||||
input.trim().to_string()
|
||||
}
|
||||
|
||||
fn set_current_item(&mut self, item: Item) {
|
||||
self.current_item = item;
|
||||
}
|
||||
|
||||
/// tries to get the first item (the one that starts)
|
||||
fn select_current_item(&mut self) {
|
||||
let mut attempt = 0;
|
||||
loop {
|
||||
if attempt == 2 {
|
||||
error("come on... you can do better!".to_string());
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
let item_start =
|
||||
TicTacToe::get_user_input("first player, choose your item: ('X' or 'O')");
|
||||
let item = Item::try_from(item_start);
|
||||
match item {
|
||||
Ok(i) => {
|
||||
self.set_current_item(i);
|
||||
break;
|
||||
}
|
||||
Err(e) => error(e.to_string()),
|
||||
}
|
||||
attempt += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// on each turn, set the next item who has to play
|
||||
fn next_item(&mut self) {
|
||||
match self.current_item {
|
||||
Item::X => self.set_current_item(Item::O),
|
||||
Item::O => self.set_current_item(Item::X),
|
||||
_ => return,
|
||||
}
|
||||
self.turn += 1;
|
||||
}
|
||||
|
||||
fn run(&mut self) {
|
||||
self.select_current_item();
|
||||
self.board.show();
|
||||
|
||||
loop {
|
||||
if self.board.is_full() {
|
||||
info("game over!".to_string());
|
||||
self.board.show();
|
||||
break;
|
||||
}
|
||||
|
||||
let item_icon: String = self.current_item.into();
|
||||
let turn_message = format!(
|
||||
"{} turn, which case do you wan to fill ?",
|
||||
item_icon.clone()
|
||||
);
|
||||
let idx = TicTacToe::get_user_input(&turn_message);
|
||||
match self.board.set_item(self.current_item.clone(), idx) {
|
||||
Ok(i) => {
|
||||
// no need to check for a winner until the turn 5
|
||||
if self.turn > 4 && self.board.check_win(self.current_item.clone(), i) {
|
||||
info(format!("{} wins!", item_icon.clone()));
|
||||
self.board.show();
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error(e.to_string());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
self.next_item();
|
||||
self.board.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn info(msg: String) {
|
||||
println!("{}", format!("[TIC TAC TOE] [INFO] => {}", msg))
|
||||
}
|
||||
|
||||
fn error(msg: String) {
|
||||
eprintln!("{}", format!("[TIC TAC TOE] [ERROR] => {}", msg))
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut game = TicTacToe::default();
|
||||
game.run();
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user