Skip to content

Commit

Permalink
Stops timer on resign/draw
Browse files Browse the repository at this point in the history
  • Loading branch information
klautcomputing committed Jan 8, 2024
1 parent 9f131a5 commit 8fc581a
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 94 deletions.
2 changes: 1 addition & 1 deletion apis/src/components/molecules/display_challenge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ pub fn DisplayChallenge(challenge: StoredValue<ChallengeResponse>, single: bool)
TimeMode::RealTime => {
view! {
<p>
"Realtime: " {challenge().time_base.expect("Time exists")} "m"
"Realtime: " {challenge().time_base.expect("Time exists")/60} "m"
" + " {challenge().time_increment.expect("Increment exists")}
"s"
</p>
Expand Down
23 changes: 13 additions & 10 deletions apis/src/components/molecules/live_timer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,19 @@ lazy_static! {

#[component]
pub fn LiveTimer(side: Color, parent_div: NodeRef<html::Div>) -> impl IntoView {
let timer = expect_context::<TimerSignal>();
let timer_signal = expect_context::<TimerSignal>();
let timer = timer_signal.signal.get_untracked();
let white_time = move || {
let left = timer.signal.get_untracked().white_time_left.unwrap();
if timer.signal.get_untracked().turn < 2
let left = timer.white_time_left.unwrap();
if timer.turn < 2
|| left == Duration::from_millis(0)
|| timer.signal.get_untracked().turn % 2 == 1
|| timer.turn % 2 == 1
|| timer.finished
{
left
} else {
let left = chrono::Duration::from_std(left).unwrap();
let then = timer.signal.get_untracked().last_interaction.unwrap();
let then = timer.last_interaction.unwrap();
let future = then.checked_add_signed(left).unwrap();
let now = Utc::now();
if now > future {
Expand All @@ -38,15 +40,16 @@ pub fn LiveTimer(side: Color, parent_div: NodeRef<html::Div>) -> impl IntoView {
}
};
let black_time = move || {
let left = timer.signal.get_untracked().black_time_left.unwrap();
if timer.signal.get_untracked().turn < 2
let left = timer.black_time_left.unwrap();
if timer.turn < 2
|| left == Duration::from_millis(0)
|| timer.signal.get_untracked().turn % 2 == 0
|| timer.turn % 2 == 0
|| timer.finished
{
left
} else {
let left = chrono::Duration::from_std(left).unwrap();
let then = timer.signal.get_untracked().last_interaction.unwrap();
let then = timer.last_interaction.unwrap();
let future = then.checked_add_signed(left).unwrap();
let now = Utc::now();
if now > future {
Expand Down Expand Up @@ -87,7 +90,7 @@ pub fn LiveTimer(side: Color, parent_div: NodeRef<html::Div>) -> impl IntoView {
UseIntervalFnOptions::default().immediate(false),
);
create_effect(move |_| {
let timer = timer.signal.get();
let timer = timer_signal.signal.get();
if timer.turn > 1 {
if (side == Color::White) == (timer.turn % 2 == 0) && !timer.finished {
resume();
Expand Down
10 changes: 5 additions & 5 deletions apis/src/pages/challenge_create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,15 +78,15 @@ pub fn ChallengeCreate(close: Callback<()>) -> impl IntoView {
let total_days_per_player = RwSignal::new(0_i32);
let step_min = RwSignal::new(10_i32);
let step_sec = RwSignal::new(10_i32);
let total_min = Signal::derive(move || {
let total_seconds = Signal::derive(move || {
let step = step_min();
match step {
(match step {
1..=20 => step,
21 => step + 4,
22 => step + 8,
23..=32 => (step - 20) * 15,
i32::MIN..=0_i32 | 33_i32..=i32::MAX => unreachable!(),
}
}) * 60
});
let sec_per_move = Signal::derive(move || {
let step = step_sec();
Expand Down Expand Up @@ -156,7 +156,7 @@ pub fn ChallengeCreate(close: Callback<()>) -> impl IntoView {
TimeMode::RealTime => {
params
.time_base
.update_untracked(|v| *v = Some(total_min.get_untracked()));
.update_untracked(|v| *v = Some(total_seconds.get_untracked()));
params
.time_increment
.update_untracked(|v| *v = Some(sec_per_move.get_untracked()));
Expand Down Expand Up @@ -286,7 +286,7 @@ pub fn ChallengeCreate(close: Callback<()>) -> impl IntoView {

<div class="flex flex-col justify-center">
<label for="minutes">
{move || format!("Minutes per side: {}", total_min())}
{move || format!("Minutes per side: {}", total_seconds()/60)}
</label>
<input
on:input=minute_slider
Expand Down
2 changes: 1 addition & 1 deletion apis/src/providers/timer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ impl TimerSignal {
timer.turn = game.turn;
timer.white_time_left = game.white_time_left;
timer.black_time_left = game.black_time_left;
timer.time_increment = Some(Duration::from_secs(game.time_increment.unwrap() as u64));
timer.time_increment = game.time_increment.and_then(|inc| Some(Duration::from_secs(inc as u64)));
timer.time_mode = game.time_mode.to_owned();
timer.last_interaction = game.last_interaction;
});
Expand Down
14 changes: 10 additions & 4 deletions apis/src/providers/web_socket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,11 +136,17 @@ fn on_message_callback(m: String) {
navigate("/", Default::default());
}
GameControl::DrawAccept(_) => {
game_state.set_game_status(GameStatus::Finished(GameResult::Draw))
game_state.set_game_status(GameStatus::Finished(GameResult::Draw));
let timer = expect_context::<TimerSignal>();
timer.update_from(&gar.game);
}
GameControl::Resign(color) => {
game_state.set_game_status(GameStatus::Finished(GameResult::Winner(
color.opposite_color(),
)));
let timer = expect_context::<TimerSignal>();
timer.update_from(&gar.game);
}
GameControl::Resign(color) => game_state.set_game_status(
GameStatus::Finished(GameResult::Winner(color.opposite_color())),
),
GameControl::TakebackAccept(_) => reset_game_state(&gar.game),
_ => {}
}
Expand Down
148 changes: 75 additions & 73 deletions db/src/models/game.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ use std::str::FromStr;
use std::time::Duration;
use uuid::Uuid;

static NANOS_IN_A_DAY: u64 = 86400000000000_u64;
static NANOS_IN_MINUTE: u64 = 60000000000_u64;
static NANOS_IN_DAY: u64 = 86400000000000_u64;
static NANOS_IN_SECOND: u64 = 1000000000_u64;

#[derive(Insertable, Debug)]
#[diesel(table_name = games)]
Expand All @@ -46,21 +46,23 @@ pub struct NewGame {
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub time_mode: String, // Correspondence, Timed, Untimed
pub time_base: Option<i32>, // Secons
pub time_base: Option<i32>, // Seconds
pub time_increment: Option<i32>, // Seconds
pub last_interaction: Option<DateTime<Utc>>, // When was the last move made
pub black_time_left: Option<i64>,
pub white_time_left: Option<i64>,
pub black_time_left: Option<i64>, // A duration of nanos represented as an int
pub white_time_left: Option<i64>, // A duration of nanos represented as an int
}

impl NewGame {
pub fn new(white: Uuid, black: Uuid, challenge: &Challenge) -> Self {
let time_left = match challenge.time_mode.as_ref() {
"Untimed" => None,
"Real Time" => Some((challenge.time_base.unwrap() as u64 * NANOS_IN_MINUTE) as i64),
"Correspondence" => {
Some((challenge.time_base.unwrap() as u64 * NANOS_IN_MINUTE) as i64)
}
"Real Time" => challenge
.time_base
.and_then(|base| Some((base as u64 * NANOS_IN_SECOND) as i64)),
"Correspondence" => challenge
.time_base
.and_then(|base| Some((base as u64 * NANOS_IN_SECOND) as i64)),
_ => unimplemented!(),
};
Self {
Expand Down Expand Up @@ -118,7 +120,7 @@ pub struct Game {
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub time_mode: String, // Correspondence, Timed, Untimed
pub time_base: Option<i32>, // Secons
pub time_base: Option<i32>, // Seconds
pub time_increment: Option<i32>, // Seconds
pub last_interaction: Option<DateTime<Utc>>, // When was the last move made
pub black_time_left: Option<i64>,
Expand All @@ -141,9 +143,9 @@ impl Game {
return Ok(self.clone());
}
let time_left = if self.turn % 2 == 0 {
Duration::from_nanos(self.white_time_left.unwrap() as u64)
self.white_time_left_duration()?
} else {
Duration::from_nanos(self.black_time_left.unwrap() as u64)
self.black_time_left_duration()?
};
if GameStatus::NotStarted.to_string() == self.game_status {
return Ok(self.clone());
Expand Down Expand Up @@ -206,7 +208,7 @@ impl Game {

fn white_time_left_duration(&self) -> Result<Duration, DbError> {
if let Some(white_time) = self.white_time_left {
Ok(Duration::from_secs(white_time as u64 * NANOS_IN_MINUTE))
Ok(Duration::from_nanos(white_time as u64))
} else {
Err(DbError::TimeNotFound {
reason: String::from("Could not find white_time"),
Expand All @@ -216,24 +218,14 @@ impl Game {

fn black_time_left_duration(&self) -> Result<Duration, DbError> {
if let Some(black_time) = self.black_time_left {
Ok(Duration::from_secs(black_time as u64 * NANOS_IN_MINUTE))
Ok(Duration::from_nanos(black_time as u64))
} else {
Err(DbError::TimeNotFound {
reason: String::from("Could not find black_time"),
})
}
}

fn time_base_duration(&self) -> Result<Duration, DbError> {
if let Some(base) = self.time_base {
Ok(Duration::from_secs(base as u64 * NANOS_IN_MINUTE))
} else {
Err(DbError::TimeNotFound {
reason: String::from("Could not find time_base"),
})
}
}

fn time_increment_duration(&self) -> Result<Duration, DbError> {
if let Some(increment) = self.time_increment {
Ok(Duration::from_secs(increment as u64))
Expand All @@ -244,6 +236,45 @@ impl Game {
}
}

fn calculate_time_left(&self) -> Result<(Option<i64>, Option<i64>), DbError> {
let mut time_left = if self.turn % 2 == 0 {
self.white_time_left_duration()?
} else {
self.black_time_left_duration()?
};
let (mut black_time, mut white_time) = (self.black_time_left, self.white_time_left);
if let Some(last) = self.last_interaction {
let time_passed = Utc::now().signed_duration_since(last).to_std().unwrap();
if time_left > time_passed {
// substract passed time and add time_increment
time_left = time_left - time_passed;
if self.turn % 2 == 0 {
white_time = Some(time_left.as_nanos() as i64);
} else {
black_time = Some(time_left.as_nanos() as i64);
};
} else {
if self.turn % 2 == 0 {
white_time = Some(0);
} else {
black_time = Some(0);
}
}
}
Ok((white_time, black_time))
}

fn calculate_time_left_add_increment(&self) -> Result<(Option<i64>, Option<i64>), DbError> {
let (mut white_time, mut black_time) = self.calculate_time_left()?;
let increment = self.time_increment_duration()?.as_nanos() as i64;
if self.turn % 2 == 0 {
white_time = white_time.and_then(|time| Some(time + increment));
} else {
black_time = black_time.and_then(|time| Some(time + increment));
};
Ok((white_time, black_time))
}

pub async fn make_move(
&self,
mut board_move: String,
Expand Down Expand Up @@ -275,54 +306,19 @@ impl Game {

if self.time_mode == "Real Time" {
if self.turn < 2 {
white_time = Some(
(self.time_base.expect("Realtime games to have base_time") as u64
* NANOS_IN_MINUTE) as i64,
);
black_time = Some(
(self.time_base.expect("Realtime games to have base_time") as u64
* NANOS_IN_MINUTE) as i64,
);
white_time = self.white_time_left;
black_time = self.black_time_left;
} else {
// get time left for the current player
let mut time_left = if self.turn % 2 == 0 {
self.white_time_left_duration()?
(white_time, black_time) = self.calculate_time_left_add_increment()?;
if self.turn % 2 == 0 {
if white_time == Some(0) {
timed_out = true;
new_game_status = GameStatus::Finished(GameResult::Winner(Color::Black));
}
} else {
self.black_time_left_duration()?
};

// if the player has time left
if let Some(last) = self.last_interaction {
let time_passed = Utc::now().signed_duration_since(last).to_std().unwrap();
println!("Time left: {:?}", time_left);
println!("Time passed: {:?}", time_passed);
if time_left > time_passed {
// substract passed time and add time_increment
time_left = time_left - time_passed + self.time_increment_duration()?;
if self.turn % 2 == 0 {
black_time = self.black_time_left;
white_time = Some(time_left.as_nanos() as i64);
} else {
white_time = self.white_time_left;
black_time = Some(time_left.as_nanos() as i64);
};
} else {
// Out of time, no new GC or board move should be accepted
if black_time == Some(0) {
timed_out = true;
game_control_string = String::new();
board_move = String::new();
// let's end the game
if self.turn % 2 == 0 {
white_time = Some(0 as i64);
black_time = self.black_time_left;
new_game_status =
GameStatus::Finished(GameResult::Winner(Color::Black));
} else {
black_time = Some(0);
white_time = self.white_time_left;
new_game_status =
GameStatus::Finished(GameResult::Winner(Color::White));
}
new_game_status = GameStatus::Finished(GameResult::Winner(Color::White));
}
}
}
Expand All @@ -344,10 +340,10 @@ impl Game {
// reset the time to X days
if self.turn % 2 == 0 {
black_time = self.black_time_left;
white_time = Some(self.time_base.unwrap() as i64 * NANOS_IN_A_DAY as i64);
white_time = Some(self.time_base.unwrap() as i64 * NANOS_IN_DAY as i64);
} else {
white_time = self.white_time_left;
black_time = Some(self.time_base.unwrap() as i64 * NANOS_IN_A_DAY as i64);
black_time = Some(self.time_base.unwrap() as i64 * NANOS_IN_DAY as i64);
};
}
}
Expand Down Expand Up @@ -528,8 +524,9 @@ impl Game {
let winner_color = game_control.color().opposite_color();
let new_game_status = GameStatus::Finished(GameResult::Winner(winner_color));

let (white_time, black_time) = self.calculate_time_left()?;
connection
.transaction::<_, DbError, _>(|conn| {
.transaction::<_, DbError, _>(move |conn| {
async move {
let (w_rating, b_rating, w_change, b_change) = match new_game_status.clone() {
GameStatus::Finished(game_result) => {
Expand All @@ -555,6 +552,8 @@ impl Game {
white_rating_change.eq(w_change),
black_rating_change.eq(b_change),
updated_at.eq(Utc::now()),
white_time_left.eq(white_time),
black_time_left.eq(black_time),
))
.get_result(conn)
.await?;
Expand All @@ -572,8 +571,9 @@ impl Game {
) -> Result<Game, DbError> {
let connection = &mut get_conn(pool).await?;
let game_control_string = format!("{}. {game_control};", self.turn);
let (white_time, black_time) = self.calculate_time_left()?;
connection
.transaction::<_, DbError, _>(|conn| {
.transaction::<_, DbError, _>(move |conn| {
async move {
let (w_rating, b_rating, w_change, b_change) = Rating::update(
self.rated,
Expand All @@ -594,6 +594,8 @@ impl Game {
white_rating_change.eq(w_change),
black_rating_change.eq(b_change),
updated_at.eq(Utc::now()),
white_time_left.eq(white_time),
black_time_left.eq(black_time),
))
.get_result(conn)
.await?;
Expand Down

0 comments on commit 8fc581a

Please sign in to comment.