1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
//! A futures-based chess clock which provides timeout-like functionality for other futures.
//!
//! In addition to the typical configurable base time and time-per-turn, `ChessClock` supports [1,
//! usize::max] players, which can be useful in time-controlled, turn-based games that support more
//! than two players, such as [Hanabi](https://hanabi.live).
//!
//! Per FIDE rules, time-per-turn is added on the very first move.
//!
//! # Example usage:
//! ```
//! extern crate futures;
//! extern crate tokio;
//! extern crate chess_clock;
//!
//! use chess_clock::{ChessClock, BaseTime, TimePerTurn};
//!
//! use std::thread::sleep;
//! use std::time::{Duration, Instant};
//!
//! use futures::{prelude::*, future::{self, ok, err, Either, FutureResult}};
//! use tokio;
//! use tokio::timer::Delay;
//!
//! let mut clock = ChessClock::new(
//! 2,
//! BaseTime(Duration::new(2, 0)),
//! TimePerTurn(Duration::new(2, 0)),
//! );
//!
//! let when = Instant::now() + Duration::from_secs(2);
//! let task = Delay::new(when)
//! .map_err(|_| ());
//! let clocked_task = clock.bind(task)
//! .then(|res| {
//! match res {
//! Ok(Some(_)) => {
//! println!("Task succeeded");
//! ok(())
//! },
//! Ok(None) => {
//! println!("Task timed out");
//! ok(())
//! },
//! _ => {
//! err(())
//! }
//! }
//! }
//! );
//! tokio::run(clocked_task);
//! ```
//! Output: `Task succeeded`, since the example task has duration less than the first player's base
//! time + time-per-turn
//!
//! # Implementation details
//!
//! `ChessClock` is [ARCed][std::sync::Arc] and (rw-mutexed)[std::sync::RwLock] so that it works
//! nicely with the borrow checker, but by nature of its design, which is sequential passing of
//! turns, it shouldn't be used simultaneously by two threads.
//!
//! It tracks the time of each player, a single global time-per-turn, the active player index, and
//! the time the last turn was passed, so it can calculate the time to subtract from the next
//! active player's clock.
#![feature(duration_as_u128)]
extern crate futures;
extern crate tokio;
mod chess_clock;
pub use chess_clock::{BaseTime, ChessClock, ClockedFuture, TimePerTurn};
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
use super::{BaseTime, ChessClock, TimePerTurn};
use std::thread::sleep;
use std::time::{Duration, Instant};
use futures::{
future::{self, err, ok, Either, FutureResult},
prelude::*,
};
use tokio;
use tokio::timer::Delay;
let bt = 4;
let tpt = 1;
let n_players = 2;
println!("Time control (seconds): {};{}", bt, tpt);
println!("Players: {}", n_players);
let mut clock_master = ChessClock::new(
n_players,
BaseTime(Duration::new(4, 0)),
TimePerTurn(Duration::new(1, 0)),
);
for i in 0..10 {
let clock = clock_master.clone();
let turn_length = 2;
let player_num = i % n_players + 1;
println!("---------------------------------------");
println!(
"Player {} time remaining: {}",
player_num,
clock.active_player_time_remaining().as_millis()
);
println!("Player {} turn length: {}", player_num, turn_length * 1000);
let when = Instant::now() + Duration::from_secs(turn_length);
let task = Delay::new(when).map_err(|_| ());
let clocked_task = clock.bind(task).then(move |res| match res {
Ok(Some(_)) => {
println!("Player {} successfully took their turn.", player_num);
ok(())
}
Ok(None) => {
println!("Player {} timed out", player_num);
ok(())
}
_ => err(()),
});
tokio::run(clocked_task);
}
}
}