squeezing in a challenge for the first traffic signal tutorial.

prototyping some new abstractions for specifying demand.
This commit is contained in:
Dustin Carlino 2019-12-04 15:12:18 -08:00
parent e308fa666b
commit e4ab4739df
6 changed files with 121 additions and 29 deletions

View File

@ -16,13 +16,19 @@ use sim::{SimFlags, SimOptions, TripMode};
struct Challenge { struct Challenge {
title: String, title: String,
description: Vec<String>, description: Vec<String>,
map_name: String, map_path: String,
gameplay: GameplayMode, gameplay: GameplayMode,
} }
impl abstutil::Cloneable for Challenge {} impl abstutil::Cloneable for Challenge {}
fn all_challenges() -> Vec<Challenge> { fn all_challenges() -> Vec<Challenge> {
vec![ vec![
Challenge {
title: "Traffic signal tutorial level 1".to_string(),
description: vec!["No description".to_string()],
map_path: abstutil::path_synthetic_map("traffic sig lvl1"),
gameplay: GameplayMode::FixTrafficSignalsTutorial,
},
Challenge { Challenge {
title: "Fix all of the traffic signals".to_string(), title: "Fix all of the traffic signals".to_string(),
description: vec![ description: vec![
@ -32,7 +38,7 @@ fn all_challenges() -> Vec<Challenge> {
"".to_string(), "".to_string(),
"Objective: Reduce the 50%ile trip time of all drivers by at least 30s".to_string() "Objective: Reduce the 50%ile trip time of all drivers by at least 30s".to_string()
], ],
map_name: "montlake".to_string(), map_path: abstutil::path_map("montlake"),
gameplay: GameplayMode::FixTrafficSignals, gameplay: GameplayMode::FixTrafficSignals,
}, },
Challenge { Challenge {
@ -40,7 +46,7 @@ fn all_challenges() -> Vec<Challenge> {
description: vec![ description: vec![
"Decrease the average waiting time between all of route 48's stops by at least 30s" "Decrease the average waiting time between all of route 48's stops by at least 30s"
.to_string()], .to_string()],
map_name: "montlake".to_string(), map_path: abstutil::path_map("montlake"),
gameplay: GameplayMode::OptimizeBus("48".to_string()), gameplay: GameplayMode::OptimizeBus("48".to_string()),
}, },
Challenge { Challenge {
@ -48,26 +54,26 @@ fn all_challenges() -> Vec<Challenge> {
description: vec![ description: vec![
"Decrease the average waiting time between all of 48's stops by at least 30s" "Decrease the average waiting time between all of 48's stops by at least 30s"
.to_string()], .to_string()],
map_name: "23rd".to_string(), map_path: abstutil::path_map("23rd"),
gameplay: GameplayMode::OptimizeBus("48".to_string()), gameplay: GameplayMode::OptimizeBus("48".to_string()),
}, },
Challenge { Challenge {
title: "Gridlock all of the everything".to_string(), title: "Gridlock all of the everything".to_string(),
description: vec!["Make traffic as BAD as possible!".to_string()], description: vec!["Make traffic as BAD as possible!".to_string()],
map_name: "montlake".to_string(), map_path: abstutil::path_map("montlake"),
gameplay: GameplayMode::CreateGridlock, gameplay: GameplayMode::CreateGridlock,
}, },
Challenge { Challenge {
title: "Speed up all bike trips".to_string(), title: "Speed up all bike trips".to_string(),
description: vec!["Reduce the 50%ile trip times of bikes by at least 1 minute".to_string()], description: vec!["Reduce the 50%ile trip times of bikes by at least 1 minute".to_string()],
map_name: "montlake".to_string(), map_path: abstutil::path_map("montlake"),
gameplay: GameplayMode::FasterTrips(TripMode::Bike), gameplay: GameplayMode::FasterTrips(TripMode::Bike),
}, },
Challenge { Challenge {
title: "Speed up all car trips".to_string(), title: "Speed up all car trips".to_string(),
description: vec!["Reduce the 50%ile trip times of drivers by at least 5 minutes" description: vec!["Reduce the 50%ile trip times of drivers by at least 5 minutes"
.to_string()], .to_string()],
map_name: "montlake".to_string(), map_path: abstutil::path_map("montlake"),
gameplay: GameplayMode::FasterTrips(TripMode::Drive), gameplay: GameplayMode::FasterTrips(TripMode::Drive),
}, },
] ]
@ -101,7 +107,9 @@ pub fn challenges_picker(ctx: &EventCtx) -> Box<dyn State> {
let mut flex_row = Vec::new(); let mut flex_row = Vec::new();
for challenge in all_challenges() { for challenge in all_challenges() {
let edits = abstutil::list_all_objects(abstutil::path_all_edits(&challenge.map_name)); let edits = abstutil::list_all_objects(abstutil::path_all_edits(&abstutil::basename(
&challenge.map_path,
)));
let mut txt = Text::new(); let mut txt = Text::new();
txt.add(Line(&challenge.title).size(40).fg(Color::BLACK)); txt.add(Line(&challenge.title).size(40).fg(Color::BLACK));
@ -116,8 +124,9 @@ pub fn challenges_picker(ctx: &EventCtx) -> Box<dyn State> {
txt, txt,
None, None,
Box::new(move |ctx, _| { Box::new(move |ctx, _| {
let edits = let edits = abstutil::list_all_objects(abstutil::path_all_edits(
abstutil::list_all_objects(abstutil::path_all_edits(&challenge.map_name)); &abstutil::basename(&challenge.map_path),
));
let mut summary = Text::new(); let mut summary = Text::new();
for l in &challenge.description { for l in &challenge.description {
summary.add(Line(l)); summary.add(Line(l));
@ -163,20 +172,22 @@ impl State for ChallengeSplash {
return Transition::Pop; return Transition::Pop;
} }
if self.menu.action("load existing proposal") { if self.menu.action("load existing proposal") {
let map_name = self.challenge.map_name.clone(); let map_path = self.challenge.map_path.clone();
let gameplay = self.challenge.gameplay.clone(); let gameplay = self.challenge.gameplay.clone();
return Transition::Push(WizardState::new(Box::new(move |wiz, ctx, ui| { return Transition::Push(WizardState::new(Box::new(move |wiz, ctx, ui| {
let mut wizard = wiz.wrap(ctx); let mut wizard = wiz.wrap(ctx);
let (_, new_edits) = wizard.choose("Load which map edits?", || { let (_, new_edits) = wizard.choose("Load which map edits?", || {
Choice::from( Choice::from(
abstutil::load_all_objects(abstutil::path_all_edits(&map_name)) abstutil::load_all_objects(abstutil::path_all_edits(&abstutil::basename(
.into_iter() &map_path,
.filter(|(_, edits)| gameplay.allows(edits)) )))
.collect(), .into_iter()
.filter(|(_, edits)| gameplay.allows(edits))
.collect(),
) )
})?; })?;
if &map_name != ui.primary.map.get_name() { if &abstutil::basename(&map_path) != ui.primary.map.get_name() {
ui.switch_map(ctx, &map_name); ui.switch_map(ctx, map_path.clone());
} }
apply_map_edits(&mut ui.primary, &ui.cs, ctx, new_edits); apply_map_edits(&mut ui.primary, &ui.cs, ctx, new_edits);
ui.primary.map.mark_edits_fresh(); ui.primary.map.mark_edits_fresh();
@ -191,8 +202,8 @@ impl State for ChallengeSplash {
}))); })));
} }
if self.menu.action("start challenge fresh") { if self.menu.action("start challenge fresh") {
if &self.challenge.map_name != ui.primary.map.get_name() { if &abstutil::basename(&self.challenge.map_path) != ui.primary.map.get_name() {
ui.switch_map(ctx, &self.challenge.map_name); ui.switch_map(ctx, self.challenge.map_path.clone());
} }
return Transition::Replace(Box::new(SandboxMode::new( return Transition::Replace(Box::new(SandboxMode::new(
ctx, ctx,

View File

@ -81,8 +81,7 @@ pub fn open_panel() -> Box<dyn State> {
if ui.opts.color_scheme != color_scheme { if ui.opts.color_scheme != color_scheme {
ui.opts.color_scheme = color_scheme.clone(); ui.opts.color_scheme = color_scheme.clone();
let map_name = ui.primary.map.get_name().clone(); ui.switch_map(ctx, ui.primary.current_flags.sim_flags.load.clone());
ui.switch_map(ctx, &map_name);
} }
Some(Transition::Pop) Some(Transition::Pop)

View File

@ -5,7 +5,8 @@ use crate::sandbox::overlays::Overlays;
use crate::ui::UI; use crate::ui::UI;
use ezgui::{hotkey, EventCtx, Key, ModalMenu}; use ezgui::{hotkey, EventCtx, Key, ModalMenu};
use geom::{Duration, Statistic, Time}; use geom::{Duration, Statistic, Time};
use sim::TripMode; use map_model::{IntersectionID, Map};
use sim::{BorderSpawnOverTime, OriginDestination, Scenario, TripMode};
pub struct FixTrafficSignals { pub struct FixTrafficSignals {
time: Time, time: Time,
@ -122,3 +123,78 @@ fn final_score(ui: &UI) -> Vec<String> {
} }
lines lines
} }
// TODO Hacks in here, because I'm not convinced programatically specifying this is right. I think
// the Scenario abstractions and UI need to change to make this convenient to express in JSON / the
// UI.
pub fn tutorial_scenario(map: &Map) -> Scenario {
// TODO In lieu of the deleted labels
let north = IntersectionID(4);
let south = IntersectionID(2);
// Hush, east/west is more cognitive overhead for me. >_<
let left = IntersectionID(1);
let right = IntersectionID(0);
// Motivate a separate left turn phase for north/south, but not left/right
let mut s = Scenario::empty(map);
// What's the essence of what I've specified below? Don't care about the time distribution,
// exact number of agents, different modes. It's just an OD matrix with relative weights.
//
// north south left right
// north 0 3 1 2
// south 3 ... and so on
// left
// right
//
// The table isn't super easy to grok. But it motivates the UI for entering this info:
//
// 1) Select all of the sources
// 2) Select all of the sinks (option to use the same set)
// 3) For each (src, sink) pair, ask (none, light, medium, heavy)
// Arterial straight
heavy(&mut s, map, south, north);
heavy(&mut s, map, north, south);
// Arterial left turns
medium(&mut s, map, south, left);
medium(&mut s, map, north, right);
// Arterial right turns
light(&mut s, map, south, right);
light(&mut s, map, north, left);
// Secondary straight
medium(&mut s, map, left, right);
medium(&mut s, map, right, left);
// Secondary right turns
medium(&mut s, map, left, south);
medium(&mut s, map, right, north);
// Secondary left turns
light(&mut s, map, left, north);
light(&mut s, map, right, south);
s
}
fn heavy(s: &mut Scenario, map: &Map, from: IntersectionID, to: IntersectionID) {
spawn(s, map, from, to, 100);
}
fn medium(s: &mut Scenario, map: &Map, from: IntersectionID, to: IntersectionID) {
spawn(s, map, from, to, 100);
}
fn light(s: &mut Scenario, map: &Map, from: IntersectionID, to: IntersectionID) {
spawn(s, map, from, to, 100);
}
fn spawn(s: &mut Scenario, map: &Map, from: IntersectionID, to: IntersectionID, num_cars: usize) {
s.border_spawn_over_time.push(BorderSpawnOverTime {
num_peds: 0,
num_cars,
num_bikes: 0,
percent_use_transit: 0.0,
start_time: Time::START_OF_DAY,
stop_time: Time::START_OF_DAY + Duration::minutes(5),
start_from_border: map.get_i(from).some_outgoing_road(map),
goal: OriginDestination::EndOfRoad(map.get_i(to).some_incoming_road(map)),
});
}

View File

@ -34,6 +34,7 @@ pub enum GameplayMode {
// TODO Be able to filter population by more factors // TODO Be able to filter population by more factors
FasterTrips(TripMode), FasterTrips(TripMode),
FixTrafficSignals, FixTrafficSignals,
FixTrafficSignalsTutorial,
} }
pub trait GameplayState: downcast_rs::Downcast { pub trait GameplayState: downcast_rs::Downcast {
@ -55,6 +56,9 @@ impl GameplayMode {
return None; return None;
} }
GameplayMode::PlayScenario(ref scenario) => scenario, GameplayMode::PlayScenario(ref scenario) => scenario,
GameplayMode::FixTrafficSignalsTutorial => {
return Some(fix_traffic_signals::tutorial_scenario(&ui.primary.map));
}
_ => "weekday_typical_traffic_from_psrc", _ => "weekday_typical_traffic_from_psrc",
}; };
let num_agents = ui.primary.current_flags.num_agents; let num_agents = ui.primary.current_flags.num_agents;
@ -84,14 +88,14 @@ impl GameplayMode {
pub fn can_edit_lanes(&self) -> bool { pub fn can_edit_lanes(&self) -> bool {
match self { match self {
GameplayMode::FixTrafficSignals => false, GameplayMode::FixTrafficSignals | GameplayMode::FixTrafficSignalsTutorial => false,
_ => true, _ => true,
} }
} }
pub fn can_edit_stop_signs(&self) -> bool { pub fn can_edit_stop_signs(&self) -> bool {
match self { match self {
GameplayMode::FixTrafficSignals => false, GameplayMode::FixTrafficSignals | GameplayMode::FixTrafficSignalsTutorial => false,
_ => true, _ => true,
} }
} }
@ -130,7 +134,9 @@ impl GameplayRunner {
} }
GameplayMode::CreateGridlock => create_gridlock::CreateGridlock::new(ctx), GameplayMode::CreateGridlock => create_gridlock::CreateGridlock::new(ctx),
GameplayMode::FasterTrips(trip_mode) => faster_trips::FasterTrips::new(trip_mode, ctx), GameplayMode::FasterTrips(trip_mode) => faster_trips::FasterTrips::new(trip_mode, ctx),
GameplayMode::FixTrafficSignals => fix_traffic_signals::FixTrafficSignals::new(ctx), GameplayMode::FixTrafficSignals | GameplayMode::FixTrafficSignalsTutorial => {
fix_traffic_signals::FixTrafficSignals::new(ctx)
}
}; };
ctx.loading_screen("instantiate scenario", |_, timer| { ctx.loading_screen("instantiate scenario", |_, timer| {
if let Some(scenario) = mode.scenario(ui, timer) { if let Some(scenario) = mode.scenario(ui, timer) {
@ -197,7 +203,7 @@ fn load_map(wiz: &mut Wizard, ctx: &mut EventCtx, ui: &mut UI) -> Option<Transit
.filter(|n| n != current_map) .filter(|n| n != current_map)
.collect() .collect()
}) { }) {
ui.switch_map(ctx, &name); ui.switch_map(ctx, abstutil::path_map(&name));
Some(Transition::PopThenReplace(Box::new(SandboxMode::new( Some(Transition::PopThenReplace(Box::new(SandboxMode::new(
ctx, ctx,
ui, ui,

View File

@ -126,10 +126,10 @@ impl UI {
} }
} }
pub fn switch_map(&mut self, ctx: &mut EventCtx, name: &str) { pub fn switch_map(&mut self, ctx: &mut EventCtx, load: String) {
ctx.canvas.save_camera_state(self.primary.map.get_name()); ctx.canvas.save_camera_state(self.primary.map.get_name());
let mut flags = self.primary.current_flags.clone(); let mut flags = self.primary.current_flags.clone();
flags.sim_flags.load = abstutil::path_map(name); flags.sim_flags.load = load;
*self = UI::new(flags, self.opts.clone(), ctx, false); *self = UI::new(flags, self.opts.clone(), ctx, false);
} }

View File

@ -50,12 +50,12 @@ pub struct BorderSpawnOverTime {
pub num_peds: usize, pub num_peds: usize,
pub num_cars: usize, pub num_cars: usize,
pub num_bikes: usize, pub num_bikes: usize,
pub percent_use_transit: f64,
// TODO use https://docs.rs/rand/0.5.5/rand/distributions/struct.Normal.html // TODO use https://docs.rs/rand/0.5.5/rand/distributions/struct.Normal.html
pub start_time: Time, pub start_time: Time,
pub stop_time: Time, pub stop_time: Time,
pub start_from_border: DirectedRoadID, pub start_from_border: DirectedRoadID,
pub goal: OriginDestination, pub goal: OriginDestination,
pub percent_use_transit: f64,
} }
#[derive(Clone, Serialize, Deserialize, Debug)] #[derive(Clone, Serialize, Deserialize, Debug)]