diff --git a/apis/src/components/atoms/mod.rs b/apis/src/components/atoms/mod.rs index b86ea659..1f7e354e 100644 --- a/apis/src/components/atoms/mod.rs +++ b/apis/src/components/atoms/mod.rs @@ -13,6 +13,7 @@ pub mod profile_link; pub mod rating; pub mod select_options; pub mod simple_hex; +pub mod simple_piece; pub mod status_indicator; pub mod svgs; pub mod target; diff --git a/apis/src/components/atoms/piece.rs b/apis/src/components/atoms/piece.rs index b7127d89..0187c190 100644 --- a/apis/src/components/atoms/piece.rs +++ b/apis/src/components/atoms/piece.rs @@ -1,6 +1,6 @@ use crate::common::{MoveConfirm, TileDesign, TileDots, TileRotation}; use crate::common::{PieceType, SvgPos}; -use crate::pages::analysis::InAnalysis; +use crate::pages::{analysis::InAnalysis, play::CurrentConfirm}; use crate::providers::game_state::GameStateSignal; use crate::providers::Config; use hive_lib::{Bug, Piece, Position}; @@ -62,28 +62,23 @@ pub fn Piece( "" }; - let mut game_state_signal = expect_context::(); + let mut game_state = expect_context::(); + let current_confirm = expect_context::().0; let in_analysis = use_context::().unwrap_or(InAnalysis(RwSignal::new(false))); - let onclick = move |evt: MouseEvent| { evt.stop_propagation(); let in_analysis = in_analysis.0.get_untracked(); - if in_analysis || game_state_signal.is_move_allowed() { + if in_analysis || game_state.is_move_allowed() { match piece_type { PieceType::Board => { - game_state_signal.show_moves(piece, position); + game_state.show_moves(piece, position); } PieceType::Reserve => { - game_state_signal.show_spawns(piece, position); + game_state.show_spawns(piece, position); } PieceType::Move | PieceType::Spawn => { - if in_analysis - || matches!( - (config.confirm_mode.preferred_confirm)(), - MoveConfirm::Double - ) - { - game_state_signal.move_active(); + if current_confirm() == MoveConfirm::Double { + game_state.move_active(); } } _ => {} @@ -126,7 +121,7 @@ pub fn Piece( String::new() }; - let top_piece = game_state_signal + let top_piece = game_state .signal .get_untracked() .state @@ -134,7 +129,7 @@ pub fn Piece( .top_piece(position) .unwrap_or(piece); - let active_piece = create_read_slice(game_state_signal.signal, |gs| gs.move_info.active); + let active_piece = create_read_slice(game_state.signal, |gs| gs.move_info.active); let show_ds = move || { if let Some(active) = active_piece() { if active == piece { @@ -142,13 +137,7 @@ pub fn Piece( } return "#ds"; }; - if match game_state_signal - .signal - .get_untracked() - .state - .board - .last_move - { + if match game_state.signal.get_untracked().state.board.last_move { (Some(_), Some(pos)) => position != pos || piece != top_piece, (Some(pos), None) => position != pos || piece != top_piece, (None, Some(pos)) => position != pos || piece != top_piece, diff --git a/apis/src/components/atoms/simple_hex.rs b/apis/src/components/atoms/simple_hex.rs index cf59bb7f..b3a70ea5 100644 --- a/apis/src/components/atoms/simple_hex.rs +++ b/apis/src/components/atoms/simple_hex.rs @@ -1,20 +1,15 @@ use crate::{ common::{Hex, HexType}, - components::atoms::{active::Active, last_move::LastMove, piece::Piece, target::Target}, + components::atoms::simple_piece::SimplePiece, }; use leptos::*; #[component] pub fn SimpleHex(hex: Hex) -> impl IntoView { - match hex.kind { - HexType::Active(_) => view! { }, - HexType::Tile(piece, piece_type) => { - view! { } - } - HexType::LastMove(dir) => { - view! { } - } - HexType::Target => view! { }, + if let HexType::Tile(piece, _) = hex.kind { + view! { } + } else { + view! {}.into_view() } } diff --git a/apis/src/components/atoms/simple_piece.rs b/apis/src/components/atoms/simple_piece.rs new file mode 100644 index 00000000..2a9b1e6d --- /dev/null +++ b/apis/src/components/atoms/simple_piece.rs @@ -0,0 +1,132 @@ +use crate::common::SvgPos; +use crate::common::{TileDesign, TileDots, TileRotation}; +use crate::providers::game_state::GameStateSignal; +use crate::providers::Config; +use hive_lib::{Bug, Piece, Position}; +use leptos::*; + +#[component] +pub fn SimplePiece( + // WARN piece and position are untracked and might break reactivity if passed in as signals in the future + #[prop(into)] piece: MaybeSignal, + #[prop(into)] position: MaybeSignal, + #[prop(into)] level: MaybeSignal, +) -> impl IntoView { + let game_state = expect_context::(); + let config = expect_context::(); + let piece = piece.get_untracked(); + let position = position.get_untracked(); + let center = move || SvgPos::center_for_level(position, level()); + let order = piece.order(); + let ds_transform = move || format!("translate({},{})", center().0, center().1); + let transform = move || { + if (config.tile_rotation.preferred_tile_rotation)() == TileRotation::Yes { + format!( + "translate({},{}) rotate({})", + center().0, + center().1, + order.saturating_sub(1) * 60 + ) + } else { + format!("translate({},{})", center().0, center().1) + } + }; + let bug = piece.bug(); + let color = piece.color(); + + let dot_color = move || { + if (config.tile_design.preferred_tile_design)() == TileDesign::Official { + match bug { + Bug::Ant => " color: #289ee0", + Bug::Beetle => " color: #9a7fc7", + Bug::Grasshopper => " color: #42b23c", + Bug::Spider => " color: #a4572a", + _ => " color: #FF0000", + } + } else { + match bug { + Bug::Ant => " color: #3574a5", + Bug::Beetle => " color: #7a4fab", + Bug::Grasshopper => " color: #3f9b3a", + Bug::Spider => " color: #993c1e", + _ => " color: #FF0000", + } + } + }; + + let bug_transform = move || { + if (config.tile_design.preferred_tile_design)() == TileDesign::Official { + "scale(0.56, 0.56) translate(-45, -50)" + } else { + "scale(0.56, 0.56) translate(-50, -45)" + } + }; + + let bug_svg = move || { + if (config.tile_design.preferred_tile_design)() == TileDesign::Official { + format!("#{}", bug) + } else { + format!("#f{}", bug) + } + }; + + let tile_svg = move || { + if (config.tile_design.preferred_tile_design)() == TileDesign::Official { + format!("#{}", color) + } else { + format!("#f{}", color) + } + }; + + let dots = move || { + if (config.tile_dots.preferred_tile_dots)() == TileDots::Yes { + if (config.tile_design.preferred_tile_design)() == TileDesign::Official { + return format!("#{}", order); + } else { + return format!("#f{}", order); + } + } + String::new() + }; + + let top_piece = game_state + .signal + .get_untracked() + .state + .board + .top_piece(position) + .unwrap_or(piece); + + let active_piece = create_read_slice(game_state.signal, |gs| gs.move_info.active); + let show_ds = move || { + if let Some(active) = active_piece() { + if active == piece { + return "#no_ds"; + } + return "#ds"; + }; + if match game_state.signal.get_untracked().state.board.last_move { + (Some(_), Some(pos)) => position != pos || piece != top_piece, + (Some(pos), None) => position != pos || piece != top_piece, + (None, Some(pos)) => position != pos || piece != top_piece, + _ => true, + } { + "#ds" + } else { + "#no_ds" + } + }; + + view! { + + + + + + + + + + + } +} diff --git a/apis/src/components/atoms/target.rs b/apis/src/components/atoms/target.rs index 8d810a13..82b1491e 100644 --- a/apis/src/components/atoms/target.rs +++ b/apis/src/components/atoms/target.rs @@ -1,8 +1,7 @@ use crate::common::MoveConfirm; use crate::common::SvgPos; -use crate::pages::analysis::InAnalysis; +use crate::pages::{analysis::InAnalysis, play::CurrentConfirm}; use crate::providers::game_state::GameStateSignal; -use crate::providers::Config; use hive_lib::Position; use leptos::*; @@ -14,22 +13,20 @@ pub fn Target( ) -> impl IntoView { let center = move || SvgPos::center_for_level(position, level()); let transform = move || format!("translate({},{})", center().0, center().1); - let mut game_state_signal = expect_context::(); - let config = expect_context::(); + let mut game_state = expect_context::(); let in_analysis = use_context::().unwrap_or(InAnalysis(RwSignal::new(false))); + let current_confirm = expect_context::().0; // Select the target position and make a move if it's the correct mode let onclick = move |_| { let in_analysis = in_analysis.0.get_untracked(); - if in_analysis || game_state_signal.is_move_allowed() { - game_state_signal.set_target(position); - if matches!( - (config.confirm_mode.preferred_confirm)(), - MoveConfirm::Single - ) && !in_analysis - { - game_state_signal.move_active(); - } + if in_analysis || game_state.is_move_allowed() { + batch(move || { + game_state.set_target(position); + if current_confirm() == MoveConfirm::Single || in_analysis { + game_state.move_active(); + } + }); } }; diff --git a/apis/src/components/molecules/board_pieces.rs b/apis/src/components/molecules/board_pieces.rs index 8d9bf488..607223e2 100644 --- a/apis/src/components/molecules/board_pieces.rs +++ b/apis/src/components/molecules/board_pieces.rs @@ -8,11 +8,11 @@ use leptos::*; #[component] pub fn BoardPieces() -> impl IntoView { - let game_state_signal = expect_context::(); + let game_state = expect_context::(); // TODO get the BOARD_SIZE from board let board = move || { let mut board = Vec::new(); - let game_state = (game_state_signal.signal)(); + let game_state = (game_state.signal)(); let targets = game_state.move_info.target_positions; let last_move = game_state.state.board.last_move; let active_piece = ( diff --git a/apis/src/components/molecules/challenge_row.rs b/apis/src/components/molecules/challenge_row.rs index 41453f20..89ac6b10 100644 --- a/apis/src/components/molecules/challenge_row.rs +++ b/apis/src/components/molecules/challenge_row.rs @@ -183,7 +183,8 @@ pub fn ChallengeRow(challenge: StoredValue, single: bool) ->
diff --git a/apis/src/functions/config/confirm_mode.rs b/apis/src/functions/config/confirm_mode.rs index d3350e41..bc3f5640 100644 --- a/apis/src/functions/config/confirm_mode.rs +++ b/apis/src/functions/config/confirm_mode.rs @@ -1,8 +1,12 @@ use crate::common::MoveConfirm; use leptos::*; +use shared_types::GameSpeed; #[server] -pub async fn toggle_confirm_mode(move_confirm: MoveConfirm) -> Result { +pub async fn toggle_confirm_mode( + move_confirm: MoveConfirm, + game_speed: GameSpeed, +) -> Result<(GameSpeed, MoveConfirm), ServerFnError> { use actix_web::http::header::{HeaderMap, HeaderValue, SET_COOKIE}; use chrono::Duration; use leptos_actix::{ResponseOptions, ResponseParts}; @@ -18,13 +22,12 @@ pub async fn toggle_confirm_mode(move_confirm: MoveConfirm) -> Result); pub fn Analysis(#[prop(optional)] extend_tw_classes: &'static str) -> impl IntoView { provide_context(TargetStack(RwSignal::new(None))); provide_context(InAnalysis(RwSignal::new(true))); + provide_context(CurrentConfirm(Memo::new(move |_| MoveConfirm::Single))); let is_tall = expect_context::().is_tall; let parent_container_style = move || { if is_tall() { diff --git a/apis/src/pages/config.rs b/apis/src/pages/config.rs index de8c4079..0b568b20 100644 --- a/apis/src/pages/config.rs +++ b/apis/src/pages/config.rs @@ -1,24 +1,58 @@ -use crate::components::organisms::{ - confirm_mode_toggle::ConfirmModeToggle, darkmode_toggle::DarkModeToggle, - preview_tiles::PreviewTiles, tile_design_toggle::TileDesignToggle, - tile_dots_toggle::TileDotsToggle, tile_rotation_toggle::TileRotationToggle, +use crate::components::{ + atoms::{rating::icon_for_speed, select_options::SelectOption}, + organisms::{ + confirm_mode_toggle::ConfirmModeToggle, darkmode_toggle::DarkModeToggle, + preview_tiles::PreviewTiles, tile_design_toggle::TileDesignToggle, + tile_dots_toggle::TileDotsToggle, tile_rotation_toggle::TileRotationToggle, + }, }; use leptos::*; +use leptos_icons::Icon; +use shared_types::GameSpeed; +use std::str::FromStr; #[component] pub fn Config() -> impl IntoView { + let game_speed = RwSignal::new(GameSpeed::Blitz); + let icon = move || { + view! { } + }; + let toggle = move || { + let game_speed = game_speed(); + view! { } + }; view! { -
+
- -

Colorscheme:

+ + {toggle} +

Colorscheme:

-

Preview:

+

Preview:

diff --git a/apis/src/pages/play.rs b/apis/src/pages/play.rs index e1a3dcb2..d34dfc1f 100644 --- a/apis/src/pages/play.rs +++ b/apis/src/pages/play.rs @@ -1,4 +1,5 @@ use crate::{ + common::MoveConfirm, components::{ atoms::history_button::{HistoryButton, HistoryNavigation}, layouts::base_layout::{ControlsSignal, OrientationSignal}, @@ -13,7 +14,7 @@ use crate::{ side_board::SideboardTabs, }, }, - providers::{game_state::GameStateSignal, AuthContext}, + providers::{config::Config, game_state::GameStateSignal, AuthContext}, }; use hive_lib::{Color, Position}; use leptos::*; @@ -21,12 +22,32 @@ use leptos::*; #[derive(Clone)] pub struct TargetStack(pub RwSignal>); +#[derive(Clone)] +pub struct CurrentConfirm(pub Memo); + #[component] pub fn Play(#[prop(optional)] extend_tw_classes: &'static str) -> impl IntoView { provide_context(TargetStack(RwSignal::new(None))); let orientation_signal = expect_context::(); let game_state = expect_context::(); let auth_context = expect_context::(); + let config = expect_context::(); + let current_confirm = Memo::new(move |_| { + game_state + .loaded + .get() + .then(|| { + let preferred_confirms = (config.confirm_mode.preferred_confirms)(); + game_state + .signal + .get_untracked() + .get_game_speed() + .and_then(|game_speed| preferred_confirms.get(&game_speed).cloned()) + .unwrap_or(MoveConfirm::Single) + }) + .unwrap_or(MoveConfirm::Single) + }); + provide_context(CurrentConfirm(current_confirm)); let user = move || match (auth_context.user)() { Some(Ok(Some(user))) => Some(user), _ => None, diff --git a/apis/src/providers/config/confirm_mode.rs b/apis/src/providers/config/confirm_mode.rs index 460517d3..57709bb1 100644 --- a/apis/src/providers/config/confirm_mode.rs +++ b/apis/src/providers/config/confirm_mode.rs @@ -1,29 +1,31 @@ use crate::common::MoveConfirm; use crate::functions::config::confirm_mode::ToggleConfirmMode; use leptos::*; +use shared_types::GameSpeed; +use std::collections::HashMap; #[cfg(not(feature = "ssr"))] -fn initial_prefers_confirm() -> MoveConfirm { +fn initial_prefers_confirm(game_speed: GameSpeed) -> MoveConfirm { use wasm_bindgen::JsCast; let doc = document().unchecked_into::(); let cookie = doc.cookie().unwrap_or_default(); - if cookie.contains("confirm_mode=Clock") { + if cookie.contains(&format!("{game_speed}_confirm_mode=Clock")) { return MoveConfirm::Clock; - } else if cookie.contains("confirm_mode=Single") { + } else if cookie.contains(&format!("{game_speed}_confirm_mode=Single")) { return MoveConfirm::Single; } return MoveConfirm::Double; } #[cfg(feature = "ssr")] -fn initial_prefers_confirm() -> MoveConfirm { +fn initial_prefers_confirm(game_speed: GameSpeed) -> MoveConfirm { use std::str::FromStr; if let Some(request) = use_context::() { if let Ok(cookies) = request.cookies() { for cookie in cookies.iter() { - if cookie.name() == "confirm_mode" { + if cookie.name() == format!("{game_speed}_confirm_mode") { if let Ok(confirm_mode) = MoveConfirm::from_str(cookie.value()) { return confirm_mode; } @@ -36,8 +38,8 @@ fn initial_prefers_confirm() -> MoveConfirm { #[derive(Clone)] pub struct ConfirmMode { - pub action: Action>, - pub preferred_confirm: Signal, + pub action: Action>, + pub preferred_confirms: Signal>, } impl Default for ConfirmMode { @@ -55,20 +57,28 @@ impl ConfirmMode { let value = toggle_move_confirm_action.value(); let prefers_confirm_fn = move || { - let initial = initial_prefers_confirm(); + let mut move_confirms = HashMap::new(); + for game_speed in GameSpeed::all() { + let initial = initial_prefers_confirm(game_speed.clone()); + move_confirms.insert(game_speed, initial); + } match (input(), value()) { - // if there's some current input, use that optimistically - (Some(submission), _) => submission.move_confirm, - // otherwise, if there was a previous value confirmed by server, use that - (_, Some(Ok(value))) => value, - // otherwise, use the initial value - _ => initial, + (Some(submission), _) => { + move_confirms.insert(submission.game_speed, submission.move_confirm); + } + + (_, Some(Ok(value))) => { + move_confirms.insert(value.0, value.1); + } + + _ => {} } + move_confirms }; ConfirmMode { action: toggle_move_confirm_action, - preferred_confirm: Signal::derive(prefers_confirm_fn), + preferred_confirms: Signal::derive(prefers_confirm_fn), } } } diff --git a/apis/src/providers/game_state.rs b/apis/src/providers/game_state.rs index 87219dee..a9f0fb7a 100644 --- a/apis/src/providers/game_state.rs +++ b/apis/src/providers/game_state.rs @@ -4,7 +4,7 @@ use crate::responses::GameResponse; use hive_lib::{Color, GameControl, GameStatus, GameType, Piece, Position, State, Turn}; use leptos::logging::log; use leptos::*; -use shared_types::GameId; +use shared_types::{GameId, GameSpeed}; use uuid::Uuid; use super::auth_context::AuthContext; @@ -197,7 +197,7 @@ impl GameStateSignal { pub fn set_game_response(&mut self, game_response: GameResponse) { self.signal - .update(|s| s.game_response = Some(game_response)) + .update(|s| s.game_response = Some(game_response)); } pub fn is_finished(&self) -> Memo { @@ -434,6 +434,10 @@ impl GameState { self.history_turn = None; } } + + pub fn get_game_speed(&self) -> Option { + self.game_response.as_ref().map(|gr| gr.speed.clone()) + } } pub fn provide_game_state() { diff --git a/apis/src/providers/websocket/game/reaction/join.rs b/apis/src/providers/websocket/game/reaction/join.rs index abdabcf9..f3871fa0 100644 --- a/apis/src/providers/websocket/game/reaction/join.rs +++ b/apis/src/providers/websocket/game/reaction/join.rs @@ -1,7 +1,7 @@ use crate::{ common::GameActionResponse, providers::{ - game_state::GameStateSignal, games::GamesSignal, timer::TimerSignal, + game_state::GameStateSignal, timer::TimerSignal, websocket::game::reaction::handler::reset_game_state, }, }; @@ -9,26 +9,20 @@ use hive_lib::GameControl; use leptos::*; pub fn handle_join(gar: GameActionResponse) { - let _games = expect_context::(); let game_state = expect_context::(); game_state.loaded.set(false); - //log!("joined the game, reconstructing game state"); - reset_game_state(&gar.game); - let timer = expect_context::(); - timer.update_from(&gar.game); - //game_state has loaded - game_state.loaded.set(true); - // TODO: @leex try this again once the play page works correctly. - if let Some((_turn, gc)) = gar.game.game_control_history.last() { - //log!("Got a GC: {}", gc); - match gc { - GameControl::DrawOffer(_) | GameControl::TakebackRequest(_) => { - game_state.set_pending_gc(gc.clone()) + batch(move || { + reset_game_state(&gar.game); + let timer = expect_context::(); + timer.update_from(&gar.game); + game_state.loaded.set(true); + if let Some((_turn, gc)) = gar.game.game_control_history.last() { + match gc { + GameControl::DrawOffer(_) | GameControl::TakebackRequest(_) => { + game_state.set_pending_gc(gc.clone()) + } + _ => {} } - _ => {} } - } - // TODO: @leex - // Check here whether it's one of your own GCs and only show it when it's not - // your own GC also only if user is a player. + }); }