Overhaul how buildings get colored in different states, and reset things

when changing depots
This commit is contained in:
Dustin Carlino 2020-11-28 17:07:14 -08:00
parent 02d5e3cea3
commit f030086e8e
3 changed files with 148 additions and 120 deletions

View File

@ -1,14 +1,14 @@
use std::collections::HashMap; use std::collections::HashMap;
use abstutil::{prettyprint_usize, Timer}; use abstutil::prettyprint_usize;
use geom::{Circle, Distance, Duration, Line, Polygon, Pt2D, Speed}; use geom::{Circle, Distance, Duration, Line, Polygon, Pt2D, Speed};
use map_gui::tools::{nice_map_name, CityPicker, ColorScale, SimpleMinimap}; use map_gui::tools::{nice_map_name, CityPicker, ColorScale, DivergingScale, SimpleMinimap};
use map_gui::{Cached, SimpleApp, ID}; use map_gui::{Cached, SimpleApp, ID};
use map_model::{BuildingID, BuildingType, PathConstraints}; use map_model::{BuildingID, BuildingType, PathConstraints};
use widgetry::{ use widgetry::{
lctrl, Btn, Checkbox, Color, Drawable, EventCtx, Fill, GeomBatch, GfxCtx, HorizontalAlignment, lctrl, Btn, Checkbox, Color, Drawable, EventCtx, Fill, GeomBatch, GfxCtx, HorizontalAlignment,
Key, Line, LinearGradient, Outcome, Panel, State, Text, TextExt, Transition, UpdateType, Key, Line, LinearGradient, Outcome, Panel, RewriteColor, State, Text, TextExt, Transition,
VerticalAlignment, Widget, UpdateType, VerticalAlignment, Widget,
}; };
use crate::animation::Animator; use crate::animation::Animator;
@ -28,11 +28,7 @@ pub struct Game {
} }
impl Game { impl Game {
pub fn new( pub fn new(ctx: &mut EventCtx, app: &SimpleApp) -> Box<dyn State<SimpleApp>> {
ctx: &mut EventCtx,
app: &SimpleApp,
timer: &mut Timer,
) -> Box<dyn State<SimpleApp>> {
ctx.canvas.cam_zoom = ZOOM; ctx.canvas.cam_zoom = ZOOM;
// Start on a commerical building // Start on a commerical building
@ -47,7 +43,7 @@ impl Game {
.unwrap(); .unwrap();
let sleigh = depot.label_center; let sleigh = depot.label_center;
ctx.canvas.center_on_map_pt(sleigh); ctx.canvas.center_on_map_pt(sleigh);
let state = SleighState::new(ctx, app, depot.id, timer); let state = SleighState::new(ctx, app, depot.id);
let panel = Panel::new(Widget::col(vec![ let panel = Panel::new(Widget::col(vec![
Widget::row(vec![ Widget::row(vec![
@ -135,8 +131,7 @@ impl Game {
self.state.upzones_used += 1; self.state.upzones_used += 1;
self.state.houses.insert(b, BldgState::Depot); self.state.houses.insert(b, BldgState::Depot);
self.state.depot = b; self.state.depot = b;
self.state.redraw(ctx, app); self.state.recalc_depots(ctx, app);
self.state.redraw_depots(ctx, app);
self.sleigh = app.map.get_b(b).label_center; self.sleigh = app.map.get_b(b).label_center;
ctx.canvas.cam_zoom = ZOOM; ctx.canvas.cam_zoom = ZOOM;
ctx.canvas.center_on_map_pt(self.sleigh); ctx.canvas.center_on_map_pt(self.sleigh);
@ -148,11 +143,13 @@ impl State<SimpleApp> for Game {
let mut recharging = false; let mut recharging = false;
if let Some(dt) = ctx.input.nonblocking_is_update_event() { if let Some(dt) = ctx.input.nonblocking_is_update_event() {
if let Some(b) = self.over_bldg.key() { if let Some(b) = self.over_bldg.key() {
if ctx.is_key_down(Key::Space) && self.state.recharge(ctx, app, b, dt) { if ctx.is_key_down(Key::Space) && self.state.recharge(b, dt) {
self.state.depot = b;
self.state.redraw(ctx, app);
self.update_panel(ctx); self.update_panel(ctx);
recharging = true; recharging = true;
if self.state.depot != b {
self.state.depot = b;
self.state.recalc_depots(ctx, app);
}
} }
} }
@ -173,10 +170,15 @@ impl State<SimpleApp> for Game {
self.sleigh = self.sleigh.offset(dx, dy); self.sleigh = self.sleigh.offset(dx, dy);
ctx.canvas.center_on_map_pt(self.sleigh); ctx.canvas.center_on_map_pt(self.sleigh);
let key = OverBldg::key(app, self.sleigh, &self.state);
let is_depot = key
.map(|b| match self.state.houses.get(&b) {
Some(BldgState::Depot) => true,
_ => false,
})
.unwrap_or(false);
self.over_bldg self.over_bldg
.update(OverBldg::key(app, self.sleigh, &self.state), |key| { .update(key, |key| OverBldg::value(ctx, app, key, is_depot));
OverBldg::value(ctx, app, key)
});
} }
} }
@ -213,12 +215,10 @@ impl State<SimpleApp> for Game {
ctx, ctx,
app, app,
Box::new(|ctx, app| { Box::new(|ctx, app| {
ctx.loading_screen("setup again", |ctx, mut timer| { Transition::Multi(vec![
Transition::Multi(vec![ Transition::Pop,
Transition::Pop, Transition::Replace(Game::new(ctx, app)),
Transition::Replace(Game::new(ctx, app, &mut timer)), ])
])
})
}), }),
)); ));
} }
@ -228,7 +228,7 @@ impl State<SimpleApp> for Game {
.houses .houses
.iter() .iter()
.filter_map(|(id, state)| match state { .filter_map(|(id, state)| match state {
BldgState::Undelivered { .. } => Some(*id), BldgState::Undelivered(_) => Some(*id),
_ => None, _ => None,
}) })
.collect(); .collect();
@ -253,18 +253,24 @@ impl State<SimpleApp> for Game {
fn draw(&self, g: &mut GfxCtx, app: &SimpleApp) { fn draw(&self, g: &mut GfxCtx, app: &SimpleApp) {
self.panel.draw(g); self.panel.draw(g);
if self.state.has_energy() { if self.state.has_energy() {
self.minimap self.minimap.draw_with_extra_layers(
.draw_with_extra_layer(g, app, Some(&self.state.draw_done)); g,
app,
vec![&self.state.draw_todo_houses, &self.state.draw_done_houses],
);
} else { } else {
self.minimap self.minimap
.draw_with_extra_layer(g, app, Some(&self.state.draw_all_depots)); .draw_with_extra_layers(g, app, vec![&self.state.draw_all_depots]);
} }
g.redraw(&self.state.draw_scores); g.redraw(&self.state.draw_todo_houses);
g.redraw(&self.state.draw_done); g.redraw(&self.state.draw_done_houses);
if let Some(draw) = self.over_bldg.value() { if let Some(draw) = self.over_bldg.value() {
g.redraw(&draw.0); g.redraw(&draw.0);
} }
if !self.state.has_energy() {
g.redraw(&self.state.draw_all_depots);
}
g.draw_polygon( g.draw_polygon(
Color::RED, Color::RED,
Circle::new(self.sleigh, Distance::meters(5.0)).to_polygon(), Circle::new(self.sleigh, Distance::meters(5.0)).to_polygon(),
@ -282,61 +288,31 @@ struct Config {
} }
struct SleighState { struct SleighState {
depot: BuildingID, config: Config,
score: usize, score: usize,
upzones_used: usize,
energy: Duration, energy: Duration,
houses: HashMap<BuildingID, BldgState>, houses: HashMap<BuildingID, BldgState>,
draw_scores: Drawable,
draw_done: Drawable, depot: BuildingID,
config: Config, cost_to_house: HashMap<BuildingID, Duration>,
upzones_used: usize,
upzoned_depots: Vec<BuildingID>, upzoned_depots: Vec<BuildingID>,
draw_all_depots: Drawable, draw_all_depots: Drawable,
// This gets covered up by draw_done_houses, instead of an expensive update
draw_todo_houses: Drawable,
draw_done_houses: Drawable,
} }
impl SleighState { impl SleighState {
fn new( fn new(ctx: &mut EventCtx, app: &SimpleApp, depot: BuildingID) -> SleighState {
ctx: &mut EventCtx,
app: &SimpleApp,
depot: BuildingID,
timer: &mut Timer,
) -> SleighState {
timer.start("calculate costs from depot");
let house_costs = map_model::connectivity::all_costs_from(
&app.map,
depot,
Duration::hours(3),
PathConstraints::Pedestrian,
);
timer.stop("calculate costs from depot");
let mut houses = HashMap::new(); let mut houses = HashMap::new();
let mut batch = GeomBatch::new();
timer.start_iter("assign score to houses", app.map.all_buildings().len());
for b in app.map.all_buildings() { for b in app.map.all_buildings() {
timer.next();
if let BuildingType::Residential(_) = b.bldg_type { if let BuildingType::Residential(_) = b.bldg_type {
let score = b.id.0; let score = b.id.0;
let cost = house_costs.get(&b.id).cloned().unwrap_or(Duration::ZERO); houses.insert(b.id, BldgState::Undelivered(score));
let color = if cost < Duration::minutes(5) {
Color::GREEN
} else if cost < Duration::minutes(15) {
Color::YELLOW
} else {
Color::RED
};
houses.insert(b.id, BldgState::Undelivered { score, cost });
// TODO Very expensive
batch.append(
Text::from_multiline(vec![
Line(format!("{}", score)),
Line(format!("{}", cost)).fg(color),
])
.render_to_batch(ctx.prerender)
.scale(0.1)
.centered_on(b.label_center),
);
} else if !b.amenities.is_empty() { } else if !b.amenities.is_empty() {
// TODO Maybe just food? // TODO Maybe just food?
houses.insert(b.id, BldgState::Depot); houses.insert(b.id, BldgState::Depot);
@ -350,51 +326,100 @@ impl SleighState {
max_energy: Duration::minutes(90), max_energy: Duration::minutes(90),
upzone_rate: 30_000, upzone_rate: 30_000,
}; };
let energy = config.max_energy;
let mut s = SleighState { let mut s = SleighState {
depot,
score: 0,
upzones_used: 0,
energy: config.max_energy,
houses,
draw_scores: ctx.upload(batch),
draw_done: Drawable::empty(ctx),
config, config,
score: 0,
energy,
houses,
depot,
cost_to_house: HashMap::new(),
upzones_used: 0,
upzoned_depots: Vec::new(), upzoned_depots: Vec::new(),
draw_all_depots: Drawable::empty(ctx), draw_all_depots: Drawable::empty(ctx),
draw_todo_houses: Drawable::empty(ctx),
draw_done_houses: Drawable::empty(ctx),
}; };
s.redraw(ctx, app);
s.redraw_depots(ctx, app); s.recalc_depots(ctx, app);
s.recalc_deliveries(ctx, app);
s s
} }
fn redraw(&mut self, ctx: &mut EventCtx, app: &SimpleApp) { fn recalc_depots(&mut self, ctx: &mut EventCtx, app: &SimpleApp) {
let mut batch = GeomBatch::new(); let mut batch = GeomBatch::new();
for (b, state) in &self.houses {
if let BldgState::Done = state {
batch.push(Color::BLACK, app.map.get_b(*b).polygon.clone());
}
}
// TODO This doesnt seem to be working
for b in &self.upzoned_depots { for b in &self.upzoned_depots {
batch.push( batch.push(
app.cs.commerical_building, app.cs.commerical_building,
app.map.get_b(*b).polygon.clone(), app.map.get_b(*b).polygon.clone(),
); );
} }
batch.push(Color::GREEN, app.map.get_b(self.depot).polygon.clone()); batch.append(
self.draw_done = ctx.upload(batch); GeomBatch::load_svg(ctx.prerender, "system/assets/tools/star.svg")
} .centered_on(app.map.get_b(self.depot).label_center)
.color(RewriteColor::ChangeAll(Color::YELLOW)),
);
fn redraw_depots(&mut self, ctx: &mut EventCtx, app: &SimpleApp) { self.cost_to_house = map_model::connectivity::all_costs_from(
&app.map,
self.depot,
Duration::hours(3),
PathConstraints::Pedestrian,
);
let worst_duration = Duration::minutes(15);
let cost_scale =
DivergingScale::new(Color::hex("#5D9630"), Color::WHITE, Color::hex("#A32015"))
.range(0.0, worst_duration.inner_seconds());
for b in app.map.all_buildings() {
match self.houses.get(&b.id) {
Some(BldgState::Undelivered(_)) => {
if let Some(cost) = self.cost_to_house.get(&b.id) {
let color = cost_scale.eval(cost.inner_seconds()).unwrap();
batch.push(color, b.polygon.clone());
continue;
}
}
Some(BldgState::Depot) => continue,
_ => {}
}
// If the house isn't reachable at all or it's not a depot or residence, just blank it
// out
batch.push(Color::BLACK, b.polygon.clone());
}
self.draw_todo_houses = ctx.upload(batch);
// Now highlight all depots for when we run out
let mut batch = GeomBatch::new(); let mut batch = GeomBatch::new();
for b in &self.upzoned_depots {
batch.push(Color::YELLOW, app.map.get_b(*b).polygon.clone());
}
for (b, state) in &self.houses { for (b, state) in &self.houses {
if let BldgState::Depot = state { if let BldgState::Depot = state {
batch.push(Color::RED, app.map.get_b(*b).polygon.clone()); batch.push(Color::YELLOW, app.map.get_b(*b).polygon.clone());
} }
} }
self.draw_all_depots = ctx.upload(batch); self.draw_all_depots = ctx.upload(batch);
} }
fn recalc_deliveries(&mut self, ctx: &mut EventCtx, app: &SimpleApp) {
let mut batch = GeomBatch::new();
for (b, state) in &self.houses {
if let BldgState::Done = state {
batch.push(Color::BLACK, app.map.get_b(*b).polygon.clone());
}
}
self.draw_done_houses = ctx.upload(batch);
}
// If something changed, return the update to the score // If something changed, return the update to the score
fn present_dropped( fn present_dropped(
&mut self, &mut self,
@ -402,28 +427,23 @@ impl SleighState {
app: &SimpleApp, app: &SimpleApp,
id: BuildingID, id: BuildingID,
) -> Option<usize> { ) -> Option<usize> {
if let Some(BldgState::Undelivered { score, cost }) = self.houses.get(&id).cloned() { if let Some(BldgState::Undelivered(score)) = self.houses.get(&id).cloned() {
self.score += score; if let Some(cost) = self.cost_to_house.get(&id) {
self.houses.insert(id, BldgState::Done); self.score += score;
self.energy -= cost; self.houses.insert(id, BldgState::Done);
self.redraw(ctx, app); self.energy -= *cost;
return Some(score); self.recalc_deliveries(ctx, app);
return Some(score);
}
} }
None None
} }
// True if state change // True if state change
fn recharge( fn recharge(&mut self, id: BuildingID, dt: Duration) -> bool {
&mut self,
ctx: &mut EventCtx,
app: &SimpleApp,
id: BuildingID,
dt: Duration,
) -> bool {
if let Some(BldgState::Depot) = self.houses.get(&id) { if let Some(BldgState::Depot) = self.houses.get(&id) {
self.energy += self.config.recharge_rate * dt; self.energy += self.config.recharge_rate * dt;
self.energy = self.energy.min(self.config.max_energy); self.energy = self.energy.min(self.config.max_energy);
self.redraw(ctx, app);
return true; return true;
} }
false false
@ -448,7 +468,8 @@ impl SleighState {
#[derive(Clone)] #[derive(Clone)]
enum BldgState { enum BldgState {
Undelivered { score: usize, cost: Duration }, // Score
Undelivered(usize),
Depot, Depot,
Done, Done,
} }
@ -474,11 +495,20 @@ impl OverBldg {
None None
} }
fn value(ctx: &mut EventCtx, app: &SimpleApp, key: BuildingID) -> OverBldg { fn value(ctx: &mut EventCtx, app: &SimpleApp, key: BuildingID, is_depot: bool) -> OverBldg {
OverBldg(ctx.upload(GeomBatch::from(vec![( let mut batch = GeomBatch::new();
Color::YELLOW, // We only want to highlight when we're hovering over a depot and could recharge
app.map.get_b(key).polygon.clone(), if is_depot {
)]))) let polygon = &app.map.get_b(key).polygon;
batch.push(
Color::YELLOW,
polygon
.to_outline(Distance::meters(0.5))
.unwrap_or_else(|_| polygon.clone()),
);
}
OverBldg(ctx.upload(batch))
} }
} }

View File

@ -8,9 +8,7 @@ fn main() {
let mut opts = map_gui::options::Options::default(); let mut opts = map_gui::options::Options::default();
opts.color_scheme = map_gui::colors::ColorSchemeChoice::NightMode; opts.color_scheme = map_gui::colors::ColorSchemeChoice::NightMode;
let app = map_gui::SimpleApp::new_with_opts(ctx, abstutil::CmdArgs::new(), opts); let app = map_gui::SimpleApp::new_with_opts(ctx, abstutil::CmdArgs::new(), opts);
let states = ctx.loading_screen("setup", |ctx, mut timer| { let states = vec![game::Game::new(ctx, &app)];
vec![game::Game::new(ctx, &app, &mut timer)]
});
(app, states) (app, states)
}); });
} }

View File

@ -304,10 +304,10 @@ impl SimpleMinimap {
} }
pub fn draw(&self, g: &mut GfxCtx, app: &SimpleApp) { pub fn draw(&self, g: &mut GfxCtx, app: &SimpleApp) {
self.draw_with_extra_layer(g, app, None); self.draw_with_extra_layers(g, app, Vec::new());
} }
pub fn draw_with_extra_layer(&self, g: &mut GfxCtx, app: &SimpleApp, extra: Option<&Drawable>) { pub fn draw_with_extra_layers(&self, g: &mut GfxCtx, app: &SimpleApp, extra: Vec<&Drawable>) {
self.panel.draw(g); self.panel.draw(g);
if !self.zoomed { if !self.zoomed {
return; return;
@ -334,7 +334,7 @@ impl SimpleMinimap {
g.redraw(&app.draw_map.draw_all_unzoomed_parking_lots); g.redraw(&app.draw_map.draw_all_unzoomed_parking_lots);
g.redraw(&app.draw_map.draw_all_unzoomed_roads_and_intersections); g.redraw(&app.draw_map.draw_all_unzoomed_roads_and_intersections);
g.redraw(&app.draw_map.draw_all_buildings); g.redraw(&app.draw_map.draw_all_buildings);
if let Some(draw) = extra { for draw in extra {
g.redraw(draw); g.redraw(draw);
} }
// Not the building or parking lot paths // Not the building or parking lot paths