From 8fc581a4e32b3d6621815f40f1d2567315ef2021 Mon Sep 17 00:00:00 2001 From: Felix Glaser Date: Mon, 8 Jan 2024 13:11:01 +0100 Subject: [PATCH] Stops timer on resign/draw --- .../components/molecules/display_challenge.rs | 2 +- apis/src/components/molecules/live_timer.rs | 23 +-- apis/src/pages/challenge_create.rs | 10 +- apis/src/providers/timer.rs | 2 +- apis/src/providers/web_socket.rs | 14 +- db/src/models/game.rs | 148 +++++++++--------- 6 files changed, 105 insertions(+), 94 deletions(-) diff --git a/apis/src/components/molecules/display_challenge.rs b/apis/src/components/molecules/display_challenge.rs index 5d10588e..a348f901 100644 --- a/apis/src/components/molecules/display_challenge.rs +++ b/apis/src/components/molecules/display_challenge.rs @@ -93,7 +93,7 @@ pub fn DisplayChallenge(challenge: StoredValue, single: bool) TimeMode::RealTime => { view! {

- "Realtime: " {challenge().time_base.expect("Time exists")} "m" + "Realtime: " {challenge().time_base.expect("Time exists")/60} "m" " + " {challenge().time_increment.expect("Increment exists")} "s"

diff --git a/apis/src/components/molecules/live_timer.rs b/apis/src/components/molecules/live_timer.rs index 56cfe8c0..aa55ff22 100644 --- a/apis/src/components/molecules/live_timer.rs +++ b/apis/src/components/molecules/live_timer.rs @@ -17,17 +17,19 @@ lazy_static! { #[component] pub fn LiveTimer(side: Color, parent_div: NodeRef) -> impl IntoView { - let timer = expect_context::(); + let timer_signal = expect_context::(); + 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 { @@ -38,15 +40,16 @@ pub fn LiveTimer(side: Color, parent_div: NodeRef) -> 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 { @@ -87,7 +90,7 @@ pub fn LiveTimer(side: Color, parent_div: NodeRef) -> 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(); diff --git a/apis/src/pages/challenge_create.rs b/apis/src/pages/challenge_create.rs index df876693..7aea6224 100644 --- a/apis/src/pages/challenge_create.rs +++ b/apis/src/pages/challenge_create.rs @@ -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(); @@ -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())); @@ -286,7 +286,7 @@ pub fn ChallengeCreate(close: Callback<()>) -> impl IntoView {
{ - game_state.set_game_status(GameStatus::Finished(GameResult::Draw)) + game_state.set_game_status(GameStatus::Finished(GameResult::Draw)); + let timer = expect_context::(); + timer.update_from(&gar.game); + } + GameControl::Resign(color) => { + game_state.set_game_status(GameStatus::Finished(GameResult::Winner( + color.opposite_color(), + ))); + let timer = expect_context::(); + 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), _ => {} } diff --git a/db/src/models/game.rs b/db/src/models/game.rs index e6d3586d..a734f53e 100644 --- a/db/src/models/game.rs +++ b/db/src/models/game.rs @@ -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)] @@ -46,21 +46,23 @@ pub struct NewGame { pub created_at: DateTime, pub updated_at: DateTime, pub time_mode: String, // Correspondence, Timed, Untimed - pub time_base: Option, // Secons + pub time_base: Option, // Seconds pub time_increment: Option, // Seconds pub last_interaction: Option>, // When was the last move made - pub black_time_left: Option, - pub white_time_left: Option, + pub black_time_left: Option, // A duration of nanos represented as an int + pub white_time_left: Option, // 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 { @@ -118,7 +120,7 @@ pub struct Game { pub created_at: DateTime, pub updated_at: DateTime, pub time_mode: String, // Correspondence, Timed, Untimed - pub time_base: Option, // Secons + pub time_base: Option, // Seconds pub time_increment: Option, // Seconds pub last_interaction: Option>, // When was the last move made pub black_time_left: Option, @@ -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()); @@ -206,7 +208,7 @@ impl Game { fn white_time_left_duration(&self) -> Result { 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"), @@ -216,7 +218,7 @@ impl Game { fn black_time_left_duration(&self) -> Result { 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"), @@ -224,16 +226,6 @@ impl Game { } } - fn time_base_duration(&self) -> Result { - 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 { if let Some(increment) = self.time_increment { Ok(Duration::from_secs(increment as u64)) @@ -244,6 +236,45 @@ impl Game { } } + fn calculate_time_left(&self) -> Result<(Option, Option), 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, Option), 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, @@ -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)); } } } @@ -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); }; } } @@ -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) => { @@ -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?; @@ -572,8 +571,9 @@ impl Game { ) -> Result { 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, @@ -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?;