Door placement
This commit is contained in:
parent
01e316d979
commit
354a114e1c
15
LICENSE.md
Normal file
15
LICENSE.md
Normal file
@ -0,0 +1,15 @@
|
||||
Incorporates GraphLab's implementation of Union/Find, which is by Richard Griffith. Here are the licensing terms of that:
|
||||
|
||||
> The BSD 2-Clause License
|
||||
> http://www.opensource.org/licenses/bsd-license.php
|
||||
>
|
||||
> Copyright (c) 2013, Richard Griffith
|
||||
> All rights reserved.
|
||||
>
|
||||
> Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
>
|
||||
> Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
> Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
>
|
||||
> The views and conclusions contained in the software and documentation are those of the author and should not be interpreted as representing official policies, either expressed or implied, of the FreeBSD Project.
|
Binary file not shown.
Before Width: | Height: | Size: 867 B After Width: | Height: | Size: 915 B |
Binary file not shown.
Before Width: | Height: | Size: 820 B After Width: | Height: | Size: 7.8 KiB |
Binary file not shown.
Before Width: | Height: | Size: 792 B After Width: | Height: | Size: 915 B |
33
lib/algorithms/kruskal.dart
Normal file
33
lib/algorithms/kruskal.dart
Normal file
@ -0,0 +1,33 @@
|
||||
import 'package:dartterm/algorithms/union_find.dart';
|
||||
|
||||
class Edge<T> {
|
||||
final int src, dst;
|
||||
final T value;
|
||||
final double score; // higher score is better
|
||||
|
||||
const Edge(this.src, this.dst, this.value, this.score);
|
||||
}
|
||||
|
||||
List<Edge<T>> kruskal<T>(int nRegions, List<Edge<T>> srcEdges) {
|
||||
var edges = List.from(srcEdges); // copy so we can mutate it
|
||||
edges.sort((e0, e1) => -e0.score.compareTo(e1.score));
|
||||
|
||||
List<Edge<T>> spanningEdges = [];
|
||||
var connected = UnionFind(nRegions);
|
||||
|
||||
for (var i = 0; i < edges.length; i++) {
|
||||
var edge = edges[i];
|
||||
if (connected.find(edge.src) == connected.find(edge.dst)) {
|
||||
continue;
|
||||
}
|
||||
spanningEdges.add(edge);
|
||||
connected.union(edge.src, edge.dst);
|
||||
|
||||
// break early if we run out of regions
|
||||
nRegions -= 1;
|
||||
if (nRegions == 1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return spanningEdges;
|
||||
}
|
@ -26,7 +26,8 @@ class Region {
|
||||
}
|
||||
}
|
||||
|
||||
List<Region> regionalize(geo.Rect rect, bool Function(int, int) isAccessible) {
|
||||
(List<Region>, Map<(int, int), int>) regionalize(
|
||||
geo.Rect rect, bool Function(int, int) isAccessible) {
|
||||
int nextRegion = 0;
|
||||
Map<(int, int), int> regions = {};
|
||||
|
||||
@ -62,7 +63,7 @@ List<Region> regionalize(geo.Rect rect, bool Function(int, int) isAccessible) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return _toExplicit(regions, nextRegion);
|
||||
return (_toExplicit(regions, nextRegion), regions);
|
||||
}
|
||||
|
||||
List<Region> _toExplicit(Map<(int, int), int> pointRegions, int nRegions) {
|
||||
|
78
lib/algorithms/union_find.dart
Normal file
78
lib/algorithms/union_find.dart
Normal file
@ -0,0 +1,78 @@
|
||||
// Copyright (c) 2012, scribeGriff (Richard Griffith)
|
||||
// https://github.com/scribeGriff/graphlab
|
||||
// All rights reserved. Please see the LICENSE.md file.
|
||||
//
|
||||
// Converted to modern Dart by Pyrex Panjakar.
|
||||
|
||||
/// A disjoint sets ADT implemented with a Union-Find data structure.
|
||||
///
|
||||
/// Performs union-by-rank and path compression. Elements are represented
|
||||
/// by ints, numbered from zero.
|
||||
///
|
||||
/// Each disjoint set has one element designated as its root.
|
||||
/// Negative values indicate the element is the root of a set. The absolute
|
||||
/// value of a negative value is the number of elements in the set.
|
||||
/// Positive values are an index to where the root was last known to be.
|
||||
/// If the set has been unioned with another, the last known root will point
|
||||
/// to a more recent root.
|
||||
///
|
||||
/// var a = new UnionFind(myGraph.length);
|
||||
///
|
||||
/// var u = a.find(myGraph[i][0]);
|
||||
/// var v = a.find(myGraph[i][1]);
|
||||
/// a.union(u, v);
|
||||
///
|
||||
/// Reference: Direct port of Mark Allen Weiss' UnionFind.java
|
||||
class UnionFind {
|
||||
late final List<int> array;
|
||||
|
||||
/// Construct a disjoint sets object.
|
||||
///
|
||||
/// numElements is the initial number of elements--also the initial
|
||||
/// number of disjoint sets, since every element is initially in its
|
||||
/// own set.
|
||||
UnionFind(int numElements) {
|
||||
// The array is zero based but the vertices are 1 based,
|
||||
// so we extend the array by 1 element to account for this.
|
||||
array = [for (var i = 0; i < numElements; i++) -1];
|
||||
}
|
||||
|
||||
/// union() unites two disjoint sets into a single set. A union-by-rank
|
||||
/// heuristic is used to choose the new root.
|
||||
///
|
||||
/// a is an element in the first set.
|
||||
/// b is an element in the first set.
|
||||
void union(int a, int b) {
|
||||
int rootA = find(a);
|
||||
int rootB = find(b);
|
||||
|
||||
if (rootA == rootB) return;
|
||||
|
||||
if (array[rootB] < array[rootA]) {
|
||||
// root_b has more elements, so leave it as the root.
|
||||
// first, indicate that the set represented by root_b has grown.
|
||||
array[rootB] += array[rootA];
|
||||
// Then, point the root of set a at set b.
|
||||
array[rootA] = rootB;
|
||||
} else {
|
||||
array[rootA] += array[rootB];
|
||||
array[rootB] = rootA;
|
||||
}
|
||||
}
|
||||
|
||||
/// find() finds the (int) name of the set containing a given element.
|
||||
/// Performs path compression along the way.
|
||||
///
|
||||
/// x is the element sought.
|
||||
/// returns the set containing x.
|
||||
int find(int x) {
|
||||
if (array[x] < 0) {
|
||||
return x; // x is the root of the tree; return it
|
||||
} else {
|
||||
// Find out who the root is; compress path by making the root
|
||||
// x's parent.
|
||||
array[x] = find(array[x]);
|
||||
return array[x]; // Return the root
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
import 'dart:math' as math;
|
||||
import 'package:dartterm/assets.dart';
|
||||
import 'package:dartterm/algorithms/geometry.dart' as geo;
|
||||
import 'package:dartterm/colors.dart';
|
||||
import 'package:dartterm/gen/generator.dart';
|
||||
import 'package:dartterm/input.dart';
|
||||
import 'package:dartterm/skreek.dart';
|
||||
@ -38,11 +39,12 @@ void main() async {
|
||||
var cursor = at(x * 2, y * 2).big();
|
||||
switch (output.tiles.get(x, y)) {
|
||||
case VaultTile.bspfloor:
|
||||
cursor.puts(" ");
|
||||
case VaultTile.floor:
|
||||
cursor.puts(".");
|
||||
cursor.puts(" ");
|
||||
case VaultTile.doorpronefloor:
|
||||
cursor.puts("-");
|
||||
case VaultTile.door:
|
||||
cursor.puts("d");
|
||||
cursor.fg(Palette.subtitle).puts("+");
|
||||
case VaultTile.wall:
|
||||
case VaultTile.defaultwall:
|
||||
cursor.puts("#");
|
||||
@ -60,7 +62,7 @@ void main() async {
|
||||
}
|
||||
inpLoop:
|
||||
await for (var inp in rawInput()) {
|
||||
skreek("$inp");
|
||||
skreek("$inp $seed");
|
||||
switch (inp) {
|
||||
case Keystroke(text: "a"):
|
||||
seed -= 1;
|
||||
|
@ -2,6 +2,7 @@ import 'dart:math' as math;
|
||||
|
||||
import 'package:dartterm/algorithms/geometry.dart' as geo;
|
||||
import 'package:dartterm/algorithms/regionalize.dart';
|
||||
import 'package:dartterm/algorithms/kruskal.dart';
|
||||
import 'package:dartterm/bitmap.dart';
|
||||
import 'package:dartterm/skreek.dart';
|
||||
|
||||
@ -222,7 +223,7 @@ class Generator {
|
||||
|
||||
Vault _fillMetaRegions(Requirement requirement, Vault vault) {
|
||||
var geo.Size(:dx, :dy) = vault.size;
|
||||
var metaregions = regionalize(geo.Rect(0, 0, dx, dy),
|
||||
var (metaregions, _) = regionalize(geo.Rect(0, 0, dx, dy),
|
||||
(x, y) => vault.tiles.get(x, y) == VaultTile.meta0);
|
||||
|
||||
for (var i in metaregions) {
|
||||
@ -249,22 +250,25 @@ class Generator {
|
||||
|
||||
Vault _finalize(Vault subj) {
|
||||
var vx = subj.vx, vy = subj.vy;
|
||||
subj = Vault.blankWith(vx, vy, (x, y) {
|
||||
var bed = VaultTile.defaultwall;
|
||||
if (x == 0 || x == vx - 1 || y == 0 || y == vy - 1) {
|
||||
bed = VaultTile.wall;
|
||||
}
|
||||
var tile = mergeVaultTile(bed, subj.tiles.get(x, y)!);
|
||||
return tile;
|
||||
}, subj.smooth.clone());
|
||||
|
||||
bool canSupportArch(VaultTile? tile) {
|
||||
var orthoOffsets = [(0, -1), (0, 1), (-1, 0), (1, 0)];
|
||||
|
||||
// == build arches ==
|
||||
bool floorlike(VaultTile? tile) {
|
||||
return tile == VaultTile.bspfloor ||
|
||||
tile == VaultTile.floor ||
|
||||
tile == VaultTile.doorpronefloor ||
|
||||
tile == VaultTile.exit;
|
||||
}
|
||||
|
||||
var orthoOffsets = [(0, -1), (0, 1), (-1, 0), (1, 0)];
|
||||
bool walkable(VaultTile? tile) {
|
||||
return tile == VaultTile.bspfloor ||
|
||||
tile == VaultTile.floor ||
|
||||
tile == VaultTile.doorpronefloor ||
|
||||
tile == VaultTile.exit ||
|
||||
tile == VaultTile.door;
|
||||
}
|
||||
|
||||
List<(int, int)> newArches = [];
|
||||
for (int x = 0; x < vx; x++) {
|
||||
for (int y = 0; y < vy; y++) {
|
||||
@ -273,7 +277,7 @@ class Generator {
|
||||
var supporters = 0;
|
||||
for (var (dx, dy) in orthoOffsets) {
|
||||
VaultTile? neighbor = subj.tiles.get(x + dx, y + dy);
|
||||
if (canSupportArch(neighbor)) {
|
||||
if (floorlike(neighbor)) {
|
||||
supporters++;
|
||||
}
|
||||
}
|
||||
@ -292,6 +296,88 @@ class Generator {
|
||||
for (var (ax, ay) in newArches) {
|
||||
subj.tiles.set(ax, ay, VaultTile.floor);
|
||||
}
|
||||
|
||||
// == build doors ==
|
||||
var (regions, toRegion) =
|
||||
regionalize(geo.Rect(0, 0, subj.vx, subj.vy), (x, y) {
|
||||
return walkable(subj.tiles.get(x, y));
|
||||
});
|
||||
|
||||
double doorPoints(int x, int y) {
|
||||
return subj.tiles.get(x, y) == VaultTile.doorpronefloor ? 0.5 : 0.0;
|
||||
}
|
||||
|
||||
//
|
||||
List<Edge<(int, int)>> possibleDoors = [];
|
||||
for (var x = 0; x < subj.vx; x++) {
|
||||
for (var y = 0; y < subj.vy; y++) {
|
||||
double points;
|
||||
int region0, region1;
|
||||
|
||||
if (subj.tiles.get(x, y) != VaultTile.wall) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var regionL = toRegion[(x - 1, y)];
|
||||
var regionR = toRegion[(x + 1, y)];
|
||||
var regionU = toRegion[(x, y - 1)];
|
||||
var regionD = toRegion[(x, y + 1)];
|
||||
|
||||
if (regionL != null &&
|
||||
regionR != null &&
|
||||
regionU == null &&
|
||||
regionD == null) {
|
||||
(region0, region1) = (regionL, regionR);
|
||||
points = doorPoints(x - 1, y) + doorPoints(x + 1, y);
|
||||
} else if (regionL == null &&
|
||||
regionR == null &&
|
||||
regionU != null &&
|
||||
regionD != null) {
|
||||
(region0, region1) = (regionU, regionD);
|
||||
points = doorPoints(x, y - 1) + doorPoints(x, y + 1);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (region0 == region1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int roomSize = math.min(
|
||||
regions[region0].points.length,
|
||||
regions[region1].points.length,
|
||||
);
|
||||
|
||||
possibleDoors.add(Edge(region0, region1, (x, y),
|
||||
doorScore(points, roomSize, _random.nextDouble())));
|
||||
}
|
||||
}
|
||||
var minimalDoors = kruskal(regions.length, possibleDoors);
|
||||
for (var d in minimalDoors) {
|
||||
var (x, y) = d.value;
|
||||
subj.tiles.set(x, y, VaultTile.door);
|
||||
}
|
||||
|
||||
for (var x = 0; x < subj.vx; x++) {
|
||||
for (var y = 0; y < subj.vy; y++) {
|
||||
if (subj.tiles.get(x, y) == VaultTile.doorpronefloor) {
|
||||
subj.tiles.set(x, y, VaultTile.floor);
|
||||
}
|
||||
}
|
||||
}
|
||||
return subj;
|
||||
}
|
||||
}
|
||||
|
||||
// components:
|
||||
// - points for placement
|
||||
// - size of the underlying room
|
||||
// - random factor
|
||||
double doorScore(double pointsForPlacement, int roomSize, double randomFactor) {
|
||||
assert(pointsForPlacement >= 0.0 && pointsForPlacement <= 1.0);
|
||||
assert(roomSize >= 0 && roomSize < 100000);
|
||||
assert(randomFactor >= 0.0 && randomFactor < 1.0);
|
||||
return pointsForPlacement * 100000 +
|
||||
(100000 - roomSize).toDouble() +
|
||||
randomFactor;
|
||||
}
|
||||
|
@ -14,7 +14,8 @@ class Vaults {
|
||||
static Future<Vaults> load(String name) async {
|
||||
var basis = await Bitmap.load(name, colorToVaultTile);
|
||||
|
||||
var regions = regionalize(basis.rect, (x, y) => basis.get(x, y) != null);
|
||||
var (regions, _) =
|
||||
regionalize(basis.rect, (x, y) => basis.get(x, y) != null);
|
||||
|
||||
var vs = Vaults();
|
||||
|
||||
@ -73,6 +74,8 @@ enum VaultTile {
|
||||
door,
|
||||
bspfloor,
|
||||
floor,
|
||||
doorpronefloor,
|
||||
|
||||
defaultwall, // defaultwall is in generated rooms and is overwritten by anything
|
||||
archpronewall, // archpronewall cannot overwrite wall or archwall
|
||||
archwall, // archwall cannot overwrite wall.
|
||||
@ -98,6 +101,8 @@ VaultTile? colorToVaultTile(int c) {
|
||||
return VaultTile.archwall;
|
||||
case 0x7F4000:
|
||||
return VaultTile.archpronewall;
|
||||
case 0xBCCFFF:
|
||||
return VaultTile.doorpronefloor;
|
||||
case 0xFFFFFF:
|
||||
case 0xFFFF00:
|
||||
case 0xFF00FF:
|
||||
|
Loading…
Reference in New Issue
Block a user