Commit 4a1f3629 authored by Enrico Ghiorzi's avatar Enrico Ghiorzi

Big `rusthello` update

Big update. Many small fixes, but mainly dependencies are changed: now
use `reversi`, `rand` and `rayon`. Also, I bumped `termion` version!
parent 07ee108f
......@@ -6,11 +6,17 @@ authors = ["Ticki <Ticki@users.noreply.github.com>", "Marcelo Paez <paezao@users
[dependencies]
libgo = "0.1.0"
liner = "0.1.2"
termion = "1.0"
rand = "0.3.*"
rayon = "0.8.*"
termion = "1.4.*"
[dependencies.extra]
git = "https://github.com/redox-os/libextra.git"
[dependencies.reversi]
version = "0.4.*"
git = "https://github.com/EGhiorzi/reversi/"
[[bin]]
name = "minesweeper"
path = "src/minesweeper/main.rs"
......
This diff is collapsed.
......@@ -2,9 +2,9 @@
use interface;
use reversi::{turn, game};
use ::{Action, Result};
use {Action, Result};
/// The type of human players. Its 'make_move' calls the interface to ask user for an input.
/// The type of human players. Its `make_move` calls the interface to ask user for an input.
pub struct HumanPlayer;
impl game::IsPlayer<::OtherAction> for HumanPlayer {
......
This diff is collapsed.
//! RUSThello (ver. 2.0.0)
//! `RUSThello` (v. 2.1.0)
//! A simple Reversi game written in Rust with love.
//! Based on `reversi` library (by the same author).
//! Released under MIT license.
......@@ -6,17 +6,17 @@
#![crate_name = "rusthello"]
#![crate_type = "bin"]
#![feature(rand)]
// External crates
extern crate termion;
extern crate rand;
extern crate rayon;
extern crate reversi;
extern crate termion;
// Modules
mod interface;
mod human_player;
mod ai_player;
mod reversi;
use reversi::{ReversiError, Side};
use reversi::game::{PlayerAction, IsPlayer, Game};
......@@ -99,7 +99,7 @@ fn play_game() -> Result<()> {
interface::draw_board(game.get_current_turn());
// Proceed with turn after turn till the game ends
while !game.is_ended() {
while !game.is_endgame() {
let state_side = game.get_current_state().unwrap();
match game.play_turn() {
Ok(action) => {
......@@ -132,21 +132,19 @@ fn play_game() -> Result<()> {
}
Err(err) => {
match err {
ReversiError::NoUndo => {
interface::no_undo_message(game.get_current_state().unwrap())
}
ReversiError::NoUndo => interface::no_undo_message(game.get_current_turn().get_state().unwrap()),
_ => return Err(err),
}
}
}
}
let (score_dark, score_light) = game.get_current_score();
let (score_dark, score_light) = game.get_current_turn().get_score();
interface::endgame_message(match score_dark.cmp(&score_light) {
Ordering::Greater => Some(Side::Dark),
Ordering::Less => Some(Side::Light),
Ordering::Equal => None,
});
Ordering::Greater => Some(Side::Dark),
Ordering::Less => Some(Side::Light),
Ordering::Equal => None,
});
Ok(())
}
//! Implementation of a 2D board (and of its constituing elements) with coordinates and iterators.
use reversi;
use reversi::Result;
/// The number of cells per side of the board.
pub const BOARD_SIZE: usize = 8;
/// The total number of cells of the board. Derived from `BOARD_SIZE` for ease of use.
pub const NUM_CELLS: usize = BOARD_SIZE * BOARD_SIZE;
/// Enums all the cardinal directions.
/// #Examples
/// If I am in cell `(4, 5)` and move `NE`, I go to cell `(3, 6)`.
#[derive(Debug, Clone, Copy)]
pub enum Direction {
North,
NE,
East,
SE,
South,
SW,
West,
NW,
}
/// Lists all cardinal directions from `Direction`.
pub const DIRECTIONS: [Direction; 8] = [
Direction::North,
Direction::NE,
Direction::East,
Direction::SE,
Direction::South,
Direction::SW,
Direction::West,
Direction::NW
];
/// Coordinates of a cell, given by a row and a column.
/// Follows matrices conventions (see <https://en.wikipedia.org/wiki/Matrix_(mathematics)>) but for starting indexes at 0.
#[derive(Debug, Clone, Copy)]
pub struct Coord {
row: usize,
col: usize,
}
impl Coord {
pub fn new(row: usize, col: usize) -> Coord {
Coord {
row: row,
col: col,
}
}
/// Returns coordinate's components.
pub fn get_row_col(&self) -> (usize, usize) {
(self.row, self.col)
}
/// Returns coordinate's first component.
pub fn get_row(&self) -> usize {
self.row
}
/// Returns coordinate's second component.
pub fn get_col(&self) -> usize {
self.col
}
/// Checks both upper and lower bounds.
pub fn check_bounds(&self) -> Result<()> {
if self.row >= BOARD_SIZE || self.col >= BOARD_SIZE {
Err(reversi::ReversiError::OutOfBoundCoord(*self))
} else {
Ok(())
}
}
/// Produces new indexes moving along a direction and checking for out-of-bound errors (meaning sub-zero indexes).
pub fn step(&mut self, dir: Direction) -> Result<()> {
match dir {
Direction::North if self.row > 0 => Ok({self.row -= 1;}),
Direction::NE if self.row > 0 => Ok({self.row -= 1; self.col += 1;}),
Direction::East => Ok({self.col += 1;}),
Direction::SE => Ok({self.row += 1; self.col += 1;}),
Direction::South => Ok({self.row += 1;}),
Direction::SW if self.col > 0 => Ok({self.row += 1; self.col -= 1;}),
Direction::West if self.col > 0 => Ok({self.col -= 1;}),
Direction::NW if self.row > 0 && self.col > 0 => Ok({self.row -= 1; self.col -= 1;}),
_ => Err(reversi::ReversiError::OutOfBoundStep(*self, dir)),
}
}
}
/// A disk is characterized by its two sides, one Dark and one Light.
#[derive(Debug, Clone, Copy)]
pub struct Disk(reversi::Side);
impl Disk {
/// Creates a new disk with given side.
pub fn new(side: reversi::Side) -> Disk {
Disk(side)
}
/// Return's a disk's side.
pub fn get_side(&self) -> reversi::Side {
self.0
}
/// Turns the disk on the other side.
pub fn flip(&mut self) {
self.0 = self.0.opposite();
}
}
/// Each cell in the board can either be empty or taken by one of the players.
pub type Cell = Option<Disk>;
#[derive(Debug, Clone)]
pub struct Board([[Cell; BOARD_SIZE]; BOARD_SIZE]);
/// `Board` is the type of boards, which are made by a `Frame`.
impl Board {
/// Creates a new board, given its cells.
pub fn new(cells: &[[Cell; BOARD_SIZE]; BOARD_SIZE]) -> Board {
Board(*cells)
}
/// Returns a non-mutable reference to the array of cells.
pub fn get_all_cells(&self) -> &[[Cell; BOARD_SIZE]; BOARD_SIZE] {
&self.0
}
/// Returns a non-mutable cell.
pub fn get_cell(&self, coord: Coord) -> Result<Cell> {
try!(self.0.get(coord.get_row()).ok_or(reversi::ReversiError::OutOfBoundCoord(coord)))
.get(coord.get_col()).ok_or(reversi::ReversiError::OutOfBoundCoord(coord)).map(|&cell| cell)
}
/// Returns a non-mutable disk.
pub fn get_disk(&self, coord: Coord) -> Result<Disk> {
match try!(self.get_cell(coord)) {
None => Err(reversi::ReversiError::EmptyCell(coord)),
Some(disk) => Ok(disk),
}
}
/// Returns a mutable reference to a cell (which is why it's private).
fn get_mut_cell(&mut self, coord: Coord) -> Result<&mut Cell> {
try!(self.0.get_mut(coord.get_row()).ok_or(reversi::ReversiError::OutOfBoundCoord(coord)))
.get_mut(coord.get_col()).ok_or(reversi::ReversiError::OutOfBoundCoord(coord))
}
/// Flips the disk on a non-empty cell.
pub fn flip_disk(&mut self, coord: Coord) -> Result<()> {
let cell = try!(self.get_mut_cell(coord));
match cell {
&mut Some(mut disk) => Ok({
disk.flip();
*cell = Some(disk);
}),
&mut None => Err(reversi::ReversiError::EmptyCell(coord)),
}
}
/// Place a disk on an empty cell.
pub fn place_disk(&mut self, side: reversi::Side, coord: Coord) -> Result<()> {
let cell = try!(self.get_mut_cell(coord));
match cell {
&mut Some(_) => Err(reversi::ReversiError::CellAlreadyTaken(coord)),
&mut None => Ok( *cell = Some(Disk::new(side)) ),
}
}
}
//! Implementation of a complete Reversi match.
use std::marker::{PhantomData, Sized};
use reversi;
use reversi::board::*;
use reversi::turn::*;
use ::Result;
pub enum PlayerAction<A> {
Move(Coord),
Undo,
Other(A),
}
/// Being able to make moves is the trait characterizing players.
pub trait IsPlayer<A> {
fn make_move(&self, turn: &Turn) -> Result<PlayerAction<A>>;
}
/// A game is given by a list of past turns (with the successive move), a current turn, and the two players.
pub struct Game<'a, A, D: 'a + ?Sized + IsPlayer<A>, L: 'a + ?Sized + IsPlayer<A>> {
current_turn: Turn,
turns_history: Vec<(Turn, Coord)>,
dark: &'a D,
light: &'a L,
phantom: PhantomData<A>
}
impl<'a, A, D: 'a + ?Sized + IsPlayer<A>, L: 'a + ?Sized + IsPlayer<A>> Game<'a, A, D, L> {
/// Creates a new game, with first turn already set and empty turns' history.
/// It requires the two players as input.
pub fn new(dark: &'a D, light: &'a L) -> Game<'a, A, D, L> where D: IsPlayer<A>, L: IsPlayer<A> {
Game {
current_turn: Turn::first_turn(),
turns_history: vec![],
dark: dark,
light: light,
phantom: PhantomData,
}
}
/// Gets the board of the current turn.
pub fn get_current_board(&self) -> &Board {
self.current_turn.get_board()
}
/// Gets current turn.
pub fn get_current_turn(&self) -> &Turn {
&self.current_turn
}
/// Gets current score.
pub fn get_current_score(&self) -> (u8, u8) {
self.current_turn.get_score()
}
/// Gets the state of the current turn.
pub fn get_current_state(&self) -> State {
self.current_turn.get_state()
}
/// Returns true if the game is ended.
pub fn is_ended(&self) -> bool {
self.get_current_state().is_none()
}
/// It has the correct player return an action and applies its effects.
pub fn play_turn(&mut self) -> Result<PlayerAction<A>> {
let action = match self.current_turn.get_state() {
None => return Err(reversi::ReversiError::EndedGame),
Some(reversi::Side::Dark) => try!(self.dark.make_move(&self.current_turn)),
Some(reversi::Side::Light) => try!(self.light.make_move(&self.current_turn)),
};
match action {
// If that move is legal, it is applied and the turns' history is updated.
PlayerAction::Move(coord) => try!(self.make_move(coord)),
PlayerAction::Undo => try!(self.undo()),
_ => {}
}
Ok(action)
}
/// A move (given by `coord`) is applied. If that move is legal, game's history is updated.
fn make_move(&mut self, coord: Coord) -> Result<()> {
let new_turn = try!(self.current_turn.make_move(coord));
self.turns_history.push((self.current_turn.clone(), coord));
self.current_turn = new_turn;
Ok(())
}
/// Undo last move(s) till the player asking for undoing can play again.
fn undo(&mut self) -> Result<()> {
let backup = self.turns_history.clone();
match self.get_current_state() {
None => {
self.current_turn = try!(self.turns_history.pop().ok_or(reversi::ReversiError::NoUndo)).0;
let last_side = self.get_current_state().unwrap();
while let Some((previous_turn, _)) = self.turns_history.pop() {
if last_side.opposite() == previous_turn.get_state().unwrap() {
self.current_turn = previous_turn;
return Ok(());
}
}
self.turns_history = backup;
return Err(reversi::ReversiError::NoUndo);
},
Some(current_side) => {
while let Some((previous_turn, _)) = self.turns_history.pop() {
if current_side == previous_turn.get_state().unwrap() {
self.current_turn = previous_turn;
return Ok(());
}
}
self.turns_history = backup;
return Err(reversi::ReversiError::NoUndo);
}
}
}
}
//! The `reversi` library (ver. 0.1.0) provides the main structures and mechanics required to run a Reversi game.
//! In view of possible AIs developement, the library keeps an eye on performances.
//! Released under MIT license.
// Roadmap to 0.1.0:
// ReversiError::NoUndo needs a turn::Turn value
// board::Board needs IntoIter so that its (unique) component can be made private
pub mod board;
pub mod turn;
pub mod game;
use std::{error, fmt, result};
use self::board::{Coord, Direction};
/// The errors that may be generated by running a Reversi game.
#[derive(Debug, Clone, Copy)]
pub enum ReversiError {
/// It has been attempted to create or use a coordinate with out-of-bound indexes.
OutOfBoundCoord(Coord),
/// It has been attempted to step out of the board's bounds.
OutOfBoundStep(Coord, Direction),
/// It has been attempted to move on a cell which was already taken.
CellAlreadyTaken(Coord),
/// It was looked for a disk in a cell which is empty.
EmptyCell(Coord),
/// It has been attempted to move on a illegal cell.
IllegalMove(Coord),
/// Undoing a turn is not possible
NoUndo,
/// It has been tried to move when the game was already ended.
EndedGame,
}
/// Aliasing given by taking `ReversiError` as standard error value.
pub type Result<T> = result::Result<T, ReversiError>;
impl fmt::Display for ReversiError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
ReversiError::OutOfBoundCoord(coord) => write!(f, "Out of bound coordinates: {:?}", coord),
ReversiError::OutOfBoundStep(coord, dir) => write!(f, "Out of bound step: {:?} going {:?}", coord, dir),
ReversiError::CellAlreadyTaken(coord) => write!(f, "The cell you want to move to is already taken: {:?}", coord),
ReversiError::IllegalMove(coord) => write!(f, "Illegal move: {:?}", coord),
ReversiError::EmptyCell(coord) => write!(f, "The cell you want is empty: {:?}", coord),
ReversiError::NoUndo => write!(f, "Undoing is not possible"),
ReversiError::EndedGame => write!(f, "The game is already ended"),
}
}
}
impl error::Error for ReversiError {
fn description(&self) -> &str {
match *self {
ReversiError::OutOfBoundCoord(_) => "Out of bound coordinates",
ReversiError::OutOfBoundStep(_, _) => "Out of bound step",
ReversiError::CellAlreadyTaken(_) => "The cell you want to move to is already taken",
ReversiError::IllegalMove(_) => "Illegal move",
ReversiError::EmptyCell(_) => "The cell you want is empty",
ReversiError::NoUndo => "Undoing is not possible",
ReversiError::EndedGame => "The game is already ended",
}
}
fn cause(&self) -> Option<&error::Error> {
None
}
}
/// There are two sides in Reversi: `Dark` and `Light`
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Side {
Dark,
Light,
}
impl Side {
/// Get self's opposite side.
pub fn opposite(&self) -> Side {
match *self {
Side::Dark => Side::Light,
Side::Light => Side::Dark,
}
}
}
//! Implementation of Reversi rules to play a turn.
use reversi;
use reversi::board::*;
use ::Result;
/// A turn can be in two states: either running (with a side to play next) or ended.
pub type State = Option<reversi::Side>;
/// A turn is given by a board and by which player has to move next.
/// For convenience we also annotate current scores.
#[derive(Debug, Clone)]
pub struct Turn {
board: Board,
state: State,
score_dark: u8,
score_light: u8,
}
impl Turn {
/// Initializing a new first turn: starting positions on the board and Dark is the first to play
pub fn first_turn() -> Turn {
let mut board = Board::new(&[[None; BOARD_SIZE]; BOARD_SIZE]);
board.place_disk(reversi::Side::Dark, Coord::new(3, 4)).expect("This cannot fail");
board.place_disk(reversi::Side::Dark, Coord::new(4, 3)).expect("This cannot fail");
board.place_disk(reversi::Side::Light, Coord::new(3, 3)).expect("This cannot fail");
board.place_disk(reversi::Side::Light, Coord::new(4, 4)).expect("This cannot fail");
Turn {
board: board,
state: Some(reversi::Side::Dark),
score_dark: 2,
score_light: 2,
}
}
/// Returns the turn's board
pub fn get_board(&self) -> &Board {
&self.board
}
/// Returns the board's cell corresponding to the given coordinates.
pub fn get_cell(&self, coord: Coord) -> Result<Cell> {
self.board.get_cell(coord)
}
/// Returns the turn's status
pub fn get_state(&self) -> State {
self.state
}
/// Returns whether the turn is an endgame
pub fn is_endgame(&self) -> bool {
self.state == None
}
/// Returns the current score of the match.
pub fn get_score(&self) -> (u8, u8) {
(self.score_dark, self.score_light)
}
/// 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 turn's tempo (how many disks there are on the board).
pub fn get_tempo(&self) -> u8 {
self.score_light + self.score_dark
}
/// Check whether a given move is legal
pub fn check_move (&self, coord: Coord) -> Result<()> {
if self.state.is_none() {
// If the game is ended, no further moves are possible
Err(reversi::ReversiError::EndedGame)
} else if try!(self.board.get_cell(coord)).is_some() { // This also checks `coord`
// If a cell is already taken, it's not possible to move there
Err(reversi::ReversiError::CellAlreadyTaken(coord))
} else {
// If a move leads to eat in at least one direction, then it is legal
for &dir in &DIRECTIONS {
if self.check_move_along_direction(coord, dir) {
return Ok(());
}
}
// Otherwise, the move is not legal
Err(reversi::ReversiError::IllegalMove(coord))
}
}
/// Check whether a move leads to eat in a specified direction
fn check_move_along_direction (&self, coord: Coord, dir: Direction) -> bool {
let mut next_coord = coord;
if let Ok(Ok(Some(next_disk))) = next_coord.step(dir).map(|()| self.get_cell(next_coord)) {
if Some(next_disk.get_side().opposite()) == self.state {
while let Ok(Ok(Some(successive_disk))) = next_coord.step(dir).map(|()| self.get_cell(next_coord)) {
if Some(successive_disk.get_side()) == self.state {
return true;
}
}
}
}
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.
fn make_move_along_direction (&mut self, coord: Coord, dir: Direction) -> Result<()> {
let side = try!(self.state.ok_or(reversi::ReversiError::EndedGame));
let mut next_coord = coord;
let _ = try!(next_coord.step(dir).map(|()| self.board.flip_disk(next_coord)));
let mut eating: u8 = 1;
while side != try!(try!(next_coord.step(dir).map(|()| self.board.get_disk(next_coord))).map(|disk| disk.get_side())) {
try!(self.board.flip_disk(next_coord));
eating += 1;
}
match side {
reversi::Side::Light => {
self.score_light += eating;
self.score_dark -= eating;
}
reversi::Side::Dark => {
self.score_light -= eating;
self.score_dark += eating;
}
};
Ok(())
}
/// Current player performs a move, after verifying that it is legal.
/// It returns either the new turn or the error preventing the move to be performed.
pub fn make_move (&self, coord: Coord) -> Result<Turn> {
if let Ok(None) = self.board.get_cell(coord) {
let mut turn_after_move = self.clone();
let mut legal = false;
if let Some(turn_side) = self.state {
for &dir in &DIRECTIONS {
if self.check_move_along_direction(coord, dir) {
try!(turn_after_move.make_move_along_direction(coord, dir));
legal = true;
}
}
if legal {
try!(turn_after_move.board.place_disk(turn_side, coord));
match turn_side {
reversi::Side::Dark => turn_after_move.score_dark += 1,
reversi::Side::Light => turn_after_move.score_light += 1,
}
// 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 turn_after_move.get_tempo() == NUM_CELLS as u8 {
// Quick check to rule out games with filled up boards as ended.
turn_after_move.state = None;
} else {
// Turn passes to the other player.
turn_after_move.state = Some(turn_side.opposite());
if !turn_after_move.can_move() {
// If the other player cannot move, turn passes back to the first player.
turn_after_move.state = Some(turn_side);
if !turn_after_move.can_move() {
// If neither platers can move, game is ended.
turn_after_move.state = None;
}
}
}
Ok(turn_after_move)
} else {
Err(reversi::ReversiError::IllegalMove(coord))
}
} else {
Err(reversi::ReversiError::EndedGame)
}
} else {
Err(reversi::ReversiError::CellAlreadyTaken(coord))
}
}
/// Returns whether or not next_player can make any move at all.
/// To be used privately. User should rather look at turn's state.
fn can_move(&self) -> bool {
for (row, &row_array) in self.board.get_all_cells().into_iter().enumerate() {
for (col, &cell) in row_array.into_iter().enumerate() {
if cell.is_none() {
for &dir in &DIRECTIONS {
if self.check_move_along_direction(Coord::new(row, col), dir) {
return true;
}
}
}
}
}
false
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment