Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
ae02672
start of console game version
PhilipCramer Mar 15, 2024
1d27d8e
player vs ai can now be played in terminal
PhilipCramer Mar 15, 2024
49631d0
Merge branch 'ai_optimization' of github:PhilipCramer/RustyOthelloAI …
PhilipCramer Mar 15, 2024
ab6b720
now plays ai vs ai
PhilipCramer Mar 17, 2024
c068f08
exits after console game is done
PhilipCramer Mar 17, 2024
db71152
Merge branch 'ai_optimization' of github:PhilipCramer/RustyOthelloAI …
PhilipCramer Mar 17, 2024
5c910bb
now allows testing different ai setups against eachother
PhilipCramer Mar 17, 2024
d5be41f
Merge branch 'ai_optimization' of github:PhilipCramer/RustyOthelloAI …
PhilipCramer Mar 17, 2024
9f647a0
created testing binary for faster ai vs ai simulation
PhilipCramer Mar 17, 2024
c9abf19
added new files
PhilipCramer Mar 17, 2024
39688dc
Merge branch 'memory_reduction' of github:PhilipCramer/RustyOthelloAI…
PhilipCramer Mar 18, 2024
b7bd16f
updated to match memory reduction changes
PhilipCramer Mar 18, 2024
be345e6
Merge branch 'master' of github:PhilipCramer/RustyOthelloAI into cons…
PhilipCramer Mar 18, 2024
1dea1ca
Merge branch 'master' of github:PhilipCramer/RustyOthelloAI into cons…
PhilipCramer Mar 20, 2024
239fae1
updated to match changes from master branch
PhilipCramer Mar 20, 2024
45bfe94
initial bitwise othello
PhilipCramer Dec 20, 2024
b0b59cc
chore: DRY
PhilipCramer Dec 20, 2024
af28e7b
started bitwise game state implementation
PhilipCramer Feb 21, 2025
607ab2a
removed unneeded assignments
PhilipCramer Feb 21, 2025
672483c
chore: refactored player_turn function for readability
PhilipCramer Mar 14, 2025
6d2668f
fix: missing io flush and input trim
PhilipCramer Mar 14, 2025
0f15e90
Formatting
PhilipCramer Apr 4, 2025
f9a9a9f
Formatting
PhilipCramer Apr 4, 2025
dec80b6
Formatting
PhilipCramer Apr 4, 2025
0ad752e
removed unused PlayerCommand struct
PhilipCramer Apr 4, 2025
f244e9f
finished bitwise game state implementation
PhilipCramer Apr 25, 2025
8c686a8
fixed edge cases
PhilipCramer Apr 25, 2025
a700a2f
updated to match bitwise game state implementation
PhilipCramer Apr 25, 2025
2522631
improved player experience
PhilipCramer Apr 25, 2025
46da844
fixed warnings
PhilipCramer Apr 25, 2025
7f5d984
changed test module name to snake_case
PhilipCramer Apr 25, 2025
9c58807
removed commented out code
PhilipCramer Apr 30, 2025
3ae7351
Merge branch 'master' of github:PhilipCramer/RustyOthelloAI into bitw…
PhilipCramer Apr 30, 2025
df66ac5
Fixed parse_state to match implementation
PhilipCramer Apr 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions src/bin/ai-test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use rusty_othello_ai::mcts::MCTS;
use rusty_othello_ai::othello::{caculate_win, Color, State};
use std::isize;

pub fn main() {
let args: Vec<String> = std::env::args().collect();
let mut win_balance: isize = 0;
let a: f32 = args
.get(1)
.expect("Missing value for A")
.parse()
.expect("Not a valid floatingpoint number");
let b: f32 = args
.get(2)
.expect("Missing value for A")
.parse()
.expect("Not a valid floatingpoint number");

let mut state = State::new();
let mut mcts = MCTS::new("true", a);
let mut mcts2 = MCTS::new("false", b);
let mut ai_iterations = 500;
loop {
state = ai_turn(&mut mcts, state.clone(), ai_iterations);
if state.remaining_moves == 0 {
break;
}
state = ai_turn(&mut mcts2, state.clone(), ai_iterations);
if state.remaining_moves == 0 {
break;
}
ai_iterations += ai_iterations / 100;
}
win_balance += match caculate_win(state) {
Some(Color::WHITE) => 1,
Some(Color::BLACK) => -1,
None => 0,
};
println!("{win_balance}")
}

fn ai_turn(mcts: &mut MCTS, state: State, iterations: usize) -> State {
let dev_null = |_a: usize, _b: usize, _c: &Color| -> () {};
let action = mcts.search(state.clone(), iterations, dev_null);
if action.is_ok() {
state.clone().do_action(Some(action.unwrap().clone()))
} else {
state.clone().do_action(None)
}
}
130 changes: 130 additions & 0 deletions src/console_game.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
use std::io::Write;
use std::isize;
use std::process::exit;

use crate::mcts::MCTS;
use crate::othello::{caculate_win, print_state, Action, Color, Position, State};

enum GameCommand {
SKIP,
QUIT,
INVALID,
MOVE(usize, usize),
}

