diff --git a/experiment/src/after_level.rs b/experiment/src/after_level.rs index b62192b122..059cec2a2b 100644 --- a/experiment/src/after_level.rs +++ b/experiment/src/after_level.rs @@ -1,43 +1,46 @@ use abstutil::prettyprint_usize; -use map_gui::tools::PopupMsg; +use map_gui::tools::{ColorLegend, PopupMsg}; use widgetry::{ - Btn, EventCtx, GfxCtx, HorizontalAlignment, Key, Line, Outcome, Panel, State, Text, - VerticalAlignment, Widget, + Btn, Color, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, Line, Outcome, + Panel, State, Text, VerticalAlignment, Widget, }; +use crate::buildings::{BldgState, Buildings}; use crate::levels::Level; use crate::title::TitleScreen; use crate::{App, Transition}; const ZOOM: f64 = 2.0; -// TODO Display route, the buildings still undelivered, etc. Let the player strategize about how to -// do better. - -pub struct Results { +pub struct Strategize { panel: Panel, unlock_messages: Option>, + draw_all: Drawable, } -impl Results { +impl Strategize { pub fn new( ctx: &mut EventCtx, app: &mut App, score: usize, level: &Level, + bldgs: &Buildings, ) -> Box> { ctx.canvas.cam_zoom = ZOOM; - ctx.canvas.center_on_map_pt(app.map.get_bounds().center()); + let start = app + .map + .get_i(app.map.find_i_by_osm_id(level.start).unwrap()) + .polygon + .center(); + ctx.canvas.center_on_map_pt(start); let unlock_messages = app.session.record_score(level.title.clone(), score); let mut txt = Text::new(); txt.add(Line(format!("Results for {}", level.title)).small_heading()); txt.add(Line(format!( - "You delivered {} presents in {}. Your goal was {}", - prettyprint_usize(score), - level.time_limit, - prettyprint_usize(level.goal) + "You delivered {} presents", + prettyprint_usize(score) ))); txt.add(Line("")); txt.add(Line("High scores:")); @@ -45,20 +48,71 @@ impl Results { txt.add(Line(format!("{}) {}", idx + 1, prettyprint_usize(*score)))); } + // Partly duplicated with Buildings::new, but we want to label upzones and finished houses + // differently + let mut batch = GeomBatch::new(); + for b in app.map.all_buildings() { + match bldgs.buildings[&b.id] { + BldgState::Undelivered(num_housing_units) => { + batch.push( + if num_housing_units > 5 { + app.session.colors.apartment + } else { + app.session.colors.house + }, + b.polygon.clone(), + ); + if num_housing_units > 1 { + 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), + ); + } + } + BldgState::Store => { + batch.push( + if bldgs.upzones.contains(&b.id) { + Color::PINK + } else { + app.session.colors.store + }, + b.polygon.clone(), + ); + } + BldgState::Done => { + batch.push(Color::RED, b.polygon.clone()); + } + BldgState::Ignore => { + batch.push(app.session.colors.visited, b.polygon.clone()); + } + } + } + let panel = Panel::new(Widget::col(vec![ txt.draw(ctx), Btn::text_bg2("Back to title screen").build_def(ctx, Key::Enter), + Widget::row(vec![ + ColorLegend::row(ctx, app.session.colors.house, "house"), + ColorLegend::row(ctx, app.session.colors.apartment, "apartment"), + ColorLegend::row(ctx, app.session.colors.store, "store"), + ColorLegend::row(ctx, Color::PINK, "upzoned store"), + ColorLegend::row(ctx, Color::RED, "delivered!"), + ]) + .evenly_spaced(), ])) - .aligned(HorizontalAlignment::Center, VerticalAlignment::Top) + .aligned(HorizontalAlignment::Right, VerticalAlignment::Top) .build(ctx); - Box::new(Results { + Box::new(Strategize { panel, unlock_messages, + draw_all: ctx.upload(batch), }) } } -impl State for Results { +impl State for Strategize { fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition { ctx.canvas_movement(); @@ -88,6 +142,77 @@ impl State for Results { Transition::Keep } + fn draw(&self, g: &mut GfxCtx, app: &App) { + g.redraw(&self.draw_all); + self.panel.draw(g); + app.session.music.draw(g); + } +} + +pub struct Results { + panel: Panel, +} + +impl Results { + pub fn new( + ctx: &mut EventCtx, + app: &mut App, + score: usize, + level: &Level, + ) -> Box> { + let mut txt = Text::new(); + if score < level.goal { + txt.add(Line("Not quite...").small_heading()); + txt.add(Line(format!( + "You only delivered {} / {} presents", + prettyprint_usize(score), + prettyprint_usize(level.goal) + ))); + txt.add(Line("Review your route and try again.")); + } else { + txt.add(Line("Thank you, Santa!").small_heading()); + txt.add(Line(format!( + "You delivered {} presents, more than the goal of {}!", + prettyprint_usize(score), + prettyprint_usize(level.goal) + ))); + let high_score = app.session.high_scores[&level.title][0]; + if high_score == score { + txt.add(Line("Wow, a new high score!")); + } else { + txt.add(Line(format!( + "But can you beat the high score of {}?", + prettyprint_usize(high_score) + ))); + } + } + + let panel = Panel::new(Widget::col(vec![ + txt.draw(ctx), + Btn::text_bg2("OK").build_def(ctx, Key::Enter), + ])) + .build(ctx); + Box::new(Results { panel }) + } +} + +impl State for Results { + fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition { + match self.panel.event(ctx) { + Outcome::Clicked(x) => match x.as_ref() { + "OK" => { + return Transition::Pop; + } + _ => unreachable!(), + }, + _ => {} + } + + app.session.update_music(ctx); + + Transition::Keep + } + fn draw(&self, g: &mut GfxCtx, app: &App) { self.panel.draw(g); app.session.music.draw(g); diff --git a/experiment/src/buildings.rs b/experiment/src/buildings.rs index f4016b1892..f8b6bac0ca 100644 --- a/experiment/src/buildings.rs +++ b/experiment/src/buildings.rs @@ -12,6 +12,7 @@ pub struct Buildings { // could even replace the one in DrawMap. pub draw_all: Drawable, pub total_housing_units: usize, + pub upzones: HashSet, } #[derive(Clone)] @@ -19,8 +20,9 @@ pub enum BldgState { // Score Undelivered(usize), Store, - // Or not a relevant building Done, + // Not a relevant building + Ignore, } impl Buildings { @@ -54,7 +56,6 @@ impl Buildings { 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) @@ -72,7 +73,7 @@ impl Buildings { } // If it's not a residence or store, just blank it out. - buildings.insert(b.id, BldgState::Done); + buildings.insert(b.id, BldgState::Ignore); batch.push(colors.visited, b.polygon.clone()); } @@ -80,6 +81,7 @@ impl Buildings { buildings, draw_all: ctx.upload(batch), total_housing_units, + upzones, } } diff --git a/experiment/src/game.rs b/experiment/src/game.rs index 88ab6e525e..586b79433c 100644 --- a/experiment/src/game.rs +++ b/experiment/src/game.rs @@ -9,7 +9,7 @@ use widgetry::{ Outcome, Panel, State, Text, TextExt, UpdateType, VerticalAlignment, Widget, }; -use crate::after_level::Results; +use crate::after_level::{Results, Strategize}; use crate::animation::{Animator, Effect, SnowEffect}; use crate::buildings::{BldgState, Buildings}; use crate::levels::Level; @@ -278,7 +278,7 @@ impl Game { ); } } - BldgState::Done => {} + BldgState::Done | BldgState::Ignore => {} } } if !met_goal && self.state.met_goal() { @@ -335,12 +335,16 @@ impl State for Game { } if self.animator.is_done() { - return Transition::Replace(Results::new( - ctx, - app, - self.state.score, - &self.state.level, - )); + return Transition::Multi(vec![ + Transition::Replace(Strategize::new( + ctx, + app, + self.state.score, + &self.state.level, + &self.state.bldgs, + )), + Transition::Push(Results::new(ctx, app, self.state.score, &self.state.level)), + ]); } ctx.request_update(UpdateType::Game); diff --git a/experiment/src/levels.rs b/experiment/src/levels.rs index 3928e000ea..d32826711f 100644 --- a/experiment/src/levels.rs +++ b/experiment/src/levels.rs @@ -21,6 +21,20 @@ pub struct Level { impl Level { pub fn all() -> Vec { vec![ + Level { + title: "Montlake".to_string(), + description: "With the Montlake Market closed, how will you manage to bring cheer \ + to this sleepy little pocket of the city?" + .to_string(), + map: MapName::seattle("montlake"), + start: osm::NodeID(53084814), + minimap_zoom: 1, + time_limit: Duration::seconds(5.0), + goal: 20, + + unlock_upzones: 2, + unlock_vehicles: vec![], + }, Level { title: "University District".to_string(), description: "Tear yourself away from all the bubble tea to deliver presents to \ @@ -65,20 +79,6 @@ impl Level { unlock_upzones: 2, unlock_vehicles: vec![], }, - Level { - title: "Montlake".to_string(), - description: "With the Montlake Market closed, how will you manage to bring cheer \ - to this sleepy little pocket of the city?" - .to_string(), - map: MapName::seattle("montlake"), - start: osm::NodeID(53084814), - minimap_zoom: 1, - time_limit: Duration::seconds(5.0), - goal: 25, - - unlock_upzones: 2, - unlock_vehicles: vec![], - }, Level { title: "Magnolia".to_string(), description: "Struggle past the intense hills and restrictive zoning to tackle \