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 {
|
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,
|
||||||
|
@ -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)
|
||||||
|
@ -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)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)]
|
||||||
|
Loading…
Reference in New Issue
Block a user