feat: Phase 2 — game feel complete
Chemistry & squads: - Chemistry calculation on GET /squad (club/league/nation links, max 100) - Formation validation on POST /squad (exactly 11 starters, exactly 1 GK) Objectives: - Weekly objectives JSON (4 objectives: warrior, goals, dedicated, SBC) - Daily objectives auto-reset at midnight UTC (background task) SBC validation expanded: - max_overall per-player enforcement - required_clubs validation - min_players_from_same_nation validation - min_players_from_same_club validation Market: - GET /market?min_overall=X&position=Y filtering - Expiry cleanup runs before every NPC refresh - NPC market auto-refresh every 24h (background task, runs at startup) Matches: - GET /matches/opponent?difficulty=beginner|professional|world_class|legendary generates a random AI opponent squad from the card pool Statistics: - win_streak and best_win_streak tracking (migration 0002) - GET /statistics/history?limit=N — last N matches with summary stats Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,8 +5,80 @@ use crate::{
|
||||
match_result::{Match, MatchRewardResult, SubmitMatchRequest},
|
||||
objective::ObjectiveDefinition,
|
||||
},
|
||||
services::{club, objective, profile, statistics},
|
||||
services::{card_db::CardDb, club, objective, profile, statistics},
|
||||
};
|
||||
use rand::{seq::SliceRandom, Rng};
|
||||
|
||||
/// Generate a random AI opponent squad for Squad Battles.
|
||||
pub fn generate_opponent(card_db: &CardDb, difficulty: &str) -> serde_json::Value {
|
||||
let (min_overall, names): (u8, &[&str]) = match difficulty {
|
||||
"professional" => (
|
||||
70,
|
||||
&[
|
||||
"Athletic CF",
|
||||
"City Wanderers",
|
||||
"The Rovers",
|
||||
"United Select",
|
||||
"Blue Stars FC",
|
||||
],
|
||||
),
|
||||
"world_class" => (
|
||||
78,
|
||||
&[
|
||||
"Elite Stars FC",
|
||||
"Champions Select",
|
||||
"Premier XI",
|
||||
"Galaxy United",
|
||||
"Titan FC",
|
||||
],
|
||||
),
|
||||
"legendary" => (
|
||||
85,
|
||||
&[
|
||||
"Legends United",
|
||||
"Ultimate XI",
|
||||
"Gold Standard FC",
|
||||
"The Icons",
|
||||
"Heritage FC",
|
||||
],
|
||||
),
|
||||
_ => (
|
||||
58,
|
||||
&[
|
||||
"Amateur Town FC",
|
||||
"Sunday League XI",
|
||||
"Park FC",
|
||||
"Village Stars",
|
||||
"Reserve XI",
|
||||
],
|
||||
),
|
||||
};
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
let all = card_db.by_min_overall(min_overall);
|
||||
let mut indices: Vec<usize> = (0..all.len()).collect();
|
||||
indices.shuffle(&mut rng);
|
||||
let cards: Vec<_> = indices
|
||||
.into_iter()
|
||||
.take(11)
|
||||
.map(|i| all[i].clone())
|
||||
.collect();
|
||||
|
||||
let squad_rating = if cards.is_empty() {
|
||||
0
|
||||
} else {
|
||||
cards.iter().map(|c| c.overall as i64).sum::<i64>() / cards.len() as i64
|
||||
};
|
||||
let name = names[rng.gen_range(0..names.len())];
|
||||
|
||||
serde_json::json!({
|
||||
"opponent_name": name,
|
||||
"difficulty": difficulty,
|
||||
"squad_rating": squad_rating,
|
||||
"formation": "4-4-2",
|
||||
"cards": cards,
|
||||
})
|
||||
}
|
||||
|
||||
const COINS_WIN: i64 = 400;
|
||||
const COINS_DRAW: i64 = 150;
|
||||
|
||||
Reference in New Issue
Block a user