Add a second meter to the experiment.

This commit is contained in:
Dustin Carlino 2020-11-26 16:47:12 -08:00
parent e589f89922
commit 62dc979677
3 changed files with 89 additions and 32 deletions

View File

@ -24,8 +24,6 @@ impl Controller for InstantController {
let mut dy = 0.0; let mut dy = 0.0;
if let Some(dt) = ctx.input.nonblocking_is_update_event() { if let Some(dt) = ctx.input.nonblocking_is_update_event() {
ctx.input.use_update_event();
let dist = (dt * self.speed).inner_meters(); let dist = (dt * self.speed).inner_meters();
if ctx.is_key_down(Key::LeftArrow) { if ctx.is_key_down(Key::LeftArrow) {
dx -= dist; dx -= dist;
@ -68,8 +66,6 @@ impl Controller for RotateController {
let mut dy = 0.0; let mut dy = 0.0;
if let Some(dt) = ctx.input.nonblocking_is_update_event() { if let Some(dt) = ctx.input.nonblocking_is_update_event() {
ctx.input.use_update_event();
if ctx.is_key_down(Key::LeftArrow) { if ctx.is_key_down(Key::LeftArrow) {
self.angle = self self.angle = self
.angle .angle

View File

@ -1,9 +1,10 @@
use std::collections::HashMap; use std::collections::HashMap;
use geom::{Circle, Distance, Pt2D, Speed}; use abstutil::Timer;
use geom::{Circle, Distance, Duration, Pt2D, Speed};
use map_gui::tools::{nice_map_name, CityPicker}; use map_gui::tools::{nice_map_name, CityPicker};
use map_gui::{Cached, SimpleApp, ID}; use map_gui::{Cached, SimpleApp, ID};
use map_model::{BuildingID, BuildingType}; use map_model::{BuildingID, BuildingType, PathConstraints};
use widgetry::{ use widgetry::{
lctrl, Btn, Checkbox, Color, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, lctrl, Btn, Checkbox, Color, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key,
Line, Outcome, Panel, State, Text, TextExt, Transition, UpdateType, VerticalAlignment, Widget, Line, Outcome, Panel, State, Text, TextExt, Transition, UpdateType, VerticalAlignment, Widget,
@ -16,14 +17,18 @@ pub struct Game {
controls: Box<dyn Controller>, controls: Box<dyn Controller>,
sleigh: Pt2D, sleigh: Pt2D,
score: Score, state: SleighState,
over_bldg: Cached<BuildingID, OverBldg>, over_bldg: Cached<BuildingID, OverBldg>,
} }
impl Game { impl Game {
pub fn new(ctx: &mut EventCtx, app: &SimpleApp) -> Box<dyn State<SimpleApp>> { pub fn new(
ctx: &mut EventCtx,
app: &SimpleApp,
timer: &mut Timer,
) -> Box<dyn State<SimpleApp>> {
// Start on a commerical building // Start on a commerical building
let sleigh = app let depot = app
.map .map
.all_buildings() .all_buildings()
.into_iter() .into_iter()
@ -31,9 +36,10 @@ impl Game {
BuildingType::Commercial(_) => true, BuildingType::Commercial(_) => true,
_ => false, _ => false,
}) })
.map(|b| b.label_center)
.unwrap(); .unwrap();
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);
Box::new(Game { Box::new(Game {
panel: Panel::new(Widget::col(vec![ panel: Panel::new(Widget::col(vec![
@ -47,14 +53,19 @@ impl Game {
Some(nice_map_name(app.map.get_name())), Some(nice_map_name(app.map.get_name())),
) )
.build(ctx, "change map", lctrl(Key::L))]), .build(ctx, "change map", lctrl(Key::L))]),
format!("Score: 0").draw_text(ctx).named("score"), format!("Score: {}", state.score)
.draw_text(ctx)
.named("score"),
format!("Energy: {}", state.energy)
.draw_text(ctx)
.named("energy"),
])) ]))
.aligned(HorizontalAlignment::Right, VerticalAlignment::Top) .aligned(HorizontalAlignment::Right, VerticalAlignment::Top)
.build(ctx), .build(ctx),
controls: Box::new(InstantController::new(Speed::miles_per_hour(30.0))), controls: Box::new(InstantController::new(Speed::miles_per_hour(30.0))),
sleigh, sleigh,
score: Score::new(ctx, app), state,
over_bldg: Cached::new(), over_bldg: Cached::new(),
}) })
} }
@ -63,7 +74,12 @@ impl Game {
self.panel.replace( self.panel.replace(
ctx, ctx,
"score", "score",
format!("Score: {}", abstutil::prettyprint_usize(self.score.score)).draw_text(ctx), format!("Score: {}", abstutil::prettyprint_usize(self.state.score)).draw_text(ctx),
);
self.panel.replace(
ctx,
"energy",
format!("Energy: {}", self.state.energy).draw_text(ctx),
); );
} }
} }
@ -76,20 +92,25 @@ impl State<SimpleApp> for Game {
ctx.canvas.center_on_map_pt(self.sleigh); ctx.canvas.center_on_map_pt(self.sleigh);
self.over_bldg self.over_bldg
.update(OverBldg::key(app, self.sleigh, &self.score), |key| { .update(OverBldg::key(app, self.sleigh, &self.state), |key| {
OverBldg::value(ctx, app, key) OverBldg::value(ctx, app, key)
}); });
} }
if let Some(b) = self.over_bldg.key() { if let Some(b) = self.over_bldg.key() {
if ctx.input.pressed(Key::Space) { if ctx.input.pressed(Key::Space) {
if self.score.present_dropped(ctx, app, b) { if self.state.present_dropped(ctx, app, b) {
self.over_bldg.clear(); self.over_bldg.clear();
self.update_panel(ctx); self.update_panel(ctx);
} }
} }
} }
if let Some(dt) = ctx.input.nonblocking_is_update_event() {
self.state.energy -= dt;
self.update_panel(ctx);
}
match self.panel.event(ctx) { match self.panel.event(ctx) {
Outcome::Clicked(x) => match x.as_ref() { Outcome::Clicked(x) => match x.as_ref() {
"close" => { "close" => {
@ -100,10 +121,12 @@ impl State<SimpleApp> for Game {
ctx, ctx,
app, app,
Box::new(|ctx, app| { Box::new(|ctx, app| {
Transition::Multi(vec![ ctx.loading_screen("setup again", |ctx, mut timer| {
Transition::Pop, Transition::Multi(vec![
Transition::Replace(Game::new(ctx, app)), Transition::Pop,
]) Transition::Replace(Game::new(ctx, app, &mut timer)),
])
})
}), }),
)); ));
} }
@ -126,8 +149,8 @@ impl State<SimpleApp> for Game {
fn draw(&self, g: &mut GfxCtx, _: &SimpleApp) { fn draw(&self, g: &mut GfxCtx, _: &SimpleApp) {
self.panel.draw(g); self.panel.draw(g);
g.redraw(&self.score.draw_scores); g.redraw(&self.state.draw_scores);
g.redraw(&self.score.draw_done); g.redraw(&self.state.draw_done);
if let Some(draw) = self.over_bldg.value() { if let Some(draw) = self.over_bldg.value() {
g.redraw(&draw.0); g.redraw(&draw.0);
} }
@ -138,33 +161,67 @@ impl State<SimpleApp> for Game {
} }
} }
struct Score { struct SleighState {
depot: BuildingID,
score: usize, score: usize,
energy: Duration,
houses: HashMap<BuildingID, BldgState>, houses: HashMap<BuildingID, BldgState>,
house_costs: HashMap<BuildingID, Duration>,
draw_scores: Drawable, draw_scores: Drawable,
draw_done: Drawable, draw_done: Drawable,
} }
impl Score { impl SleighState {
fn new(ctx: &mut EventCtx, app: &SimpleApp) -> Score { fn new(
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(); 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);
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)); houses.insert(b.id, BldgState::Undelivered(score));
batch.append( batch.append(
Text::from(Line(format!("{}", score))) Text::from_multiline(vec![
.render_to_batch(ctx.prerender) Line(format!("{}", score)),
.scale(0.1) Line(format!("{}", cost)).fg(color),
.centered_on(b.label_center), ])
.render_to_batch(ctx.prerender)
.scale(0.1)
.centered_on(b.label_center),
); );
} }
} }
Score { SleighState {
depot,
score: 0, score: 0,
energy: Duration::minutes(90),
houses, houses,
house_costs,
draw_scores: ctx.upload(batch), draw_scores: ctx.upload(batch),
draw_done: Drawable::empty(ctx), draw_done: Drawable::empty(ctx),
} }
@ -177,6 +234,7 @@ impl Score {
batch.push(Color::BLACK, app.map.get_b(*b).polygon.clone()); batch.push(Color::BLACK, app.map.get_b(*b).polygon.clone());
} }
} }
batch.push(Color::GREEN, app.map.get_b(self.depot).polygon.clone());
self.draw_done = ctx.upload(batch); self.draw_done = ctx.upload(batch);
} }
@ -185,6 +243,7 @@ impl Score {
if let Some(BldgState::Undelivered(score)) = self.houses.get(&id) { if let Some(BldgState::Undelivered(score)) = self.houses.get(&id) {
self.score += score; self.score += score;
self.houses.insert(id, BldgState::Done); self.houses.insert(id, BldgState::Done);
self.energy -= self.house_costs.get(&id).cloned().unwrap_or(Duration::ZERO);
self.redraw(ctx, app); self.redraw(ctx, app);
return true; return true;
} }
@ -201,14 +260,14 @@ enum BldgState {
struct OverBldg(Drawable); struct OverBldg(Drawable);
impl OverBldg { impl OverBldg {
fn key(app: &SimpleApp, sleigh: Pt2D, score: &Score) -> Option<BuildingID> { fn key(app: &SimpleApp, sleigh: Pt2D, state: &SleighState) -> Option<BuildingID> {
for id in app for id in app
.draw_map .draw_map
.get_matching_objects(Circle::new(sleigh, Distance::meters(3.0)).get_bounds()) .get_matching_objects(Circle::new(sleigh, Distance::meters(3.0)).get_bounds())
{ {
if let ID::Building(b) = id { if let ID::Building(b) = id {
if app.map.get_b(b).polygon.contains_pt(sleigh) { if app.map.get_b(b).polygon.contains_pt(sleigh) {
if let Some(BldgState::Undelivered(_)) = score.houses.get(&b) { if let Some(BldgState::Undelivered(_)) = state.houses.get(&b) {
return Some(b); return Some(b);
} }
} }

View File

@ -4,7 +4,9 @@ mod game;
fn main() { fn main() {
widgetry::run(widgetry::Settings::new("experiment"), |ctx| { widgetry::run(widgetry::Settings::new("experiment"), |ctx| {
let app = map_gui::SimpleApp::new(ctx, abstutil::CmdArgs::new()); let app = map_gui::SimpleApp::new(ctx, abstutil::CmdArgs::new());
let states = vec![game::Game::new(ctx, &app)]; let states = ctx.loading_screen("setup", |ctx, mut timer| {
vec![game::Game::new(ctx, &app, &mut timer)]
});
(app, states) (app, states)
}); });
} }