Make the web client game-generic against the platform API
Send creation options as the nested options object the lobby expects, drive the creation form from each game's options_schema, and render history/leaderboard from the generic result payload (nullable teams, float points). The lobby uses the payload's seats_open, and the scopone-specific table, hand-end and game-over views are gated on its game_type with an unsupported-game fallback for future games. The websocket protocol is unchanged.
This commit is contained in:
+34
-5
@@ -329,13 +329,20 @@ pub fn GamePage(id: String) -> View {
|
||||
}
|
||||
}
|
||||
Some(g) if g.phase == "lobby" => lobby_view(g),
|
||||
Some(g) => table_view(g, on_hand_card, selected, now),
|
||||
// The live table is scopone-specific; other games get a
|
||||
// fallback until they ship their own play view.
|
||||
Some(g) if g.game_type == "scopone_scientifico" => {
|
||||
table_view(g, on_hand_card, selected, now)
|
||||
}
|
||||
Some(g) => unsupported_game_view(g),
|
||||
})
|
||||
(move || capture_choice.get_clone().map(|(card, options)| {
|
||||
capture_picker(card, options, socket, capture_choice)
|
||||
}))
|
||||
(move || match game.get_clone() {
|
||||
Some(g) if g.phase == "hand_end" => hand_summary_modal(g, socket, now),
|
||||
Some(g) if g.phase == "hand_end" && g.game_type == "scopone_scientifico" => {
|
||||
hand_summary_modal(g, socket, now)
|
||||
}
|
||||
_ => view! {},
|
||||
})
|
||||
(move || game_over_view(over.get_clone(), game.get_clone()))
|
||||
@@ -343,9 +350,13 @@ pub fn GamePage(id: String) -> View {
|
||||
}
|
||||
}
|
||||
|
||||
/// Lobby view while waiting for the fourth player.
|
||||
/// Lobby view while waiting for the remaining players. The seat count
|
||||
/// comes from the payload (`seats_open`), so games with other player
|
||||
/// counts render correctly.
|
||||
fn lobby_view(game: GameView) -> View {
|
||||
let seats_open = 4usize.saturating_sub(game.players.len());
|
||||
let seats_open = game
|
||||
.seats_open
|
||||
.unwrap_or_else(|| 4usize.saturating_sub(game.players.len()));
|
||||
let join_code = game.join_code.clone();
|
||||
let players = game
|
||||
.players
|
||||
@@ -374,6 +385,18 @@ fn lobby_view(game: GameView) -> View {
|
||||
}
|
||||
}
|
||||
|
||||
/// Fallback for live games the web client has no play view for yet.
|
||||
fn unsupported_game_view(game: GameView) -> View {
|
||||
let game_type = game.game_type.clone();
|
||||
view! {
|
||||
div(class="panel status-panel") {
|
||||
h2 { "Unsupported game" }
|
||||
p { "Live play for “" (game_type) "” isn't supported in the web client yet." }
|
||||
p { a(href="/") { "Back to lobby" } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn player_for_seat(game: &GameView, seat: usize) -> Option<PlayerView> {
|
||||
game.players.iter().find(|p| p.seat == seat).cloned()
|
||||
}
|
||||
@@ -537,8 +560,14 @@ fn capture_picker(
|
||||
}
|
||||
}
|
||||
|
||||
/// End-of-match overlay.
|
||||
/// End-of-match overlay (scopone's team score; other games render
|
||||
/// nothing until they ship their own play view).
|
||||
fn game_over_view(over: Option<(Scores, Option<String>)>, game: Option<GameView>) -> View {
|
||||
if let Some(g) = &game {
|
||||
if g.game_type != "scopone_scientifico" {
|
||||
return view! {};
|
||||
}
|
||||
}
|
||||
let result = over.or_else(|| {
|
||||
game.clone()
|
||||
.filter(|g| g.phase == "finished")
|
||||
|
||||
Reference in New Issue
Block a user