Add configurable napola rule with instant win on a full denari sweep
This commit is contained in:
+2
-2
@@ -35,9 +35,9 @@ pub async fn game_types() -> Result<Vec<GameTypeInfo>, String> {
|
||||
Ok(page.results)
|
||||
}
|
||||
|
||||
pub async fn create_game(game_type: &str, target_score: i32) -> Result<GameView, String> {
|
||||
pub async fn create_game(game_type: &str, target_score: i32, napola: bool) -> Result<GameView, String> {
|
||||
let resp = Request::post("/api/games")
|
||||
.json(&serde_json::json!({ "game_type": game_type, "target_score": target_score }))
|
||||
.json(&serde_json::json!({ "game_type": game_type, "target_score": target_score, "napola": napola }))
|
||||
.map_err(|e| e.to_string())?
|
||||
.send()
|
||||
.await
|
||||
|
||||
@@ -113,6 +113,45 @@ pub fn summary_rows(summary: HandSummary) -> View {
|
||||
summary.award.primiera.clone(),
|
||||
"+1",
|
||||
),
|
||||
// Napola (only when the rule is enabled for this match)
|
||||
match summary.napola {
|
||||
None => view! {},
|
||||
Some(napola) => {
|
||||
let n = match summary.award.napola.as_deref() {
|
||||
Some("A") => napola.a,
|
||||
Some("B") => napola.b,
|
||||
_ => 0,
|
||||
};
|
||||
let text = match (&summary.award.napola, n) {
|
||||
(Some(t), 10) => format!(
|
||||
"Team {t} swept the whole denari suit — napola! Instant match win"
|
||||
),
|
||||
(Some(t), n) => format!(
|
||||
"Team {t} captured {n} consecutive denari from the ace"
|
||||
),
|
||||
(None, _) => "No napola this hand".to_string(),
|
||||
};
|
||||
let chip = match &summary.award.napola {
|
||||
Some(t) => format!("Team {t} +{n}"),
|
||||
None => "tie".to_string(),
|
||||
};
|
||||
let cls = match summary.award.napola.as_deref() {
|
||||
Some("A") => "score-row team-a",
|
||||
Some("B") => "score-row team-b",
|
||||
_ => "score-row tie",
|
||||
};
|
||||
view! {
|
||||
div(class=cls) {
|
||||
div(class="score-icon") { (card_img("01D".to_string(), "score-mini")) }
|
||||
div(class="score-body") {
|
||||
div(class="score-title") { "Napola" }
|
||||
div(class="score-text") { (text) }
|
||||
}
|
||||
div(class="score-points") { (chip) }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// Scope
|
||||
{
|
||||
let a = summary.scope.a;
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
|
||||
fn default_true() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[allow(dead_code)]
|
||||
pub struct User {
|
||||
@@ -75,6 +79,8 @@ pub struct Award {
|
||||
pub settebello: Option<String>,
|
||||
#[serde(default)]
|
||||
pub primiera: Option<String>,
|
||||
#[serde(default)]
|
||||
pub napola: Option<String>,
|
||||
}
|
||||
|
||||
/// The scoring breakdown of one completed hand.
|
||||
@@ -86,6 +92,10 @@ pub struct HandSummary {
|
||||
pub settebello: TeamBools,
|
||||
pub primiera: TeamCounts,
|
||||
pub scope: TeamCounts,
|
||||
/// Napola run lengths per team; absent when the rule is disabled (or
|
||||
/// the summary predates the option).
|
||||
#[serde(default)]
|
||||
pub napola: Option<TeamCounts>,
|
||||
pub award: Award,
|
||||
#[serde(default)]
|
||||
pub hand: i32,
|
||||
@@ -104,6 +114,9 @@ pub struct GameView {
|
||||
/// Which card game this match is (id from /api/game-types).
|
||||
#[serde(default)]
|
||||
pub game_type: String,
|
||||
/// Whether the napola rule is scored in this match.
|
||||
#[serde(default = "default_true")]
|
||||
pub napola: bool,
|
||||
pub phase: String,
|
||||
#[serde(default)]
|
||||
pub target_score: i32,
|
||||
|
||||
+12
-1
@@ -24,6 +24,7 @@ pub fn LobbyPage() -> View {
|
||||
let code = create_signal(String::new());
|
||||
let game_types = create_signal(fallback_game_types());
|
||||
let selected_game = create_signal("scopone_scientifico".to_string());
|
||||
let napola = create_signal(true);
|
||||
|
||||
spawn_local(async move {
|
||||
match api::me().await {
|
||||
@@ -47,8 +48,9 @@ pub fn LobbyPage() -> View {
|
||||
|
||||
let on_create = move |target: i32| {
|
||||
let game_type = selected_game.get_clone();
|
||||
let napola = napola.get();
|
||||
spawn_local(async move {
|
||||
match api::create_game(&game_type, target).await {
|
||||
match api::create_game(&game_type, target, napola).await {
|
||||
Ok(game) => navigate(&format!("/game/{}", game.id)),
|
||||
Err(e) => error.set(Some(e)),
|
||||
}
|
||||
@@ -101,6 +103,15 @@ pub fn LobbyPage() -> View {
|
||||
key=|g| g.id.clone(),
|
||||
)
|
||||
}
|
||||
label(class="check", r#for="napola") {
|
||||
input(id="napola", r#type="checkbox", bind:checked=napola)
|
||||
" Napola"
|
||||
}
|
||||
p(class="hint") {
|
||||
"A-2-3 of denari scores 3 points, plus 1 per extra "
|
||||
"consecutive denari card; sweeping the whole suit "
|
||||
"wins the match instantly."
|
||||
}
|
||||
p { "First team to reach the target score wins." }
|
||||
div(class="target-buttons") {
|
||||
button(class="button", on:click=move |_| on_create(11)) { "Target 11" }
|
||||
|
||||
Reference in New Issue
Block a user