verticopolis_engine/brush.rs
1//! The floor and lobby brush: [`Simulation::paint_brush`] lays a run of
2//! floor or lobby tiles in one command.
3
4use crate::charges::INVALID_REQUEST;
5use crate::facilities::Kind;
6use crate::sim::Simulation;
7
8/// The most tiles one brush may carry.
9pub const BRUSH_TILE_LIMIT: usize = 32;
10
11/// What a brush laid.
12#[derive(Clone, Debug, PartialEq)]
13pub struct BrushResult {
14 /// How many tiles it laid.
15 pub placed: i64,
16 /// The last refusal one of its tiles got, if any did.
17 pub reason: Option<String>,
18}
19
20impl BrushResult {
21 #[doc(hidden)]
22 pub fn invalid() -> BrushResult {
23 BrushResult {
24 placed: 0,
25 reason: Some(INVALID_REQUEST.into()),
26 }
27 }
28}
29
30impl Simulation {
31 /// Lays `kind` (floor or lobby) on each tile column in `tiles` on
32 /// `floor`, charging as that many [`build`](Simulation::build) calls
33 /// would, in one command. It skips a tile that already carries `kind`,
34 /// and tries a refused tile again once a neighbor it needed is laid, so
35 /// the order of `tiles` does not matter.
36 ///
37 /// A lobby brush also turns plain floor into lobby (a sky lobby). Any
38 /// other kind, or more than [`BRUSH_TILE_LIMIT`] tiles, lays nothing and
39 /// answers `INVALID_REQUEST`.
40 ///
41 /// ```
42 /// use verticopolis_engine::clock::GameMode;
43 /// use verticopolis_engine::facilities::Kind;
44 /// use verticopolis_engine::sim::Simulation;
45 ///
46 /// let mut sim = Simulation::new_game(1, GameMode::Classic);
47 /// let tiles: Vec<i64> = (100..124).collect();
48 /// let laid = sim.paint_brush(Kind::Lobby, 1, &tiles);
49 /// assert_eq!(laid.placed, 24);
50 /// ```
51 pub fn paint_brush(&mut self, kind: Kind, floor: i64, tiles: &[i64]) -> BrushResult {
52 if !matches!(kind, Kind::Floor | Kind::Lobby) || tiles.len() > BRUSH_TILE_LIMIT {
53 return BrushResult::invalid();
54 }
55 self.with_lobby_upgrades(|sim| {
56 let mut placed = 0;
57 let mut reason = None;
58 let mut progress = true;
59 while progress {
60 progress = false;
61 for &tx in tiles {
62 let existing = sim.tower.structure_kind_at(floor, tx);
63 if existing == Some(kind) || (existing.is_some() && kind != Kind::Lobby) {
64 continue;
65 }
66 let r = sim.build(kind, floor, tx);
67 if r.ok {
68 placed += 1;
69 progress = true;
70 } else {
71 reason = r.reason;
72 }
73 }
74 }
75 BrushResult { placed, reason }
76 })
77 }
78}