Permalink
Please sign in to comment.
Showing
with
2 additions
and 2,228 deletions.
- +2 −5 Makefile
- +0 −4 filesystem/apps/rusthello/_REDOX
- +0 −299 filesystem/apps/rusthello/interface.rs~
- +0 −118 filesystem/apps/rusthello/main.rs~
- +0 −159 filesystem/apps/rusthello/players/ai_medium.rs
- +0 −159 filesystem/apps/rusthello/players/ai_medium.rs~
- +0 −200 filesystem/apps/rusthello/players/mod.rs
- +0 −200 filesystem/apps/rusthello/players/mod.rs~
- +0 −299 filesystem/apps/rusthello/src/interface.rs
- +0 −118 filesystem/apps/rusthello/src/main.rs
- +0 −159 filesystem/apps/rusthello/src/players/ai_medium.rs
- +0 −201 filesystem/apps/rusthello/src/players/mod.rs
- +0 −307 filesystem/apps/rusthello/src/reversi.rs
- BIN filesystem/ui/apps/rusthello.bmp
7
Makefile
4
filesystem/apps/rusthello/_REDOX
@@ -1,4 +0,0 @@ | ||
-name=RUSThello | ||
-icon=file:/ui/apps/rusthello.bmp | ||
-author=Enrico Ghiorzi | ||
-description=A simple Reversi game, written in Rust with love |
299
filesystem/apps/rusthello/interface.rs~
@@ -1,299 +0,0 @@ | ||
-// This module provides interface functionalities and manages all the input/output part of the program | ||
- | ||
-use std::cmp::Ordering; | ||
-use std::io::{self, Write}; | ||
-use players; | ||
-use reversi; | ||
- | ||
-pub enum UserCommand { | ||
- NewGame, | ||
- NewPlayer(players::Player), | ||
- Move(usize, usize), | ||
- Help, | ||
- Undo, | ||
- Quit, | ||
-} | ||
- | ||
-pub const INTRO: &'static str = | ||
-"\n\n | ||
- RUSThello | ||
- ● ○ ● ○ ● | ||
- a simple Reversi game | ||
- written in Rust with love | ||
- Redox Edition | ||
- v 1.1.0\n\n"; | ||
- | ||
-pub const MAIN_MENU: &'static str = | ||
-"\nMain Menu: | ||
- n - New match | ||
- h - Help | ||
- q - Quit RUSThello"; | ||
- | ||
- pub const NEW_PLAYER_MENU: &'static str = | ||
- "\nChoose a player: | ||
- hp - Human Player | ||
- ai - Artificial Intelligence | ||
- q - Quit match"; | ||
- | ||
-pub const COMMANDS_INFO: &'static str = | ||
-"\nStarting new game… | ||
-Type a cell's coordinates to place your disk there. Exaple: \"c4\" | ||
-Type 'help' or 'h' instead of a move to display help message. | ||
-Type 'undo' or 'u' instead of a move to undo last move. | ||
-Type 'quit' or 'q' instead of a move to abandon the game."; | ||
- | ||
-pub const HELP: &'static str = "\ | ||
-\n\n\n\tHOW TO PLAY REVERSI:\n | ||
-Reversi is a board game where two players compete against each other. \ | ||
-The game is played on a 8x8 board, just like chess but for the squares’ colour which is always green. \ | ||
-There are 64 identical pieces called disks, which are white on one side and black on the other. \ | ||
-A player is Light, using disks’ white side, and the other one is Dark, using disks' black side. \ | ||
-The game starts with four disks already placed at the centre of the board, two for each side. \ | ||
-Dark moves first.\n | ||
-Let’s say it’s Dark’s turn, for simplicity's sake, as for Light the rules are just the same. \ | ||
-Dark has to place a disk in a free square of the board, with the black side facing up. \ | ||
-Whenever the newly placed black disk and any other previously placed black disk enclose a sequence of white disks (horizontal, vertical or diagonal and of any length), all of those flip and turn black. \ | ||
-It is mandatory to place the new disk such that at least a white disk is flipped, otherwise the move is not valid.\n | ||
-Usually players’ turn alternate, passing from one to the other. \ | ||
-When a player cannot play any legal move, the turn goes back to the other player, thus allowing the same player to play consecutive turns. \ | ||
-When neither player can play a legal move, the game ends. \ | ||
-In particular, this is true whenever the board has been completely filled up (for a total of 60 moves), but games happen sometimes to end before that, leaving empty squares on the board.\n | ||
-When the game ends, the player with more disks turned to its side wins. \ | ||
-Ties are possible as well, if both player have the same number of disks.\n\n\n | ||
-\tHOW TO USE RUSThello:\n | ||
-To play RUSThello you first have to choose who is playing on each side, Dark and Light. \ | ||
-You can choose among human players or AIs of various strength. \ | ||
-Choose human for both players and challenge a friend, or test your skills against an AI, or even relax and watch as two AIs compete with each other: all matches are possible!\n | ||
-As a human player, you move by entering the coordinates (a letter and a number) of the square you want to place your disk on, e.g. all of 'c4', 'C4', '4c' and '4C' are valid and equivalent coordinates. \ | ||
-For your ease of use, all legal moves are marked on the board with a *.\n | ||
-Furthermore, on your turn you can also input special commands: 'undo' to undo your last move (and yes, you can 'undo' as many times as you like) and 'quit' to quit the game.\n\n\n | ||
-\tCREDITS:\n | ||
-RUSThello v. 1.1.0 Redox Edition | ||
-by Enrico Ghiorzi, with the invaluable help of the Redox community | ||
-Copyright (c) 2015 by Enrico Ghiorzi | ||
-Released under the MIT license\n\n\n"; | ||
- | ||
- | ||
-pub fn input_main_menu() -> UserCommand { | ||
- | ||
- loop { | ||
- print!("Insert input: "); | ||
- match get_user_command() { | ||
- Some(UserCommand::NewGame) => return UserCommand::NewGame, | ||
- Some(UserCommand::Help) => return UserCommand::Help, | ||
- Some(UserCommand::Quit) => { | ||
- println!("\nGoodbye!\n\n\n"); | ||
- return UserCommand::Quit; | ||
- } | ||
- _ => println!("This is not a valid command!"), | ||
- } | ||
- } | ||
-} | ||
- | ||
-pub fn new_player(side: reversi::Disk) -> Option<players::Player> { | ||
- loop { | ||
- match side { | ||
- reversi::Disk::Light => print!("● Light player: "), | ||
- reversi::Disk::Dark => print!("○ Dark player: "), | ||
- } | ||
- match get_user_command() { | ||
- Some(UserCommand::NewPlayer(player)) => return Some(player), | ||
- Some(UserCommand::Quit) => return None, | ||
- _ => println!("This is not a valid command!"), | ||
- } | ||
- } | ||
-} | ||
- | ||
-/// It gets an input from the user and tries to parse it, then returns a Option<UserCommand>`. | ||
-/// If the input is recognized as a legit command, it returns the relative `Option::Some(UserCommand)`. | ||
-/// If the input is not recognized as a legit command, it returns a `Option::None`. | ||
-pub fn get_user_command() -> Option<UserCommand> { | ||
- | ||
- // Read the input | ||
- let _ = io::stdout().flush(); | ||
- | ||
- let mut input = String::new(); | ||
- | ||
- io::stdin().read_line(&mut input) | ||
- .ok() | ||
- .expect("failed to read line"); | ||
- | ||
- let input = input.trim().to_lowercase(); | ||
- | ||
- match &*input { | ||
- "hp" => Some(UserCommand::NewPlayer(players::Player::Human)), | ||
- "ai" => Some(UserCommand::NewPlayer(players::Player::AiMedium)), | ||
- "n" | "new game" => Some(UserCommand::NewGame), | ||
- "h" | "help" => Some(UserCommand::Help), | ||
- "u" | "undo" => Some(UserCommand::Undo), | ||
- "q" | "quit" => Some(UserCommand::Quit), | ||
- _ => { | ||
- | ||
- let mut row: Option<usize> = None; | ||
- let mut col: Option<usize> = None; | ||
- | ||
- for curr_char in input.chars() { | ||
- match curr_char { | ||
- '1'...'8' => { | ||
- if let None = row { | ||
- row = Some(curr_char as usize - '1' as usize); | ||
- } else { | ||
- return None; | ||
- } | ||
- } | ||
- 'a'...'h' => { | ||
- if let None = col { | ||
- col = Some(curr_char as usize - 'a' as usize); | ||
- } else { | ||
- return None; | ||
- } | ||
- } | ||
- _ => return None, | ||
- } | ||
- } | ||
- | ||
- if row.is_none() || col.is_none() { | ||
- None | ||
- } else { | ||
- // The move is not checked! | ||
- Some(UserCommand::Move(row.unwrap(), col.unwrap())) | ||
- } | ||
- } | ||
- } | ||
-} | ||
- | ||
- | ||
- | ||
-/// draw_board draws the board (using text characters) in a pleasant-looking way, converting the board in a string (board_to_string) and then printing this. | ||
-pub fn draw_board(game: &reversi::Game) { | ||
- | ||
- let board = game.get_board(); | ||
- | ||
- // Declare board_to_string and add column reference at the top | ||
- let mut board_to_string: String = "\n\n\n\t a b c d e f g h\n".to_string(); | ||
- | ||
- // For every row add a row reference to the left | ||
- for (row, row_array) in board.iter().enumerate() { | ||
- board_to_string.push('\t'); | ||
- board_to_string.push_str(&(row + 1).to_string()); | ||
- board_to_string.push(' '); | ||
- | ||
- // For every column, add the appropriate character depending on the content of the current cell | ||
- for (col, &cell) in row_array.iter().enumerate() { | ||
- | ||
- match cell { | ||
- // Light and Dark cells are represented by white and black bullets | ||
- reversi::Cell::Taken { disk: reversi::Disk::Light } => board_to_string.push_str(" ● "), | ||
- reversi::Cell::Taken { disk: reversi::Disk::Dark } => board_to_string.push_str(" ○ "), | ||
- | ||
- // An empty cell will display a plus or a multiplication sign if the current player can move in that cell | ||
- // or a little central dot otherwise | ||
- reversi::Cell::Empty => { | ||
- if game.check_move((row, col)) { | ||
- if let reversi::Status::Running { .. } = game.get_status() { | ||
- board_to_string.push_str(" * "); | ||
- } | ||
- } else { | ||
- board_to_string.push_str(" ∙ "); | ||
- } | ||
- } | ||
- } | ||
- } | ||
- | ||
- // Add a row reference to the right | ||
- board_to_string.push(' '); | ||
- board_to_string.push_str(&(row + 1).to_string()); | ||
- board_to_string.push('\n'); | ||
- } | ||
- | ||
- // Add column reference at the bottom | ||
- board_to_string.push_str("\t a b c d e f g h\n"); | ||
- | ||
- // Print board | ||
- println!("{}", board_to_string); | ||
- | ||
- // Print current score and game info | ||
- let (score_light, score_dark) = game.get_score(); | ||
- | ||
- match game.get_status() { | ||
- reversi::Status::Running { current_turn } => { | ||
- match current_turn { | ||
- reversi::Disk::Light => println!("\t {:>2} ○ >> ● {:<2}\n", score_dark, score_light), | ||
- reversi::Disk::Dark => println!("\t {:>2} ○ << ● {:<2}\n", score_dark, score_light), | ||
- } | ||
- } | ||
- reversi::Status::Ended => { | ||
- println!("\t {:>2} ○ ● {:<2}\n", score_dark, score_light); | ||
- match score_light.cmp(&score_dark) { | ||
- Ordering::Greater => println!("Light wins!"), | ||
- Ordering::Less => println!("Dark wins!"), | ||
- Ordering::Equal => println!("Draw!"), | ||
- } | ||
- } | ||
- } | ||
-} | ||
- | ||
- | ||
- | ||
-/// Prints a message with info on a move. | ||
-pub fn print_move(game: &reversi::Game, (row, col): (usize, usize)) { | ||
- | ||
- let char_col = (('a' as u8) + (col as u8)) as char; | ||
- if let reversi::Status::Running { current_turn } = game.get_status() { | ||
- match current_turn { | ||
- reversi::Disk::Light => println!("● Light moves: {}{}", char_col, row + 1), | ||
- reversi::Disk::Dark => println!("○ Dark moves: {}{}", char_col, row + 1), | ||
- } | ||
- } | ||
-} | ||
- | ||
- | ||
- | ||
-/// It get_status a human player's input and convert it into a move. | ||
-/// If the move if illegal, it ask for another input until the given move is a legal one. | ||
-pub fn human_make_move(game: &reversi::Game) -> UserCommand { | ||
- | ||
- if let reversi::Status::Running { current_turn } = game.get_status() { | ||
- match current_turn { | ||
- reversi::Disk::Light => print!("● Light moves: "), | ||
- reversi::Disk::Dark => print!("○ Dark moves: "), | ||
- } | ||
- } | ||
- | ||
- loop { | ||
- if let Some(user_command) = get_user_command() { | ||
- match user_command { | ||
- UserCommand::Move(row, col) => { | ||
- if game.check_move((row, col)) { | ||
- return UserCommand::Move(row, col); | ||
- } else { | ||
- print!("Illegal move, try again: "); | ||
- continue; | ||
- } | ||
- } | ||
- _ => return user_command, | ||
- } | ||
- } else { | ||
- print!("This doesn't look like a valid command. Try again: "); | ||
- continue; | ||
- } | ||
- } | ||
-} | ||
- | ||
- | ||
- | ||
-// Print a last message before a player quits the game | ||
-pub fn quitting_message(coward: reversi::Disk) { | ||
- match coward { | ||
- reversi::Disk::Light => println!("Light is running away, the coward!"), | ||
- reversi::Disk::Dark => println!("Dark is running away, the coward!"), | ||
- } | ||
-} | ||
- | ||
-// Print a last message when 'undo' is not possible | ||
-pub fn no_undo_message(undecided: reversi::Disk) { | ||
- match undecided { | ||
- reversi::Disk::Light => println!("There is no move Light can undo."), | ||
- reversi::Disk::Dark => println!("There is no move Dark can undo."), | ||
- } | ||
-} |
118
filesystem/apps/rusthello/main.rs~
@@ -1,118 +0,0 @@ | ||
-//! A simple Reversi game written in Rust with love | ||
-//! by Enrico Ghiorzi | ||
- | ||
-extern crate rand; | ||
- | ||
-// Import modules | ||
-mod reversi; | ||
-mod interface; | ||
-mod players; | ||
- | ||
- | ||
- | ||
-pub fn main() { | ||
- // Main intro | ||
- println!("{}", interface::INTRO); | ||
- | ||
- loop { | ||
- println!("{}", interface::MAIN_MENU); | ||
- | ||
- match interface::input_main_menu() { | ||
- // Runs the game | ||
- interface::UserCommand::NewGame => play_game(), | ||
- // Prints help message | ||
- interface::UserCommand::Help => println!("{}", interface::HELP), | ||
- // Quit RUSThello | ||
- interface::UserCommand::Quit => break, | ||
- _ => panic!("Main got a user command it shouldn't have got!"), | ||
- } | ||
- } | ||
-} | ||
- | ||
- | ||
- | ||
-fn play_game() { | ||
- | ||
- // Get the two players | ||
- println!("{}", interface::NEW_PLAYER_MENU); | ||
- let dark = match interface::new_player(reversi::Disk::Dark) { | ||
- None => return, | ||
- Some(player) => player, | ||
- }; | ||
- let light = match interface::new_player(reversi::Disk::Light) { | ||
- None => return, | ||
- Some(player) => player, | ||
- }; | ||
- | ||
- // Create a new game | ||
- let mut game = reversi::Game::new(); | ||
- let mut hystory: Vec<reversi::Game> = Vec::new(); | ||
- | ||
- println!("{}", interface::COMMANDS_INFO); | ||
- | ||
- // Draw the current board and game info | ||
- interface::draw_board(&game); | ||
- | ||
- // Proceed with turn after turn till the game ends | ||
- 'turn: while let reversi::Status::Running { current_turn } = game.get_status() { | ||
- | ||
- // If the game is running, get the coordinates of the new move from the right player | ||
- let action = match current_turn { | ||
- reversi::Disk::Light => light.make_move(&game), | ||
- reversi::Disk::Dark => dark.make_move(&game), | ||
- }; | ||
- | ||
- match action { | ||
- // If the new move is valid, perform it; otherwise panic | ||
- // Player's make_move method is responsible for returning a legal move | ||
- // so the program should never print this message unless something goes horribly wrong | ||
- interface::UserCommand::Move(row, col) => { | ||
- | ||
- if game.check_move((row, col)) { | ||
- hystory.push(game.clone()); | ||
- game.make_move((row, col)); | ||
- interface::draw_board(&game); | ||
- } else { | ||
- panic!("Invalid move sent to main::game!"); | ||
- } | ||
- } | ||
- | ||
- // Manage hystory | ||
- interface::UserCommand::Undo => { | ||
- let mut recovery: Vec<reversi::Game> = Vec::new(); | ||
- | ||
- while let Some(previous_game) = hystory.pop() { | ||
- recovery.push(previous_game.clone()); | ||
- if let reversi::Status::Running { current_turn: previous_player } = previous_game.get_status() { | ||
- if previous_player == current_turn { | ||
- game = previous_game; | ||
- interface::draw_board(&game); | ||
- continue 'turn; | ||
- } | ||
- } | ||
- } | ||
- | ||
- while let Some(recovered_game) = recovery.pop() { | ||
- hystory.push(recovered_game.clone()); | ||
- } | ||
- | ||
- interface::no_undo_message(current_turn); | ||
- } | ||
- | ||
- interface::UserCommand::Help => { | ||
- println!("{}", interface::HELP); | ||
- interface::draw_board(&game); | ||
- } | ||
- | ||
- // Quit Match | ||
- interface::UserCommand::Quit => { | ||
- interface::quitting_message(current_turn); | ||
- break; | ||
- } | ||
- | ||
- _ => { | ||
- panic!("Something's wrong here!"); | ||
- } | ||
- } | ||
- } | ||
-} |
159
filesystem/apps/rusthello/players/ai_medium.rs
@@ -1,159 +0,0 @@ | ||
-//use rand; | ||
-//use rand::Rng; | ||
- | ||
-use reversi; | ||
-use players::Score; | ||
- | ||
-const MOBILITY: u8 = 1; | ||
-//const RANDOMNESS: f32 = 1.0; | ||
- | ||
- | ||
- | ||
-pub fn ai_eval(game: &reversi::Game, depth: u8) -> Score { | ||
- | ||
- match game.get_status() { | ||
- reversi::Status::Running { current_turn } => { | ||
- if depth == 0 { | ||
- Score::Running(heavy_eval(game) as f32) | ||
- } else { | ||
- let mut best_score: Option<Score> = None; | ||
- let mut num_moves: u8 = 0; | ||
- let mut game_after_move = game.clone(); | ||
- | ||
- for (row, &rows) in game.get_board().iter().enumerate() { | ||
- for (col, _) in rows.iter().enumerate() { | ||
- if game_after_move.make_move((row, col)) { | ||
- | ||
- num_moves += 1; | ||
- let new_score = ai_eval(&game_after_move, depth - 1); | ||
- match best_score.clone() { | ||
- Some(old_score) => { | ||
- if Score::is_better_for(new_score.clone(), old_score, current_turn) { | ||
- best_score = Some(new_score); | ||
- } | ||
- } | ||
- None => best_score = Some(new_score), | ||
- } | ||
- game_after_move = game.clone(); | ||
- | ||
- } | ||
- } | ||
- } | ||
- if let Some(score) = best_score { | ||
- if let Score::Running(val) = score { | ||
- return match current_turn { | ||
- reversi::Disk::Light => Score::Running(val + ( num_moves * MOBILITY ) as f32 ), | ||
- reversi::Disk::Dark => Score::Running(val - ( num_moves * MOBILITY ) as f32 ), | ||
- } | ||
- } else { | ||
- return score; | ||
- } | ||
- } else { | ||
- panic!("ai_eval produced no best_score!"); | ||
- } | ||
- } | ||
- } | ||
- reversi::Status::Ended => { | ||
- Score::EndGame(game.get_score_diff()) | ||
- } | ||
- } | ||
-} | ||
- | ||
- | ||
- | ||
-fn heavy_eval(game: &reversi::Game) -> i16 { | ||
- const CORNER_BONUS: i16 = 15; | ||
- const ODD_MALUS: i16 = 3; | ||
- const EVEN_BONUS: i16 = 3; | ||
- const ODD_CORNER_MALUS: i16 = 10; | ||
- const EVEN_CORNER_BONUS: i16 = 5; | ||
- const FIXED_BONUS: i16 = 3; | ||
- | ||
- const SIDES: [( (usize, usize), (usize, usize), (usize, usize), (usize, usize), (usize, usize), (usize, usize), (usize, usize) ); 4] = [ | ||
- ( (0,0), (0,1), (1,1), (0,2), (2,2), (1,0), (2,0) ), // NW corner | ||
- ( (0,7), (1,7), (1,6), (2,7), (2,5), (0,6), (0,5) ), // NE corner | ||
- ( (7,0), (6,0), (6,1), (5,0), (5,2), (7,1), (7,2) ), // SW corner | ||
- ( (7,7), (6,7), (6,6), (5,7), (5,5), (7,6), (7,5) ), // SE corner | ||
- ]; | ||
- | ||
- let mut score: i16 = 0; | ||
- | ||
- for &(corner, odd, odd_corner, even, even_corner, counter_odd, counter_even) in SIDES.iter() { | ||
- | ||
- if let reversi::Cell::Taken { disk } = game.get_cell(corner) { | ||
- match disk { | ||
- reversi::Disk::Light => { | ||
- score += CORNER_BONUS; | ||
- if let reversi::Cell::Taken { disk: reversi::Disk::Light } = game.get_cell(odd) { | ||
- score += FIXED_BONUS; | ||
- if let reversi::Cell::Taken { disk: reversi::Disk::Light } = game.get_cell(even) { | ||
- score += FIXED_BONUS; | ||
- } | ||
- } | ||
- if let reversi::Cell::Taken { disk: reversi::Disk::Light } = game.get_cell(counter_odd) { | ||
- score += FIXED_BONUS; | ||
- if let reversi::Cell::Taken { disk: reversi::Disk::Light } = game.get_cell(counter_even) { | ||
- score += FIXED_BONUS; | ||
- } | ||
- } | ||
- } | ||
- reversi::Disk::Dark => { | ||
- score -= CORNER_BONUS; | ||
- if let reversi::Cell::Taken { disk: reversi::Disk::Dark } = game.get_cell(odd) { | ||
- score -= FIXED_BONUS; | ||
- if let reversi::Cell::Taken { disk: reversi::Disk::Dark } = game.get_cell(even) { | ||
- score -= FIXED_BONUS; | ||
- } | ||
- } | ||
- if let reversi::Cell::Taken { disk: reversi::Disk::Dark } = game.get_cell(counter_odd) { | ||
- score -= FIXED_BONUS; | ||
- if let reversi::Cell::Taken { disk: reversi::Disk::Dark } = game.get_cell(counter_even) { | ||
- score -= FIXED_BONUS; | ||
- } | ||
- } | ||
- } | ||
- } | ||
- | ||
- } else { | ||
- | ||
- if let reversi::Cell::Taken { disk } = game.get_cell(odd) { | ||
- score += match disk { | ||
- reversi::Disk::Light => -ODD_MALUS, | ||
- reversi::Disk::Dark => ODD_MALUS, | ||
- } | ||
- } else if let reversi::Cell::Taken { disk } = game.get_cell(even) { | ||
- score += match disk { | ||
- reversi::Disk::Light => EVEN_BONUS, | ||
- reversi::Disk::Dark => -EVEN_BONUS, | ||
- } | ||
- } | ||
- | ||
- if let reversi::Cell::Taken { disk } = game.get_cell(counter_odd) { | ||
- score += match disk { | ||
- reversi::Disk::Light => -ODD_MALUS, | ||
- reversi::Disk::Dark => ODD_MALUS, | ||
- } | ||
- } else if let reversi::Cell::Taken { disk } = game.get_cell(counter_even) { | ||
- score += match disk { | ||
- reversi::Disk::Light => EVEN_BONUS, | ||
- reversi::Disk::Dark => -EVEN_BONUS, | ||
- } | ||
- } | ||
- | ||
- if let reversi::Cell::Taken { disk } = game.get_cell(odd_corner) { | ||
- score += match disk { | ||
- reversi::Disk::Light => -ODD_CORNER_MALUS, | ||
- reversi::Disk::Dark => ODD_CORNER_MALUS, | ||
- } | ||
- | ||
- } else if let reversi::Cell::Taken { disk } = game.get_cell(even_corner) { | ||
- score += match disk { | ||
- reversi::Disk::Light => EVEN_CORNER_BONUS, | ||
- reversi::Disk::Dark => -EVEN_CORNER_BONUS, | ||
- } | ||
- } | ||
- } | ||
- } | ||
- | ||
- score | ||
-} |
159
filesystem/apps/rusthello/players/ai_medium.rs~
@@ -1,159 +0,0 @@ | ||
-use rand; | ||
-use rand::Rng; | ||
- | ||
-use reversi; | ||
-use players::Score; | ||
- | ||
-const MOBILITY: u8 = 1; | ||
-const RANDOMNESS: f32 = 1.0; | ||
- | ||
- | ||
- | ||
-pub fn ai_eval(game: &reversi::Game, depth: u8) -> Score { | ||
- | ||
- match game.get_status() { | ||
- reversi::Status::Running { current_turn } => { | ||
- if depth == 0 { | ||
- Score::Running(heavy_eval(game) as f32) | ||
- } else { | ||
- let mut best_score: Option<Score> = None; | ||
- let mut num_moves: u8 = 0; | ||
- let mut game_after_move = game.clone(); | ||
- | ||
- for (row, &rows) in game.get_board().iter().enumerate() { | ||
- for (col, _) in rows.iter().enumerate() { | ||
- if game_after_move.make_move((row, col)) { | ||
- | ||
- num_moves += 1; | ||
- let new_score = ai_eval(&game_after_move, depth - 1); | ||
- match best_score.clone() { | ||
- Some(old_score) => { | ||
- if Score::is_better_for(new_score.clone(), old_score, current_turn) { | ||
- best_score = Some(new_score); | ||
- } | ||
- } | ||
- None => best_score = Some(new_score), | ||
- } | ||
- game_after_move = game.clone(); | ||
- | ||
- } | ||
- } | ||
- } | ||
- if let Some(score) = best_score { | ||
- if let Score::Running(val) = score { | ||
- return match current_turn { | ||
- reversi::Disk::Light => Score::Running(val + ( num_moves * MOBILITY ) as f32 + rand::thread_rng().gen_range::<f32>(-RANDOMNESS, RANDOMNESS) ), | ||
- reversi::Disk::Dark => Score::Running(val - ( num_moves * MOBILITY ) as f32 + rand::thread_rng().gen_range::<f32>(-RANDOMNESS, RANDOMNESS) ), | ||
- } | ||
- } else { | ||
- return score; | ||
- } | ||
- } else { | ||
- panic!("ai_eval produced no best_score!"); | ||
- } | ||
- } | ||
- } | ||
- reversi::Status::Ended => { | ||
- Score::EndGame(game.get_score_diff()) | ||
- } | ||
- } | ||
-} | ||
- | ||
- | ||
- | ||
-fn heavy_eval(game: &reversi::Game) -> i16 { | ||
- const CORNER_BONUS: i16 = 15; | ||
- const ODD_MALUS: i16 = 3; | ||
- const EVEN_BONUS: i16 = 3; | ||
- const ODD_CORNER_MALUS: i16 = 10; | ||
- const EVEN_CORNER_BONUS: i16 = 5; | ||
- const FIXED_BONUS: i16 = 3; | ||
- | ||
- const SIDES: [( (usize, usize), (usize, usize), (usize, usize), (usize, usize), (usize, usize), (usize, usize), (usize, usize) ); 4] = [ | ||
- ( (0,0), (0,1), (1,1), (0,2), (2,2), (1,0), (2,0) ), // NW corner | ||
- ( (0,7), (1,7), (1,6), (2,7), (2,5), (0,6), (0,5) ), // NE corner | ||
- ( (7,0), (6,0), (6,1), (5,0), (5,2), (7,1), (7,2) ), // SW corner | ||
- ( (7,7), (6,7), (6,6), (5,7), (5,5), (7,6), (7,5) ), // SE corner | ||
- ]; | ||
- | ||
- let mut score: i16 = 0; | ||
- | ||
- for &(corner, odd, odd_corner, even, even_corner, counter_odd, counter_even) in SIDES.iter() { | ||
- | ||
- if let reversi::Cell::Taken { disk } = game.get_cell(corner) { | ||
- match disk { | ||
- reversi::Disk::Light => { | ||
- score += CORNER_BONUS; | ||
- if let reversi::Cell::Taken { disk: reversi::Disk::Light } = game.get_cell(odd) { | ||
- score += FIXED_BONUS; | ||
- if let reversi::Cell::Taken { disk: reversi::Disk::Light } = game.get_cell(even) { | ||
- score += FIXED_BONUS; | ||
- } | ||
- } | ||
- if let reversi::Cell::Taken { disk: reversi::Disk::Light } = game.get_cell(counter_odd) { | ||
- score += FIXED_BONUS; | ||
- if let reversi::Cell::Taken { disk: reversi::Disk::Light } = game.get_cell(counter_even) { | ||
- score += FIXED_BONUS; | ||
- } | ||
- } | ||
- } | ||
- reversi::Disk::Dark => { | ||
- score -= CORNER_BONUS; | ||
- if let reversi::Cell::Taken { disk: reversi::Disk::Dark } = game.get_cell(odd) { | ||
- score -= FIXED_BONUS; | ||
- if let reversi::Cell::Taken { disk: reversi::Disk::Dark } = game.get_cell(even) { | ||
- score -= FIXED_BONUS; | ||
- } | ||
- } | ||
- if let reversi::Cell::Taken { disk: reversi::Disk::Dark } = game.get_cell(counter_odd) { | ||
- score -= FIXED_BONUS; | ||
- if let reversi::Cell::Taken { disk: reversi::Disk::Dark } = game.get_cell(counter_even) { | ||
- score -= FIXED_BONUS; | ||
- } | ||
- } | ||
- } | ||
- } | ||
- | ||
- } else { | ||
- | ||
- if let reversi::Cell::Taken { disk } = game.get_cell(odd) { | ||
- score += match disk { | ||
- reversi::Disk::Light => -ODD_MALUS, | ||
- reversi::Disk::Dark => ODD_MALUS, | ||
- } | ||
- } else if let reversi::Cell::Taken { disk } = game.get_cell(even) { | ||
- score += match disk { | ||
- reversi::Disk::Light => EVEN_BONUS, | ||
- reversi::Disk::Dark => -EVEN_BONUS, | ||
- } | ||
- } | ||
- | ||
- if let reversi::Cell::Taken { disk } = game.get_cell(counter_odd) { | ||
- score += match disk { | ||
- reversi::Disk::Light => -ODD_MALUS, | ||
- reversi::Disk::Dark => ODD_MALUS, | ||
- } | ||
- } else if let reversi::Cell::Taken { disk } = game.get_cell(counter_even) { | ||
- score += match disk { | ||
- reversi::Disk::Light => EVEN_BONUS, | ||
- reversi::Disk::Dark => -EVEN_BONUS, | ||
- } | ||
- } | ||
- | ||
- if let reversi::Cell::Taken { disk } = game.get_cell(odd_corner) { | ||
- score += match disk { | ||
- reversi::Disk::Light => -ODD_CORNER_MALUS, | ||
- reversi::Disk::Dark => ODD_CORNER_MALUS, | ||
- } | ||
- | ||
- } else if let reversi::Cell::Taken { disk } = game.get_cell(even_corner) { | ||
- score += match disk { | ||
- reversi::Disk::Light => EVEN_CORNER_BONUS, | ||
- reversi::Disk::Dark => -EVEN_CORNER_BONUS, | ||
- } | ||
- } | ||
- } | ||
- } | ||
- | ||
- score | ||
-} |
200
filesystem/apps/rusthello/players/mod.rs
@@ -1,200 +0,0 @@ | ||
-use interface; | ||
-use reversi; | ||
- | ||
-use std::thread; | ||
-use std::sync::mpsc; | ||
-use std::sync::mpsc::{Sender, Receiver}; | ||
-use std::time; | ||
- | ||
- | ||
- | ||
-mod ai_medium; | ||
- | ||
-const STARTING_DEPTH: u8 = 2; | ||
-const TIME_LIMIT: i64 = 1; | ||
- | ||
- | ||
- | ||
-#[derive(Clone)] | ||
-pub enum Score { | ||
- Running(f32), | ||
- EndGame(i16), | ||
-} | ||
- | ||
-impl Score { | ||
- | ||
- pub fn is_better_for(first: Score, second: Score, side: reversi::Disk) -> bool { | ||
- match side { | ||
- reversi::Disk::Light => Score::is_better(first, second), | ||
- reversi::Disk::Dark => !Score::is_better(first, second), | ||
- } | ||
- } | ||
- | ||
- pub fn is_better(first: Score, second: Score) -> bool { | ||
- match first { | ||
- Score::Running(val1) => { | ||
- match second { | ||
- Score::Running(val2) => val1 > val2, | ||
- Score::EndGame(scr2) => scr2 < 0i16 || ( scr2 == 0i16 && val1 > 0f32 ), | ||
- } | ||
- } | ||
- Score::EndGame(scr1) => { | ||
- match second { | ||
- Score::Running(val2) => scr1 > 0i16 || ( scr1 == 0i16 && val2 < 0f32 ), | ||
- Score::EndGame(scr2) => scr1 > scr2, | ||
- } | ||
- } | ||
- } | ||
- } | ||
-} | ||
- | ||
- | ||
- | ||
-#[derive(Clone)] | ||
-struct MoveScore{ | ||
- score: Score, | ||
- coord: (usize, usize), | ||
-} | ||
- | ||
-impl MoveScore { | ||
- pub fn is_better_for(first: MoveScore, second: MoveScore, side: reversi::Disk) -> bool { | ||
- match side { | ||
- reversi::Disk::Light => Score::is_better(first.score, second.score), | ||
- reversi::Disk::Dark => !Score::is_better(first.score, second.score), | ||
- } | ||
- } | ||
-} | ||
- | ||
- | ||
- | ||
- | ||
-/// It represents the different kind of player who can take part to the game. | ||
-#[derive(Clone)] | ||
-pub enum Player { | ||
- Human, | ||
- AiMedium, | ||
-} | ||
- | ||
- | ||
-impl Player { | ||
- | ||
- /// It produces the new move from each kind of Player. | ||
- pub fn make_move(&self, game: &reversi::Game) -> interface::UserCommand { | ||
- | ||
- if let reversi::Status::Ended = game.get_status() { | ||
- panic!("make_move called on ended game!"); | ||
- } | ||
- | ||
- if let Player::Human = *self { | ||
- interface::human_make_move(game) | ||
- } else { | ||
- let (row, col) = ai_make_move(game, &self.clone()); | ||
- | ||
- interface::print_move(game, (row, col)); | ||
- | ||
- interface::UserCommand::Move(row, col) | ||
- } | ||
- } | ||
-} | ||
- | ||
- | ||
- | ||
-pub fn ai_make_move(game: &reversi::Game, player: &Player) -> (usize, usize) { | ||
- let mut num_moves = 0; | ||
- let mut forced_move: (usize, usize) = (reversi::BOARD_SIZE, reversi::BOARD_SIZE); | ||
- let mut game_after_move = game.clone(); | ||
- | ||
- // To save computation time, first check whether the move is forced. | ||
- for (row, &rows) in game.get_board().iter().enumerate() { | ||
- for (col, _) in rows.iter().enumerate() { | ||
- if game_after_move.make_move((row, col)) { | ||
- num_moves += 1; | ||
- forced_move = (row, col); | ||
- game_after_move = game.clone(); | ||
- } | ||
- } | ||
- } | ||
- | ||
- match num_moves { | ||
- 0 => panic!("No valid move is possible!"), | ||
- 1 => forced_move, | ||
- _ => { | ||
- let start_time = time::Instant::now(); | ||
- let mut depth = STARTING_DEPTH; | ||
- let mut best_move = (0, 0); | ||
- | ||
- while start_time.elapsed() < time::Duration::new(TIME_LIMIT, 0) { | ||
- if game.get_tempo() + 2 * (depth - 1) >= ( reversi::BOARD_SIZE * reversi::BOARD_SIZE ) as u8 { | ||
- return find_best_move(game, &player, (reversi::BOARD_SIZE * reversi::BOARD_SIZE) as u8 - game.get_tempo()); | ||
- } else { | ||
- best_move = find_best_move(game, &player, depth); | ||
- } | ||
- depth += 1; | ||
- } | ||
- best_move | ||
- } | ||
- } | ||
-} | ||
- | ||
- | ||
- | ||
-pub fn find_best_move(game: &reversi::Game, player: &Player, depth: u8) -> (usize, usize) { | ||
- | ||
- if let reversi::Status::Running { current_turn } = game.get_status() { | ||
- | ||
- let ai_eval: fn(&reversi::Game, u8) -> Score = match *player { | ||
- Player::AiMedium => ai_medium::ai_eval, | ||
- Player::Human => panic!("A human is not an AI!") | ||
- }; | ||
- | ||
- let mut best_move: Option<MoveScore> = None; | ||
- | ||
- let mut num_moves: u8 = 0; | ||
- | ||
- let (tx, rx): (Sender<MoveScore>, Receiver<MoveScore>) = mpsc::channel(); | ||
- | ||
- let mut game_after_move = game.clone(); | ||
- | ||
- for (row, &rows) in game.get_board().iter().enumerate() { | ||
- for (col, _) in rows.iter().enumerate() { | ||
- if game_after_move.make_move((row, col)) { | ||
- | ||
- num_moves +=1; | ||
- let thread_tx = tx.clone(); | ||
- | ||
- thread::spawn(move || { | ||
- let new_move = MoveScore { | ||
- score: ai_eval(&game_after_move, depth), | ||
- coord: (row, col), | ||
- }; | ||
- thread_tx.send(new_move).unwrap(); | ||
- }); | ||
- | ||
- game_after_move = game.clone(); | ||
- | ||
- } | ||
- } | ||
- } | ||
- | ||
- for _ in 0..num_moves { | ||
- let new_move = rx.recv().ok().expect("Could not receive answer"); | ||
- | ||
- if let Some(old_move) = best_move.clone() { | ||
- if MoveScore::is_better_for(new_move.clone(), old_move, current_turn) { | ||
- best_move = Some(new_move); | ||
- } | ||
- } else { | ||
- best_move = Some(new_move); | ||
- } | ||
- } | ||
- | ||
- if let Some(some_move) = best_move { | ||
- some_move.coord | ||
- } else { | ||
- panic!("best_eval is None"); | ||
- } | ||
- | ||
- } else { | ||
- panic!{"Game ended, cannot make a move!"}; | ||
- } | ||
-} |
200
filesystem/apps/rusthello/players/mod.rs~
@@ -1,200 +0,0 @@ | ||
-use interface; | ||
-use reversi; | ||
- | ||
-use std::thread; | ||
-use std::sync::mpsc; | ||
-use std::sync::mpsc::{Sender, Receiver}; | ||
-use std::time; | ||
- | ||
- | ||
- | ||
-mod ai_medium; | ||
- | ||
-const STARTING_DEPTH: u8 = 2; | ||
-const TIME_LIMIT: f64 = 1.0; | ||
- | ||
- | ||
- | ||
-#[derive(Clone)] | ||
-pub enum Score { | ||
- Running(f32), | ||
- EndGame(i16), | ||
-} | ||
- | ||
-impl Score { | ||
- | ||
- pub fn is_better_for(first: Score, second: Score, side: reversi::Disk) -> bool { | ||
- match side { | ||
- reversi::Disk::Light => Score::is_better(first, second), | ||
- reversi::Disk::Dark => !Score::is_better(first, second), | ||
- } | ||
- } | ||
- | ||
- pub fn is_better(first: Score, second: Score) -> bool { | ||
- match first { | ||
- Score::Running(val1) => { | ||
- match second { | ||
- Score::Running(val2) => val1 > val2, | ||
- Score::EndGame(scr2) => scr2 < 0i16 || ( scr2 == 0i16 && val1 > 0f32 ), | ||
- } | ||
- } | ||
- Score::EndGame(scr1) => { | ||
- match second { | ||
- Score::Running(val2) => scr1 > 0i16 || ( scr1 == 0i16 && val2 < 0f32 ), | ||
- Score::EndGame(scr2) => scr1 > scr2, | ||
- } | ||
- } | ||
- } | ||
- } | ||
-} | ||
- | ||
- | ||
- | ||
-#[derive(Clone)] | ||
-struct MoveScore{ | ||
- score: Score, | ||
- coord: (usize, usize), | ||
-} | ||
- | ||
-impl MoveScore { | ||
- pub fn is_better_for(first: MoveScore, second: MoveScore, side: reversi::Disk) -> bool { | ||
- match side { | ||
- reversi::Disk::Light => Score::is_better(first.score, second.score), | ||
- reversi::Disk::Dark => !Score::is_better(first.score, second.score), | ||
- } | ||
- } | ||
-} | ||
- | ||
- | ||
- | ||
- | ||
-/// It represents the different kind of player who can take part to the game. | ||
-#[derive(Clone)] | ||
-pub enum Player { | ||
- Human, | ||
- AiMedium, | ||
-} | ||
- | ||
- | ||
-impl Player { | ||
- | ||
- /// It produces the new move from each kind of Player. | ||
- pub fn make_move(&self, game: &reversi::Game) -> interface::UserCommand { | ||
- | ||
- if let reversi::Status::Ended = game.get_status() { | ||
- panic!("make_move called on ended game!"); | ||
- } | ||
- | ||
- if let Player::Human = *self { | ||
- interface::human_make_move(game) | ||
- } else { | ||
- let (row, col) = ai_make_move(game, &self.clone()); | ||
- | ||
- interface::print_move(game, (row, col)); | ||
- | ||
- interface::UserCommand::Move(row, col) | ||
- } | ||
- } | ||
-} | ||
- | ||
- | ||
- | ||
-pub fn ai_make_move(game: &reversi::Game, player: &Player) -> (usize, usize) { | ||
- let mut num_moves = 0; | ||
- let mut forced_move: (usize, usize) = (reversi::BOARD_SIZE, reversi::BOARD_SIZE); | ||
- let mut game_after_move = game.clone(); | ||
- | ||
- // To save computation time, first check whether the move is forced. | ||
- for (row, &rows) in game.get_board().iter().enumerate() { | ||
- for (col, _) in rows.iter().enumerate() { | ||
- if game_after_move.make_move((row, col)) { | ||
- num_moves += 1; | ||
- forced_move = (row, col); | ||
- game_after_move = game.clone(); | ||
- } | ||
- } | ||
- } | ||
- | ||
- match num_moves { | ||
- 0 => panic!("No valid move is possible!"), | ||
- 1 => forced_move, | ||
- _ => { | ||
- let start_time = time::Instant::now(); | ||
- let mut depth = STARTING_DEPTH; | ||
- let mut best_move = (0, 0); | ||
- | ||
- while start_time.elapsed() < time::Duration::new(1, 0) { | ||
- if game.get_tempo() + 2 * (depth - 1) >= ( reversi::BOARD_SIZE * reversi::BOARD_SIZE ) as u8 { | ||
- return find_best_move(game, &player, (reversi::BOARD_SIZE * reversi::BOARD_SIZE) as u8 - game.get_tempo()); | ||
- } else { | ||
- best_move = find_best_move(game, &player, depth); | ||
- } | ||
- depth += 1; | ||
- } | ||
- best_move | ||
- } | ||
- } | ||
-} | ||
- | ||
- | ||
- | ||
-pub fn find_best_move(game: &reversi::Game, player: &Player, depth: u8) -> (usize, usize) { | ||
- | ||
- if let reversi::Status::Running { current_turn } = game.get_status() { | ||
- | ||
- let ai_eval: fn(&reversi::Game, u8) -> Score = match *player { | ||
- Player::AiMedium => ai_medium::ai_eval, | ||
- Player::Human => panic!("A human is not an AI!") | ||
- }; | ||
- | ||
- let mut best_move: Option<MoveScore> = None; | ||
- | ||
- let mut num_moves: u8 = 0; | ||
- | ||
- let (tx, rx): (Sender<MoveScore>, Receiver<MoveScore>) = mpsc::channel(); | ||
- | ||
- let mut game_after_move = game.clone(); | ||
- | ||
- for (row, &rows) in game.get_board().iter().enumerate() { | ||
- for (col, _) in rows.iter().enumerate() { | ||
- if game_after_move.make_move((row, col)) { | ||
- | ||
- num_moves +=1; | ||
- let thread_tx = tx.clone(); | ||
- | ||
- thread::spawn(move || { | ||
- let new_move = MoveScore { | ||
- score: ai_eval(&game_after_move, depth), | ||
- coord: (row, col), | ||
- }; | ||
- thread_tx.send(new_move).unwrap(); | ||
- }); | ||
- | ||
- game_after_move = game.clone(); | ||
- | ||
- } | ||
- } | ||
- } | ||
- | ||
- for _ in 0..num_moves { | ||
- let new_move = rx.recv().ok().expect("Could not receive answer"); | ||
- | ||
- if let Some(old_move) = best_move.clone() { | ||
- if MoveScore::is_better_for(new_move.clone(), old_move, current_turn) { | ||
- best_move = Some(new_move); | ||
- } | ||
- } else { | ||
- best_move = Some(new_move); | ||
- } | ||
- } | ||
- | ||
- if let Some(some_move) = best_move { | ||
- some_move.coord | ||
- } else { | ||
- panic!("best_eval is None"); | ||
- } | ||
- | ||
- } else { | ||
- panic!{"Game ended, cannot make a move!"}; | ||
- } | ||
-} |
299
filesystem/apps/rusthello/src/interface.rs
@@ -1,299 +0,0 @@ | ||
-// This module provides interface functionalities and manages all the input/output part of the program | ||
- | ||
-use std::cmp::Ordering; | ||
-use std::io::{self, Write}; | ||
-use players; | ||
-use reversi; | ||
- | ||
-pub enum UserCommand { | ||
- NewGame, | ||
- NewPlayer(players::Player), | ||
- Move(usize, usize), | ||
- Help, | ||
- Undo, | ||
- Quit, | ||
-} | ||
- | ||
-pub const INTRO: &'static str = | ||
-"\n\n | ||
- RUSThello | ||
- ● ○ ● ○ ● | ||
- a simple Reversi game | ||
- written in Rust with love | ||
- Redox Edition | ||
- v 1.1.0\n\n"; | ||
- | ||
-pub const MAIN_MENU: &'static str = | ||
-"\nMain Menu: | ||
- n - New match | ||
- h - Help | ||
- q - Quit RUSThello"; | ||
- | ||
- pub const NEW_PLAYER_MENU: &'static str = | ||
- "\nChoose a player: | ||
- hp - Human Player | ||
- ai - Artificial Intelligence | ||
- q - Quit match"; | ||
- | ||
-pub const COMMANDS_INFO: &'static str = | ||
-"\nStarting new game… | ||
-Type a cell's coordinates to place your disk there. Exaple: \"c4\" | ||
-Type 'help' or 'h' instead of a move to display help message. | ||
-Type 'undo' or 'u' instead of a move to undo last move. | ||
-Type 'quit' or 'q' instead of a move to abandon the game."; | ||
- | ||
-pub const HELP: &'static str = "\ | ||
-\n\n\n\tHOW TO PLAY REVERSI:\n | ||
-Reversi is a board game where two players compete against each other. \ | ||
-The game is played on a 8x8 board, just like chess but for the squares’ colour which is always green. \ | ||
-There are 64 identical pieces called disks, which are white on one side and black on the other. \ | ||
-A player is Light, using disks’ white side, and the other one is Dark, using disks' black side. \ | ||
-The game starts with four disks already placed at the centre of the board, two for each side. \ | ||
-Dark moves first.\n | ||
-Let’s say it’s Dark’s turn, for simplicity's sake, as for Light the rules are just the same. \ | ||
-Dark has to place a disk in a free square of the board, with the black side facing up. \ | ||
-Whenever the newly placed black disk and any other previously placed black disk enclose a sequence of white disks (horizontal, vertical or diagonal and of any length), all of those flip and turn black. \ | ||
-It is mandatory to place the new disk such that at least a white disk is flipped, otherwise the move is not valid.\n | ||
-Usually players’ turn alternate, passing from one to the other. \ | ||
-When a player cannot play any legal move, the turn goes back to the other player, thus allowing the same player to play consecutive turns. \ | ||
-When neither player can play a legal move, the game ends. \ | ||
-In particular, this is true whenever the board has been completely filled up (for a total of 60 moves), but games happen sometimes to end before that, leaving empty squares on the board.\n | ||
-When the game ends, the player with more disks turned to its side wins. \ | ||
-Ties are possible as well, if both player have the same number of disks.\n\n\n | ||
-\tHOW TO USE RUSThello:\n | ||
-To play RUSThello you first have to choose who is playing on each side, Dark and Light. \ | ||
-You can choose a human players or an AI. \ | ||
-Choose human for both players and challenge a friend, or test your skills against an AI, or even relax and watch as two AIs compete with each other: all matches are possible!\n | ||
-As a human player, you move by entering the coordinates (a letter and a number) of the square you want to place your disk on, e.g. all of 'c4', 'C4', '4c' and '4C' are valid and equivalent coordinates. \ | ||
-For your ease of use, all legal moves are marked on the board with a *.\n | ||
-Furthermore, on your turn you can also input special commands: 'undo' to undo your last move (and yes, you can 'undo' as many times as you like) and 'quit' to quit the game.\n\n\n | ||
-\tCREDITS:\n | ||
-RUSThello v. 1.1.0 Redox Edition | ||
-by Enrico Ghiorzi, with the invaluable help of the Redox community | ||
-Copyright (c) 2015 by Enrico Ghiorzi | ||
-Released under the MIT license\n\n\n"; | ||
- | ||
- | ||
-pub fn input_main_menu() -> UserCommand { | ||
- | ||
- loop { | ||
- print!("Insert input: "); | ||
- match get_user_command() { | ||
- Some(UserCommand::NewGame) => return UserCommand::NewGame, | ||
- Some(UserCommand::Help) => return UserCommand::Help, | ||
- Some(UserCommand::Quit) => { | ||
- println!("\nGoodbye!\n\n\n"); | ||
- return UserCommand::Quit; | ||
- } | ||
- _ => println!("This is not a valid command!"), | ||
- } | ||
- } | ||
-} | ||
- | ||
-pub fn new_player(side: reversi::Disk) -> Option<players::Player> { | ||
- loop { | ||
- match side { | ||
- reversi::Disk::Light => print!("● Light player: "), | ||
- reversi::Disk::Dark => print!("○ Dark player: "), | ||
- } | ||
- match get_user_command() { | ||
- Some(UserCommand::NewPlayer(player)) => return Some(player), | ||
- Some(UserCommand::Quit) => return None, | ||
- _ => println!("This is not a valid command!"), | ||
- } | ||
- } | ||
-} | ||
- | ||
-/// It gets an input from the user and tries to parse it, then returns a Option<UserCommand>`. | ||
-/// If the input is recognized as a legit command, it returns the relative `Option::Some(UserCommand)`. | ||
-/// If the input is not recognized as a legit command, it returns a `Option::None`. | ||
-pub fn get_user_command() -> Option<UserCommand> { | ||
- | ||
- // Read the input | ||
- let _ = io::stdout().flush(); | ||
- | ||
- let mut input = String::new(); | ||
- | ||
- io::stdin().read_line(&mut input) | ||
- .ok() | ||
- .expect("failed to read line"); | ||
- | ||
- let input = input.trim().to_lowercase(); | ||
- | ||
- match &*input { | ||
- "hp" => Some(UserCommand::NewPlayer(players::Player::Human)), | ||
- "ai" => Some(UserCommand::NewPlayer(players::Player::AiMedium)), | ||
- "n" | "new game" => Some(UserCommand::NewGame), | ||
- "h" | "help" => Some(UserCommand::Help), | ||
- "u" | "undo" => Some(UserCommand::Undo), | ||
- "q" | "quit" => Some(UserCommand::Quit), | ||
- _ => { | ||
- | ||
- let mut row: Option<usize> = None; | ||
- let mut col: Option<usize> = None; | ||
- | ||
- for curr_char in input.chars() { | ||
- match curr_char { | ||
- '1'...'8' => { | ||
- if let None = row { | ||
- row = Some(curr_char as usize - '1' as usize); | ||
- } else { | ||
- return None; | ||
- } | ||
- } | ||
- 'a'...'h' => { | ||
- if let None = col { | ||
- col = Some(curr_char as usize - 'a' as usize); | ||
- } else { | ||
- return None; | ||
- } | ||
- } | ||
- _ => return None, | ||
- } | ||
- } | ||
- | ||
- if row.is_none() || col.is_none() { | ||
- None | ||
- } else { | ||
- // The move is not checked! | ||
- Some(UserCommand::Move(row.unwrap(), col.unwrap())) | ||
- } | ||
- } | ||
- } | ||
-} | ||
- | ||
- | ||
- | ||
-/// draw_board draws the board (using text characters) in a pleasant-looking way, converting the board in a string (board_to_string) and then printing this. | ||
-pub fn draw_board(game: &reversi::Game) { | ||
- | ||
- let board = game.get_board(); | ||
- | ||
- // Declare board_to_string and add column reference at the top | ||
- let mut board_to_string: String = "\n\n\n\t a b c d e f g h\n".to_string(); | ||
- | ||
- // For every row add a row reference to the left | ||
- for (row, row_array) in board.iter().enumerate() { | ||
- board_to_string.push('\t'); | ||
- board_to_string.push_str(&(row + 1).to_string()); | ||
- board_to_string.push(' '); | ||
- | ||
- // For every column, add the appropriate character depending on the content of the current cell | ||
- for (col, &cell) in row_array.iter().enumerate() { | ||
- | ||
- match cell { | ||
- // Light and Dark cells are represented by white and black bullets | ||
- reversi::Cell::Taken { disk: reversi::Disk::Light } => board_to_string.push_str(" ● "), | ||
- reversi::Cell::Taken { disk: reversi::Disk::Dark } => board_to_string.push_str(" ○ "), | ||
- | ||
- // An empty cell will display a plus or a multiplication sign if the current player can move in that cell | ||
- // or a little central dot otherwise | ||
- reversi::Cell::Empty => { | ||
- if game.check_move((row, col)) { | ||
- if let reversi::Status::Running { .. } = game.get_status() { | ||
- board_to_string.push_str(" * "); | ||
- } | ||
- } else { | ||
- board_to_string.push_str(" ∙ "); | ||
- } | ||
- } | ||
- } | ||
- } | ||
- | ||
- // Add a row reference to the right | ||
- board_to_string.push(' '); | ||
- board_to_string.push_str(&(row + 1).to_string()); | ||
- board_to_string.push('\n'); | ||
- } | ||
- | ||
- // Add column reference at the bottom | ||
- board_to_string.push_str("\t a b c d e f g h\n"); | ||
- | ||
- // Print board | ||
- println!("{}", board_to_string); | ||
- | ||
- // Print current score and game info | ||
- let (score_light, score_dark) = game.get_score(); | ||
- | ||
- match game.get_status() { | ||
- reversi::Status::Running { current_turn } => { | ||
- match current_turn { | ||
- reversi::Disk::Light => println!("\t {:>2} ○ >> ● {:<2}\n", score_dark, score_light), | ||
- reversi::Disk::Dark => println!("\t {:>2} ○ << ● {:<2}\n", score_dark, score_light), | ||
- } | ||
- } | ||
- reversi::Status::Ended => { | ||
- println!("\t {:>2} ○ ● {:<2}\n", score_dark, score_light); | ||
- match score_light.cmp(&score_dark) { | ||
- Ordering::Greater => println!("Light wins!"), | ||
- Ordering::Less => println!("Dark wins!"), | ||
- Ordering::Equal => println!("Draw!"), | ||
- } | ||
- } | ||
- } | ||
-} | ||
- | ||
- | ||
- | ||
-/// Prints a message with info on a move. | ||
-pub fn print_move(game: &reversi::Game, (row, col): (usize, usize)) { | ||
- | ||
- let char_col = (('a' as u8) + (col as u8)) as char; | ||
- if let reversi::Status::Running { current_turn } = game.get_status() { | ||
- match current_turn { | ||
- reversi::Disk::Light => println!("● Light moves: {}{}", char_col, row + 1), | ||
- reversi::Disk::Dark => println!("○ Dark moves: {}{}", char_col, row + 1), | ||
- } | ||
- } | ||
-} | ||
- | ||
- | ||
- | ||
-/// It get_status a human player's input and convert it into a move. | ||
-/// If the move if illegal, it ask for another input until the given move is a legal one. | ||
-pub fn human_make_move(game: &reversi::Game) -> UserCommand { | ||
- | ||
- if let reversi::Status::Running { current_turn } = game.get_status() { | ||
- match current_turn { | ||
- reversi::Disk::Light => print!("● Light moves: "), | ||
- reversi::Disk::Dark => print!("○ Dark moves: "), | ||
- } | ||
- } | ||
- | ||
- loop { | ||
- if let Some(user_command) = get_user_command() { | ||
- match user_command { | ||
- UserCommand::Move(row, col) => { | ||
- if game.check_move((row, col)) { | ||
- return UserCommand::Move(row, col); | ||
- } else { | ||
- print!("Illegal move, try again: "); | ||
- continue; | ||
- } | ||
- } | ||
- _ => return user_command, | ||
- } | ||
- } else { | ||
- print!("This doesn't look like a valid command. Try again: "); | ||
- continue; | ||
- } | ||
- } | ||
-} | ||
- | ||
- | ||
- | ||
-// Print a last message before a player quits the game | ||
-pub fn quitting_message(coward: reversi::Disk) { | ||
- match coward { | ||
- reversi::Disk::Light => println!("Light is running away, the coward!"), | ||
- reversi::Disk::Dark => println!("Dark is running away, the coward!"), | ||
- } | ||
-} | ||
- | ||
-// Print a last message when 'undo' is not possible | ||
-pub fn no_undo_message(undecided: reversi::Disk) { | ||
- match undecided { | ||
- reversi::Disk::Light => println!("There is no move Light can undo."), | ||
- reversi::Disk::Dark => println!("There is no move Dark can undo."), | ||
- } | ||
-} |
118
filesystem/apps/rusthello/src/main.rs
@@ -1,118 +0,0 @@ | ||
-//! A simple Reversi game written in Rust with love | ||
-//! by Enrico Ghiorzi | ||
- | ||
-//extern crate rand; | ||
- | ||
-// Import modules | ||
-mod reversi; | ||
-mod interface; | ||
-mod players; | ||
- | ||
- | ||
- | ||
-pub fn main() { | ||
- // Main intro | ||
- println!("{}", interface::INTRO); | ||
- | ||
- loop { | ||
- println!("{}", interface::MAIN_MENU); | ||
- | ||
- match interface::input_main_menu() { | ||
- // Runs the game | ||
- interface::UserCommand::NewGame => play_game(), | ||
- // Prints help message | ||
- interface::UserCommand::Help => println!("{}", interface::HELP), | ||
- // Quit RUSThello | ||
- interface::UserCommand::Quit => break, | ||
- _ => panic!("Main got a user command it shouldn't have got!"), | ||
- } | ||
- } | ||
-} | ||
- | ||
- | ||
- | ||
-fn play_game() { | ||
- | ||
- // Get the two players | ||
- println!("{}", interface::NEW_PLAYER_MENU); | ||
- let dark = match interface::new_player(reversi::Disk::Dark) { | ||
- None => return, | ||
- Some(player) => player, | ||
- }; | ||
- let light = match interface::new_player(reversi::Disk::Light) { | ||
- None => return, | ||
- Some(player) => player, | ||
- }; | ||
- | ||
- // Create a new game | ||
- let mut game = reversi::Game::new(); | ||
- let mut hystory: Vec<reversi::Game> = Vec::new(); | ||
- | ||
- println!("{}", interface::COMMANDS_INFO); | ||
- | ||
- // Draw the current board and game info | ||
- interface::draw_board(&game); | ||
- | ||
- // Proceed with turn after turn till the game ends | ||
- 'turn: while let reversi::Status::Running { current_turn } = game.get_status() { | ||
- | ||
- // If the game is running, get the coordinates of the new move from the right player | ||
- let action = match current_turn { | ||
- reversi::Disk::Light => light.make_move(&game), | ||
- reversi::Disk::Dark => dark.make_move(&game), | ||
- }; | ||
- | ||
- match action { | ||
- // If the new move is valid, perform it; otherwise panic | ||
- // Player's make_move method is responsible for returning a legal move | ||
- // so the program should never print this message unless something goes horribly wrong | ||
- interface::UserCommand::Move(row, col) => { | ||
- | ||
- if game.check_move((row, col)) { | ||
- hystory.push(game.clone()); | ||
- game.make_move((row, col)); | ||
- interface::draw_board(&game); | ||
- } else { | ||
- panic!("Invalid move sent to main::game!"); | ||
- } | ||
- } | ||
- | ||
- // Manage hystory | ||
- interface::UserCommand::Undo => { | ||
- let mut recovery: Vec<reversi::Game> = Vec::new(); | ||
- | ||
- while let Some(previous_game) = hystory.pop() { | ||
- recovery.push(previous_game.clone()); | ||
- if let reversi::Status::Running { current_turn: previous_player } = previous_game.get_status() { | ||
- if previous_player == current_turn { | ||
- game = previous_game; | ||
- interface::draw_board(&game); | ||
- continue 'turn; | ||
- } | ||
- } | ||
- } | ||
- | ||
- while let Some(recovered_game) = recovery.pop() { | ||
- hystory.push(recovered_game.clone()); | ||
- } | ||
- | ||
- interface::no_undo_message(current_turn); | ||
- } | ||
- | ||
- interface::UserCommand::Help => { | ||
- println!("{}", interface::HELP); | ||
- interface::draw_board(&game); | ||
- } | ||
- | ||
- // Quit Match | ||
- interface::UserCommand::Quit => { | ||
- interface::quitting_message(current_turn); | ||
- break; | ||
- } | ||
- | ||
- _ => { | ||
- panic!("Something's wrong here!"); | ||
- } | ||
- } | ||
- } | ||
-} |
159
filesystem/apps/rusthello/src/players/ai_medium.rs
@@ -1,159 +0,0 @@ | ||
-//use rand; | ||
-//use rand::Rng; | ||
- | ||
-use reversi; | ||
-use players::Score; | ||
- | ||
-const MOBILITY: u8 = 1; | ||
-//const RANDOMNESS: f32 = 1.0; | ||
- | ||
- | ||
- | ||
-pub fn ai_eval(game: &reversi::Game, depth: u8) -> Score { | ||
- | ||
- match game.get_status() { | ||
- reversi::Status::Running { current_turn } => { | ||
- if depth == 0 { | ||
- Score::Running(heavy_eval(game) as f32) | ||
- } else { | ||
- let mut best_score: Option<Score> = None; | ||
- let mut num_moves: u8 = 0; | ||
- let mut game_after_move = game.clone(); | ||
- | ||
- for (row, &rows) in game.get_board().iter().enumerate() { | ||
- for (col, _) in rows.iter().enumerate() { | ||
- if game_after_move.make_move((row, col)) { | ||
- | ||
- num_moves += 1; | ||
- let new_score = ai_eval(&game_after_move, depth - 1); | ||
- match best_score.clone() { | ||
- Some(old_score) => { | ||
- if Score::is_better_for(new_score.clone(), old_score, current_turn) { | ||
- best_score = Some(new_score); | ||
- } | ||
- } | ||
- None => best_score = Some(new_score), | ||
- } | ||
- game_after_move = game.clone(); | ||
- | ||
- } | ||
- } | ||
- } | ||
- if let Some(score) = best_score { | ||
- if let Score::Running(val) = score { | ||
- return match current_turn { | ||
- reversi::Disk::Light => Score::Running(val + ( num_moves * MOBILITY ) as f32 ), | ||
- reversi::Disk::Dark => Score::Running(val - ( num_moves * MOBILITY ) as f32 ), | ||
- } | ||
- } else { | ||
- return score; | ||
- } | ||
- } else { | ||
- panic!("ai_eval produced no best_score!"); | ||
- } | ||
- } | ||
- } | ||
- reversi::Status::Ended => { | ||
- Score::EndGame(game.get_score_diff()) | ||
- } | ||
- } | ||
-} | ||
- | ||
- | ||
- | ||
-fn heavy_eval(game: &reversi::Game) -> i16 { | ||
- const CORNER_BONUS: i16 = 15; | ||
- const ODD_MALUS: i16 = 3; | ||
- const EVEN_BONUS: i16 = 3; | ||
- const ODD_CORNER_MALUS: i16 = 10; | ||
- const EVEN_CORNER_BONUS: i16 = 5; | ||
- const FIXED_BONUS: i16 = 3; | ||
- | ||
- const SIDES: [( (usize, usize), (usize, usize), (usize, usize), (usize, usize), (usize, usize), (usize, usize), (usize, usize) ); 4] = [ | ||
- ( (0,0), (0,1), (1,1), (0,2), (2,2), (1,0), (2,0) ), // NW corner | ||
- ( (0,7), (1,7), (1,6), (2,7), (2,5), (0,6), (0,5) ), // NE corner | ||
- ( (7,0), (6,0), (6,1), (5,0), (5,2), (7,1), (7,2) ), // SW corner | ||
- ( (7,7), (6,7), (6,6), (5,7), (5,5), (7,6), (7,5) ), // SE corner | ||
- ]; | ||
- | ||
- let mut score: i16 = 0; | ||
- | ||
- for &(corner, odd, odd_corner, even, even_corner, counter_odd, counter_even) in SIDES.iter() { | ||
- | ||
- if let reversi::Cell::Taken { disk } = game.get_cell(corner) { | ||
- match disk { | ||
- reversi::Disk::Light => { | ||
- score += CORNER_BONUS; | ||
- if let reversi::Cell::Taken { disk: reversi::Disk::Light } = game.get_cell(odd) { | ||
- score += FIXED_BONUS; | ||
- if let reversi::Cell::Taken { disk: reversi::Disk::Light } = game.get_cell(even) { | ||
- score += FIXED_BONUS; | ||
- } | ||
- } | ||
- if let reversi::Cell::Taken { disk: reversi::Disk::Light } = game.get_cell(counter_odd) { | ||
- score += FIXED_BONUS; | ||
- if let reversi::Cell::Taken { disk: reversi::Disk::Light } = game.get_cell(counter_even) { | ||
- score += FIXED_BONUS; | ||
- } | ||
- } | ||
- } | ||
- reversi::Disk::Dark => { | ||
- score -= CORNER_BONUS; | ||
- if let reversi::Cell::Taken { disk: reversi::Disk::Dark } = game.get_cell(odd) { | ||
- score -= FIXED_BONUS; | ||
- if let reversi::Cell::Taken { disk: reversi::Disk::Dark } = game.get_cell(even) { | ||
- score -= FIXED_BONUS; | ||
- } | ||
- } | ||
- if let reversi::Cell::Taken { disk: reversi::Disk::Dark } = game.get_cell(counter_odd) { | ||
- score -= FIXED_BONUS; | ||
- if let reversi::Cell::Taken { disk: reversi::Disk::Dark } = game.get_cell(counter_even) { | ||
- score -= FIXED_BONUS; | ||
- } | ||
- } | ||
- } | ||
- } | ||
- | ||
- } else { | ||
- | ||
- if let reversi::Cell::Taken { disk } = game.get_cell(odd) { | ||
- score += match disk { | ||
- reversi::Disk::Light => -ODD_MALUS, | ||
- reversi::Disk::Dark => ODD_MALUS, | ||
- } | ||
- } else if let reversi::Cell::Taken { disk } = game.get_cell(even) { | ||
- score += match disk { | ||
- reversi::Disk::Light => EVEN_BONUS, | ||
- reversi::Disk::Dark => -EVEN_BONUS, | ||
- } | ||
- } | ||
- | ||
- if let reversi::Cell::Taken { disk } = game.get_cell(counter_odd) { | ||
- score += match disk { | ||
- reversi::Disk::Light => -ODD_MALUS, | ||
- reversi::Disk::Dark => ODD_MALUS, | ||
- } | ||
- } else if let reversi::Cell::Taken { disk } = game.get_cell(counter_even) { | ||
- score += match disk { | ||
- reversi::Disk::Light => EVEN_BONUS, | ||
- reversi::Disk::Dark => -EVEN_BONUS, | ||
- } | ||
- } | ||
- | ||
- if let reversi::Cell::Taken { disk } = game.get_cell(odd_corner) { | ||
- score += match disk { | ||
- reversi::Disk::Light => -ODD_CORNER_MALUS, | ||
- reversi::Disk::Dark => ODD_CORNER_MALUS, | ||
- } | ||
- | ||
- } else if let reversi::Cell::Taken { disk } = game.get_cell(even_corner) { | ||
- score += match disk { | ||
- reversi::Disk::Light => EVEN_CORNER_BONUS, | ||
- reversi::Disk::Dark => -EVEN_CORNER_BONUS, | ||
- } | ||
- } | ||
- } | ||
- } | ||
- | ||
- score | ||
-} |
201
filesystem/apps/rusthello/src/players/mod.rs
@@ -1,201 +0,0 @@ | ||
-use interface; | ||
-use reversi; | ||
- | ||
-use std::thread; | ||
-use std::sync::mpsc; | ||
-use std::sync::mpsc::{Sender, Receiver}; | ||
-use std::time; | ||
- | ||
- | ||
- | ||
-mod ai_medium; | ||
- | ||
-const STARTING_DEPTH: u8 = 2; | ||
-const TIME_LIMIT: f64 = 1.0; | ||
- | ||
- | ||
- | ||
-#[derive(Clone)] | ||
-pub enum Score { | ||
- Running(f32), | ||
- EndGame(i16), | ||
-} | ||
- | ||
-impl Score { | ||
- | ||
- pub fn is_better_for(first: Score, second: Score, side: reversi::Disk) -> bool { | ||
- match side { | ||
- reversi::Disk::Light => Score::is_better(first, second), | ||
- reversi::Disk::Dark => !Score::is_better(first, second), | ||
- } | ||
- } | ||
- | ||
- pub fn is_better(first: Score, second: Score) -> bool { | ||
- match first { | ||
- Score::Running(val1) => { | ||
- match second { | ||
- Score::Running(val2) => val1 > val2, | ||
- Score::EndGame(scr2) => scr2 < 0i16 || ( scr2 == 0i16 && val1 > 0f32 ), | ||
- } | ||
- } | ||
- Score::EndGame(scr1) => { | ||
- match second { | ||
- Score::Running(val2) => scr1 > 0i16 || ( scr1 == 0i16 && val2 < 0f32 ), | ||
- Score::EndGame(scr2) => scr1 > scr2, | ||
- } | ||
- } | ||
- } | ||
- } | ||
-} | ||
- | ||
- | ||
- | ||
-#[derive(Clone)] | ||
-struct MoveScore{ | ||
- score: Score, | ||
- coord: (usize, usize), | ||
-} | ||
- | ||
-impl MoveScore { | ||
- pub fn is_better_for(first: MoveScore, second: MoveScore, side: reversi::Disk) -> bool { | ||
- match side { | ||
- reversi::Disk::Light => Score::is_better(first.score, second.score), | ||
- reversi::Disk::Dark => !Score::is_better(first.score, second.score), | ||
- } | ||
- } | ||
-} | ||
- | ||
- | ||
- | ||
- | ||
-/// It represents the different kind of player who can take part to the game. | ||
-#[derive(Clone)] | ||
-pub enum Player { | ||
- Human, | ||
- AiMedium, | ||
-} | ||
- | ||
- | ||
-impl Player { | ||
- | ||
- /// It produces the new move from each kind of Player. | ||
- pub fn make_move(&self, game: &reversi::Game) -> interface::UserCommand { | ||
- | ||
- if let reversi::Status::Ended = game.get_status() { | ||
- panic!("make_move called on ended game!"); | ||
- } | ||
- | ||
- if let Player::Human = *self { | ||
- interface::human_make_move(game) | ||
- } else { | ||
- let (row, col) = ai_make_move(game, &self.clone()); | ||
- | ||
- interface::print_move(game, (row, col)); | ||
- | ||
- interface::UserCommand::Move(row, col) | ||
- } | ||
- } | ||
-} | ||
- | ||
- | ||
- | ||
-pub fn ai_make_move(game: &reversi::Game, player: &Player) -> (usize, usize) { | ||
- | ||
- let mut num_moves = 0; | ||
- let mut forced_move: (usize, usize) = (reversi::BOARD_SIZE, reversi::BOARD_SIZE); | ||
- let mut game_after_move = game.clone(); | ||
- | ||
- // To save computation time, first check whether the move is forced. | ||
- for (row, &rows) in game.get_board().iter().enumerate() { | ||
- for (col, _) in rows.iter().enumerate() { | ||
- if game_after_move.make_move((row, col)) { | ||
- num_moves += 1; | ||
- forced_move = (row, col); | ||
- game_after_move = game.clone(); | ||
- } | ||
- } | ||
- } | ||
- | ||
- match num_moves { | ||
- 0 => panic!("No valid move is possible!"), | ||
- 1 => forced_move, | ||
- _ => { | ||
- let start_time = time::Instant::now(); | ||
- let mut depth = STARTING_DEPTH; | ||
- let mut best_move = (0, 0); | ||
- | ||
- while start_time.elapsed() < time::Duration::new(1, 0) { | ||
- if game.get_tempo() + 2 * (depth - 1) >= ( reversi::BOARD_SIZE * reversi::BOARD_SIZE ) as u8 { | ||
- return find_best_move(game, &player, (reversi::BOARD_SIZE * reversi::BOARD_SIZE) as u8 - game.get_tempo()); | ||
- } else { | ||
- best_move = find_best_move(game, &player, depth); | ||
- } | ||
- depth += 1; | ||
- } | ||
- best_move | ||
- } | ||
- } | ||
-} | ||
- | ||
- | ||
- | ||
-pub fn find_best_move(game: &reversi::Game, player: &Player, depth: u8) -> (usize, usize) { | ||
- | ||
- if let reversi::Status::Running { current_turn } = game.get_status() { | ||
- | ||
- let ai_eval: fn(&reversi::Game, u8) -> Score = match *player { | ||
- Player::AiMedium => ai_medium::ai_eval, | ||
- Player::Human => panic!("A human is not an AI!") | ||
- }; | ||
- | ||
- let mut best_move: Option<MoveScore> = None; | ||
- | ||
- let mut num_moves: u8 = 0; | ||
- | ||
- let (tx, rx): (Sender<MoveScore>, Receiver<MoveScore>) = mpsc::channel(); | ||
- | ||
- let mut game_after_move = game.clone(); | ||
- | ||
- for (row, &rows) in game.get_board().iter().enumerate() { | ||
- for (col, _) in rows.iter().enumerate() { | ||
- if game_after_move.make_move((row, col)) { | ||
- | ||
- num_moves +=1; | ||
- let thread_tx = tx.clone(); | ||
- | ||
- thread::spawn(move || { | ||
- let new_move = MoveScore { | ||
- score: ai_eval(&game_after_move, depth), | ||
- coord: (row, col), | ||
- }; | ||
- thread_tx.send(new_move).unwrap(); | ||
- }); | ||
- | ||
- game_after_move = game.clone(); | ||
- | ||
- } | ||
- } | ||
- } | ||
- | ||
- for _ in 0..num_moves { | ||
- let new_move = rx.recv().ok().expect("Could not receive answer"); | ||
- | ||
- if let Some(old_move) = best_move.clone() { | ||
- if MoveScore::is_better_for(new_move.clone(), old_move, current_turn) { | ||
- best_move = Some(new_move); | ||
- } | ||
- } else { | ||
- best_move = Some(new_move); | ||
- } | ||
- } | ||
- | ||
- if let Some(some_move) = best_move { | ||
- some_move.coord | ||
- } else { | ||
- panic!("best_eval is None"); | ||
- } | ||
- | ||
- } else { | ||
- panic!{"Game ended, cannot make a move!"}; | ||
- } | ||
-} |
307
filesystem/apps/rusthello/src/reversi.rs
@@ -1,307 +0,0 @@ | ||
-//! It provides the main structures and mechanics for a Reversi game. | ||
- | ||
-/// There are two players playing the match: Light and Dark | ||
-#[derive(Clone, Copy, PartialEq)] | ||
-pub enum Disk { | ||
- Light, | ||
- Dark, | ||
-} | ||
- | ||
-impl Disk { | ||
- /// Get self's opposite side | ||
- pub fn opposite(&self) -> Disk { | ||
- match *self { | ||
- Disk::Light => Disk::Dark, | ||
- Disk::Dark => Disk::Light, | ||
- } | ||
- } | ||
-} | ||
- | ||
- | ||
- | ||
-/// A game can be in two status: either running (with a next player to play) or ended. | ||
-#[derive(Clone)] | ||
-pub enum Status { | ||
- Running { current_turn: Disk }, | ||
- Ended, | ||
-} | ||
- | ||
- | ||
- | ||
-/// Each cell in the board can either be empty or taken by one of the players. | ||
-#[derive(Clone, Copy)] | ||
-pub enum Cell { | ||
- Taken { disk: Disk }, | ||
- Empty, | ||
-} | ||
- | ||
- | ||
- | ||
-/// An array listing all the cardinal directions, represented by the coordinate delta to move in that direction. | ||
-/// #Examples | ||
-/// If I am in cell (4, 5) and move NE, I go to cell (4, 5) + (1, -1) = (5, 4). | ||
-pub const DIRECTIONS: [(i8, i8); 8] = [ | ||
- ( 1, 0), //North | ||
- ( 1, 1), //NE | ||
- ( 0, 1), //East | ||
- (-1, 1), //SE | ||
- (-1, 0), //South | ||
- (-1, -1), //SW | ||
- ( 0, -1), //West | ||
- ( 1, -1), //NW | ||
- ]; | ||
- | ||
-/// The size of the board is a constant. | ||
-pub const BOARD_SIZE: usize = 8; | ||
- | ||
-/// Board is the type of boards, that is, bidimensional arrays of Cells of size BOARD_SIZE. | ||
-pub type Board = [[Cell; BOARD_SIZE]; BOARD_SIZE]; | ||
- | ||
- | ||
- | ||
-/// The board is given by a matrix of cells of size BOARD_SIZE and by which player has to move next. | ||
-#[derive(Clone)] | ||
-pub struct Game { | ||
- board: Board, | ||
- status: Status, | ||
- score_light: u8, | ||
- score_dark: u8, | ||
-} | ||
- | ||
-impl Game { | ||
- /// Initializing a new game: starting positions on the board and Dark is the first to play | ||
- pub fn new() -> Game { | ||
- let mut board: Board = [[Cell::Empty; BOARD_SIZE]; BOARD_SIZE]; | ||
- board[3][3] = Cell::Taken { disk: Disk::Light }; | ||
- board[4][4] = Cell::Taken { disk: Disk::Light }; | ||
- board[3][4] = Cell::Taken { disk: Disk::Dark }; | ||
- board[4][3] = Cell::Taken { disk: Disk::Dark }; | ||
- | ||
- Game { | ||
- board: board, | ||
- status: Status::Running { current_turn: Disk::Dark }, | ||
- score_light: 2, | ||
- score_dark: 2, | ||
- } | ||
- } | ||
- | ||
- | ||
- | ||
- /// Returns the game's board | ||
- pub fn get_board(&self) -> &Board { | ||
- &self.board | ||
- } | ||
- | ||
- /// Returns the game's status | ||
- pub fn get_status(&self) -> Status { | ||
- self.status.clone() | ||
- } | ||
- | ||
- /// Returns the current score of the match. | ||
- pub fn get_score(&self) -> (u8, u8) { | ||
- (self.score_light, self.score_dark) | ||
- } | ||
- | ||
- /// Returns the difference in score between Light and Dark. | ||
- pub fn get_score_diff(&self) -> i16 { | ||
- self.score_light as i16 - self.score_dark as i16 | ||
- } | ||
- | ||
- /// Returns game's tempo (how many disks there are on the board). | ||
- pub fn get_tempo(&self) -> u8 { | ||
- self.score_light + self.score_dark | ||
- } | ||
- | ||
- /// Returns the board's cell corresponding to the given coordinates. | ||
- pub fn get_cell(&self, (row, col): (usize, usize)) -> Cell { | ||
- self.board[row][col] | ||
- } | ||
- | ||
- | ||
- | ||
- /// Check that a given move is legal | ||
- pub fn check_move (&self, (row, col): (usize, usize)) -> bool { | ||
- | ||
- // If the given coordinate falls out of the board or in a taken cell, the move cannot be legal | ||
- if row >= BOARD_SIZE || col >= BOARD_SIZE { | ||
- return false; | ||
- } else if let Cell::Taken { .. } = self.board[row][col] { | ||
- return false; | ||
- } | ||
- | ||
- if let Status::Running { current_turn } = self.status { | ||
- return self.quick_check_move(current_turn, (row, col)); | ||
- } | ||
- | ||
- false | ||
- } | ||
- | ||
- | ||
- | ||
- /// Check that a given move is legal, but faster (by skipping unnecessary checks) | ||
- fn quick_check_move (&self, current_turn: Disk, (row, col): (usize, usize)) -> bool { | ||
- | ||
- // If a move leads to eat in at least one direction, then it is legal | ||
- for &dir in DIRECTIONS.iter() { | ||
- if self.quick_check_move_along_direction(current_turn, (row, col), dir) { | ||
- return true; | ||
- } | ||
- } | ||
- | ||
- false | ||
- } | ||
- | ||
- | ||
- | ||
- /// Check whether a move leads to eat in a specified direction, but faster (by skipping unnecessary checks) | ||
- /// Does NOT perform checks already performed by check_move! | ||
- fn quick_check_move_along_direction (&self, current_turn: Disk, (row, col): (usize, usize), (delta_ns, delta_ew): (i8, i8)) -> bool { | ||
- | ||
- // Need at least two cells' space in the given direction | ||
- let mut col_i8: i8 = col as i8 + 2*delta_ew; | ||
- if ( col_i8 < 0 ) || ( col_i8 >= BOARD_SIZE as i8 ) { | ||
- return false; | ||
- } | ||
- | ||
- let mut row_i8: i8 = row as i8 + 2*delta_ns; | ||
- if ( row_i8 < 0 ) || ( row_i8 >= BOARD_SIZE as i8 ) { | ||
- return false; | ||
- } | ||
- | ||
- // Next cell has to be owned by the other player | ||
- if let Cell::Taken { disk } = self.board[ ( row as i8 + delta_ns ) as usize ][ ( col as i8 + delta_ew ) as usize] { | ||
- if disk == current_turn { | ||
- return false; | ||
- } | ||
- | ||
- while let Some(&rows) = self.board.get(row_i8 as usize) { | ||
- if let Some(&cell) = rows.get(col_i8 as usize) { | ||
- if let Cell::Taken { disk } = cell { | ||
- if disk == current_turn { | ||
- return true; | ||
- } | ||
- col_i8 += delta_ew; | ||
- row_i8 += delta_ns; | ||
- } else { | ||
- return false; | ||
- } | ||
- } else { | ||
- return false; | ||
- } | ||
- } | ||
- } | ||
- | ||
- false | ||
- } | ||
- | ||
- | ||
- | ||
- | ||
- /// Eats all of the opponent's occupied cells from a specified cell (given by its coordinates) in a specified direction | ||
- /// until it finds a cell of the current player | ||
- /// WARNING: this function do NOT perform any check about whether or not the move is legal | ||
- fn eat_along_direction (&mut self, current_turn: Disk, (row, col): (usize, usize), (delta_ns, delta_ew): (i8, i8)) { | ||
- | ||
- self.board[ ( row as i8 + delta_ns ) as usize ][ ( col as i8 + delta_ew ) as usize] = Cell::Taken { disk: current_turn }; | ||
- | ||
- let (mut row_i8, mut col_i8): (i8, i8) = (row as i8 + 2*delta_ns, col as i8 + 2*delta_ew); | ||
- | ||
- let mut eating: u8 = 1; | ||
- | ||
- while let Some(rows) = self.board.get_mut(row_i8 as usize) { | ||
- if let Some(cell) = rows.get_mut(col_i8 as usize) { | ||
- if let Cell::Taken { disk } = *cell { | ||
- if current_turn == disk { | ||
- break; | ||
- } else { | ||
- *cell = Cell::Taken { disk: current_turn }; | ||
- eating += 1; | ||
- row_i8 += delta_ns; | ||
- col_i8 += delta_ew; | ||
- } | ||
- } | ||
- } | ||
- } | ||
- | ||
- match current_turn { | ||
- Disk::Light => { | ||
- self.score_light += eating; | ||
- self.score_dark -= eating; | ||
- } | ||
- Disk::Dark => { | ||
- self.score_light -= eating; | ||
- self.score_dark += eating; | ||
- } | ||
- } | ||
- } | ||
- | ||
- | ||
- /// Current player perform a move, after verifying that it is legal. | ||
- /// It returns whether the move is legal or not. | ||
- pub fn make_move (&mut self, (row, col): (usize, usize)) -> bool { | ||
- | ||
- if row >= BOARD_SIZE || col >= BOARD_SIZE { | ||
- return false; | ||
- } else if let Cell::Taken { .. } = self.board[row][col] { | ||
- return false; | ||
- } | ||
- | ||
- let mut legal: bool = false; | ||
- | ||
- if let Status::Running { current_turn } = self.status { | ||
- for &dir in DIRECTIONS.iter() { | ||
- if self.quick_check_move_along_direction(current_turn, (row, col), dir) { | ||
- self.eat_along_direction(current_turn, (row, col), dir); | ||
- legal = true; | ||
- } | ||
- } | ||
- } | ||
- | ||
- // If a move is legal, the next player to play has to be determined | ||
- // If the opposite player can make any move at all, it gets the turn | ||
- // If not, if the previous player can make any move at all, it gets the turn | ||
- // If not (that is, if no player can make any move at all) the game is ended | ||
- if legal { | ||
- if let Status::Running { current_turn } = self.status { | ||
- self.board[row][col] = Cell::Taken { disk: current_turn }; | ||
- match current_turn { | ||
- Disk::Light => self.score_light += 1, | ||
- Disk::Dark => self.score_dark += 1, | ||
- } | ||
- | ||
- if self.get_tempo() == BOARD_SIZE as u8 * BOARD_SIZE as u8 { | ||
- self.status = Status::Ended; | ||
- } else { | ||
- self.status = Status::Running { current_turn: current_turn.opposite() }; | ||
- //self.flip_game_status(); | ||
- if !self.can_move() { | ||
- self.status = Status::Running { current_turn: current_turn }; | ||
- //self.flip_game_status(); | ||
- if !self.can_move() { | ||
- self.status = Status::Ended; | ||
- } | ||
- } | ||
- } | ||
- } | ||
- } | ||
- | ||
- legal | ||
- } | ||
- | ||
- /// Returns whether or not next_player can make any move at all. | ||
- fn can_move(&self) -> bool { | ||
- if let Status::Running{ current_turn } = self.status { | ||
- for (row_n, row) in self.board.iter().enumerate() { | ||
- for (col_n, &cell) in row.iter().enumerate() { | ||
- if let Cell::Empty = cell { | ||
- if self.quick_check_move(current_turn, (row_n, col_n)) { | ||
- return true; | ||
- } | ||
- } | ||
- } | ||
- } | ||
- } | ||
- false | ||
- } | ||
- | ||
-} |
BIN
filesystem/ui/apps/rusthello.bmp
Binary file not shown.
0 comments on commit
8ab8e3a