diff --git a/src/game.rs b/src/game.rs index 1809ef8..fa9a311 100644 --- a/src/game.rs +++ b/src/game.rs @@ -354,7 +354,7 @@ pub struct Game { #[serde(default)] pub reserve: f32, // value stored in the soul core (the rest lies in the treasury tiles) #[serde(skip)] - pub incoming: Vec<(u8, f32)>, // treasures added since the last treasury update + pub incoming: Vec<(u8, f32, Option)>, // treasures added since the last treasury update (kind, value, delivered at) #[serde(default)] pub training_bill: f32, // training costs, paid on the next payday #[serde(default)] @@ -651,11 +651,16 @@ impl Game { // adds treasure of one kind (as far as there is room); returns the value accepted pub fn add_res(&mut self, amount: f32, res: u8) -> f32 { + self.add_res_at(amount, res, None) + } + + // treasure delivered at a tile: it fills the treasury tiles nearest to that place + pub fn add_res_at(&mut self, amount: f32, res: u8, at: Option) -> f32 { let cap = self.gold_cap(); let acc = amount.min((cap - self.gold).max(0.0)); self.gold += acc; if acc > 0.0 { - self.incoming.push((res, acc)); + self.incoming.push((res, acc, at)); } acc } @@ -700,22 +705,37 @@ impl Game { self.reserve = (self.reserve - need.max(0.0)).max(0.0); } else if diff > 0.5 { let mut left = diff; - let mut parts: Vec<(u8, f32)> = incoming; + let mut parts: Vec<(u8, f32, Option)> = incoming; let typed: f32 = parts.iter().map(|p| p.1).sum(); if typed < left { - parts.push((RES_GOLD, left - typed)); + parts.push((RES_GOLD, left - typed, None)); } - for (res, amount) in parts { + for (res, amount, at) in parts { let mut a = amount.min(left); left -= a; - // tiles holding this treasure first, then empty tiles - for pass in 0..2 { - for &i in &tiles { + // delivered treasure goes to the treasury tiles nearest to the place of delivery + let mut order = tiles.clone(); + if let Some(at) = at { + let (ax, ay) = xy(at); + order.sort_by_key(|&i| { + let (x, y) = xy(i); + (x - ax).pow(2) + (y - ay).pow(2) + }); + } + // without a place: tiles holding this treasure first, then empty tiles; + // with a place: strictly the nearest tiles that can take it + let passes: &[u8] = if at.is_some() { &[2] } else { &[0, 1] }; + for &pass in passes { + for &i in &order { if a <= 0.5 { break; } let t = &mut self.map.t[i]; - let fits = if pass == 0 { t.gold > 0 && t.res == res } else { t.gold <= 0 }; + let fits = match pass { + 0 => t.gold > 0 && t.res == res, + 1 => t.gold <= 0, + _ => (t.gold > 0 && t.res == res && (t.gold as f32) < TREASURY_TILE) || t.gold <= 0, + }; if !fits { continue; } @@ -2126,7 +2146,7 @@ impl Game { Job::Deliver => { if self.is_deposit(cur) { let amount = e.carry as f32; - let got = self.add_res(amount, e.carry_res); + let got = self.add_res_at(amount, e.carry_res, Some(cur)); if got < amount { self.hint(3, "hint.treasury_full"); } @@ -4087,6 +4107,29 @@ mod tests { assert!(g.reserve > 0.0 && g.reserve <= CORE_STORE + 1.0); } + #[test] + fn treasure_lands_in_the_nearest_treasury() { + let mut g = Game::new(3, 64, 64); + let (cx, cy) = g.map.core; + // a second treasury far from the core ring + for y in cy - 9..=cy - 7 { + for x in cx + 10..=cx + 12 { + let t = &mut g.map.t[idx(x, y)]; + t.kind = Kind::Treasury; + t.owner = Owner::Player; + t.gold = 0; + } + } + g.recount(); + g.update(0.01); + let far = |g: &Game| -> i32 { (cy - 9..=cy - 7).flat_map(|y| (cx + 10..=cx + 12).map(move |x| (x, y))).map(|(x, y)| g.map.t[idx(x, y)].gold).sum() }; + let before = far(&g); + let at = idx(cx + 11, cy - 8); + g.add_res_at(900.0, 2, Some(at)); + g.update(0.01); + assert_eq!(far(&g) - before, 900, "the rubies lie where they were delivered"); + } + #[test] fn training_is_billed_on_payday() { let mut g = Game::new(3, 64, 64);