While having experience with other languages I only recently started learning Rust and I was hoping to get some feedback on my little beginner project, especially regarding style (stuff like too many functions, should rather be refactored into struct with methods, etc.) and safety (whether there are situations that could crash the program).
I already ran Clippy but intentionally ignored two warnings because I preferred my own solution.
One thing I'm already unsure of is the way I solved draws and wins as I find my current solution not really nice.
use std::{fmt, io};
fn main() {
let mut field = [FieldState::Blank; 9];
print_field(&field);
loop {
println!("Player 1 (X) take your turn");
take_turn(FieldState::Player1, &mut field);
print_field(&field);
print_win(check_win(&field));
print_draw(&field);
println!("Player 2 (O) take your turn");
take_turn(FieldState::Player2, &mut field);
print_field(&field);
print_win(check_win(&field));
print_draw(&field);
}
}
#[derive(Copy, Clone, PartialEq)]
enum FieldState {
Blank,
Player1,
Player2,
}
impl fmt::Display for FieldState {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
FieldState::Blank => write!(f, " "),
FieldState::Player1 => write!(f, "X"),
FieldState::Player2 => write!(f, "O"),
}
}
}
fn print_field(field: &[FieldState; 9]) {
for i in 0..2 {
println!("{}|{}|{}", field[i * 3], field[1 + i * 3], field[2 + i * 3]);
println!("-----");
}
println!("{}|{}|{}", field[2 * 3], field[1 + 2 * 3], field[2 + 2 * 3]);
}
fn take_turn(p: FieldState, f: &mut [FieldState; 9]) {
loop {
let choice = (read_user_input() - 1) as usize;
if f[choice] != FieldState::Blank {
println!("There's already a marking there! Pick another place.");
continue;
} else {
f[choice] = p;
return;
}
}
}
fn read_user_input() -> u8 {
loop {
let mut ans = String::new();
io::stdin()
.read_line(&mut ans)
.expect("Failed to read input");
let ans: i16 = match ans.trim().parse() {
Ok(n) => n,
Err(_) => {
println!("Please enter a number from 1 to 9");
continue;
}
};
if ans < 1 || ans > 9 {
println!("Please enter a number from 1 to 9");
continue;
} else {
return ans.try_into().unwrap();
}
}
}
fn check_win(f: &[FieldState; 9]) -> FieldState {
// Columns
for x in 0..3 {
if f[x] != FieldState::Blank && f[x] == f[3 + x] && f[x] == f[6 + x] {
return f[x];
}
}
// Rows
for y in 0..3 {
if f[3 * y] != FieldState::Blank
&& f[3 * y] == f[1 + 3 * y]
&& f[3 * y] == f[2 + 3 * y]
{
return f[3 * y];
}
}
// Diagonals
if f[0] != FieldState::Blank && f[0] == f[4] && f[0] == f[8] {
return f[0];
}
if f[2] != FieldState::Blank && f[2] == f[4] && f[0] == f[6] {
return f[2];
}
FieldState::Blank
}
fn print_draw(f: &[FieldState; 9]) {
for i in 0..9 {
if f[i] == FieldState::Blank {
return;
}
}
println!("It's a draw!");
std::process::exit(0);
}
fn print_win(winner: FieldState) {
if winner == FieldState::Player1 {
println!("Player 1 (X) won!");
std::process::exit(0);
} else if winner == FieldState::Player2 {
println!("Player 2 (O) won!");
std::process::exit(0);
}
}