Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ repository = "https://github.com/stacks-wars/backend"

[workspace.dependencies]
sw-domain = "1.0.2"
sw-plugin = "1.0.3"
sw-plugin = "1.0.4"
sw_checkers = "1.0.3"
sw_lexi_wars = "1.0.1"
sw_ludo = "1.0.1"
Expand Down
2 changes: 1 addition & 1 deletion crates/sw-plugin/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "sw-plugin"
version = "1.0.3"
version = "1.0.4"
edition.workspace = true
license-file = "../../LICENSE"
authors.workspace = true
Expand Down
4 changes: 3 additions & 1 deletion crates/sw-plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ Implement `GameFactory` / `GameEngine` and call into `GameHost` for broadcast, p

Winner-take-all is the default: call `complete_match` with a named winner and the host issues one full-pot claim. To split a pot (or pay as ranks lock), call `issue_payout` yourself — it is a no-op on older hosts. Optional helpers `placement_share_pct` / `placement_prize` implement the first-party 70/30 and 50/30/20 splits; other games can ignore them. Finish those matches with `stats.settlement = "distributed"` so settle does not issue a second winner-take-all claim.

Match Wars Points: pass the engine winner flag into `save_player_result` / `calculate_wars_point_for` so draws do not get the win bonus. `calculate_wars_point` still treats rank 1 as a win for host-save fallbacks.

```toml
sw-plugin = "1.0.3"
sw-plugin = "1.0.4"
sw-domain = "1.0.2"
```
47 changes: 45 additions & 2 deletions crates/sw-plugin/src/kit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -318,9 +318,25 @@ pub struct WarsPointContext {
pub token_contract_id: Option<String>,
}

/// Match Wars Points. `is_winner` is the engine's winner flag (draws get no win bonus).
pub fn calculate_wars_point_for(ctx: &WarsPointContext, is_winner: bool) -> i64 {
let rank = ctx.rank.max(1) as i64;
let participants = ctx.participants.max(1) as i64;
let mut points = 5;
points += (participants - rank).max(0) * 2;
if is_winner {
points += 8;
}
let paid = !ctx.is_sponsored && ctx.entry_amount.unwrap_or(0.0) > 0.0;
if paid {
points += 3;
}
points.clamp(0, 40)
}

/// Fallback used by engines when the host save fails. Treats rank 1 as a win.
pub fn calculate_wars_point(ctx: &WarsPointContext) -> i64 {
let base_points = (ctx.participants as i64 - ctx.rank as i64 + 1) * 2;
base_points.clamp(0, 50)
calculate_wars_point_for(ctx, ctx.rank == 1)
}

/// Optional first-party placement split. Other games can ignore this and
Expand Down Expand Up @@ -437,4 +453,31 @@ mod tests {
assert_eq!(paid_place_count(2), 2);
assert_eq!(paid_place_count(5), 3);
}

fn ctx(rank: usize, participants: usize, paid: bool, sponsored: bool) -> WarsPointContext {
WarsPointContext {
user_id: Uuid::new_v4(),
game_id: None,
rank,
prize: None,
participants,
entry_amount: if paid { Some(1.0) } else { None },
current_amount: None,
is_sponsored: sponsored,
creator_id: None,
active_players: participants,
token_symbol: None,
token_contract_id: None,
}
}

#[test]
fn wars_points_heads_up_and_paid() {
assert_eq!(calculate_wars_point_for(&ctx(2, 2, false, false), false), 5);
assert_eq!(calculate_wars_point_for(&ctx(1, 2, false, false), true), 15);
assert_eq!(calculate_wars_point_for(&ctx(1, 2, true, false), true), 18);
assert_eq!(calculate_wars_point_for(&ctx(1, 4, false, false), true), 19);
assert_eq!(calculate_wars_point_for(&ctx(1, 2, true, true), true), 15);
assert_eq!(calculate_wars_point(&ctx(1, 2, false, false)), 15);
}
}
3 changes: 2 additions & 1 deletion crates/sw-plugin/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ pub use factory::GameFactory;
pub use game_error::GameError;
pub use host::{GameHost, GameHostRef};
pub use kit::{
calculate_wars_point, placement_prize, placement_share_pct, paid_place_count, ClockReading,
calculate_wars_point, calculate_wars_point_for, placement_prize, placement_share_pct,
paid_place_count, ClockReading,
GameBootstrap, GamePlayerState, GameResults, GameStatus, GameSummary, PlayerClocks,
PlayerRanking, PlayerResult, TurnRotation, WarsPointContext,
};
Expand Down
6 changes: 3 additions & 3 deletions crates/sw-plugin/src/nop_host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use serde_json::Value;
use sw_domain::UserId;

use crate::dto::PlayerStateWire;
use crate::kit::{calculate_wars_point, PlayerResult, WarsPointContext};
use crate::kit::{calculate_wars_point_for, PlayerResult, WarsPointContext};
use crate::{GameHost, MatchResult, PluginResult};

/// Placeholder host used between `GameFactory::create` and `GameEngine::start`.
Expand Down Expand Up @@ -45,12 +45,12 @@ impl GameHost for NopHost {
async fn save_player_result(
&self,
ctx: &WarsPointContext,
_is_winner: bool,
is_winner: bool,
) -> PluginResult<PlayerResult> {
Ok(PlayerResult {
rank: ctx.rank,
prize: ctx.prize,
wars_point: calculate_wars_point(ctx),
wars_point: calculate_wars_point_for(ctx, is_winner),
})
}
}
1 change: 1 addition & 0 deletions crates/sw-server/src/data/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub mod lobby_runtime;
pub mod lobby_status;
pub mod matches;
pub mod push;
pub mod quest_claims;
pub mod seasons;
pub mod seat_holds;
pub mod stats;
Expand Down
Loading