pinch_points/sim/board/
signposts.rs1use super::*;
9
10impl Board {
11 pub fn can_place_signpost(&self, player: PlayerId, x: u8, y: u8) -> bool {
19 if seat(player).is_none() || !self.in_bounds(i32::from(x), i32::from(y)) {
20 return false;
21 }
22 let t = self.index(i32::from(x), i32::from(y)) as usize;
23 if self.tiles[t] != TileKind::Empty {
24 return false;
25 }
26 match self.signposts[t] {
27 Some(sp) => sp.owner == player,
29 None => {
31 self.signpost_count(player) < self.signpost_cap as usize
32 || self.cap_policy == CapPolicy::Evict
33 }
34 }
35 }
36
37 pub fn place_signpost(&mut self, player: PlayerId, x: u8, y: u8, dir: Direction) -> bool {
38 if !self.can_place_signpost(player, x, y) {
39 return false;
40 }
41 let t = self.index(i32::from(x), i32::from(y)) as usize;
42 if self.signposts[t].is_none() && self.signpost_count(player) >= self.signpost_cap as usize
46 {
47 let oldest = self
49 .signposts
50 .iter()
51 .enumerate()
52 .filter_map(|(i, slot)| slot.filter(|sp| sp.owner == player).map(|sp| (sp.seq, i)))
53 .min();
54 let (_, i) = oldest.expect("at cap implies at least one signpost");
55 self.signposts[i] = None;
56 }
57 self.stamp_signpost(t, player, dir);
58 true
59 }
60
61 pub(super) fn stamp_signpost(&mut self, t: usize, player: PlayerId, dir: Direction) {
64 let seq = self.signpost_seq;
65 self.signpost_seq += 1;
66 self.signposts[t] = Some(Signpost {
67 dir,
68 owner: player,
69 health: SignpostHealth::Full,
70 seq,
71 placed: self.tick,
72 });
73 }
74
75 pub fn signpost_fade(&self, sp: &Signpost) -> f32 {
78 match self.cap_policy {
79 CapPolicy::Reject => 1.0,
80 CapPolicy::Evict => {
81 let age = self.tick.saturating_sub(sp.placed) as f32;
82 (1.0 - age / f32::from(SIGNPOST_LIFETIME as u16)).max(0.0)
83 }
84 }
85 }
86
87 pub(super) fn expire_signposts(&mut self) {
88 if self.cap_policy != CapPolicy::Evict {
89 return;
90 }
91 let now = self.tick;
92 for slot in &mut self.signposts {
93 if let Some(sp) = slot
94 && now.saturating_sub(sp.placed) >= u64::from(SIGNPOST_LIFETIME)
95 {
96 *slot = None;
97 }
98 }
99 }
100
101 pub fn newest_signpost_of(&self, player: PlayerId) -> Option<(u8, u8, u64)> {
110 self.signposts
111 .iter()
112 .enumerate()
113 .filter_map(|(tile, slot)| {
114 let sp = slot.as_ref().filter(|sp| sp.owner == player)?;
115 Some((tile as u16, sp.seq, sp.placed))
116 })
117 .max_by_key(|&(_, seq, _)| seq)
118 .map(|(tile, _, placed)| {
119 let (x, y) = self.coords(tile);
120 (x as u8, y as u8, placed)
121 })
122 }
123
124 pub fn signpost_count(&self, player: PlayerId) -> usize {
126 self.signposts
127 .iter()
128 .flatten()
129 .filter(|sp| sp.owner == player)
130 .count()
131 }
132
133 pub fn remove_signpost(&mut self, player: PlayerId, x: u8, y: u8) -> bool {
135 if !self.in_bounds(i32::from(x), i32::from(y)) {
136 return false;
137 }
138 let t = self.index(i32::from(x), i32::from(y)) as usize;
139 match self.signposts[t] {
140 Some(sp) if sp.owner == player => {
141 self.signposts[t] = None;
142 true
143 }
144 _ => false,
145 }
146 }
147
148 pub fn signpost_at(&self, x: u8, y: u8) -> Option<Signpost> {
149 assert!(self.in_bounds(i32::from(x), i32::from(y)));
150 self.signposts[self.index(i32::from(x), i32::from(y)) as usize]
151 }
152
153 pub fn signpost_rule(&self) -> (u8, CapPolicy) {
155 (self.signpost_cap, self.cap_policy)
156 }
157}