-
Notifications
You must be signed in to change notification settings - Fork 5
/
visualizer.dart
331 lines (299 loc) · 10.8 KB
/
visualizer.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
/**
* This file is used to run the algorithms in the browser.
*/
import 'dart:html' hide Node;
import 'dart:async';
import 'dajkstra/dajkstra.dart';
import 'graph-painter.dart';
var scale = 1;
var nodeCount = 50 * scale;
var xmax = 18 * scale;
var ymax = 12 * scale;
var mapWidthMax = 900 * scale;
var mapHeightMax = 600 * scale;
var runSpeed = 100;
const easyGraph = 1;
const hardGraph = 30;
void disableDijkstra() {
query("#btn_dijkstra_step").disabled = true;
query("#btn_dijkstra_run").disabled = true;
}
void disableNaive() {
query("#btn_naive_step").disabled = true;
query("#btn_naive_backstep").disabled = true;
query("#btn_naive_run").disabled = true;
}
void updateCodeLocation(String loc) {
print("Updating location to $loc");
IFrameElement base = (query("#code") as IFrameElement);
base.src = "naive-simple.html#$loc";
}
num getRunSpeed() {
Element elm = query("#inp_run_speed");
return (elm == null) ? runSpeed : int.parse(elm.value, onError: (_) => runSpeed);
}
void main() {
print("Running main");
var buttons = ["#btn_reset", "#btn_easy_map", "#btn_hard_map", "#btn_naive_step", "#btn_naive_backstep",
"#btn_naive_run", "#btn_dijkstra_step", "#btn_dijkstra_run"];
buttons = new List.from(buttons.map((String s) => query(s)));
print(buttons);
buttons[0].disabled = true;
enableAllButtons() => new List.from(buttons.map((var elm) => elm.disabled = false));
// An empty timer in order to avoid null values.
var timer = new Timer(new Duration(seconds: 0), () {});
ShortestPathDriver resetGraph(int graphComplexity) {
timer.cancel();
enableAllButtons();
var driver = new ShortestPathDriver(query("#map"), nodeCount,
xmax, ymax, mapWidthMax, mapHeightMax)..generateGraph(graphComplexity);
driver.onNaiveStateChange.listen((String stateName) => updateCodeLocation(stateName));
return driver;
}
var driver = resetGraph(easyGraph);
// Setting up buttons.
query("#btn_reset").onClick.listen((e) {
enableAllButtons();
driver.resetPath();
updateCodeLocation("");
});
query("#btn_easy_map").onClick.listen((e) {driver = resetGraph(easyGraph); });
query("#btn_hard_map").onClick.listen((e) {driver = resetGraph(hardGraph); });
query("#btn_naive_step").onClick.listen((e) {
disableDijkstra();
driver.takeNaiveStep();
});
query("#btn_naive_backstep").onClick.listen((e) {
disableDijkstra();
driver.takeNaiveBackStep();
});
bool runningNaive = false;
query("#btn_naive_run").onClick.listen((e) {
var button = query("#btn_naive_run");
disableDijkstra();
timer.cancel();
if (runningNaive) {
timer.cancel();
button.value = "Run Naive";
runningNaive = false;
} else {
timer = driver.runNaive(driver.takeNaiveStep, getRunSpeed());
button.value = "Stop naive run";
runningNaive = true;
}
});
query("#btn_dijkstra_step").onClick.listen((e) {
disableNaive();
driver.takeDijkstraStep();
});
bool runningDijkstra = false;
query("#btn_dijkstra_run").onClick.listen((e) {
var button = query("#btn_dijkstra_run");
disableNaive();
timer.cancel();
if (runningDijkstra) {
timer.cancel();
button.value = "Run Dijkstra";
runningDijkstra = false;
} else {
//Dijkstras should run a little slower, because it does not have as many steps.
timer = driver.runNaive(driver.takeDijkstraStep, 2* getRunSpeed());
button.value = "Stop Dijkstra run";
runningDijkstra = true;
}
});
}
class ShortestPathDriver {
num _nodeCount;
num _xmax;
num _ymax;
GraphGenerator _graphGenerator = new GraphGenerator();
NaiveAlgorithm _naiveAlgorithm = new NaiveAlgorithm();
DijkstraAlgorithm _dijkstraAlgorithm;
GraphPainter _graphPainter;
DisplayableGraph _graph;
List<State> _state = new List();
Timer _timer = new Timer(new Duration(seconds: 0), () {}); //Default timer to avoid null
StreamController<String> _naiveStateChangeController;
Stream<String> onNaiveStateChange;
ShortestPathDriver(CanvasElement canvasElement, this._nodeCount, this._xmax, this._ymax,
mapWidthMax, mapHeightMax) {
_graphPainter = new GraphPainter(canvasElement, _xmax, _ymax, mapWidthMax, mapHeightMax);
resetStreamController();
}
void resetStreamController() {
if (_naiveStateChangeController != null) {
_naiveStateChangeController.close();
}
_naiveStateChangeController = new StreamController();
onNaiveStateChange = _naiveStateChangeController.stream;
}
void generateGraph(num numberOfRoutes) {
int nrOfPaths = 0;
DisplayableGraph graph = null;
do {
nrOfPaths = 0;
graph = _graphGenerator.generateGraph(_nodeCount, _xmax, _ymax);
_naiveAlgorithm.findShortestPath(graph.graph, onPath: (_) => nrOfPaths++);
} while (nrOfPaths < numberOfRoutes);
_graph = graph;
_graphPainter.drawGraph(_graph);
_dijkstraAlgorithm = new DijkstraAlgorithm(_graph.graph);
}
void resetPath() {
_state.clear();
_timer.cancel();
_graphPainter.drawGraph(_graph);
_dijkstraAlgorithm = new DijkstraAlgorithm(_graph.graph);
}
PList<Node> _extractPath(Context cont) {
if (cont is EmptyContext)
return new PList();
else {
var context = (cont as EdgesContext);
return context.currentFullPath;
}
}
PList<PList<Edge>> _extractEdgesToDo(Context cont) {
if (cont is EmptyContext) {
return new PList();
} else {
return _extractEdgesToDo(cont.cont).cons(cont.edges);
}
}
PList flatten(PList<PList> list) {
if (list.isEmpty) return new PList();
else {
return list.hd.foldr((e, acc) => acc.cons(e), flatten(list.tl));
}
}
PList<Node> _extractCycle(PList<Node> cycle) {
Node head = cycle.hd;
PList<Node> visit(PList<Node> cycle) {
if (cycle.isEmpty) {
throw new Exception("Did not find the $head in the list, which is an error, as it is a cycle");
} else if (cycle.hd == head) {
return new PList().cons(cycle.hd);
} else {
return visit(cycle.tl).cons(cycle.hd);
}
}
return visit(cycle.tl).cons(head);
}
// Calls fn succesively until it stops (with a delay of speed ms).
// Returns the timer that handles the call to takeNaiveStep.
Timer runNaive(bool fn(), int speed) {
_timer = new Timer.repeating(new Duration(milliseconds: speed), (Timer timer) {
if (fn()) {
timer.cancel();
}
});
return _timer;
}
bool takeDijkstraStep() {
var currentEndNode = _dijkstraAlgorithm.takeStep();
PList<Node> currentPath = _dijkstraAlgorithm.getPath(currentEndNode);
print(currentPath);
String pathColor = (currentEndNode == _graph.graph.end) ? "lightgreen" : "blue";
_graphPainter.drawPath(_graph,
edgeColorFn: (Node src, Node dst) =>
(visit(currentPath, src, dst))
? pathColor : "gray",
nodeColorFn: (Node n) =>
_dijkstraAlgorithm.visited.contains(n) ? "white"
: _dijkstraAlgorithm.allCosts[n] != double.INFINITY ? "lightblue" : "gray",
nodeTextFn: (Node n) =>
_dijkstraAlgorithm.allCosts[n] != double.INFINITY ? "${(_dijkstraAlgorithm.allCosts[n]*10).floor()/10}" : "");
return (currentEndNode == _graph.graph.end);
}
bool visit(PList<Node> path, Node src, Node dst) {
if (path.isEmpty) return false;
// Remember that the graph is undirected
if (path.hd == dst && !path.tl.isEmpty && path.tl.hd == src ||
path.hd == src && !path.tl.isEmpty && path.tl.hd == dst) return true;
else return visit(path.tl, src, dst);
}
bool visitEdges(PList<Edge> edges, Node src, Node dst) {
return edges.any((Edge otherE) => otherE.src == src && otherE.dest == dst ||
otherE.src == dst && otherE.dest == src);
}
// Takes a step and repaint the graph with the given path.
// It returns true, when there are no more steps to take.
bool takeNaiveStep() {
if (_state.isEmpty) _state.add(new NaiveAutomaton().startStepping(_graph.graph));
else _state.add(_state.last.step());
_repaintNaive(_state.last);
return (_state.last is FinalState);
}
// Takes a back step and repaints the graph with the given path.
// Returns true if no back step is possible
bool takeNaiveBackStep() {
if (_state.length < 2) return true;
// This popped is currently shown.
_state.removeLast();
_repaintNaive(_state.last);
return false;
}
void _repaintNaive(State state) {
dynamic idFun(dynamic x) => x;
// // If we have a EdgesState we continue, we could continue.
// _state = _state.match(onEdges: (state) => _state.step(), onCycle: idFun, onPath: idFun, onFinal: idFun,
// onNode: idFun, onCont: idFun);
PList<Node> currentPath = new PList();
PList<Node> cycle = new PList();
PList<Node> endPath = new PList();
Context context = new EmptyContext();
Map<Node, num> nodeCosts = new Map();
state.match(
onCycle: (state) {
context = state.cont.cont;
currentPath = state.cyclePath;
cycle = _extractCycle(currentPath);
},
onPath: (state) {
context = state.cont.cont;
Result result = state.cont.result;
currentPath = result.path;
nodeCosts[result.path.hd] = result.cost;
endPath = currentPath;
},
onFinal: (state) {
Result result = state.result;
currentPath = result.path;
nodeCosts[_graph.graph.end] = result.cost;
endPath = currentPath;
},
onNode: (state) {
context = state.cont;
currentPath = state.currentPath.cons(state.currentNode);
},
onEdges: (state) {
context = state.cont;
currentPath = state.currentFullPath;
},
onCont: (state) {
context = state.cont;
currentPath = _extractPath(state.cont);
}
);
PList<Edge> todoEdges = flatten(_extractEdgesToDo(context));
context.foldr((EdgesContext e, _) {
nodeCosts[e.currentFullPath.hd] = e.currentCost;
}, []);
_graphPainter.drawPath(_graph,
edgeColorFn: (Node src, Node dst) =>
(visit(cycle, src, dst))
? "red" : (visit(endPath, src, dst))
? "green" : (visit(currentPath, src, dst))
? "blue" : (visitEdges(todoEdges, src, dst))
? "lightblue" : "gray",
nodeColorFn: (Node n) => currentPath.hd == n
? "lightblue" : (currentPath.any((Node other) => n.id == other.id))? "white": "gray",
nodeTextFn: (Node n) => nodeCosts.containsKey(n) ? "${(nodeCosts[n]*10).floor()/10}" : "");
print(currentPath);
String newStateName = state.match(onEdges: (_) => "EdgesState", onCycle: (_) => "CycleState",
onPath: (_) => "PathState", onFinal: (_) => "FinalState",
onNode: (_) => "NodeState", onCont: (_) => "ContState");
_naiveStateChangeController.add(newStateName);
}
}