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