pub struct Simulation {Show 13 fields
pub mode: GameMode,
pub modern_calendar: CalendarKind,
pub auto_bridge: bool,
pub founder: bool,
pub clock: Clock,
pub crowd: Crowd,
pub events: EventSystem,
pub tower: Tower,
pub money: f64,
pub star: i64,
pub milestones: Vec<String>,
pub log: Vec<LogEntry>,
pub gameplay: GameplayEvents,
/* private fields */
}Expand description
One game of Verticopolis.
Start one with Simulation::new_game, or load one from a save with
crate::load::deserialize. Advance it with Simulation::tick and
change it only through its commands (build,
add_car, adjust_rent
and the rest), which apply the rules, charge the money and refuse what a
player could not do. The public fields are for reading; writing one skips
those rules.
A renderer reads the tower through crate::view or crate::frame,
and an analytics or achievements layer drains Simulation::gameplay.
Fields§
§mode: GameModeThe game mode, fixed when the game starts.
modern_calendar: CalendarKindThe calendar a Modern game runs on. A Classic game always runs the canon calendar.
auto_bridge: boolWhether a new floor joins the floor next to it automatically. Only a
Modern game can turn this off (Simulation::toggle_auto_bridge).
founder: boolWhether the tower was started before saves carried a build stamp. The web game shows these towers a founder badge.
clock: ClockThe game clock: day, hour, minute, quarter and year.
crowd: CrowdThe people moving through the tower.
events: EventSystemEmergencies, and the choice waiting for the player’s answer.
tower: TowerThe tower: its floors, rooms and transport. A renderer reads it through
crate::view instead of walking it.
money: f64The player’s money, in dollars. It can go negative.
star: i64The tower’s rating: 1 to 5 stars, and 6 once it is a TOWER.
milestones: Vec<String>The milestone ids the tower has reached, in the order it reached them. The ids are stable across versions.
log: Vec<LogEntry>The recent messages, oldest first, at most LOG_RING_CAP.
crate::frame::log_since returns the ones after a sequence number.
gameplay: GameplayEventsThe gameplay events since the host last drained them. Call
sim.gameplay.drain() after each frame. Never saved.
Implementations§
Source§impl Simulation
impl Simulation
Sourcepub fn paint_brush(
&mut self,
kind: Kind,
floor: i64,
tiles: &[i64],
) -> BrushResult
pub fn paint_brush( &mut self, kind: Kind, floor: i64, tiles: &[i64], ) -> BrushResult
Lays kind (floor or lobby) on each tile column in tiles on
floor, charging as that many build calls
would, in one command. It skips a tile that already carries kind,
and tries a refused tile again once a neighbor it needed is laid, so
the order of tiles does not matter.
A lobby brush also turns plain floor into lobby (a sky lobby). Any
other kind, or more than BRUSH_TILE_LIMIT tiles, lays nothing and
answers INVALID_REQUEST.
use verticopolis_engine::clock::GameMode;
use verticopolis_engine::facilities::Kind;
use verticopolis_engine::sim::Simulation;
let mut sim = Simulation::new_game(1, GameMode::Classic);
let tiles: Vec<i64> = (100..124).collect();
let laid = sim.paint_brush(Kind::Lobby, 1, &tiles);
assert_eq!(laid.placed, 24);Source§impl Simulation
impl Simulation
Sourcepub fn toggle_auto_bridge(&mut self) -> bool
pub fn toggle_auto_bridge(&mut self) -> bool
Turns automatic floor bridging off or on, and returns the new setting.
Only a Modern game can turn it off; a Classic game always bridges and
this returns true.
Sourcepub fn can_build(&self, kind: Kind, floor: i64, x: i64) -> CanBuild
pub fn can_build(&self, kind: Kind, floor: i64, x: i64) -> CanBuild
The dry run build makes first: whether a
kind at (floor, x) would be placed, why not, and its full price.
Changes nothing.
use verticopolis_engine::clock::GameMode;
use verticopolis_engine::facilities::Kind;
use verticopolis_engine::sim::Simulation;
let sim = Simulation::new_game(1, GameMode::Classic);
let quote = sim.can_build(Kind::Lobby, 1, 100);
assert!(quote.ok);
assert!(quote.cost > 0.0);Sourcepub fn build(&mut self, kind: Kind, floor: i64, x: i64) -> BuildResult
pub fn build(&mut self, kind: Kind, floor: i64, x: i64) -> BuildResult
Places a floor tile, a lobby tile or a room with its left edge at
tile x and its bottom on floor, and charges for it.
A room needs a floor under each of its tiles or bridges the gap when
auto_bridge is on, paying for the floor it
lays. Floors and lobbies are laid one tile at a time; to lay a run, call
paint_brush. A refused build changes
nothing. Floor 1 is the ground floor and basements are 0 and below.
use verticopolis_engine::clock::GameMode;
use verticopolis_engine::facilities::Kind;
use verticopolis_engine::sim::Simulation;
let mut sim = Simulation::new_game(1, GameMode::Classic);
assert!(sim.build(Kind::Lobby, 1, 100).ok);
// Offices open at one star, but not on a bare lot five floors up.
let refused = sim.build(Kind::Office, 5, 300);
assert!(!refused.ok);
println!("{}", refused.reason.unwrap());Sourcepub fn can_build_transport(
&self,
kind: Kind,
x: i64,
bottom: i64,
top: i64,
) -> CanBuild
pub fn can_build_transport( &self, kind: Kind, x: i64, bottom: i64, top: i64, ) -> CanBuild
The dry run build_transport makes
first: whether a shaft of kind at x spanning bottom..=top would
be placed, why not, and its full price. Changes nothing.
An elevator costs the catalog cost plus transportFloorCost for
each floor past its first; stairs and escalators cost the catalog
cost. Refusals come in the build’s order: not unlocked yet, then
money, then placement. The price is only a shaft’s price for a
transport kind with top above bottom; anything else is refused.
Sourcepub fn build_transport(
&mut self,
kind: Kind,
x: i64,
bottom: i64,
top: i64,
) -> BuildResult
pub fn build_transport( &mut self, kind: Kind, x: i64, bottom: i64, top: i64, ) -> BuildResult
Places an elevator, stairs or an escalator with its left edge at tile
x, serving floors bottom to top, and charges for it. A new
elevator starts with one car for every six floors it serves, up to its
kind’s limit; add_car adds more. Stairs and
escalators span exactly two floors.
use verticopolis_engine::clock::GameMode;
use verticopolis_engine::facilities::Kind;
use verticopolis_engine::sim::Simulation;
let mut sim = Simulation::new_game(1, GameMode::Classic);
for x in 100..124 {
sim.build(Kind::Lobby, 1, x);
}
for floor in 2..=4 {
for x in 100..124 {
sim.build(Kind::Floor, floor, x);
}
}
let shaft = sim.build_transport(Kind::ElevatorStandard, 110, 1, 4);
assert!(shaft.ok, "{:?}", shaft.reason);Sourcepub fn sell_at(&mut self, floor: i64, x: i64) -> bool
pub fn sell_at(&mut self, floor: i64, x: i64) -> bool
Sells whatever stands at a tile: a room first, then a shaft, then the
floor or lobby tile. Returns whether anything was sold. The refund and
the refusals are remove_facility’s
with RemovalMethod::Sell.
Source§impl Simulation
impl Simulation
Sourcepub fn add_car(&mut self, id: i64) -> ChargeResult
pub fn add_car(&mut self, id: i64) -> ChargeResult
Buys one more car for elevator id at ADD_CAR_COST. Refused at the
elevator’s car limit and when the money is short.
Sourcepub fn remove_car(&mut self, id: i64) -> ChargeResult
pub fn remove_car(&mut self, id: i64) -> ChargeResult
Sells one car of elevator id back for half of ADD_CAR_COST.
Refused on the last car.
Sourcepub fn begin_extend(&mut self, id: i64) -> bool
pub fn begin_extend(&mut self, id: i64) -> bool
Starts a drag that stretches elevator id. Until
end_extend, the
extend_transport calls bill each floor
once, however far the player drags back and forth. Returns false
when id is not an elevator.
Sourcepub fn end_extend(&mut self)
pub fn end_extend(&mut self)
Ends the drag begin_extend started.
Sourcepub fn extend_transport(
&mut self,
id: i64,
end: ExtendEnd,
target_floor: i64,
) -> ExtendResult
pub fn extend_transport( &mut self, id: i64, end: ExtendEnd, target_floor: i64, ) -> ExtendResult
Moves one end of elevator id toward target_floor, charging
TRANSPORT_FLOOR_COST for each new floor. It grows only as far as
the money pays for. During a drag, a floor already paid for is not
billed again.
use verticopolis_engine::charges::ExtendEnd;
use verticopolis_engine::clock::GameMode;
use verticopolis_engine::facilities::Kind;
use verticopolis_engine::sim::Simulation;
let mut sim = Simulation::new_game(1, GameMode::Classic);
for floor in 1..=6 {
let kind = if floor == 1 { Kind::Lobby } else { Kind::Floor };
for x in 100..124 {
sim.build(kind, floor, x);
}
}
sim.build_transport(Kind::ElevatorStandard, 110, 1, 3);
let id = sim.tower.transports[0].id;
sim.begin_extend(id);
let grown = sim.extend_transport(id, ExtendEnd::Up, 6);
sim.end_extend();
assert_eq!((grown.bottom, grown.top, grown.added), (1, 6, 3));Sourcepub fn remove_facility(
&mut self,
id: i64,
method: RemovalMethod,
) -> ChargeResult
pub fn remove_facility( &mut self, id: i64, method: RemovalMethod, ) -> ChargeResult
Removes room or shaft id and refunds half its build price (nothing
for a room a fire gutted). method says whether the player sold it or
bulldozed it, for the gameplay event. Refused for a burning room and
for a room the mode keeps for good. Rooms and shafts share one id
sequence, so id names exactly one thing.
Source§impl Simulation
impl Simulation
Sourcepub fn change_film(
&mut self,
id: i64,
tier: FilmTier,
) -> Result<f64, FilmRefusal>
pub fn change_film( &mut self, id: i64, tier: FilmTier, ) -> Result<f64, FilmRefusal>
Shows a tier film at cinema id: charges the price (the
catalog’s films) and starts the film on
its first day. Returns the price paid, or why the film did not change
(a tier the mode does not offer is Unavailable).
Sourcepub fn rebook_spent_films(&mut self)
pub fn rebook_spent_films(&mut self)
Where the mode rebooks spent films (Modern), each open theater whose film is spent books its policy’s film again. Runs after the day’s rollover, so the spent film’s last night is counted first. A theater that cannot pay keeps its spent film, and the log says so on the morning the film is spent.
Source§impl Simulation
impl Simulation
Sourcepub fn set_film_policy(&mut self, id: i64, policy: &str) -> Option<&'static str>
pub fn set_film_policy(&mut self, id: i64, policy: &str) -> Option<&'static str>
Sets the film cinema id books again once its film is spent (Modern):
auto or feature (a feature), revival, blockbuster, or off.
Returns the policy set, or None when id is not a cinema or
policy is none of the five.
Sourcepub fn resolve_choice(&mut self, accept: bool)
pub fn resolve_choice(&mut self, accept: bool)
Answers the pending choice: true pays, false
declines. Accepting without the money to pay counts as declining. Does
nothing when no choice is pending. A declined fire spreads until it
burns out; a declined bomb threat starts the search for the bomb.
Source§impl Simulation
impl Simulation
Sourcepub fn apply_rent_batch(
&mut self,
kind: Kind,
target: BatchTarget,
only_default_priced: bool,
) -> Option<BatchRentResult>
pub fn apply_rent_batch( &mut self, kind: Kind, target: BatchTarget, only_default_priced: bool, ) -> Option<BatchRentResult>
Sets the price of every room of kind to target. With
only_default_priced, rooms the player priced themselves keep their
price. Returns None, changing nothing, for a kind that has no price,
for BatchTarget::NoRate in Modern, or for a price that is not a
finite number.
use verticopolis_engine::clock::GameMode;
use verticopolis_engine::facilities::Kind;
use verticopolis_engine::rent::BatchTarget;
use verticopolis_engine::sim::Simulation;
let mut sim = Simulation::new_game(1, GameMode::Modern);
let none = sim.apply_rent_batch(Kind::Office, BatchTarget::Default, false).unwrap();
assert_eq!(none.matched, 0);
assert!(sim.apply_rent_batch(Kind::Lobby, BatchTarget::Default, false).is_none());Sourcepub fn price_unit(&mut self, id: i64, target: f64) -> Option<f64>
pub fn price_unit(&mut self, id: i64, target: f64) -> Option<f64>
Sets room id’s price to target (snapped to a rung in Classic, kept
inside the band in Modern) and puts it back on the market. Returns the
price it set, or None for a room with no price, a sold condo or a
target that is not a finite number.
Sourcepub fn set_no_rate(&mut self, id: i64) -> bool
pub fn set_no_rate(&mut self, id: i64) -> bool
Takes room id off the market, so it takes no new tenants. Classic
only, as in the original. Returns false for a room that cannot be,
such as a sold condo or any room in Modern.
Sourcepub fn adjust_rent(&mut self, id: i64, dir: i64) -> Option<f64>
pub fn adjust_rent(&mut self, id: i64, dir: i64) -> Option<f64>
Moves room id’s price up (dir 1) or down (dir -1) by one rung in
Classic or one step in Modern. Returns the new price, or None as
price_unit does.
Source§impl Simulation
impl Simulation
Sourcepub fn population(&self) -> i64
pub fn population(&self) -> i64
How many people live and work in the tower: the number the star ratings count.
Sourcepub fn new_game(seed: u32, mode: GameMode) -> Simulation
pub fn new_game(seed: u32, mode: GameMode) -> Simulation
Starts a new game: an empty lot, STARTING_MONEY, 07:00 on day 0.
The same seed and the same commands give the same game. A Modern
game gets the real-world calendar and automatic floor bridging; use
Simulation::new_game_with to choose them.
use verticopolis_engine::clock::GameMode;
use verticopolis_engine::sim::Simulation;
let sim = Simulation::new_game(7, GameMode::Modern);
assert_eq!(sim.star, 1);
assert_eq!(sim.population(), 0);Sourcepub fn new_game_with(
seed: u32,
mode: GameMode,
modern_calendar: CalendarKind,
start_unbridged: bool,
) -> Simulation
pub fn new_game_with( seed: u32, mode: GameMode, modern_calendar: CalendarKind, start_unbridged: bool, ) -> Simulation
Starts a new game with a choice of calendar and bridging. Both apply to a Modern game only: a Classic game always runs the canon calendar with bridging on, whatever these say.
use verticopolis_engine::clock::{CalendarKind, GameMode};
use verticopolis_engine::sim::Simulation;
let sim = Simulation::new_game_with(7, GameMode::Modern, CalendarKind::Canon, true);
assert!(!sim.auto_bridge);Source§impl Simulation
impl Simulation
Sourcepub fn tick(&mut self, dt_minutes: f64)
pub fn tick(&mut self, dt_minutes: f64)
Advances the game by dt_minutes game minutes.
Any dt works: the engine splits it into steps of at most 30 minutes
that never cross an hour, so hourly rules always run. The same seed,
the same commands and the same sequence of dt give the same game.
A host that runs on display frames should let
FrameStepper choose the dt, so
the tower comes out the same at any frame rate.
use verticopolis_engine::clock::GameMode;
use verticopolis_engine::sim::Simulation;
let mut sim = Simulation::new_game(1, GameMode::Classic);
let day = sim.clock.day();
sim.tick(24.0 * 60.0);
assert_eq!(sim.clock.day(), day + 1);Source§impl Simulation
impl Simulation
Sourcepub fn serialize(&self) -> Value
pub fn serialize(&self) -> Value
The whole game as save JSON, the object a .vctower file wraps.
A host that edits or inspects the save uses this, then
crate::load::stamp_save and crate::load::encode_vctower to
write the file. To go straight to the file’s bytes, use
crate::load::encode_simulation_vctower.
use verticopolis_engine::clock::GameMode;
use verticopolis_engine::sim::{Simulation, SAVE_VERSION};
let sim = Simulation::new_game(3, GameMode::Classic);
let save = sim.serialize();
assert_eq!(save["version"], SAVE_VERSION);Source§impl Simulation
impl Simulation
Sourcepub fn tower_signature(&self) -> u64
pub fn tower_signature(&self) -> u64
A fingerprint of the tower, for undo: read it before and after a command, and the command changed the tower exactly when the two differ (barring a 64-bit collision). Money is not part of it; compare that beside it. It walks every room and shaft, so read it once per command; it is too slow to read every frame.
use verticopolis_engine::clock::GameMode;
use verticopolis_engine::facilities::Kind;
use verticopolis_engine::sim::Simulation;
let mut sim = Simulation::new_game(1, GameMode::Classic);
let before = sim.tower_signature();
sim.build(Kind::Lobby, 1, 100);
assert_ne!(sim.tower_signature(), before);