diff --git a/resouces/board.png b/resouces/board.png new file mode 100644 index 0000000..4a2abbf Binary files /dev/null and b/resouces/board.png differ diff --git a/resouces/bracc.png b/resouces/bracc.png new file mode 100644 index 0000000..66c0f42 Binary files /dev/null and b/resouces/bracc.png differ diff --git a/src/bored/mod.rs b/src/bored/mod.rs index 87c2771..10cdbf0 100644 --- a/src/bored/mod.rs +++ b/src/bored/mod.rs @@ -1,2 +1,124 @@ +use raylib::{ + prelude::{Color, MouseButton}, + RaylibHandle, +}; + +use crate::gamedata::{algorithms::minimax_decision, Board, Disk}; +pub const STARTY: i32 = 9; +pub const STARTX: i32 = 7; +const WX: i32 = 14; +const WY: i32 = 14; +const CIRCLEWIDTH: i32 = 56; + #[cfg(test)] mod tests; +#[derive(Clone)] +pub struct PlayState { + pub circles: Vec<(i32, i32, Disk)>, + pub bottom: Vec, + pub player_turn: bool, + pub board: Board, +} +impl Default for PlayState { + fn default() -> Self { + Self { + circles: vec![], + bottom: vec![6, 6, 6, 6, 6, 6, 6], + player_turn: true, + board: Board::default(), + } + } +} +impl PlayState { + pub fn play_human(&mut self, column: i32) { + self.board.play(Disk::P1, column as usize); + + self.bottom[column as usize] -= 1; + let (x, y) = get_circle_coords(column, self.bottom[column as usize]); + self.circles.push((x, y, Disk::P1)); + self.player_turn = false; + } + pub fn play_cpu(&mut self, diff: &i32, cook: fn(&Board, Disk, &i32) -> Board) { + self.board.play( + Disk::P2, + cook(&self.board, Disk::P2, diff).last_move as usize, + ); + let column: i32 = self.board.last_move; + self.bottom[column as usize] -= 1; + let (x, y) = get_circle_coords(column, self.bottom[column as usize]); + self.circles.push((x, y, Disk::P2)); + self.player_turn = false; + self.player_turn = true; + } +} +#[derive(Clone)] +pub struct MenuState { + pub difficulty: i32, + p1: (Color, Disk), + p2: (Color, Disk), + pub selection: Selection, + pub strategy: Strategy, +} +#[derive(Clone)] +pub enum Strategy { + MiniMax, + AlphaBeta, +} +impl Default for MenuState { + fn default() -> Self { + Self { + difficulty: 3, + p1: (Color::RED, Disk::P1), + p2: (Color::YELLOW, Disk::P2), + selection: Selection::Difficulty, + strategy: Strategy::MiniMax, + } + } +} +impl MenuState { + pub fn init(&mut self, rl: &RaylibHandle) -> bool { + dbg!(&self.selection); + match self.selection { + Selection::Difficulty => { + if rl.is_mouse_button_pressed(MouseButton::MOUSE_LEFT_BUTTON) { + self.difficulty = 3; + self.selection = Selection::Cooking; + } + if rl.is_mouse_button_pressed(MouseButton::MOUSE_RIGHT_BUTTON) { + self.difficulty = 5; + self.selection = Selection::Cooking; + } + } + Selection::Cooking => { + if rl.is_mouse_button_pressed(MouseButton::MOUSE_LEFT_BUTTON) { + self.difficulty = 3; + self.selection = Selection::Done; + } + if rl.is_mouse_button_pressed(MouseButton::MOUSE_RIGHT_BUTTON) { + self.difficulty = 5; + self.selection = Selection::Done; + } + } + Selection::Done => return true, + } + false + } +} +#[derive(Clone)] +pub enum GameState { + Play(PlayState), + MainMenu(MenuState), +} +#[derive(Clone, Debug)] +pub enum Selection { + Difficulty, + Cooking, + Done, +} + +fn get_circle_coords(x: i32, y: i32) -> (i32, i32) { + let mut returned: (i32, i32) = (0, 0); + returned.0 = STARTY + (CIRCLEWIDTH * x) + (WX * x); + returned.1 = STARTX + (CIRCLEWIDTH * y) + (WY * y); + returned +} diff --git a/src/gamedata/algorithms.rs b/src/gamedata/algorithms.rs index 31e7919..d3132f6 100644 --- a/src/gamedata/algorithms.rs +++ b/src/gamedata/algorithms.rs @@ -8,7 +8,7 @@ pub fn minimax_decision(board: &Board, disk: Disk, depth: &i32) -> Board { } fn maximise(board: &Board, disk: &Disk, depth: &i32) -> (Option, i32) { match board.game_over() || *depth == 0 { - true => return (None, get_score(board, *disk)), + true => (None, get_score(board, *disk)), false => { let (mut max_child, mut max_utility): (Option, i32) = (None, i32::MIN); for child in board.get_children(*disk) { @@ -23,7 +23,7 @@ fn maximise(board: &Board, disk: &Disk, depth: &i32) -> (Option, i32) { } fn minimise(board: &Board, disk: &Disk, depth: &i32) -> (Option, i32) { match board.game_over() || *depth == 0 { - true => return (None, get_score(board, flip_disk(*disk))), + true => (None, get_score(board, flip_disk(*disk))), false => { let (mut min_child, mut min_utility): (Option, i32) = (None, i32::MAX); for child in board.get_children(*disk) { @@ -36,20 +36,86 @@ fn minimise(board: &Board, disk: &Disk, depth: &i32) -> (Option, i32) { } } } + +pub fn minimax_decision_pruning(board: &Board, disk: Disk, depth: &i32) -> Board { + let (child, _) = maximise_pruning(board, &disk, depth, i32::MIN, i32::MAX); + match child { + Some(state) => state, + None => Board::default(), + } +} +fn maximise_pruning( + board: &Board, + disk: &Disk, + depth: &i32, + alpha: i32, + mut beta: i32, +) -> (Option, i32) { + match board.game_over() || *depth == 0 { + true => (None, get_score(board, *disk)), + false => { + let (mut max_child, mut max_utility): (Option, i32) = (None, i32::MIN); + for child in board.get_children(*disk) { + let (_, utility) = + minimise_pruning(&child, &flip_disk(*disk), &(depth - 1), alpha, beta); + if utility <= alpha { + break; + } + if utility < beta { + beta = utility; + } + + if utility > max_utility { + (max_child, max_utility) = (Some(child), utility) + } + } + (max_child, max_utility) + } + } +} +fn minimise_pruning( + board: &Board, + disk: &Disk, + depth: &i32, + mut alpha: i32, + beta: i32, +) -> (Option, i32) { + match board.game_over() || *depth == 0 { + true => (None, get_score(board, flip_disk(*disk))), + false => { + let (mut min_child, mut min_utility): (Option, i32) = (None, i32::MAX); + for child in board.get_children(*disk) { + let (_, utility) = + maximise_pruning(&child, &flip_disk(*disk), &(depth - 1), alpha, beta); + if utility >= beta { + break; + } + if utility > alpha { + alpha = utility; + } + if utility < min_utility { + (min_child, min_utility) = (Some(child), utility) + } + } + (min_child, min_utility) + } + } +} #[test] fn minimax_test() { let mut board = Board::default(); - let mut disk = Disk::BLU; - let depth = 5; - let turn1 = board.play(disk, minimax_decision(&board, disk, &5).last_move); + let mut disk = Disk::P2; + let _depth = 5; + let _turn1 = board.play(disk, minimax_decision(&board, disk, &5).last_move as usize); + dbg!(&board.columns); disk = flip_disk(disk); - let turn2 = board.play(disk, minimax_decision(&board, disk, &5).last_move); + let _turn2 = board.play(disk, minimax_decision(&board, disk, &5).last_move as usize); disk = flip_disk(disk); - let turn3 = board.play(disk, minimax_decision(&board, disk, &5).last_move); + let _turn3 = board.play(disk, minimax_decision(&board, disk, &5).last_move as usize); disk = flip_disk(disk); - let turn4 = board.play(disk, minimax_decision(&board, disk, &5).last_move); + let _turn4 = board.play(disk, minimax_decision(&board, disk, &5).last_move as usize); disk = flip_disk(disk); - let turn5 = board.play(disk, minimax_decision(&board, disk, &5).last_move); + let _turn5 = board.play(disk, minimax_decision(&board, disk, &5).last_move as usize); for column in board.columns.as_rows() { column .iter() @@ -58,7 +124,49 @@ fn minimax_test() { x }) .count(); - println!(""); + println!(); + } + assert!(false); +} +#[test] +fn minimax_pruning_test() { + let mut board = Board::default(); + let mut disk = Disk::P2; + let _depth = 5; + let _turn1 = board.play( + disk, + minimax_decision_pruning(&board, disk, &5).last_move as usize, + ); + dbg!(&board.columns); + disk = flip_disk(disk); + let _turn2 = board.play( + disk, + minimax_decision_pruning(&board, disk, &5).last_move as usize, + ); + disk = flip_disk(disk); + let _turn3 = board.play( + disk, + minimax_decision_pruning(&board, disk, &5).last_move as usize, + ); + disk = flip_disk(disk); + let _turn4 = board.play( + disk, + minimax_decision_pruning(&board, disk, &5).last_move as usize, + ); + disk = flip_disk(disk); + let _turn5 = board.play( + disk, + minimax_decision_pruning(&board, disk, &5).last_move as usize, + ); + for column in board.columns.as_rows() { + column + .iter() + .map(|x| { + print!("{:#?},", x); + x + }) + .count(); + println!(); } assert!(false); } diff --git a/src/gamedata/heuristic.rs b/src/gamedata/heuristic.rs index a16b44c..4b18552 100644 --- a/src/gamedata/heuristic.rs +++ b/src/gamedata/heuristic.rs @@ -6,25 +6,24 @@ use super::{ Board, Disk, }; //multipliers -const POT_STREAK: i32 = 4; //one streak is kind of poopy -const POT_STREAKS: i32 = 6; +const POT_STREAK: i32 = 3; //one streak is kind of poopy +const POT_STREAKS: i32 = 4; const POT_WIN: i32 = 5; // should be nerfed if its just 1 potential win -const POT_WINS: i32 = 8; -const SCORE_DIFF: i32 = 6; -const MAX_WINS: i32 = 17; +const POT_WINS: i32 = 6; +const SCORE_DIFF: i32 = 40; pub fn get_score(board: &Board, disk: Disk) -> i32 { //this should be summing up a bunch of functions defined below this one let sequences = get_streaks(&board.columns, &disk); let score: i32 = match disk { - Disk::RED => board.red_score - board.blu_score, - Disk::BLU => board.blu_score - board.red_score, + Disk::P1 => board.p1_score - board.p2_score, + Disk::P2 => board.p2_score - board.p1_score, Disk::EMPTY => panic!("Why would you ever"), }; potential_streaks(&sequences, &disk) + potential_wins(&sequences, &disk) + score * SCORE_DIFF } fn potential_wins(sequences: &Vec>, _disk: &Disk) -> i32 { - let count: i32 = sequences.iter().count() as i32; + let count: i32 = sequences.len() as i32; // for win in sequences { // if win // .iter() @@ -70,7 +69,7 @@ fn get_streaks(board: &Array2D, player_disk: &Disk) -> Vec> { for index in empty_indices { let moves = get_legal_moves(&index, (board.num_rows(), board.num_columns())); for direction in moves { - let mut sequence = heur_scan(&board, &index, direction.clone(), 4, *player_disk); + let sequence = heur_scan(board, &index, direction.clone(), 4, *player_disk); //dbg!(&index, &direction, &poopy); match sequence .iter() @@ -95,7 +94,7 @@ fn get_wins(board: &Array2D, player_disk: &Disk) -> Vec> { for direction in moves { let mut win: Vec = Vec::with_capacity(4); win.append(&mut heur_scan( - &board, + board, &index, direction.clone(), 4, @@ -115,7 +114,7 @@ fn get_wins(board: &Array2D, player_disk: &Disk) -> Vec> { } let opp_direction = flip_direction(direction.clone()); let mut opp_sequence = heur_scan( - &board, + board, &index, opp_direction, (4 - win.len() + 1) as i32, @@ -157,31 +156,31 @@ fn heur_scan( // dbg!(current_index); //go to next element match direction { - Direction::DOWN => { + Direction::Down => { if current_index.0 == 0 { break; } current_index = dec_row(¤t_index, 1); } - Direction::UP => { + Direction::Up => { if current_index.0 == board.num_rows() - 1 { break; } current_index = inc_row(¤t_index, 1); } - Direction::LEFT => { + Direction::Left => { if current_index.1 == 0 { break; } current_index = dec_col(¤t_index, 1); } - Direction::RIGHT => { + Direction::Right => { if current_index.1 == board.num_columns() - 1 { break; } current_index = inc_col(¤t_index, 1); } - Direction::UPRIGHT => { + Direction::UpRight => { if current_index.0 == board.num_rows() - 1 || current_index.1 == board.num_columns() - 1 { @@ -189,20 +188,20 @@ fn heur_scan( } current_index = inc_both(¤t_index, 1); } - Direction::UPLEFT => { + Direction::UpLeft => { if current_index.0 == board.num_columns() - 1 || current_index.1 == 0 { break; } current_index = inc_dec(¤t_index, 1); } - Direction::DOWNRIGHT => { + Direction::DownRight => { if current_index.0 == 0 || current_index.1 == board.num_columns() - 1 { break; } current_index = dec_inc(¤t_index, 1); } - Direction::DOWNLEFT => { + Direction::DownLeft => { if current_index.0 == 0 || current_index.1 == 0 { break; } @@ -225,44 +224,44 @@ fn heur_scan( #[test] fn streak_test_1() { let mut board = Board::default(); - board.play(Disk::BLU, 3); - board.play(Disk::BLU, 3); - board.play(Disk::BLU, 3); - board.play(Disk::BLU, 2); - board.play(Disk::BLU, 1); - let sequences = get_streaks(&board.columns, &Disk::BLU); - assert_eq!(18, potential_streaks(&sequences, &Disk::BLU)); - board.play(Disk::BLU, 0); - let sequences = get_streaks(&board.columns, &Disk::BLU); - board.play(Disk::BLU, 3); - board.play(Disk::BLU, 3); - board.play(Disk::BLU, 3); - board.play(Disk::BLU, 3); - let sequences = get_streaks(&board.columns, &Disk::BLU); - assert_eq!(12, potential_streaks(&sequences, &Disk::BLU)); + board.play(Disk::P2, 3); + board.play(Disk::P2, 3); + board.play(Disk::P2, 3); + board.play(Disk::P2, 2); + board.play(Disk::P2, 1); + let sequences = get_streaks(&board.columns, &Disk::P2); + assert_eq!(POT_STREAKS * 3, potential_streaks(&sequences, &Disk::P2)); + board.play(Disk::P2, 0); + let _sequences = get_streaks(&board.columns, &Disk::P2); + board.play(Disk::P2, 3); + board.play(Disk::P2, 3); + board.play(Disk::P2, 3); + board.play(Disk::P2, 3); + let sequences = get_streaks(&board.columns, &Disk::P2); + assert_eq!(POT_STREAKS * 2, potential_streaks(&sequences, &Disk::P2)); } #[test] fn win_test_flipping() { let mut board = Board::default(); - board.play(Disk::BLU, 3); - board.play(Disk::RED, 4); - board.play(Disk::BLU, 4); - board.play(Disk::RED, 5); - board.play(Disk::RED, 5); - board.play(Disk::RED, 6); - board.play(Disk::RED, 6); - board.play(Disk::RED, 6); - board.play(Disk::BLU, 6); - let sequences = get_wins(&board.columns, &Disk::BLU); + board.play(Disk::P2, 3); + board.play(Disk::P1, 4); + board.play(Disk::P2, 4); + board.play(Disk::P1, 5); + board.play(Disk::P1, 5); + board.play(Disk::P1, 6); + board.play(Disk::P1, 6); + board.play(Disk::P1, 6); + board.play(Disk::P2, 6); + let sequences = get_wins(&board.columns, &Disk::P2); dbg!(&sequences); - assert_eq!(POT_WIN, potential_wins(&sequences, &Disk::BLU)); + assert_eq!(POT_WIN, potential_wins(&sequences, &Disk::P2)); } #[test] fn win_test_flipping_hard() { let mut board = Board::default(); - board.play(Disk::BLU, 1); - board.play(Disk::BLU, 2); - board.play(Disk::BLU, 4); - let sequences = get_wins(&board.columns, &Disk::BLU); - assert_eq!(POT_WIN, potential_wins(&sequences, &Disk::BLU)); + board.play(Disk::P2, 1); + board.play(Disk::P2, 2); + board.play(Disk::P2, 4); + let sequences = get_wins(&board.columns, &Disk::P2); + assert_eq!(POT_WIN, potential_wins(&sequences, &Disk::P2)); } diff --git a/src/gamedata/indices.rs b/src/gamedata/indices.rs index f49bc40..2b4f9b1 100644 --- a/src/gamedata/indices.rs +++ b/src/gamedata/indices.rs @@ -1,15 +1,15 @@ pub fn inc_row((row, col): &(usize, usize), value: usize) -> (usize, usize) { - (row + value as usize, *col) + (row + value, *col) } pub fn inc_col((row, col): &(usize, usize), value: usize) -> (usize, usize) { - (*row, col + value as usize) + (*row, col + value) } pub fn dec_row((row, col): &(usize, usize), value: usize) -> (usize, usize) { - (row - value as usize, *col) + (row - value, *col) } pub fn dec_col((row, col): &(usize, usize), value: usize) -> (usize, usize) { - (*row, col - value as usize) + (*row, col - value) } pub fn inc_both((row, col): &(usize, usize), value: usize) -> (usize, usize) { (row + value, col + value) diff --git a/src/gamedata/mod.rs b/src/gamedata/mod.rs index 31c78f0..9506583 100644 --- a/src/gamedata/mod.rs +++ b/src/gamedata/mod.rs @@ -1,4 +1,4 @@ -mod algorithms; +pub mod algorithms; mod heuristic; mod indices; mod score_checkers; @@ -10,10 +10,10 @@ pub use indices::*; #[derive(Clone)] pub struct Board { - red_score: i32, - blu_score: i32, + p1_score: i32, + p2_score: i32, columns: Array2D, - last_move: usize, + pub last_move: i32, } impl Default for Board { @@ -21,8 +21,8 @@ impl Default for Board { let columns = Array2D::filled_with(Disk::EMPTY, 6, 7); Self { - red_score: 0, - blu_score: 0, + p1_score: 0, + p2_score: 0, columns, last_move: 0, } @@ -30,18 +30,19 @@ impl Default for Board { } impl Board { - fn getscore(&self) -> (i32, i32) { - (self.red_score, self.blu_score) + pub fn getscore(&self) -> (i32, i32) { + (self.p1_score, self.p2_score) } - fn play(&mut self, disk: Disk, col: usize) -> bool { - let column = &self.columns.as_columns()[col as usize]; + pub fn play(&mut self, disk: Disk, col: usize) -> bool { + let column = &self.columns.as_columns()[col]; let empty = column.iter().filter(|&a| matches!(a, Disk::EMPTY)).count(); // dbg!(empty); let top = column.len() - empty; - match self.columns.set(top, col as usize, disk) { + match self.columns.set(top, col, disk) { Ok(_) => { self.score_check((top, col)); - self.last_move = col; + self.last_move = col as i32; + //dbg!(self.p1_score, self.p2_score); true } Err(_) => false, @@ -55,7 +56,7 @@ impl Board { ); match self.columns.get(index.0, index.1) { Some(disk) => match disk { - Disk::RED => { + Disk::P1 => { for _move in moves { let mut consecutive = scan(&self.columns, &index, _move.clone(), 4); if consecutive < 4 { @@ -67,11 +68,11 @@ impl Board { ) } if consecutive - 1 == 4 { - self.red_score += 1 + self.p1_score += 1 } } } - Disk::BLU => { + Disk::P2 => { for _move in moves { let mut consecutive = scan(&self.columns, &index, _move.clone(), 4); if consecutive < 4 { @@ -83,16 +84,16 @@ impl Board { ) } if consecutive - 1 == 4 { - self.blu_score += 1 + self.p2_score += 1 } } } - Disk::EMPTY => return, + Disk::EMPTY => (), }, - None => return, + None => (), } } - fn game_over(&self) -> bool { + pub fn game_over(&self) -> bool { self.columns .as_row_major() .iter() @@ -115,14 +116,14 @@ impl Board { #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum Disk { - RED, - BLU, + P1, + P2, EMPTY, } pub fn flip_disk(disk: Disk) -> Disk { match disk { - Disk::RED => Disk::BLU, - Disk::BLU => Disk::RED, + Disk::P1 => Disk::P2, + Disk::P2 => Disk::P1, Disk::EMPTY => Disk::EMPTY, //why..just why } } diff --git a/src/gamedata/score_checkers.rs b/src/gamedata/score_checkers.rs index 387f344..e2730a9 100644 --- a/src/gamedata/score_checkers.rs +++ b/src/gamedata/score_checkers.rs @@ -10,9 +10,8 @@ pub fn scan( direction: Direction, depth: i32, ) -> i32 { - let current_disk: &Disk; - match board.get(index.0, index.1) { - Some(disk) => current_disk = disk, + let current_disk: &Disk = match board.get(index.0, index.1) { + Some(disk) => disk, None => return 0, }; let mut current_index = *index; @@ -28,33 +27,33 @@ pub fn scan( // dbg!(current_index); //go to next element match direction { - Direction::DOWN => { + Direction::Down => { if current_index.0 == 0 { break; } current_index = dec_row(¤t_index, 1); } - Direction::UP => { + Direction::Up => { if current_index.0 == board.num_rows() - 1 { break; } current_index = inc_row(¤t_index, 1); } - Direction::LEFT => { + Direction::Left => { if current_index.1 == 0 { break; } current_index = dec_col(¤t_index, 1); // current_index.1 -= 1; } - Direction::RIGHT => { + Direction::Right => { if current_index.1 == board.num_columns() - 1 { break; } current_index = inc_col(¤t_index, 1); // current_index.1 += 1; } - Direction::UPRIGHT => { + Direction::UpRight => { if current_index.0 == board.num_rows() - 1 || current_index.1 == board.num_columns() - 1 { @@ -64,7 +63,7 @@ pub fn scan( // current_index.1 += 1; // current_index.0 += 1; } - Direction::UPLEFT => { + Direction::UpLeft => { if current_index.0 == board.num_columns() - 1 || current_index.1 == 0 { break; } @@ -73,7 +72,7 @@ pub fn scan( // current_index.0 += 1; current_index = inc_dec(¤t_index, 1); } - Direction::DOWNRIGHT => { + Direction::DownRight => { if current_index.0 == 0 || current_index.1 == board.num_columns() - 1 { break; } @@ -81,7 +80,7 @@ pub fn scan( // current_index.1 += 1; // current_index.0 -= 1; } - Direction::DOWNLEFT => { + Direction::DownLeft => { if current_index.0 == 0 || current_index.1 == 0 { break; } @@ -108,46 +107,46 @@ pub fn get_legal_moves( let _max_row = ncol - 1; let mut moves: Vec = vec![]; match *col { - 0 => moves.push(Direction::UP), - _max_row => moves.push(Direction::DOWN), + 0 => moves.push(Direction::Up), + _max_row => moves.push(Direction::Down), _ => { - moves.push(Direction::UP); - moves.push(Direction::DOWN); + moves.push(Direction::Up); + moves.push(Direction::Down); } }; match *row { - 0 => moves.push(Direction::RIGHT), - _max_row => moves.push(Direction::LEFT), + 0 => moves.push(Direction::Right), + _max_row => moves.push(Direction::Left), _ => { - moves.push(Direction::LEFT); - moves.push(Direction::RIGHT) + moves.push(Direction::Left); + moves.push(Direction::Right) } }; - if moves.contains(&Direction::UP) && moves.contains(&Direction::LEFT) { - moves.push(Direction::UPLEFT); + if moves.contains(&Direction::Up) && moves.contains(&Direction::Left) { + moves.push(Direction::UpLeft); } - if moves.contains(&Direction::UP) && moves.contains(&Direction::RIGHT) { - moves.push(Direction::UPRIGHT); + if moves.contains(&Direction::Up) && moves.contains(&Direction::Right) { + moves.push(Direction::UpRight); } - if moves.contains(&Direction::DOWN) && moves.contains(&Direction::LEFT) { - moves.push(Direction::DOWNLEFT); + if moves.contains(&Direction::Down) && moves.contains(&Direction::Left) { + moves.push(Direction::DownLeft); } - if moves.contains(&Direction::DOWN) && moves.contains(&Direction::RIGHT) { - moves.push(Direction::DOWNRIGHT); + if moves.contains(&Direction::Down) && moves.contains(&Direction::Right) { + moves.push(Direction::DownRight); } moves } #[derive(Clone, Debug, PartialEq, Eq)] pub enum Direction { - UP, - DOWN, - LEFT, - RIGHT, - UPLEFT, - UPRIGHT, - DOWNLEFT, - DOWNRIGHT, + Up, + Down, + Left, + Right, + UpLeft, + UpRight, + DownLeft, + DownRight, } // serves nothing except do what matches!() should have done all along // matches works too I'm just dumb @@ -156,13 +155,13 @@ pub fn variant_eq(a: &T, b: &T) -> bool { } pub fn flip_direction(direction: Direction) -> Direction { match direction { - Direction::UP => Direction::DOWN, - Direction::DOWN => Direction::UP, - Direction::LEFT => Direction::RIGHT, - Direction::RIGHT => Direction::LEFT, - Direction::UPLEFT => Direction::DOWNRIGHT, - Direction::UPRIGHT => Direction::DOWNLEFT, - Direction::DOWNLEFT => Direction::UPRIGHT, - Direction::DOWNRIGHT => Direction::UPLEFT, + Direction::Up => Direction::Down, + Direction::Down => Direction::Up, + Direction::Left => Direction::Right, + Direction::Right => Direction::Left, + Direction::UpLeft => Direction::DownRight, + Direction::UpRight => Direction::DownLeft, + Direction::DownLeft => Direction::UpRight, + Direction::DownRight => Direction::UpLeft, } } diff --git a/src/gamedata/tests.rs b/src/gamedata/tests.rs index b003439..4a0a595 100644 --- a/src/gamedata/tests.rs +++ b/src/gamedata/tests.rs @@ -11,128 +11,128 @@ use super::*; #[test] fn play() { let mut board = Board::default(); - assert!(board.play(Disk::BLU, 0)); - assert!(board.play(Disk::BLU, 0)); - assert!(board.play(Disk::BLU, 0)); - assert!(board.play(Disk::BLU, 0)); - assert_eq!(1, board.blu_score); - assert!(board.play(Disk::BLU, 0)); - assert!(board.play(Disk::BLU, 0)); - assert!(!board.play(Disk::BLU, 0)); + assert!(board.play(Disk::P2, 0)); + assert!(board.play(Disk::P2, 0)); + assert!(board.play(Disk::P2, 0)); + assert!(board.play(Disk::P2, 0)); + assert_eq!(1, board.p2_score); + assert!(board.play(Disk::P2, 0)); + assert!(board.play(Disk::P2, 0)); + assert!(!board.play(Disk::P2, 0)); - assert!(board.play(Disk::BLU, 1)); - assert!(board.play(Disk::BLU, 1)); - assert!(board.play(Disk::BLU, 1)); - assert!(board.play(Disk::BLU, 1)); - assert!(board.play(Disk::BLU, 1)); - assert!(board.play(Disk::BLU, 1)); - assert!(!board.play(Disk::BLU, 1)); + assert!(board.play(Disk::P2, 1)); + assert!(board.play(Disk::P2, 1)); + assert!(board.play(Disk::P2, 1)); + assert!(board.play(Disk::P2, 1)); + assert!(board.play(Disk::P2, 1)); + assert!(board.play(Disk::P2, 1)); + assert!(!board.play(Disk::P2, 1)); - assert!(board.play(Disk::BLU, 2)); - assert!(board.play(Disk::BLU, 2)); - assert!(board.play(Disk::BLU, 2)); - assert!(board.play(Disk::BLU, 2)); - assert!(board.play(Disk::BLU, 2)); - assert!(board.play(Disk::BLU, 2)); - assert!(!board.play(Disk::BLU, 2)); + assert!(board.play(Disk::P2, 2)); + assert!(board.play(Disk::P2, 2)); + assert!(board.play(Disk::P2, 2)); + assert!(board.play(Disk::P2, 2)); + assert!(board.play(Disk::P2, 2)); + assert!(board.play(Disk::P2, 2)); + assert!(!board.play(Disk::P2, 2)); - assert!(board.play(Disk::BLU, 3)); - assert!(board.play(Disk::BLU, 3)); - assert!(board.play(Disk::BLU, 3)); - assert!(board.play(Disk::BLU, 3)); - assert!(board.play(Disk::BLU, 3)); - assert!(board.play(Disk::BLU, 3)); - assert!(!board.play(Disk::BLU, 3)); + assert!(board.play(Disk::P2, 3)); + assert!(board.play(Disk::P2, 3)); + assert!(board.play(Disk::P2, 3)); + assert!(board.play(Disk::P2, 3)); + assert!(board.play(Disk::P2, 3)); + assert!(board.play(Disk::P2, 3)); + assert!(!board.play(Disk::P2, 3)); } #[test] fn scan_updown() { let mut board = Board::default(); - board.play(Disk::BLU, 0); - board.play(Disk::BLU, 0); - board.play(Disk::BLU, 0); - board.play(Disk::BLU, 0); - board.play(Disk::BLU, 0); - board.play(Disk::BLU, 0); - board.play(Disk::BLU, 0); - assert_eq!(4, scan(&board.columns, &(4, 0), Direction::DOWN, 4)); - assert_eq!(4, scan(&board.columns, &(3, 0), Direction::DOWN, 4)); + board.play(Disk::P2, 0); + board.play(Disk::P2, 0); + board.play(Disk::P2, 0); + board.play(Disk::P2, 0); + board.play(Disk::P2, 0); + board.play(Disk::P2, 0); + board.play(Disk::P2, 0); + assert_eq!(4, scan(&board.columns, &(4, 0), Direction::Down, 4)); + assert_eq!(4, scan(&board.columns, &(3, 0), Direction::Down, 4)); } #[test] fn scan_updown2() { let mut board = Board::default(); - board.play(Disk::BLU, 0); - board.play(Disk::RED, 0); - board.play(Disk::BLU, 0); - board.play(Disk::BLU, 0); - assert_eq!(1, scan(&board.columns, &(0, 0), Direction::UP, 4)); + board.play(Disk::P2, 0); + board.play(Disk::P1, 0); + board.play(Disk::P2, 0); + board.play(Disk::P2, 0); + assert_eq!(1, scan(&board.columns, &(0, 0), Direction::Up, 4)); } #[test] fn scan_forwardback() { let mut board = Board::default(); - board.play(Disk::BLU, 0); - board.play(Disk::BLU, 1); - board.play(Disk::BLU, 2); - board.play(Disk::BLU, 3); + board.play(Disk::P2, 0); + board.play(Disk::P2, 1); + board.play(Disk::P2, 2); + board.play(Disk::P2, 3); - assert_eq!(4, scan(&board.columns, &(0, 0), Direction::RIGHT, 4)); - assert_eq!(4, scan(&board.columns, &(0, 3), Direction::LEFT, 4)); + assert_eq!(4, scan(&board.columns, &(0, 0), Direction::Right, 4)); + assert_eq!(4, scan(&board.columns, &(0, 3), Direction::Left, 4)); } #[test] fn scan_forwardback2() { let mut board = Board::default(); - board.play(Disk::BLU, 0); - board.play(Disk::BLU, 1); - board.play(Disk::RED, 2); - board.play(Disk::BLU, 3); - assert_eq!(2, scan(&board.columns, &(0, 0), Direction::RIGHT, 4)); - assert_eq!(1, scan(&board.columns, &(0, 3), Direction::LEFT, 4)); + board.play(Disk::P2, 0); + board.play(Disk::P2, 1); + board.play(Disk::P1, 2); + board.play(Disk::P2, 3); + assert_eq!(2, scan(&board.columns, &(0, 0), Direction::Right, 4)); + assert_eq!(1, scan(&board.columns, &(0, 3), Direction::Left, 4)); } #[test] fn scan_diag1() { let mut board = Board::default(); - board.play(Disk::BLU, 0); - board.play(Disk::RED, 1); - board.play(Disk::BLU, 1); - board.play(Disk::RED, 2); - board.play(Disk::RED, 2); - board.play(Disk::BLU, 2); - board.play(Disk::RED, 3); - board.play(Disk::RED, 3); - board.play(Disk::RED, 3); - board.play(Disk::BLU, 3); - assert_eq!(4, scan(&board.columns, &(0, 0), Direction::UPRIGHT, 4)); - assert_eq!(4, scan(&board.columns, &(3, 3), Direction::DOWNLEFT, 4)); + board.play(Disk::P2, 0); + board.play(Disk::P1, 1); + board.play(Disk::P2, 1); + board.play(Disk::P1, 2); + board.play(Disk::P1, 2); + board.play(Disk::P2, 2); + board.play(Disk::P1, 3); + board.play(Disk::P1, 3); + board.play(Disk::P1, 3); + board.play(Disk::P2, 3); + assert_eq!(4, scan(&board.columns, &(0, 0), Direction::UpRight, 4)); + assert_eq!(4, scan(&board.columns, &(3, 3), Direction::DownLeft, 4)); } #[test] fn scan_diag2() { let mut board = Board::default(); - board.play(Disk::BLU, 3); - board.play(Disk::RED, 2); - board.play(Disk::BLU, 2); - board.play(Disk::RED, 1); - board.play(Disk::RED, 1); - board.play(Disk::BLU, 1); - board.play(Disk::RED, 0); - board.play(Disk::RED, 0); - board.play(Disk::RED, 0); - board.play(Disk::BLU, 0); + board.play(Disk::P2, 3); + board.play(Disk::P1, 2); + board.play(Disk::P2, 2); + board.play(Disk::P1, 1); + board.play(Disk::P1, 1); + board.play(Disk::P2, 1); + board.play(Disk::P1, 0); + board.play(Disk::P1, 0); + board.play(Disk::P1, 0); + board.play(Disk::P2, 0); dbg!(&board.columns.as_columns()); - assert_eq!(4, scan(&board.columns, &(0, 3), Direction::UPLEFT, 4)); - assert_eq!(4, scan(&board.columns, &(3, 0), Direction::DOWNRIGHT, 4)); + assert_eq!(4, scan(&board.columns, &(0, 3), Direction::UpLeft, 4)); + assert_eq!(4, scan(&board.columns, &(3, 0), Direction::DownRight, 4)); } #[test] fn variant_eq_test() { - assert!(score_checkers::variant_eq(&Disk::RED, &Disk::RED)); - assert!(matches!(Disk::RED, Disk::RED)); - assert!(matches!(&Disk::BLU, &Disk::BLU)); - assert!(!score_checkers::variant_eq(&Disk::BLU, &Disk::RED)); - assert!(!matches!(Disk::BLU, Disk::RED)); + assert!(score_checkers::variant_eq(&Disk::P1, &Disk::P1)); + assert!(matches!(Disk::P1, Disk::P1)); + assert!(matches!(&Disk::P2, &Disk::P2)); + assert!(!score_checkers::variant_eq(&Disk::P2, &Disk::P1)); + assert!(!matches!(Disk::P2, Disk::P1)); } #[test] fn game_over_test() { let mut board = Board::default(); assert!(!board.game_over()); - board.columns = Array2D::filled_with(Disk::BLU, 7, 6); + board.columns = Array2D::filled_with(Disk::P2, 7, 6); assert!(board.game_over()); board.columns.set(0, 0, Disk::EMPTY).expect("balls"); assert!(!board.game_over()); diff --git a/src/main.rs b/src/main.rs index 6d200cf..a98e191 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,19 +1,110 @@ +use std::time::Instant; + +use oxidised4::{ + bored::{GameState, MenuState, PlayState, Strategy}, + gamedata::{ + algorithms::{minimax_decision, minimax_decision_pruning}, + Disk, + }, +}; use raylib::prelude::*; +const NROW: i32 = 6; +const NCOL: i32 = 7; +const BOARDSTART: (i32, i32) = (0, 0); + fn main() { let (mut rl, thread) = raylib::init().size(640, 480).title("Hello, World").build(); - let _rust_orange = Color::new(222, 165, 132, 255); - let _ray_white = Color::new(255, 255, 255, 255); + //images + let board_image = Image::load_image("resouces/board.png").expect("WHAT DA HAILLL"); + let _ = rl + .load_texture(&thread, "resouces/board.png") + .expect("couldn't load texture :("); + let _ = rl + .load_texture(&thread, "resouces/bracc.png") + .expect("couldn't load texture :("); + let circle_image = Image::load_image("resouces/bracc.png").expect("WHAT DA HAILLL"); + let _ = rl + .load_texture(&thread, "resouces/bracc.png") + .expect("couldn't load texture :("); + //textures + let board_texture = rl + .load_texture_from_image(&thread, &board_image) + .expect("WHAT DA HAILL"); + + let circle_texture = rl + .load_texture_from_image(&thread, &circle_image) + .expect("WHAT DA HAILL"); + let square_widf = board_texture.width / NCOL; + dbg!(square_widf); + let square_heif = board_texture.height / NROW; + let _square_wewant = (square_widf * NROW / 2, square_heif * 3 / 2); + let _square_center = square_widf / 2; + //7,9 are the values to center the circle + let mut state: GameState = GameState::MainMenu(MenuState::default()); + let mut strategy = Strategy::MiniMax; + let mut difficulty = 3; rl.set_target_fps(60); while !rl.window_should_close() { - let pressed_key = rl.get_key_pressed(); let mut d = rl.begin_drawing(&thread); - d.clear_background(Color::WHITE); - if let Some(pressed_key) = pressed_key { - // Certain keyboards may have keys raylib does not expect. Uncomment this line if so. - // let pressed_key: u32 = unsafe { std::mem::transmute(pressed_key) }; - d.draw_text(&format!("{:?}", pressed_key), 100, 12, 10, Color::BLACK); + match &mut state { + GameState::MainMenu(ref mut mstate) => { + if mstate.init(&d) { + strategy = mstate.strategy.clone(); + difficulty = mstate.difficulty.clone(); + state = GameState::Play(PlayState::default()); + } + } + GameState::Play(ref mut state) => { + match state.player_turn { + true => { + if d.is_mouse_button_pressed(MouseButton::MOUSE_LEFT_BUTTON) { + if let Some(column) = get_mouse_column(&d, square_widf) { + state.play_human(column); + } + } + } + false => { + dbg!(); + let time = Instant::now(); + match strategy { + Strategy::MiniMax => state.play_cpu(&difficulty, minimax_decision), + Strategy::AlphaBeta => { + state.play_cpu(&difficulty, minimax_decision_pruning) + } + }; + println!( + "Time elapsed in cpu turn: {} ms", + time.elapsed().as_millis() + ); + } + } + + for circle in state.clone().circles { + let (x, y, disk) = circle; + let color = match disk { + Disk::P1 => Color::RED, + Disk::P2 => Color::YELLOW, + Disk::EMPTY => Color::WHITE, + }; + d.draw_texture(&circle_texture, x, y, color); + } + d.clear_background(Color::WHITE); + d.draw_texture(&board_texture, BOARDSTART.0, BOARDSTART.1, Color::VIOLET); + } } } } + +const STARTY: i32 = 9; +fn get_mouse_column(rl: &RaylibHandle, sw: i32) -> Option { + //row,col return + let mouse_pos = rl.get_mouse_x(); + for num in 1..NCOL + 1 { + if (mouse_pos > sw * (num - 1) + STARTY) && (mouse_pos < sw * (num) - STARTY) { + return Some(num - 1); + } + } + None +}