game cancelling by host
This commit is contained in:
parent
6850da7555
commit
6b3cce5e37
|
|
@ -732,3 +732,16 @@ dialog .tab-content {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dialog-modal:has(.cancel-game-button) {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-top: 1ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scary {
|
||||||
|
color: red;
|
||||||
|
border: 1px solid red;
|
||||||
|
background-color: black;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -149,6 +149,10 @@ pub enum ServerError {
|
||||||
InvalidRequest(String),
|
InvalidRequest(String),
|
||||||
#[error("you're already in an active game: {0}")]
|
#[error("you're already in an active game: {0}")]
|
||||||
AlreadyInActiveGame(GameId),
|
AlreadyInActiveGame(GameId),
|
||||||
|
#[error("not your game")]
|
||||||
|
NotYourGame,
|
||||||
|
#[error("this game is already over")]
|
||||||
|
GameAlreadyOver,
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
GameError(#[from] GameError),
|
GameError(#[from] GameError),
|
||||||
}
|
}
|
||||||
|
|
@ -236,11 +240,11 @@ impl axum::response::IntoResponse for ServerError {
|
||||||
fn into_response(self) -> axum::response::Response {
|
fn into_response(self) -> axum::response::Response {
|
||||||
use axum::{Json, http::StatusCode};
|
use axum::{Json, http::StatusCode};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
(
|
(
|
||||||
match self {
|
match self {
|
||||||
ServerError::AlreadyInActiveGame(_)
|
ServerError::NotYourGame
|
||||||
|
| ServerError::GameAlreadyOver
|
||||||
|
| ServerError::AlreadyInActiveGame(_)
|
||||||
| ServerError::GameError(_)
|
| ServerError::GameError(_)
|
||||||
| ServerError::InvalidCredentials
|
| ServerError::InvalidCredentials
|
||||||
| ServerError::InvalidRequest(_)
|
| ServerError::InvalidRequest(_)
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
character::CharacterId,
|
character::CharacterId,
|
||||||
error::GameError,
|
error::{GameError, ServerError},
|
||||||
game::{GameOver, GameSettings, story::GameStory},
|
game::{GameOver, GameSettings, story::GameStory},
|
||||||
message::{
|
message::{
|
||||||
CharacterIdentity,
|
CharacterIdentity,
|
||||||
|
|
@ -39,6 +39,7 @@ pub enum HostMessage {
|
||||||
ForceRoleAckFor(CharacterId),
|
ForceRoleAckFor(CharacterId),
|
||||||
PostGame(PostGameMessage),
|
PostGame(PostGameMessage),
|
||||||
Echo(ServerToHostMessage),
|
Echo(ServerToHostMessage),
|
||||||
|
CancelGame,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
|
@ -107,7 +108,7 @@ pub enum ServerToHostMessage {
|
||||||
settings: GameSettings,
|
settings: GameSettings,
|
||||||
qr_mode: bool,
|
qr_mode: bool,
|
||||||
},
|
},
|
||||||
Error(GameError),
|
Error(ServerError),
|
||||||
GameOver(GameOver),
|
GameOver(GameOver),
|
||||||
WaitingForRoleRevealAcks {
|
WaitingForRoleRevealAcks {
|
||||||
ackd: Box<[CharacterIdentity]>,
|
ackd: Box<[CharacterIdentity]>,
|
||||||
|
|
|
||||||
|
|
@ -56,18 +56,22 @@ pub fn shell(options: LeptosOptions) -> impl IntoView {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, PartialEq, Eq, Deserialize)]
|
#[derive(Debug, Clone, Serialize, PartialEq, Eq, Deserialize)]
|
||||||
pub struct TutorialSettings {
|
pub struct Preferences {
|
||||||
pub enabled: bool,
|
pub tutorials_enabled: bool,
|
||||||
|
pub show_cancel_game: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TutorialSettings {
|
impl Default for Preferences {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self { enabled: true }
|
Self {
|
||||||
|
tutorials_enabled: true,
|
||||||
|
show_cancel_game: true,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Stored for TutorialSettings {
|
impl Stored for Preferences {
|
||||||
const STORAGE_KEY: &str = "tutorial-settings";
|
const STORAGE_KEY: &str = "preferences";
|
||||||
}
|
}
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
|
|
@ -81,9 +85,9 @@ pub fn App() -> impl IntoView {
|
||||||
Effect::new(move || auth_store.init_or_update());
|
Effect::new(move || auth_store.init_or_update());
|
||||||
Effect::new(move || session_store.init_or_update());
|
Effect::new(move || session_store.init_or_update());
|
||||||
|
|
||||||
let (tut_read, tut_write, _) =
|
let (pref_read, pref_write, _) =
|
||||||
use_local_storage::<TutorialSettings, JsonSerdeCodec>(TutorialSettings::STORAGE_KEY);
|
use_local_storage::<Preferences, JsonSerdeCodec>(Preferences::STORAGE_KEY);
|
||||||
provide_context((tut_read, tut_write));
|
provide_context((pref_read, pref_write));
|
||||||
|
|
||||||
let is_logged_in = move || {
|
let is_logged_in = move || {
|
||||||
auth_store
|
auth_store
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use werewolves_proto::{
|
||||||
player::PlayerId,
|
player::PlayerId,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::app::TutorialSettings;
|
use crate::app::Preferences;
|
||||||
|
|
||||||
pub trait Sample {
|
pub trait Sample {
|
||||||
fn sample() -> Self;
|
fn sample() -> Self;
|
||||||
|
|
@ -51,11 +51,10 @@ impl Sample for Identification {
|
||||||
#[component]
|
#[component]
|
||||||
pub fn TutorialBox(children: Children) -> impl IntoView {
|
pub fn TutorialBox(children: Children) -> impl IntoView {
|
||||||
let sample_hidden = RwSignal::new(false);
|
let sample_hidden = RwSignal::new(false);
|
||||||
let (tut_read, _) =
|
let (tut_read, _) = expect_context::<(Signal<Preferences>, WriteSignal<Preferences>)>();
|
||||||
expect_context::<(Signal<TutorialSettings>, WriteSignal<TutorialSettings>)>();
|
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<div class="tutorial-box" hidden=move || !tut_read.read().enabled>
|
<div class="tutorial-box" hidden=move || !tut_read.read().tutorials_enabled>
|
||||||
<button
|
<button
|
||||||
class="hide"
|
class="hide"
|
||||||
hidden=move || sample_hidden.get()
|
hidden=move || sample_hidden.get()
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,10 @@ use werewolves_proto::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "hydrate")]
|
||||||
|
use crate::ConsoleLogError;
|
||||||
|
use crate::app::{Preferences, components::DialogModal};
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn HostGamePage(
|
pub fn HostGamePage(
|
||||||
message: Signal<Option<Srv2Host>>,
|
message: Signal<Option<Srv2Host>>,
|
||||||
|
|
@ -72,5 +76,42 @@ pub fn HostGamePage(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
view! { {content} }
|
view! {
|
||||||
|
<CancelGame reply=reply />
|
||||||
|
{content}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
fn CancelGame(reply: WriteSignal<Option<HostMessage>>) -> impl IntoView {
|
||||||
|
let open = RwSignal::new(false);
|
||||||
|
let prefs = expect_context::<(Signal<Preferences>, WriteSignal<Preferences>)>().0;
|
||||||
|
let cancel = move |_| {
|
||||||
|
open.set(false);
|
||||||
|
reply.set(Some(HostMessage::CancelGame));
|
||||||
|
#[cfg(feature = "hydrate")]
|
||||||
|
gloo::utils::window()
|
||||||
|
.location()
|
||||||
|
.replace("/")
|
||||||
|
.console_log_warn();
|
||||||
|
};
|
||||||
|
let content = move || match prefs.get().show_cancel_game {
|
||||||
|
true => view! {
|
||||||
|
<DialogModal
|
||||||
|
button_class="cancel-game-button".into()
|
||||||
|
text="cancel game".into()
|
||||||
|
open=open
|
||||||
|
>
|
||||||
|
<h1>"this will cancel the game. are you sure?"</h1>
|
||||||
|
<button on:click=cancel class="scary">
|
||||||
|
"i'm sure, cancel the game."
|
||||||
|
</button>
|
||||||
|
</DialogModal>
|
||||||
|
}
|
||||||
|
.into_any(),
|
||||||
|
false => ().into_any(),
|
||||||
|
};
|
||||||
|
view! {
|
||||||
|
{content}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,6 @@ pub fn PlayerLobby(
|
||||||
number.set(Some(nz));
|
number.set(Some(nz));
|
||||||
} else {
|
} else {
|
||||||
e.target().set_value(default.as_str());
|
e.target().set_value(default.as_str());
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let submit = move |ev: SubmitEvent| {
|
let submit = move |ev: SubmitEvent| {
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use leptos::{ev::MouseEvent, prelude::*};
|
||||||
use reactive_stores::Store;
|
use reactive_stores::Store;
|
||||||
|
|
||||||
use crate::app::{
|
use crate::app::{
|
||||||
TutorialSettings,
|
Preferences,
|
||||||
storage::user::{AuthContext, AuthContextStoreFields},
|
storage::user::{AuthContext, AuthContextStoreFields},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -18,14 +18,35 @@ pub fn UserSettings() -> impl IntoView {
|
||||||
};
|
};
|
||||||
view! { <button on:click=click>"log out"</button> }
|
view! { <button on:click=click>"log out"</button> }
|
||||||
};
|
};
|
||||||
let (tut_read, tut_write) =
|
let (prefs_read, prefs_write) =
|
||||||
expect_context::<(Signal<TutorialSettings>, WriteSignal<TutorialSettings>)>();
|
expect_context::<(Signal<Preferences>, WriteSignal<Preferences>)>();
|
||||||
let tutorial_toggle_button = move || {
|
let tutorial_toggle_button = move || match prefs_read.read().tutorials_enabled {
|
||||||
match tut_read.read().enabled {
|
true => view! {
|
||||||
true => view! { <button on:click=move |_| tut_write.write().enabled = false>"disable tutorials"</button> }
|
<button on:click=move |_| {
|
||||||
|
prefs_write.write().tutorials_enabled = false;
|
||||||
|
}>"disable tutorials"</button>
|
||||||
|
}
|
||||||
|
.into_any(),
|
||||||
|
false => view! {
|
||||||
|
<button on:click=move |_| {
|
||||||
|
prefs_write.write().tutorials_enabled = true;
|
||||||
|
}>"enable tutorials"</button>
|
||||||
|
}
|
||||||
|
.into_any(),
|
||||||
|
};
|
||||||
|
let cancel_game_toggle_button = move || match prefs_read.read().show_cancel_game {
|
||||||
|
true => view! {
|
||||||
|
<button on:click=move |_| {
|
||||||
|
prefs_write.write().show_cancel_game = false;
|
||||||
|
}>"disable \"cancel game\" button"</button>
|
||||||
|
}
|
||||||
|
.into_any(),
|
||||||
|
false => view! {
|
||||||
|
<button on:click=move |_| {
|
||||||
|
prefs_write.write().show_cancel_game = true;
|
||||||
|
}>"enable \"cancel game\" button"</button>
|
||||||
|
}
|
||||||
.into_any(),
|
.into_any(),
|
||||||
false => view! { <button on:click=move |_| tut_write.write().enabled = true>"enable tutorials"</button> }.into_any(),
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
view! {
|
view! {
|
||||||
<ul class="user-settings-list">
|
<ul class="user-settings-list">
|
||||||
|
|
@ -36,6 +57,7 @@ pub fn UserSettings() -> impl IntoView {
|
||||||
<ChangePasswordButton />
|
<ChangePasswordButton />
|
||||||
</li>
|
</li>
|
||||||
<li>{tutorial_toggle_button}</li>
|
<li>{tutorial_toggle_button}</li>
|
||||||
|
<li>{cancel_game_toggle_button}</li>
|
||||||
<li class="logout">{log_out}</li>
|
<li class="logout">{log_out}</li>
|
||||||
</ul>
|
</ul>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,7 @@ impl GameDatabase {
|
||||||
from
|
from
|
||||||
games
|
games
|
||||||
where
|
where
|
||||||
id = $1
|
id = $1 and game_status != 'Cancelled'::game_status
|
||||||
"#,
|
"#,
|
||||||
game.into_uuid(),
|
game.into_uuid(),
|
||||||
)
|
)
|
||||||
|
|
@ -372,6 +372,32 @@ impl GameDatabase {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn cancel_game(&self, user: PlayerId, game_id: GameId) -> ServerResult<()> {
|
||||||
|
let game = self.get_game(game_id).await?;
|
||||||
|
if user != game.host {
|
||||||
|
return Err(ServerError::NotYourGame);
|
||||||
|
}
|
||||||
|
if matches!(game.game_state, GameRecordState::GameOver(_)) {
|
||||||
|
return Err(ServerError::GameAlreadyOver);
|
||||||
|
}
|
||||||
|
|
||||||
|
query!(
|
||||||
|
r#"
|
||||||
|
update
|
||||||
|
games
|
||||||
|
set
|
||||||
|
game_status = 'Cancelled'::game_status
|
||||||
|
where
|
||||||
|
id = $1"#,
|
||||||
|
game_id.into_uuid(),
|
||||||
|
)
|
||||||
|
.execute(&self.pool)
|
||||||
|
.await
|
||||||
|
.into_db_result()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get_dead_chat(&self, game_id: GameId) -> ServerResult<Box<[DeadChatMessage]>> {
|
pub async fn get_dead_chat(&self, game_id: GameId) -> ServerResult<Box<[DeadChatMessage]>> {
|
||||||
Ok(query!(
|
Ok(query!(
|
||||||
r#"
|
r#"
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,10 @@
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use crate::db::Database;
|
||||||
use crate::server::runner::{ClientUpdate, HostOrClientMessage, IdentifiedClientMessage};
|
use crate::server::runner::{ClientUpdate, HostOrClientMessage, IdentifiedClientMessage};
|
||||||
use werewolves_proto::game::GameId;
|
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
|
use werewolves_proto::game::GameId;
|
||||||
use werewolves_proto::{
|
use werewolves_proto::{
|
||||||
error::GameError,
|
error::GameError,
|
||||||
game::{Game, GameOver},
|
game::{Game, GameOver},
|
||||||
|
|
@ -32,11 +33,13 @@ pub struct GameRunner<'a> {
|
||||||
game_id: GameId,
|
game_id: GameId,
|
||||||
game: &'a mut Game,
|
game: &'a mut Game,
|
||||||
roles_revealed: bool,
|
roles_revealed: bool,
|
||||||
|
db: Database,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> GameRunner<'a> {
|
impl<'a> GameRunner<'a> {
|
||||||
pub fn new(game_id: GameId, game: &'a mut Game) -> Self {
|
pub fn new(game_id: GameId, game: &'a mut Game, db: Database) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
db,
|
||||||
game,
|
game,
|
||||||
game_id,
|
game_id,
|
||||||
roles_revealed: false,
|
roles_revealed: false,
|
||||||
|
|
@ -136,6 +139,24 @@ impl<'a> GameRunner<'a> {
|
||||||
|
|
||||||
let pre_time = self.game.village().time();
|
let pre_time = self.game.village().time();
|
||||||
let mut is_host_message = false;
|
let mut is_host_message = false;
|
||||||
|
if let HostMessage::CancelGame = &msg {
|
||||||
|
if let Err(err) = self
|
||||||
|
.db
|
||||||
|
.game()
|
||||||
|
.cancel_game(
|
||||||
|
self.db.game().get_game(self.game_id).await.ok()?.host,
|
||||||
|
self.game_id,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
super::send_host(self.game_id, ServerToHostMessage::Error(err)).await;
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let game_id = self.game_id;
|
||||||
|
tokio::spawn(async move {
|
||||||
|
crate::server::delete_game(game_id).await;
|
||||||
|
});
|
||||||
|
}
|
||||||
match self.host_message(msg) {
|
match self.host_message(msg) {
|
||||||
Ok(ServerToHostMessage::DeadChatMessage(msg)) => {
|
Ok(ServerToHostMessage::DeadChatMessage(msg)) => {
|
||||||
super::send_host(self.game_id, ServerToHostMessage::DeadChatMessage(msg)).await;
|
super::send_host(self.game_id, ServerToHostMessage::DeadChatMessage(msg)).await;
|
||||||
|
|
@ -145,7 +166,7 @@ impl<'a> GameRunner<'a> {
|
||||||
super::send_host(self.game_id, resp).await;
|
super::send_host(self.game_id, resp).await;
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
super::send_host(self.game_id, ServerToHostMessage::Error(err)).await;
|
super::send_host(self.game_id, ServerToHostMessage::Error(err.into())).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let messages_for_host = self.game.dead_chats_since(start);
|
let messages_for_host = self.game.dead_chats_since(start);
|
||||||
|
|
@ -222,6 +243,12 @@ impl<'a> GameRunner<'a> {
|
||||||
Err(GameError::GameOngoing)
|
Err(GameError::GameOngoing)
|
||||||
}
|
}
|
||||||
HostMessage::Echo(echo) => Ok(echo),
|
HostMessage::Echo(echo) => Ok(echo),
|
||||||
|
HostMessage::CancelGame => {
|
||||||
|
log::error!("CancelGame should be handled above");
|
||||||
|
Ok(ServerToHostMessage::Error(
|
||||||
|
GameError::GenericError("CancelGame should be handled above".into()).into(),
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -134,7 +134,7 @@ impl<'a> Lobby<'a> {
|
||||||
| Ok(None) => None,
|
| Ok(None) => None,
|
||||||
Ok(Some(game)) => Some(game),
|
Ok(Some(game)) => Some(game),
|
||||||
Err((HostOrClientMessage::Host(_), ServerError::GameError(err))) => {
|
Err((HostOrClientMessage::Host(_), ServerError::GameError(err))) => {
|
||||||
super::send_host(self.game_id, ServerToHostMessage::Error(err)).await;
|
super::send_host(self.game_id, ServerToHostMessage::Error(err.into())).await;
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
Err((HostOrClientMessage::Host(_), err)) => {
|
Err((HostOrClientMessage::Host(_), err)) => {
|
||||||
|
|
@ -142,11 +142,7 @@ impl<'a> Lobby<'a> {
|
||||||
"server error from host request for game({}): {err}",
|
"server error from host request for game({}): {err}",
|
||||||
self.game_id
|
self.game_id
|
||||||
);
|
);
|
||||||
super::send_host(
|
super::send_host(self.game_id, ServerToHostMessage::Error(err)).await;
|
||||||
self.game_id,
|
|
||||||
ServerToHostMessage::Error(GameError::GenericError(err.to_string())),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
Err((
|
Err((
|
||||||
|
|
@ -183,6 +179,19 @@ impl<'a> Lobby<'a> {
|
||||||
msg: HostOrClientMessage,
|
msg: HostOrClientMessage,
|
||||||
) -> Result<Option<GameRecord>, ServerError> {
|
) -> Result<Option<GameRecord>, ServerError> {
|
||||||
match msg {
|
match msg {
|
||||||
|
HostOrClientMessage::Host(HostMessage::CancelGame) => {
|
||||||
|
self.db
|
||||||
|
.game()
|
||||||
|
.cancel_game(
|
||||||
|
self.db.game().get_game(self.game_id).await?.host,
|
||||||
|
self.game_id,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
let game_id = self.game_id;
|
||||||
|
tokio::spawn(async move {
|
||||||
|
crate::server::delete_game(game_id).await;
|
||||||
|
});
|
||||||
|
}
|
||||||
HostOrClientMessage::Client(IdentifiedClientMessage {
|
HostOrClientMessage::Client(IdentifiedClientMessage {
|
||||||
update: ClientUpdate::Message(ClientMessage::DeadChat(_)),
|
update: ClientUpdate::Message(ClientMessage::DeadChat(_)),
|
||||||
..
|
..
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,7 @@ pub mod qr;
|
||||||
pub mod role_reveal;
|
pub mod role_reveal;
|
||||||
pub mod runner;
|
pub mod runner;
|
||||||
|
|
||||||
use core::{
|
use core::{fmt::Display, hash::BuildHasherDefault};
|
||||||
fmt::Display,
|
|
||||||
hash::BuildHasherDefault,
|
|
||||||
};
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
hash::DefaultHasher,
|
hash::DefaultHasher,
|
||||||
|
|
@ -382,3 +379,19 @@ pub async fn clear_abandoned_game_runners() {
|
||||||
"[clear_abandoned_game_runners]".bold()
|
"[clear_abandoned_game_runners]".bold()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn delete_game(game: GameId) {
|
||||||
|
let mut hosts = HOST_CHANNELS.write().await;
|
||||||
|
let mut players = PLAYER_CHANNELS.write().await;
|
||||||
|
if let Some(GameChannels { runner_handle, .. }) = hosts.remove(&game) {
|
||||||
|
runner_handle.abort();
|
||||||
|
}
|
||||||
|
let player_keys = players
|
||||||
|
.keys()
|
||||||
|
.filter(|(gid, _)| *gid == game)
|
||||||
|
.copied()
|
||||||
|
.collect::<Box<_>>();
|
||||||
|
for key in player_keys {
|
||||||
|
players.remove(&key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ async fn next_message(
|
||||||
game_id: GameId,
|
game_id: GameId,
|
||||||
client_recv: &mut UnboundedReceiver<IdentifiedClientMessage>,
|
client_recv: &mut UnboundedReceiver<IdentifiedClientMessage>,
|
||||||
host_recv: &mut UnboundedReceiver<HostMessage>,
|
host_recv: &mut UnboundedReceiver<HostMessage>,
|
||||||
|
db: &Database,
|
||||||
) -> Option<HostOrClientMessage> {
|
) -> Option<HostOrClientMessage> {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
client_msg = client_recv.recv() => {
|
client_msg = client_recv.recv() => {
|
||||||
|
|
@ -53,6 +54,11 @@ async fn next_message(
|
||||||
}
|
}
|
||||||
host_msg = host_recv.recv() => {
|
host_msg = host_recv.recv() => {
|
||||||
match host_msg {
|
match host_msg {
|
||||||
|
Some(HostMessage::CancelGame) => {
|
||||||
|
cancel_game(game_id, db).await;
|
||||||
|
log::info!("got game cancellation request for {game_id}");
|
||||||
|
None
|
||||||
|
}
|
||||||
Some(msg) => Some(HostOrClientMessage::Host(msg)),
|
Some(msg) => Some(HostOrClientMessage::Host(msg)),
|
||||||
None => {
|
None => {
|
||||||
log::warn!("host recv for game {game_id} got None; exiting");
|
log::warn!("host recv for game {game_id} got None; exiting");
|
||||||
|
|
@ -63,6 +69,21 @@ async fn next_message(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn cancel_game(game_id: GameId, db: &Database) {
|
||||||
|
let game = match db.game().get_game(game_id).await {
|
||||||
|
Ok(g) => g,
|
||||||
|
Err(err) => {
|
||||||
|
log::error!("get game {game_id} for cancellation: {err}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if let Err(err) = db.game().cancel_game(game.host, game_id).await {
|
||||||
|
log::error!("error cancelling game: {err}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tokio::spawn(crate::server::delete_game(game_id));
|
||||||
|
}
|
||||||
|
|
||||||
async fn add_dummies(game_id: GameId, db: &Database, dummy_count: usize) {
|
async fn add_dummies(game_id: GameId, db: &Database, dummy_count: usize) {
|
||||||
let Ok(joined) = db.game().get_joined_players(game_id).await else {
|
let Ok(joined) = db.game().get_joined_players(game_id).await else {
|
||||||
return;
|
return;
|
||||||
|
|
@ -107,7 +128,7 @@ pub async fn run_game(
|
||||||
if let Some(new_record) = lobby
|
if let Some(new_record) = lobby
|
||||||
.process(
|
.process(
|
||||||
if let Some(msg) =
|
if let Some(msg) =
|
||||||
next_message(game_id, &mut client_recv, &mut host_recv).await
|
next_message(game_id, &mut client_recv, &mut host_recv, &db).await
|
||||||
{
|
{
|
||||||
msg
|
msg
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -133,7 +154,7 @@ pub async fn run_game(
|
||||||
if role_reveal
|
if role_reveal
|
||||||
.process(
|
.process(
|
||||||
if let Some(msg) =
|
if let Some(msg) =
|
||||||
next_message(game_id, &mut client_recv, &mut host_recv).await
|
next_message(game_id, &mut client_recv, &mut host_recv, &db).await
|
||||||
{
|
{
|
||||||
msg
|
msg
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -152,13 +173,14 @@ pub async fn run_game(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
GameRecordState::Started(mut current_game) => {
|
GameRecordState::Started(mut current_game) => {
|
||||||
let mut runner = GameRunner::new(game_id, &mut current_game);
|
let mut runner = GameRunner::new(game_id, &mut current_game, db.clone());
|
||||||
loop {
|
loop {
|
||||||
if runner.game_over().is_some()
|
if runner.game_over().is_some()
|
||||||
|| runner
|
|| runner
|
||||||
.process(
|
.process(
|
||||||
if let Some(msg) =
|
if let Some(msg) =
|
||||||
next_message(game_id, &mut client_recv, &mut host_recv).await
|
next_message(game_id, &mut client_recv, &mut host_recv, &db)
|
||||||
|
.await
|
||||||
{
|
{
|
||||||
msg
|
msg
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -184,7 +206,7 @@ pub async fn run_game(
|
||||||
story
|
story
|
||||||
.process(
|
.process(
|
||||||
if let Some(msg) =
|
if let Some(msg) =
|
||||||
next_message(game_id, &mut client_recv, &mut host_recv).await
|
next_message(game_id, &mut client_recv, &mut host_recv, &db).await
|
||||||
{
|
{
|
||||||
msg
|
msg
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue