For the experiment, extract the code to color buildings based on type.

Also start to dismantle the old in-game upzone logic.
This commit is contained in:
Dustin Carlino 2020-12-03 11:32:37 -08:00
parent 544dda9c5c
commit 23222bd846
4 changed files with 118 additions and 196 deletions

View File

@ -0,0 +1,91 @@
use std::collections::HashMap;
use map_gui::SimpleApp;
use map_model::{BuildingID, BuildingType};
use widgetry::{Color, Drawable, EventCtx, GeomBatch, Line, Text};
pub struct Buildings {
// Every building in the map is here, to simplify lookup logic.
pub buildings: HashMap<BuildingID, BldgState>,
// This an unchanging base layer that can get covered up by drawing on top of it. Maybe we
// could even replace the one in DrawMap.
pub draw_all: Drawable,
pub total_housing_units: usize,
}
#[derive(Clone)]
pub enum BldgState {
// Score
Undelivered(usize),
Store,
// Or not a relevant building
Done,
}
impl Buildings {
pub fn new(ctx: &mut EventCtx, app: &SimpleApp) -> Buildings {
let house_color = app.cs.residential_building;
let apartment_color = Color::CYAN;
let store_color = Color::YELLOW;
let done_color = Color::BLACK;
let mut buildings = HashMap::new();
let mut total_housing_units = 0;
let mut batch = GeomBatch::new();
for b in app.map.all_buildings() {
if let BuildingType::Residential {
num_housing_units, ..
} = b.bldg_type
{
// There are some unused commercial buildings around!
if num_housing_units > 0 {
buildings.insert(b.id, BldgState::Undelivered(num_housing_units));
total_housing_units += num_housing_units;
let color = if num_housing_units > 5 {
apartment_color
} else {
house_color
};
batch.push(color, b.polygon.clone());
// Call out non-single family homes
if num_housing_units > 1 {
// TODO Text can be slow to render, and this should be louder anyway
batch.append(
Text::from(Line(num_housing_units.to_string()).fg(Color::RED))
.render_to_batch(ctx.prerender)
.scale(0.2)
.centered_on(b.label_center),
);
}
continue;
}
} else if !b.amenities.is_empty() {
// TODO Maybe just food?
buildings.insert(b.id, BldgState::Store);
batch.push(store_color, b.polygon.clone());
continue;
}
// If it's not a residence or store, just blank it out.
buildings.insert(b.id, BldgState::Done);
batch.push(done_color, b.polygon.clone());
}
Buildings {
buildings,
draw_all: ctx.upload(batch),
total_housing_units,
}
}
pub fn all_stores(&self) -> Vec<BuildingID> {
let mut stores = Vec::new();
for (b, state) in &self.buildings {
if let BldgState::Store = state {
stores.push(*b);
}
}
stores
}
}

View File

@ -1,17 +1,16 @@
use std::collections::HashMap;
use abstutil::prettyprint_usize;
use geom::{ArrowCap, Circle, Distance, Duration, PolyLine, Pt2D, Time};
use map_gui::load::MapLoader;
use map_gui::tools::{ColorLegend, ColorScale, SimpleMinimap};
use map_gui::SimpleApp;
use map_model::{BuildingID, BuildingType};
use map_model::BuildingID;
use widgetry::{
Btn, Color, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, Line, Outcome,
Panel, State, Text, TextExt, Transition, UpdateType, VerticalAlignment, Widget,
Btn, Color, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Line, Outcome, Panel,
State, Text, TextExt, Transition, UpdateType, VerticalAlignment, Widget,
};
use crate::animation::{Animator, SnowEffect};
use crate::buildings::{BldgState, Buildings};
use crate::levels::Config;
use crate::meters::make_bar;
use crate::movement::Player;
@ -65,16 +64,9 @@ impl Game {
.named("energy")
.align_right(),
]),
Widget::row(vec![
"Next upzone:".draw_text(ctx),
Widget::draw_batch(ctx, GeomBatch::new())
.named("next upzone")
.align_right(),
]),
"use upzone".draw_text(ctx).named("use upzone"),
Widget::horiz_separator(ctx, 0.2),
// TODO Share constants for colors
ColorLegend::row(ctx, app.cs.residential_building, "single-family house"),
ColorLegend::row(ctx, app.cs.commercial_building, "2-5-family unit"),
ColorLegend::row(ctx, Color::CYAN, "apartment building"),
ColorLegend::row(ctx, Color::YELLOW, "store"),
]))
@ -107,7 +99,7 @@ impl Game {
ctx,
ColorScale(vec![Color::WHITE, Color::GREEN]),
self.state.score,
self.state.total_housing_units,
self.state.bldgs.total_housing_units,
);
self.panel.replace(ctx, "score", score_bar);
@ -118,41 +110,6 @@ impl Game {
self.state.config.max_energy,
);
self.panel.replace(ctx, "energy", energy_bar);
let (upzones_free, have_towards_next, needed_total) = self.state.get_upzones();
self.panel.replace(
ctx,
"use upzone",
if upzones_free == 0 {
Btn::text_bg2("0 upzones").inactive(ctx).named("use upzone")
} else {
// TODO Since we constantly recreate this, the button isn't clickable
Btn::text_bg2(format!(
"Apply upzone ({} available) -- press the U key",
upzones_free
))
.build(ctx, "use upzone", Key::U)
},
);
let upzone_bar = make_bar(
ctx,
// TODO Probably similar color for showing depots
ColorScale(vec![Color::hex("#EFEDF5"), Color::hex("#756BB1")]),
have_towards_next,
needed_total,
);
self.panel.replace(ctx, "next upzone", upzone_bar);
}
pub fn upzone(&mut self, ctx: &mut EventCtx, app: &SimpleApp, b: BuildingID) {
self.state.energy = self.state.config.max_energy;
self.state.upzones_used += 1;
self.state.houses.insert(b, BldgState::Depot);
self.state.recalc_depots(ctx, app);
// TODO Re-center on the player. Or maybe this is irrelevant, since we're not going to
// upzone in the middle of a level now.
ctx.canvas.cam_zoom = crate::movement::ZOOM;
}
}
@ -168,8 +125,8 @@ impl State<SimpleApp> for Game {
self.state.config.tired_speed
};
for b in self.player.update_with_speed(ctx, app, speed) {
match self.state.houses.get(&b) {
Some(BldgState::Undelivered(_)) => {
match self.state.bldgs.buildings[&b] {
BldgState::Undelivered(_) => {
if let Some(increase) = self.state.present_dropped(ctx, app, b) {
self.animator.add(
self.time,
@ -183,7 +140,7 @@ impl State<SimpleApp> for Game {
);
}
}
Some(BldgState::Depot) => {
BldgState::Store => {
let refill = self.state.config.max_energy - self.state.energy;
if refill > 0 {
self.state.energy += refill;
@ -199,7 +156,7 @@ impl State<SimpleApp> for Game {
);
}
}
_ => {}
BldgState::Done => {}
}
}
@ -214,13 +171,13 @@ impl State<SimpleApp> for Game {
if self.state.energyless_arrow.is_none() {
self.state.energyless_arrow = Some(EnergylessArrow::new(ctx, self.time));
}
let depots = self.state.all_depots();
let stores = self.state.bldgs.all_stores();
self.state.energyless_arrow.as_mut().unwrap().update(
ctx,
app,
self.time,
self.player.get_pos(),
depots,
stores,
);
}
@ -229,18 +186,6 @@ impl State<SimpleApp> for Game {
"close" => {
return Transition::Pop;
}
"use upzone" => {
let choices = self
.state
.houses
.iter()
.filter_map(|(id, state)| match state {
BldgState::Undelivered(_) => Some(*id),
_ => None,
})
.collect();
return Transition::Push(crate::upzone::Picker::new(ctx, app, choices));
}
_ => unreachable!(),
},
_ => {}
@ -257,10 +202,10 @@ impl State<SimpleApp> for Game {
self.minimap.draw_with_extra_layers(
g,
app,
vec![&self.state.draw_todo_houses, &self.state.draw_done_houses],
vec![&self.state.bldgs.draw_all, &self.state.draw_done_houses],
);
g.redraw(&self.state.draw_todo_houses);
g.redraw(&self.state.bldgs.draw_all);
g.redraw(&self.state.draw_done_houses);
if true {
@ -287,116 +232,39 @@ impl State<SimpleApp> for Game {
struct GameState {
config: Config,
total_housing_units: usize,
bldgs: Buildings,
// Number of deliveries
score: usize,
// Number of gifts currently being carried
energy: usize,
houses: HashMap<BuildingID, BldgState>,
upzones_used: usize,
upzoned_depots: Vec<BuildingID>,
// This gets covered up by draw_done_houses, instead of an expensive update
draw_todo_houses: Drawable,
draw_done_houses: Drawable,
energyless_arrow: Option<EnergylessArrow>,
}
impl GameState {
fn new(ctx: &mut EventCtx, app: &SimpleApp, config: Config) -> GameState {
let mut houses = HashMap::new();
let mut total_housing_units = 0;
for b in app.map.all_buildings() {
if let BuildingType::Residential {
num_housing_units, ..
} = b.bldg_type
{
// There are some unused commercial buildings around!
if num_housing_units > 0 {
houses.insert(b.id, BldgState::Undelivered(num_housing_units));
total_housing_units += num_housing_units;
}
} else if !b.amenities.is_empty() {
// TODO Maybe just food?
houses.insert(b.id, BldgState::Depot);
}
}
let energy = config.max_energy;
let mut s = GameState {
config,
total_housing_units,
bldgs: Buildings::new(ctx, app),
score: 0,
energy,
houses,
upzones_used: 0,
upzoned_depots: Vec::new(),
draw_todo_houses: Drawable::empty(ctx),
draw_done_houses: Drawable::empty(ctx),
energyless_arrow: None,
};
s.recalc_depots(ctx, app);
s.recalc_deliveries(ctx, app);
s
}
fn recalc_depots(&mut self, ctx: &mut EventCtx, app: &SimpleApp) {
let sfh_color = app.cs.residential_building;
let duplex_color = app.cs.commercial_building;
let apartment_color = Color::CYAN;
let depot_color = Color::YELLOW;
let mut batch = GeomBatch::new();
for b in app.map.all_buildings() {
match self.houses.get(&b.id) {
Some(BldgState::Undelivered(housing_units)) => {
let color = if *housing_units == 1 {
sfh_color
} else if *housing_units <= 5 {
duplex_color
} else {
apartment_color
};
batch.push(color, b.polygon.clone());
// Call out non-single family homes
if *housing_units > 1 {
// TODO Text can be slow to render, and this should be louder anyway
batch.append(
Text::from(Line(housing_units.to_string()).fg(Color::RED))
.render_to_batch(ctx.prerender)
.scale(0.2)
.centered_on(b.label_center),
);
}
}
Some(BldgState::Depot) => {
batch.push(depot_color, b.polygon.clone());
}
// If the house isn't a depot or residence, just blank it out
Some(BldgState::Done) | None => {
batch.push(Color::BLACK, b.polygon.clone());
}
}
}
self.draw_todo_houses = ctx.upload(batch);
}
fn recalc_deliveries(&mut self, ctx: &mut EventCtx, app: &SimpleApp) {
let mut batch = GeomBatch::new();
for (b, state) in &self.houses {
for (b, state) in &self.bldgs.buildings {
if let BldgState::Done = state {
// TODO Stick constants in buildings
batch.push(Color::BLACK, app.map.get_b(*b).polygon.clone());
}
}
@ -413,11 +281,11 @@ impl GameState {
if !self.has_energy() {
return None;
}
if let Some(BldgState::Undelivered(num_housing_units)) = self.houses.get(&id).cloned() {
if let BldgState::Undelivered(num_housing_units) = self.bldgs.buildings[&id] {
// TODO No partial deliveries.
let deliveries = num_housing_units.min(self.energy);
self.score += deliveries;
self.houses.insert(id, BldgState::Done);
self.bldgs.buildings.insert(id, BldgState::Done);
self.energy -= deliveries;
self.recalc_deliveries(ctx, app);
return Some(deliveries);
@ -428,37 +296,6 @@ impl GameState {
fn has_energy(&self) -> bool {
self.energy > 0
}
/// (upzones_free, points towards next upzone, points needed for next upzone)
fn get_upzones(&self) -> (usize, usize, usize) {
// Start with a freebie
let total = 1 + (self.score / self.config.upzone_rate);
let upzones_free = total - self.upzones_used;
let next_upzone = total * self.config.upzone_rate;
(
upzones_free,
self.score - (next_upzone - self.config.upzone_rate),
self.config.upzone_rate,
)
}
fn all_depots(&self) -> Vec<BuildingID> {
let mut depots = self.upzoned_depots.clone();
for (b, state) in &self.houses {
if let BldgState::Depot = state {
depots.push(*b);
}
}
depots
}
}
#[derive(Clone)]
enum BldgState {
// Score
Undelivered(usize),
Depot,
Done,
}
struct EnergylessArrow {
@ -482,15 +319,15 @@ impl EnergylessArrow {
app: &SimpleApp,
time: Time,
sleigh: Pt2D,
all_depots: Vec<BuildingID>,
all_stores: Vec<BuildingID>,
) {
if self.last_update == time {
return;
}
self.last_update = time;
// Find the closest depot as the crow -- or Santa -- flies
let depot = app.map.get_b(
all_depots
// Find the closest store as the crow -- or Santa -- flies
let store = app.map.get_b(
all_stores
.into_iter()
.min_by_key(|b| app.map.get_b(*b).label_center.fast_dist(sleigh))
.unwrap(),
@ -503,7 +340,7 @@ impl EnergylessArrow {
let shift = (pct * std::f64::consts::PI).sin();
let thickness = Distance::meters(5.0 + shift);
let angle = sleigh.angle_to(depot.label_center);
let angle = sleigh.angle_to(store.label_center);
let arrow = PolyLine::must_new(vec![
sleigh.project_away(Distance::meters(20.0), angle),
sleigh.project_away(Distance::meters(40.0), angle),

View File

@ -2,6 +2,7 @@
extern crate log;
mod animation;
mod buildings;
mod controls;
mod game;
mod levels;

View File

@ -62,14 +62,7 @@ impl State<SimpleApp> for Picker {
]),
Box::new(move |_, _, app| {
app.current_selection = None;
Transition::Multi(vec![
Transition::Pop,
Transition::Pop,
Transition::ModifyState(Box::new(move |state, ctx, app| {
let game = state.downcast_mut::<crate::game::Game>().unwrap();
game.upzone(ctx, app, b);
})),
])
Transition::Multi(vec![Transition::Pop, Transition::Pop])
}),
));
}