Skip to main content
Verticopolis engineGitHub

Simulation

Struct Simulation 

Source
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: GameMode

The game mode, fixed when the game starts.

§modern_calendar: CalendarKind

The calendar a Modern game runs on. A Classic game always runs the canon calendar.

§auto_bridge: bool

Whether a new floor joins the floor next to it automatically. Only a Modern game can turn this off (Simulation::toggle_auto_bridge).

§founder: bool

Whether the tower was started before saves carried a build stamp. The web game shows these towers a founder badge.

§clock: Clock

The game clock: day, hour, minute, quarter and year.

§crowd: Crowd

The people moving through the tower.

§events: EventSystem

Emergencies, and the choice waiting for the player’s answer.

§tower: Tower

The tower: its floors, rooms and transport. A renderer reads it through crate::view instead of walking it.

§money: f64

The player’s money, in dollars. It can go negative.

§star: i64

The 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: GameplayEvents

The gameplay events since the host last drained them. Call sim.gameplay.drain() after each frame. Never saved.

Implementations§

Source§

impl Simulation

Source

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

Source

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.

Source

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);
Source

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());
Source

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.

Source

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);
Source

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

Source

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.

Source

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.

Source

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.

Source

pub fn end_extend(&mut self)

Ends the drag begin_extend started.

Source

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));
Source

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

Source

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).

Source

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

Source

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.

Source

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

Source

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());
Source

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.

Source

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.

Source

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

Source

pub fn population(&self) -> i64

How many people live and work in the tower: the number the star ratings count.

Source

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);
Source

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

Source

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

Source

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

Source

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);

Trait Implementations§

Source§

impl StepSim for Simulation

Source§

fn step(&mut self, dt_minutes: f64)

Advances the game by dt_minutes.
Source§

fn minute_of_day(&self) -> f64

The game clock’s minute of the day, 0 up to 1,440.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.