feat: impl player struct + add game type selection + set dummy bot
This commit is contained in:
parent
adfa99149e
commit
4dff19228d
68
Cargo.lock
generated
68
Cargo.lock
generated
@ -2,6 +2,74 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.2.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.137"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ppv-lite86"
|
||||||
|
version = "0.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.8.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"rand_chacha",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_chacha"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||||
|
dependencies = [
|
||||||
|
"ppv-lite86",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tic-tac-toe"
|
name = "tic-tac-toe"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"rand",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.11.0+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||||
|
|||||||
@ -6,3 +6,4 @@ edition = "2021"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
rand = "0.8.5"
|
||||||
|
|||||||
271
src/main.rs
271
src/main.rs
@ -1,3 +1,4 @@
|
|||||||
|
use rand::prelude::*;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
/// Represents the `item` on the TTT board (X or O)
|
/// Represents the `item` on the TTT board (X or O)
|
||||||
@ -29,21 +30,12 @@ impl TryFrom<String> for Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Two `GameType` availables:
|
impl Item {
|
||||||
/// * TwoPlayers: no bot
|
fn next(&self) -> Self {
|
||||||
/// * Bot: player against the bot
|
match self {
|
||||||
enum GameType {
|
Item::X => Item::O,
|
||||||
TwoPlayers,
|
Item::O => Item::X,
|
||||||
Bot,
|
Item::Empty => Item::Empty,
|
||||||
Unknown,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<String> for GameType {
|
|
||||||
fn from(game_type: String) -> Self {
|
|
||||||
match &game_type[..] {
|
|
||||||
"0" => Self::TwoPlayers,
|
|
||||||
"1" => Self::Bot,
|
|
||||||
_ => Self::Unknown,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -91,21 +83,27 @@ impl Board {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_item(&mut self, item: Item, pos: String) -> Result<usize, &str> {
|
/// returns the available/free board indexes
|
||||||
let idx = if let Ok(v) = usize::from_str(&pos) {
|
fn get_available_indexes(&self) -> Vec<usize> {
|
||||||
v
|
let mut availables = vec![];
|
||||||
} else {
|
for (idx, item) in self.items.iter().enumerate() {
|
||||||
return Err("unable to parse the input, retry!");
|
if item == &Item::Empty {
|
||||||
};
|
availables.push(idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
availables
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_item(&mut self, item: Item, idx: usize) -> Result<(), &str> {
|
||||||
if idx == 0 || idx > 9 {
|
if idx == 0 || idx > 9 {
|
||||||
return Err("can't be greater than 8");
|
return Err("invalid input, must be between 1 <= x <= 9");
|
||||||
}
|
}
|
||||||
match self.items[idx - 1] {
|
match self.items[idx - 1] {
|
||||||
Item::X => Err("already set, choose another one!"),
|
Item::X => Err("already set, choose another one!"),
|
||||||
Item::O => Err("already set, choose another one!"),
|
Item::O => Err("already set, choose another one!"),
|
||||||
Item::Empty => {
|
Item::Empty => {
|
||||||
self.items[idx - 1] = item;
|
self.items[idx - 1] = item;
|
||||||
return Ok(idx - 1);
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -200,8 +198,9 @@ impl Displayer for Board {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
for item in items {
|
for item in items {
|
||||||
group_items.push(item.clone());
|
group_items.push(item);
|
||||||
if group_items.len() != 3 {
|
if group_items.len() != 3 {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -214,10 +213,68 @@ impl Displayer for Board {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
struct Player {
|
||||||
|
item: Item,
|
||||||
|
is_bot: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Player {
|
||||||
|
fn new(item: Item, is_bot: bool) -> Self {
|
||||||
|
Player { item, is_bot }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Player {
|
||||||
|
fn default() -> Self {
|
||||||
|
Player {
|
||||||
|
item: Item::Empty,
|
||||||
|
is_bot: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Player {
|
||||||
|
/// init players with the game type
|
||||||
|
fn from_game_type(game_type: GameType) -> Vec<Player> {
|
||||||
|
match game_type {
|
||||||
|
GameType::TwoPlayers => vec![
|
||||||
|
Player::new(Item::Empty, false),
|
||||||
|
Player::new(Item::Empty, false),
|
||||||
|
],
|
||||||
|
GameType::Bot => vec![
|
||||||
|
Player::new(Item::Empty, false),
|
||||||
|
Player::new(Item::Empty, true),
|
||||||
|
],
|
||||||
|
GameType::Unknown => vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Two `GameType` availables:
|
||||||
|
/// * TwoPlayers: no bot
|
||||||
|
/// * Bot: player against the bot
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
struct TicTacToe {
|
struct TicTacToe {
|
||||||
board: Board,
|
board: Board,
|
||||||
current_item: Item,
|
players: Vec<Player>,
|
||||||
game_type: GameType,
|
game_type: GameType,
|
||||||
turn: u8,
|
turn: u8,
|
||||||
}
|
}
|
||||||
@ -226,14 +283,15 @@ impl Default for TicTacToe {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
TicTacToe {
|
TicTacToe {
|
||||||
board: Board::new(),
|
board: Board::new(),
|
||||||
game_type: GameType::Bot,
|
game_type: GameType::Unknown,
|
||||||
current_item: Item::Empty,
|
players: vec![],
|
||||||
turn: 1,
|
turn: 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TicTacToe {
|
impl TicTacToe {
|
||||||
|
/// associated func in order to get user input
|
||||||
fn get_user_input(message: &str) -> String {
|
fn get_user_input(message: &str) -> String {
|
||||||
info(message.to_string());
|
info(message.to_string());
|
||||||
let mut input = String::new();
|
let mut input = String::new();
|
||||||
@ -243,12 +301,40 @@ impl TicTacToe {
|
|||||||
input.trim().to_string()
|
input.trim().to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_current_item(&mut self, item: Item) {
|
fn set_game_type(&mut self, game_type: GameType) {
|
||||||
self.current_item = item;
|
self.game_type = game_type;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// tries to get the first item (the one that starts)
|
fn set_players_item(&mut self, first_item: Item) -> Result<(), String> {
|
||||||
fn select_current_item(&mut self) {
|
if self.players.len() != 2 {
|
||||||
|
return Err(
|
||||||
|
"unable to initialize players item, 2 players must be initialized".to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.players[0].item = first_item;
|
||||||
|
self.players[1].item = first_item.next();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// on each turn, set the next player who has to play (players vec swapped)
|
||||||
|
fn get_next_player(&mut self) -> Result<Player, String> {
|
||||||
|
if self.players.len() != 2 {
|
||||||
|
return Err("unable to get next player, 2 players must be initialized".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
// on first turn, no need to swap
|
||||||
|
if self.turn > 1 {
|
||||||
|
self.players.swap(0, 1);
|
||||||
|
}
|
||||||
|
self.turn += 1;
|
||||||
|
|
||||||
|
Ok(self.players[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// set the item for the first player (the one that starts)
|
||||||
|
fn select_first_player_item(&mut self) {
|
||||||
let mut attempt = 0;
|
let mut attempt = 0;
|
||||||
loop {
|
loop {
|
||||||
if attempt == 2 {
|
if attempt == 2 {
|
||||||
@ -256,12 +342,14 @@ impl TicTacToe {
|
|||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
let item_start =
|
let input = TicTacToe::get_user_input("first player, choose your item: ('X' or 'O')");
|
||||||
TicTacToe::get_user_input("first player, choose your item: ('X' or 'O')");
|
let item_start = Item::try_from(input);
|
||||||
let item = Item::try_from(item_start);
|
match item_start {
|
||||||
match item {
|
Ok(item) => {
|
||||||
Ok(i) => {
|
match self.set_players_item(item) {
|
||||||
self.set_current_item(i);
|
Ok(()) => break,
|
||||||
|
Err(e) => fatal(e),
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Err(e) => error(e.to_string()),
|
Err(e) => error(e.to_string()),
|
||||||
@ -270,19 +358,60 @@ impl TicTacToe {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// on each turn, set the next item who has to play
|
/// select game type and init players
|
||||||
fn next_item(&mut self) {
|
fn select_game_type(&mut self) {
|
||||||
match self.current_item {
|
let mut attempt = 0;
|
||||||
Item::X => self.set_current_item(Item::O),
|
loop {
|
||||||
Item::O => self.set_current_item(Item::X),
|
if attempt == 2 {
|
||||||
_ => return,
|
error("come on... you can do better!".to_string());
|
||||||
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
self.turn += 1;
|
|
||||||
|
let input = TicTacToe::get_user_input("choose your game type: ('0': versus, '1': bot)");
|
||||||
|
let game_type = GameType::from(input);
|
||||||
|
match game_type {
|
||||||
|
GameType::Unknown => error("incorrect game type selected, retry!".to_string()),
|
||||||
|
_ => {
|
||||||
|
self.set_game_type(game_type);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
attempt += 1;
|
||||||
|
}
|
||||||
|
// init players
|
||||||
|
self.players = Player::from_game_type(self.game_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_player_item(&self) -> Result<usize, String> {
|
||||||
|
if self.players.len() != 2 {
|
||||||
|
return Err(
|
||||||
|
"unable to get player item input, 2 players must be initialized".to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let player = self.players[0];
|
||||||
|
let item_icon: String = player.item.into();
|
||||||
|
|
||||||
|
if !player.is_bot {
|
||||||
|
let turn_message = format!("{} turn, which case do you want to fill ?", item_icon);
|
||||||
|
let idx = TicTacToe::get_user_input(&turn_message);
|
||||||
|
match usize::from_str(&idx) {
|
||||||
|
Ok(v) => return Ok(v),
|
||||||
|
Err(e) => return Err(e.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// for now, use a dummy bot
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
let mut available_indexes = self.board.get_available_indexes();
|
||||||
|
available_indexes.shuffle(&mut rng);
|
||||||
|
|
||||||
|
Ok(available_indexes[0] + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(&mut self) {
|
fn run(&mut self) {
|
||||||
self.select_current_item();
|
self.select_game_type();
|
||||||
self.board.show();
|
self.select_first_player_item();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
if self.board.is_full() {
|
if self.board.is_full() {
|
||||||
@ -291,39 +420,57 @@ impl TicTacToe {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
let item_icon: String = self.current_item.into();
|
let mut player = Player::default();
|
||||||
let turn_message = format!(
|
match self.get_next_player() {
|
||||||
"{} turn, which case do you wan to fill ?",
|
Ok(p) => player = p,
|
||||||
item_icon.clone()
|
Err(e) => fatal(e),
|
||||||
);
|
}
|
||||||
let idx = TicTacToe::get_user_input(&turn_message);
|
|
||||||
match self.board.set_item(self.current_item.clone(), idx) {
|
if !player.is_bot {
|
||||||
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();
|
self.board.show();
|
||||||
break;
|
}
|
||||||
|
|
||||||
|
let item_icon: String = player.item.into();
|
||||||
|
let mut item_position;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match self.select_player_item() {
|
||||||
|
Ok(pos) => {
|
||||||
|
item_position = pos;
|
||||||
|
match self.board.set_item(player.item, pos) {
|
||||||
|
Ok(_) => break,
|
||||||
|
Err(e) => {
|
||||||
|
error(e.to_string());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error(e.to_string());
|
error(e.to_string());
|
||||||
continue;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.next_item();
|
// check after the 5th round if there's a winner
|
||||||
|
if self.turn > 4 && self.board.check_win(player.item, item_position - 1) {
|
||||||
|
info(format!("{} wins!", item_icon));
|
||||||
self.board.show();
|
self.board.show();
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn info(msg: String) {
|
fn info(msg: String) {
|
||||||
println!("{}", format!("[TIC TAC TOE] [INFO] => {}", msg))
|
println!("{}", format!("[tic-tac-toe] [info] => {}", msg))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn error(msg: String) {
|
fn error(msg: String) {
|
||||||
eprintln!("{}", format!("[TIC TAC TOE] [ERROR] => {}", msg))
|
eprintln!("{}", format!("[tic-tac-toe] [error] => {}", msg))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fatal(msg: String) {
|
||||||
|
eprintln!("{}", format!("[tic-tac-toe] [fatal] => {}", msg));
|
||||||
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user