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

View File

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

View File

@ -5,7 +5,8 @@ use crate::sandbox::overlays::Overlays;
use crate::ui::UI;
use ezgui::{hotkey, EventCtx, Key, ModalMenu};
use geom::{Duration, Statistic, Time};
use sim::TripMode;
use map_model::{IntersectionID, Map};
use sim::{BorderSpawnOverTime, OriginDestination, Scenario, TripMode};
pub struct FixTrafficSignals {
time: Time,
@ -122,3 +123,78 @@ fn final_score(ui: &UI) -> Vec<String> {
}
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
FasterTrips(TripMode),
FixTrafficSignals,
FixTrafficSignalsTutorial,
}
pub trait GameplayState: downcast_rs::Downcast {
@ -55,6 +56,9 @@ impl GameplayMode {
return None;
}
GameplayMode::PlayScenario(ref scenario) => scenario,
GameplayMode::FixTrafficSignalsTutorial => {
return Some(fix_traffic_signals::tutorial_scenario(&ui.primary.map));
}
_ => "weekday_typical_traffic_from_psrc",
};
let num_agents = ui.primary.current_flags.num_agents;
@ -84,14 +88,14 @@ impl GameplayMode {
pub fn can_edit_lanes(&self) -> bool {
match self {
GameplayMode::FixTrafficSignals => false,
GameplayMode::FixTrafficSignals | GameplayMode::FixTrafficSignalsTutorial => false,
_ => true,
}
}
pub fn can_edit_stop_signs(&self) -> bool {
match self {
GameplayMode::FixTrafficSignals => false,
GameplayMode::FixTrafficSignals | GameplayMode::FixTrafficSignalsTutorial => false,
_ => true,
}
}
@ -130,7 +134,9 @@ impl GameplayRunner {
}
GameplayMode::CreateGridlock => create_gridlock::CreateGridlock::new(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| {
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)
.collect()
}) {
ui.switch_map(ctx, &name);
ui.switch_map(ctx, abstutil::path_map(&name));
Some(Transition::PopThenReplace(Box::new(SandboxMode::new(
ctx,
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());
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);
}

View File

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