mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-11-28 12:12:00 +03:00
squeezing in a challenge for the first traffic signal tutorial.
prototyping some new abstractions for specifying demand.
This commit is contained in:
parent
e308fa666b
commit
e4ab4739df
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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)),
|
||||
});
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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)]
|
||||
|
Loading…
Reference in New Issue
Block a user