pub fn console_game() {
let mut win_balance: isize = 0;
let a = 1.0;
println!("Game mode: player vs AI\n");
let mut state = State::new();
let mut mcts = MCTS::new("true", a);
_ = std::io::stdout().flush();
let mut ai_iterations = 20000;
loop {
print_state(state);
state = player_turn(state.clone());
if state.remaining_moves == 0 {
break;
}
print_state(state);
state = ai_turn(&mut mcts, state.clone(), ai_iterations);
ai_iterations += ai_iterations / 100;

if state.remaining_moves == 0 {
break;
}
}
//print_state(state);
win_balance += match caculate_win(state) {
Some(Color::WHITE) => {
println!("White wins!");
1
}
Some(Color::BLACK) => {
println!("Black wins!");
-1
}
None => {
println!("Draw.");
0
}
};
//println!("\nGAME OVER\n");
println!("\nResult: {win_balance}")
}

fn ai_turn(mcts: &mut MCTS, state: State, iterations: usize) -> State {
let dev_null = |_a: usize, _b: usize, _c: &Color| -> () { /*println!("Progress: {a}/{b}")*/ };
let action = mcts.search(state.clone(), iterations, dev_null);
if action.is_ok() {
println!("{:?}", action.clone().unwrap().position);
state.clone().do_action(Some(action.unwrap().clone()))
} else {
state.clone().do_action(None)
}
}

fn player_turn(state: State) -> State {
let mut player_choice;
loop {
print!("Enter coordinates for desired move: ");
let _ = std::io::stdout().flush();
let cmd = read_command();
match cmd {
GameCommand::QUIT => exit(0),
GameCommand::INVALID => {
println!("Please provide a valid command 'quit' 'skip' or 'x,y'")
}
GameCommand::SKIP => {
player_choice = None;
break;
}
GameCommand::MOVE(x_index, y_index) => {
player_choice = Some(Action {
color: Color::BLACK,
position: Position {
x: x_index,
y: y_index,
},
});
if state
.get_actions()
.contains(&player_choice.clone().unwrap())
{
break;
} else {
println!("Invalid move.");
let pos: Vec<(usize, usize)> = state
.get_actions()
.iter()
.map(|a| (a.position.y, a.position.x))
.collect();
println!("Valid moves: {:?}", pos);
print_state(state);
}
}
}
}
state.clone().do_action(player_choice)
}

fn read_command() -> GameCommand {
let mut buf = String::new();
let _ = std::io::stdin().read_line(&mut buf);
match buf.to_lowercase().as_str().trim() {
"quit" => GameCommand::QUIT,
"skip" => GameCommand::SKIP,
line => {
let cmd: Vec<&str> = line.trim().split(",").clone().collect();
match (cmd.get(0), cmd.get(1)) {
(Some(cmd_1), Some(cmd_2)) => {
match (cmd_1.parse::<usize>(), cmd_2.parse::<usize>()) {
(Ok(y_index), Ok(x_index)) => GameCommand::MOVE(x_index, y_index),
_ => GameCommand::INVALID,
}
}
_ => GameCommand::INVALID,
}
}
}
}
24 changes: 16 additions & 8 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
use rusty_othello_ai::mcts::MCTS;
use rusty_othello_ai::othello::{parse_state, Action, State};
use std::thread::current;
use std::process::exit;
use std::time::Duration;
use std::usize;
use std::{borrow::Borrow, thread::sleep};
use ureq::Response;
mod console_game;
mod mcts;
mod othello;
use console_game::console_game;
use mcts::MCTS;
use othello::{parse_state, Action, Color, State};

const SERVER_URL: &str = "http://localhost:8181";

Expand All @@ -27,6 +31,10 @@ fn main() {
x if x == "1" => ai_color = "true".to_string(),
x if x == "w" => ai_color = "true".to_string(),
x if x == "white" => ai_color = "true".to_string(),
x if x == "console" => {
console_game();
exit(0);
}
_ => panic!("Please pass a proper argument to the AI"),
}
// Initialize the game state and the Monte Carlo Tree Search (MCTS)
Expand Down Expand Up @@ -60,7 +68,7 @@ fn main() {
}
// If it's not the AI's turn, it performs a search using MCTS and waits
Ok(false) => {
let dev_null = |_a: usize, _b: usize, _c: &i8| -> () {};
let dev_null = |_a: usize, _b: usize, _c: &Color| -> () {};
_ = mcts.search(state, 1000, dev_null);
//sleep(Duration::from_secs(1));
}
Expand Down Expand Up @@ -143,7 +151,7 @@ fn send_move(player: &String, ai_move: Option<Action>) -> Result<Response, ureq:
let ai_choice = ai_move.unwrap();
url = format!(
"{}/setChoice/{}/{}/{}",
SERVER_URL, ai_choice.x, ai_choice.y, player
SERVER_URL, ai_choice.position.x, ai_choice.position.y, player
);
}
// If the AI does not have a move, format the URL for the skipTurn endpoint
Expand All @@ -154,10 +162,10 @@ fn send_move(player: &String, ai_move: Option<Action>) -> Result<Response, ureq:
resp = ureq::get(&url).call()?;
Ok(resp)
}
fn send_progress(current: usize, total: usize, ai_color: &i8) {
fn send_progress(current: usize, total: usize, ai_color: &Color) {
let color = match ai_color {
1 => "false",
_ => "true",
Color::BLACK => "false",
Color::WHITE => "true",
};
let url = format!("{}/AIStatus/{}/{}/{}", SERVER_URL, current, total, color);
_ = ureq::post(&url).call();
Expand Down
Loading
Loading