Skip to content

Commit eead3e6

Browse files
feat : add interactive cli
1 parent 0e240ae commit eead3e6

3 files changed

Lines changed: 126 additions & 21 deletions

File tree

topics/tic-tac-toe/src/ai.rs

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ impl AI {
1717
/// Returns the position (0-8) of the best move
1818
pub fn find_best_move(&self, game: &Game) -> Option<usize> {
1919
let available_moves = game.available_moves();
20-
20+
2121
if available_moves.is_empty() {
2222
return None;
2323
}
@@ -40,23 +40,23 @@ impl AI {
4040
}
4141

4242
/// Minimax algorithm with depth tracking
43-
///
43+
///
4444
/// # Arguments
4545
/// * `game` - The current game state
4646
/// * `depth` - Current depth in the game tree
4747
/// * `is_maximizing` - True if maximizing player (AI), false if minimizing (Human)
48-
///
48+
///
4949
/// # Returns
5050
/// The score of the board state
5151
fn minimax(&self, game: &mut Game, depth: i32, is_maximizing: bool) -> i32 {
5252
// Terminal state: check if game is over
5353
let score = game.evaluate();
54-
54+
5555
// If AI won, return score minus depth (prefer faster wins)
5656
if score == 10 {
5757
return score - depth;
5858
}
59-
59+
6060
// If Human won, return score plus depth (prefer slower losses)
6161
if score == -10 {
6262
return score + depth;
@@ -71,24 +71,24 @@ impl AI {
7171
if is_maximizing {
7272
// Maximizing player (AI)
7373
let mut best_score = i32::MIN;
74-
74+
7575
for &position in &available_moves {
7676
let mut game_clone = self.simulate_move(game, position, Player::AI);
7777
let score = self.minimax(&mut game_clone, depth + 1, false);
7878
best_score = best_score.max(score);
7979
}
80-
80+
8181
best_score
8282
} else {
8383
// Minimizing player (Human)
8484
let mut best_score = i32::MAX;
85-
85+
8686
for &position in &available_moves {
8787
let mut game_clone = self.simulate_move(game, position, Player::Human);
8888
let score = self.minimax(&mut game_clone, depth + 1, true);
8989
best_score = best_score.min(score);
9090
}
91-
91+
9292
best_score
9393
}
9494
}
@@ -97,17 +97,17 @@ impl AI {
9797
fn simulate_move(&self, game: &Game, position: usize, player: Player) -> Game {
9898
// Create a copy of the current game using the board state
9999
let mut new_board = Board::new();
100-
100+
101101
// Copy the current board state
102102
for i in 0..9 {
103103
if let Some(crate::types::Cell::Occupied(p)) = game.board().get(i) {
104104
new_board.make_move(i, p);
105105
}
106106
}
107-
107+
108108
// Make the new move on the copied board
109109
new_board.make_move(position, player);
110-
110+
111111
// Create a new game with this board state
112112
// We need to use Game::from_board or similar
113113
// For now, let's create a helper in Game
@@ -134,12 +134,12 @@ mod tests {
134134
fn test_ai_blocks_winning_move() {
135135
let mut game = Game::new();
136136
let ai = AI::new();
137-
137+
138138
// Human has two in a row
139139
game.make_move(0); // Human X at position 0
140140
game.make_move(3); // AI O at position 3
141141
game.make_move(1); // Human X at position 1
142-
142+
143143
// AI should block position 2 to prevent human win
144144
let best_move = ai.find_best_move(&game);
145145
assert_eq!(best_move, Some(2));
@@ -149,14 +149,14 @@ mod tests {
149149
fn test_ai_takes_winning_move() {
150150
let mut game = Game::new();
151151
let ai = AI::new();
152-
152+
153153
// Setup: AI has two in a row
154154
game.make_move(0); // Human X
155155
game.make_move(3); // AI O
156156
game.make_move(1); // Human X
157157
game.make_move(4); // AI O
158158
game.make_move(8); // Human X
159-
159+
160160
// AI should take position 5 to win
161161
let best_move = ai.find_best_move(&game);
162162
assert_eq!(best_move, Some(5));
@@ -166,7 +166,7 @@ mod tests {
166166
fn test_ai_finds_move_on_empty_board() {
167167
let game = Game::new();
168168
let ai = AI::new();
169-
169+
170170
// AI should find a valid move
171171
let best_move = ai.find_best_move(&game);
172172
assert!(best_move.is_some());

topics/tic-tac-toe/src/board.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ impl Board {
2626
if position >= 9 {
2727
return false;
2828
}
29-
29+
3030
if self.cells[position].is_empty() {
3131
self.cells[position] = Cell::Occupied(player);
3232
true

topics/tic-tac-toe/src/main.rs

Lines changed: 108 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,113 @@
1-
mod types;
1+
mod ai;
22
mod board;
33
mod game;
4-
mod ai;
4+
mod types;
5+
6+
use ai::AI;
7+
use game::{Game, GameState};
8+
use std::io::{self, Write};
9+
use types::Player;
510

611
fn main() {
7-
println!("Tic-Tac-Toe game - Coming soon!");
12+
println!("=================================");
13+
println!(" Welcome to Tic-Tac-Toe!");
14+
println!("=================================");
15+
println!();
16+
println!("You are X, AI is O");
17+
println!("Enter positions 1-9 as shown:");
18+
println!();
19+
display_position_guide();
20+
println!();
21+
22+
let mut game = Game::new();
23+
let ai = AI::new();
24+
25+
loop {
26+
// Display the current board
27+
game.board().display();
28+
29+
// Check game state
30+
match game.state() {
31+
GameState::Won(Player::Human) => {
32+
println!("🎉 Congratulations! You won!");
33+
break;
34+
}
35+
GameState::Won(Player::AI) => {
36+
println!("💻 AI wins! Better luck next time!");
37+
break;
38+
}
39+
GameState::Draw => {
40+
println!("🤝 It's a draw! Well played!");
41+
break;
42+
}
43+
GameState::InProgress => {
44+
// Game continues
45+
}
46+
}
47+
48+
// Current player's turn
49+
if game.current_player() == Player::Human {
50+
// Human turn
51+
println!("Your turn (X)");
52+
let position = get_human_move(&game);
53+
54+
if !game.make_move(position) {
55+
println!("❌ Invalid move! Try again.");
56+
continue;
57+
}
58+
} else {
59+
// AI turn
60+
println!("AI is thinking... 🤔");
61+
62+
if let Some(position) = ai.find_best_move(&game) {
63+
game.make_move(position);
64+
println!("AI played position {}", position + 1);
65+
} else {
66+
println!("❌ Error: AI couldn't find a move!");
67+
break;
68+
}
69+
}
70+
}
71+
72+
println!();
73+
println!("Thanks for playing!");
74+
}
75+
76+
/// Gets a valid move from the human player
77+
fn get_human_move(game: &Game) -> usize {
78+
loop {
79+
print!("Enter position (1-9): ");
80+
io::stdout().flush().unwrap();
81+
82+
let mut input = String::new();
83+
io::stdin()
84+
.read_line(&mut input)
85+
.expect("Failed to read line");
86+
87+
// Try to parse the input
88+
match input.trim().parse::<usize>() {
89+
Ok(num) if (1..=9).contains(&num) => {
90+
let position = num - 1; // Convert to 0-indexed
91+
92+
// Check if position is available
93+
if game.available_moves().contains(&position) {
94+
return position;
95+
} else {
96+
println!("❌ That position is already taken! Try another.");
97+
}
98+
}
99+
_ => {
100+
println!("❌ Invalid input! Please enter a number between 1 and 9.");
101+
}
102+
}
103+
}
104+
}
105+
106+
/// Displays the position guide (how positions are numbered)
107+
fn display_position_guide() {
108+
println!(" 1 | 2 | 3");
109+
println!(" -----------");
110+
println!(" 4 | 5 | 6");
111+
println!(" -----------");
112+
println!(" 7 | 8 | 9");
8113
}

0 commit comments

Comments
 (0)