mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-11-24 09:24:26 +03:00
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:
parent
544dda9c5c
commit
23222bd846
91
experiment/src/buildings.rs
Normal file
91
experiment/src/buildings.rs
Normal 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
|
||||
}
|
||||
}
|
@ -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),
|
||||
|
@ -2,6 +2,7 @@
|
||||
extern crate log;
|
||||
|
||||
mod animation;
|
||||
mod buildings;
|
||||
mod controls;
|
||||
mod game;
|
||||
mod levels;
|
||||
|
@ -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])
|
||||
}),
|
||||
));
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user