2020-03-02 20:51:20 +03:00
|
|
|
use crate::app::App;
|
2020-01-24 23:02:40 +03:00
|
|
|
use crate::common::Colorer;
|
2019-08-20 23:32:27 +03:00
|
|
|
use crate::game::{State, Transition};
|
|
|
|
use crate::helpers::ID;
|
2020-02-28 00:22:57 +03:00
|
|
|
use crate::managed::WrappedComposite;
|
|
|
|
use ezgui::{Color, Composite, EventCtx, GfxCtx, Key, Line, Outcome, Text};
|
2019-11-15 07:00:52 +03:00
|
|
|
use map_model::{connectivity, LaneID, Map, PathConstraints};
|
2019-09-05 21:36:32 +03:00
|
|
|
use std::collections::HashSet;
|
2019-08-20 23:32:27 +03:00
|
|
|
|
|
|
|
pub struct Floodfiller {
|
2020-02-28 00:22:57 +03:00
|
|
|
composite: Composite,
|
2020-01-03 19:30:02 +03:00
|
|
|
colorer: Colorer,
|
2019-08-20 23:32:27 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Floodfiller {
|
2020-03-02 20:51:20 +03:00
|
|
|
pub fn new(ctx: &mut EventCtx, app: &App) -> Option<Box<dyn State>> {
|
|
|
|
let map = &app.primary.map;
|
2019-11-15 07:00:52 +03:00
|
|
|
let (reachable_lanes, unreachable_lanes, title) =
|
2020-03-02 20:51:20 +03:00
|
|
|
if let Some(ID::Lane(l)) = app.primary.current_selection {
|
2019-11-15 07:00:52 +03:00
|
|
|
let lt = map.get_l(l).lane_type;
|
|
|
|
if !lt.supports_any_movement() {
|
|
|
|
return None;
|
|
|
|
}
|
2020-03-02 20:51:20 +03:00
|
|
|
if app.per_obj.action(ctx, Key::F, "floodfill from this lane") {
|
2019-11-15 07:00:52 +03:00
|
|
|
find_reachable_from(l, map)
|
2020-03-02 20:51:20 +03:00
|
|
|
} else if app
|
2019-12-12 02:04:32 +03:00
|
|
|
.per_obj
|
|
|
|
.action(ctx, Key::S, "show strongly-connected components")
|
2019-11-15 07:00:52 +03:00
|
|
|
{
|
|
|
|
let constraints = PathConstraints::from_lt(lt);
|
|
|
|
let (good, bad) = connectivity::find_scc(map, constraints);
|
2019-10-26 00:40:52 +03:00
|
|
|
(
|
2019-11-15 07:00:52 +03:00
|
|
|
good,
|
|
|
|
bad,
|
|
|
|
format!("strongly-connected components for {:?}", constraints),
|
2019-10-26 00:40:52 +03:00
|
|
|
)
|
|
|
|
} else {
|
|
|
|
return None;
|
|
|
|
}
|
2019-08-22 00:27:28 +03:00
|
|
|
} else {
|
|
|
|
return None;
|
2019-10-26 00:40:52 +03:00
|
|
|
};
|
2019-08-20 23:32:27 +03:00
|
|
|
|
2020-03-02 20:51:20 +03:00
|
|
|
let reachable_color = app.cs.get_def("reachable lane", Color::GREEN);
|
|
|
|
let unreachable_color = app.cs.get_def("unreachable lane", Color::RED);
|
2019-08-22 00:27:28 +03:00
|
|
|
|
2020-01-24 23:02:40 +03:00
|
|
|
let mut colorer = Colorer::new(
|
2019-12-17 01:50:21 +03:00
|
|
|
Text::from(Line("lane connectivity")),
|
2019-09-06 22:36:52 +03:00
|
|
|
vec![
|
|
|
|
("unreachable", unreachable_color),
|
|
|
|
("reachable", reachable_color),
|
|
|
|
],
|
|
|
|
);
|
2019-11-15 07:00:52 +03:00
|
|
|
for l in reachable_lanes {
|
2020-01-03 19:30:02 +03:00
|
|
|
colorer.add_l(l, reachable_color, map);
|
2019-11-15 07:00:52 +03:00
|
|
|
}
|
|
|
|
let num_unreachable = unreachable_lanes.len();
|
|
|
|
for l in unreachable_lanes {
|
2020-01-03 19:30:02 +03:00
|
|
|
colorer.add_l(l, unreachable_color, map);
|
2019-11-15 07:00:52 +03:00
|
|
|
println!("{} is unreachable", l);
|
2019-08-20 23:32:27 +03:00
|
|
|
}
|
2019-10-12 06:10:12 +03:00
|
|
|
|
2019-08-22 00:27:28 +03:00
|
|
|
Some(Box::new(Floodfiller {
|
2020-02-28 00:22:57 +03:00
|
|
|
composite: WrappedComposite::quick_menu(
|
|
|
|
ctx,
|
|
|
|
title,
|
|
|
|
vec![format!("{} unreachable lanes", num_unreachable)],
|
|
|
|
vec![],
|
|
|
|
),
|
2020-03-02 20:51:20 +03:00
|
|
|
colorer: colorer.build(ctx, app),
|
2019-08-22 00:27:28 +03:00
|
|
|
}))
|
2019-08-20 23:32:27 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl State for Floodfiller {
|
2020-03-02 20:51:20 +03:00
|
|
|
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
|
2019-08-20 23:32:27 +03:00
|
|
|
if ctx.redo_mouseover() {
|
2020-03-02 20:51:20 +03:00
|
|
|
app.recalculate_current_selection(ctx);
|
2019-08-20 23:32:27 +03:00
|
|
|
}
|
2020-01-07 20:43:34 +03:00
|
|
|
ctx.canvas_movement();
|
2019-08-20 23:32:27 +03:00
|
|
|
|
2020-02-28 00:22:57 +03:00
|
|
|
match self.composite.event(ctx) {
|
|
|
|
Some(Outcome::Clicked(x)) => match x.as_ref() {
|
|
|
|
"X" => {
|
|
|
|
return Transition::Pop;
|
|
|
|
}
|
|
|
|
_ => unreachable!(),
|
|
|
|
},
|
|
|
|
None => {}
|
2019-08-20 23:32:27 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
Transition::Keep
|
|
|
|
}
|
|
|
|
|
2020-03-02 20:51:20 +03:00
|
|
|
fn draw(&self, g: &mut GfxCtx, _: &App) {
|
2020-01-03 19:44:28 +03:00
|
|
|
self.colorer.draw(g);
|
2020-02-28 00:22:57 +03:00
|
|
|
self.composite.draw(g);
|
2019-08-20 23:32:27 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-15 07:00:52 +03:00
|
|
|
// (reachable, unreachable, a title)
|
|
|
|
fn find_reachable_from(start: LaneID, map: &Map) -> (HashSet<LaneID>, HashSet<LaneID>, String) {
|
|
|
|
let constraints = PathConstraints::from_lt(map.get_l(start).lane_type);
|
|
|
|
|
2019-08-20 23:32:27 +03:00
|
|
|
let mut visited = HashSet::new();
|
|
|
|
let mut queue = vec![start];
|
|
|
|
while !queue.is_empty() {
|
|
|
|
let current = queue.pop().unwrap();
|
|
|
|
if visited.contains(¤t) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
visited.insert(current);
|
2019-11-15 07:00:52 +03:00
|
|
|
for turn in map.get_turns_for(current, constraints) {
|
|
|
|
if !visited.contains(&turn.id.dst) {
|
2019-08-20 23:32:27 +03:00
|
|
|
queue.push(turn.id.dst);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-11-15 07:00:52 +03:00
|
|
|
|
|
|
|
let mut unreached = HashSet::new();
|
|
|
|
for l in map.all_lanes() {
|
2019-11-15 21:11:31 +03:00
|
|
|
if constraints.can_use(l, map) && !visited.contains(&l.id) {
|
2019-11-15 07:00:52 +03:00
|
|
|
unreached.insert(l.id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
(
|
|
|
|
visited,
|
|
|
|
unreached,
|
|
|
|
format!("Floodfiller for {:?} from {}", constraints, start),
|
|
|
|
)
|
2019-08-20 23:32:27 +03:00
|
|
|
}
|