Basic sitemode
This commit is contained in:
72
lib/algorithms/dijkstra.dart
Normal file
72
lib/algorithms/dijkstra.dart
Normal file
@ -0,0 +1,72 @@
|
||||
import 'package:collection/collection.dart';
|
||||
|
||||
class Edge<T> {
|
||||
final double cost;
|
||||
final T destination;
|
||||
|
||||
Edge(this.cost, this.destination);
|
||||
}
|
||||
|
||||
class Result<T> {
|
||||
final double cost;
|
||||
final T? predecessor;
|
||||
final T item;
|
||||
|
||||
Result(this.cost, this.predecessor, this.item);
|
||||
}
|
||||
|
||||
Iterable<Result<T>> dijkstra<T>(
|
||||
T source, Iterable<Edge<T>> Function(T) neighbors) sync* {
|
||||
var queue = PriorityQueue<Result<T>>((i0, i1) => i0.cost.compareTo(i1.cost));
|
||||
|
||||
Set<T> seen = {source};
|
||||
queue.add(Result(0.0, null, source));
|
||||
|
||||
while (queue.isNotEmpty) {
|
||||
var u = queue.removeFirst();
|
||||
yield u;
|
||||
|
||||
for (var v in neighbors(u.item)) {
|
||||
if (seen.contains(v.destination)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
seen.add(v.destination);
|
||||
queue.add(Result(u.cost + v.cost, u.item, v.destination));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<T>? dijkstraPath<T>(
|
||||
T source, T destination, Iterable<Edge<T>> Function(T) neighbors,
|
||||
{double? maxCost}) {
|
||||
if (source == destination) {
|
||||
return [];
|
||||
}
|
||||
Map<T, T?> predecessor = {};
|
||||
|
||||
for (var r in dijkstra(source, neighbors)) {
|
||||
predecessor[r.item] = r.predecessor;
|
||||
if (maxCost != null && r.cost >= maxCost) {
|
||||
return null;
|
||||
}
|
||||
if (r.item == destination) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var revPath = [destination];
|
||||
while (true) {
|
||||
var pred = predecessor[revPath.last];
|
||||
|
||||
if (pred == source) {
|
||||
revPath.reverseRange(0, revPath.length);
|
||||
return revPath;
|
||||
} else if (pred == null) {
|
||||
throw Exception(
|
||||
"predecessor should not be null -- that would mean we missed source");
|
||||
} else {
|
||||
revPath.add(pred);
|
||||
}
|
||||
}
|
||||
}
|
@ -17,6 +17,13 @@ class Size {
|
||||
String toString() {
|
||||
return "$dx x $dy";
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
other is Size && other.dx == dx && other.dy == dy;
|
||||
|
||||
@override
|
||||
int get hashCode => (dx, dy).hashCode;
|
||||
}
|
||||
|
||||
class Offset {
|
||||
@ -29,6 +36,13 @@ class Offset {
|
||||
String toString() {
|
||||
return "@($x, $y)";
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
other is Offset && other.x == x && other.y == y;
|
||||
|
||||
@override
|
||||
int get hashCode => (x, y).hashCode;
|
||||
}
|
||||
|
||||
class Rect {
|
||||
@ -63,4 +77,15 @@ class Rect {
|
||||
String toString() {
|
||||
return "@($x0, $y0) $size";
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
other is Rect &&
|
||||
other.x0 == x0 &&
|
||||
other.y0 == y0 &&
|
||||
other.dx == dx &&
|
||||
other.dy == dy;
|
||||
|
||||
@override
|
||||
int get hashCode => (x0, y0, dx, dy).hashCode;
|
||||
}
|
||||
|
141
lib/algorithms/shadowcasting.dart
Normal file
141
lib/algorithms/shadowcasting.dart
Normal file
@ -0,0 +1,141 @@
|
||||
// Port of https://www.albertford.com/shadowcasting/
|
||||
import 'package:dartterm/algorithms/geometry.dart' as geo;
|
||||
|
||||
void shadowcast(geo.Offset origin, bool Function(geo.Offset) isBlocking,
|
||||
Function(geo.Offset) markVisible) {
|
||||
markVisible(origin);
|
||||
|
||||
for (var i = 0; i < 4; i++) {
|
||||
var quadrant = Quadrant(i, origin.x, origin.y);
|
||||
|
||||
void reveal(geo.Offset tile) {
|
||||
markVisible(quadrant.transform(tile));
|
||||
}
|
||||
|
||||
bool isWall(geo.Offset? tile) {
|
||||
if (tile == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isBlocking(quadrant.transform(tile));
|
||||
}
|
||||
|
||||
bool isFloor(geo.Offset? tile) {
|
||||
if (tile == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !isBlocking(quadrant.transform(tile));
|
||||
}
|
||||
|
||||
void scan(Row row) {
|
||||
geo.Offset? prevTile;
|
||||
for (var tile in row.tiles()) {
|
||||
if (isWall(tile) || isSymmetric(row, tile)) {
|
||||
reveal(tile);
|
||||
}
|
||||
if (isWall(prevTile) && isFloor(tile)) {
|
||||
row.startSlope = slope(tile);
|
||||
}
|
||||
if (isFloor(prevTile) && isWall(tile)) {
|
||||
Row nextRow = row.next();
|
||||
nextRow.endSlope = slope(tile);
|
||||
scan(nextRow);
|
||||
}
|
||||
prevTile = tile;
|
||||
}
|
||||
|
||||
if (isFloor(prevTile)) {
|
||||
scan(row.next());
|
||||
}
|
||||
}
|
||||
|
||||
Row firstRow = Row(1, Fraction(-1, 1), Fraction(1, 1));
|
||||
scan(firstRow);
|
||||
}
|
||||
}
|
||||
|
||||
class Quadrant {
|
||||
static const int north = 0;
|
||||
static const int east = 1;
|
||||
static const int south = 2;
|
||||
static const int west = 3;
|
||||
|
||||
final int cardinal;
|
||||
final int ox, oy;
|
||||
|
||||
Quadrant(this.cardinal, this.ox, this.oy);
|
||||
|
||||
geo.Offset transform(geo.Offset tile) {
|
||||
var geo.Offset(x: row, y: col) = tile;
|
||||
|
||||
switch (cardinal) {
|
||||
case north:
|
||||
return geo.Offset(ox + col, oy - row);
|
||||
case south:
|
||||
return geo.Offset(ox + col, oy + row);
|
||||
case east:
|
||||
return geo.Offset(ox + row, oy + col);
|
||||
case west:
|
||||
return geo.Offset(ox - row, oy + col);
|
||||
default:
|
||||
throw Exception("Quadrant must be initialized with a real cardinal");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Row {
|
||||
int depth;
|
||||
Fraction startSlope;
|
||||
Fraction endSlope;
|
||||
|
||||
Row(this.depth, this.startSlope, this.endSlope);
|
||||
|
||||
Iterable<geo.Offset> tiles() sync* {
|
||||
var minCol = roundTiesUp(startSlope.scale(depth));
|
||||
var maxCol = roundTiesDown(endSlope.scale(depth));
|
||||
for (int col = minCol; col <= maxCol; col++) {
|
||||
yield geo.Offset(depth, col);
|
||||
}
|
||||
}
|
||||
|
||||
Row next() {
|
||||
return Row(depth + 1, startSlope, endSlope);
|
||||
}
|
||||
}
|
||||
|
||||
class Fraction {
|
||||
final int numerator;
|
||||
final int denominator;
|
||||
|
||||
Fraction(this.numerator, this.denominator);
|
||||
|
||||
Fraction scale(int n) {
|
||||
return Fraction(numerator * n, denominator);
|
||||
}
|
||||
|
||||
// We're often comparing this to an int or a double, so it's OK
|
||||
// to have precision loss _so long as we do all divides after all multiplies_
|
||||
double toDouble() {
|
||||
return numerator.toDouble() / denominator.toDouble();
|
||||
}
|
||||
}
|
||||
|
||||
Fraction slope(geo.Offset tile) {
|
||||
var geo.Offset(x: rowDepth, y: col) = tile;
|
||||
return Fraction(2 * col - 1, 2 * rowDepth);
|
||||
}
|
||||
|
||||
bool isSymmetric(Row row, geo.Offset tile) {
|
||||
var geo.Offset(x: rowDepth, y: col) = tile;
|
||||
return (col >= row.startSlope.scale(rowDepth).toDouble() &&
|
||||
col <= (row.endSlope.scale(row.depth)).toDouble());
|
||||
}
|
||||
|
||||
int roundTiesUp(Fraction n) {
|
||||
return (n.toDouble() + 0.5).floor();
|
||||
}
|
||||
|
||||
int roundTiesDown(Fraction n) {
|
||||
return (n.toDouble() - 0.5).ceil();
|
||||
}
|
Reference in New Issue
Block a user