fforge_core/
match_engine.rs1use crate::rng::Rng;
14use fforge_domain::{
15 current_ability, ClubId, Lineup, PlayerId, Role, World, FORMATIONS, ROLE_WEIGHTS, XI,
16};
17
18pub fn lineup_strength(world: &World, lineup: &Lineup) -> f64 {
22 let def = lineup.formation_def();
23 let mut sum = 0.0;
24 for (slot, &pid) in lineup.players.iter().enumerate() {
25 let player = world.player(pid);
26 sum += current_ability(&player.attributes, def.slots[slot], &ROLE_WEIGHTS) as f64;
27 }
28 sum / XI as f64
29}
30
31const HOME_BASE: f64 = 1.42;
33const AWAY_BASE: f64 = 1.12;
34const DIFF_K: f64 = 0.030;
36
37pub fn simulate_match(home_strength: f64, away_strength: f64, rng: &mut Rng) -> (u8, u8) {
38 let diff = home_strength - away_strength;
39 let lambda_home = (HOME_BASE * (DIFF_K * diff).exp()).clamp(0.15, 6.0);
40 let lambda_away = (AWAY_BASE * (-DIFF_K * diff).exp()).clamp(0.10, 6.0);
41 let hg = rng.poisson(lambda_home).min(9) as u8;
42 let ag = rng.poisson(lambda_away).min(9) as u8;
43 (hg, ag)
44}
45
46pub fn ai_pick_lineup(world: &World, club: ClubId) -> Lineup {
51 let squad: Vec<PlayerId> = world.club(club).players.clone();
52 let mut best: Option<(f64, Lineup)> = None;
53
54 for (fi, formation) in FORMATIONS.iter().enumerate() {
55 let mut remaining = squad.clone();
56 let mut chosen = [PlayerId(0); XI];
57 let mut total = 0.0;
58 for (slot, &role) in formation.slots.iter().enumerate() {
59 let (idx, ca) = pick_best(world, &remaining, role);
60 chosen[slot] = remaining.remove(idx);
61 total += ca as f64;
62 }
63 let mean = total / XI as f64;
64 let candidate = Lineup {
65 formation: fi as u8,
66 players: chosen,
67 };
68 match &best {
69 Some((score, _)) if *score >= mean => {}
70 _ => best = Some((mean, candidate)),
71 }
72 }
73 best.expect("at least one formation").1
74}
75
76fn pick_best(world: &World, pool: &[PlayerId], role: Role) -> (usize, u8) {
77 let mut best_idx = 0;
78 let mut best_ca = 0u8;
79 let mut best_id = PlayerId(u32::MAX);
80 for (i, &pid) in pool.iter().enumerate() {
81 let ca = current_ability(&world.player(pid).attributes, role, &ROLE_WEIGHTS);
82 if ca > best_ca || (ca == best_ca && pid < best_id) {
83 best_idx = i;
84 best_ca = ca;
85 best_id = pid;
86 }
87 }
88 (best_idx, best_ca)
89}