From 354a114e1c20ec5ae7ed0aac6ef35046b5421e2b Mon Sep 17 00:00:00 2001 From: Nyeogmi Date: Thu, 21 Sep 2023 19:29:34 -0700 Subject: [PATCH] Door placement --- LICENSE.md | 15 ++++ assets/images/vaults/house1.png | Bin 867 -> 915 bytes assets/images/vaults/warehouse_1x1.png | Bin 820 -> 7994 bytes assets/images/vaults/warehouse_2x2.png | Bin 792 -> 915 bytes lib/algorithms/kruskal.dart | 33 ++++++++ lib/algorithms/regionalize.dart | 5 +- lib/algorithms/union_find.dart | 78 ++++++++++++++++++ lib/game.dart | 10 ++- lib/gen/generator.dart | 110 ++++++++++++++++++++++--- lib/gen/vaults.dart | 7 +- 10 files changed, 239 insertions(+), 19 deletions(-) create mode 100644 LICENSE.md create mode 100644 lib/algorithms/kruskal.dart create mode 100644 lib/algorithms/union_find.dart diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..e33ab1a --- /dev/null +++ b/LICENSE.md @@ -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. \ No newline at end of file diff --git a/assets/images/vaults/house1.png b/assets/images/vaults/house1.png index c7b4c07295a5be8dfe726d5584035f2352be97a9..76be06285febbdbedf601695bfa6ad22cae0cb17 100644 GIT binary patch delta 423 zcmaFNHkp0H6-IU@X$3v)D^n)lX4I&!_H=O!skrraMqppFf{3gB67E-gx0Y}-e>0tI zp*`c~#o7H`OiD@h9yQ-*s<}N}>|XPHQDd`$ff_E12bTqF^FBWL&99Zs_+ghe zdqPhj&jH8Wy^JlVWK{!-zcir z7nr>O#Ly3DTELpY_*QJk4qh`c4c~%N=^UK^vjeMdACUX|a`o*7u?4IQJLdmcRDW*u zB9_O`+`aW>*uw8+O_s{d94tW?S0m( z>l@S*IIqNM)&2NkDT9n)~>wf|9<1*QwJQc ztZy*lFg+Zg{NzpBqP_$ZEd^PYg_9YdUk)%aP}sR9gELdbW)7VO3 zBm0|sH@TgACv2APvTC}xqG5?0$3{*e21WtgC}UOqap5m_FZCRFc$5JMJYD@<);T3K F0RVUhw)6l1 delta 374 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 d-EX>4Tx04R~OtPXbp1S(+b?USh;AqM~* zG+{4u0h7@kD1RJDL_t(&-tCw{4ul{O1bd7R^Jfe{<7cynbp;Hv9+YIXiHQPpumW8P z2m)NU831B2;EiYczRb+bJg&e5JYWj}BH|Q47-EL_giKKw$p2gm5Rez>~1TX_I1%g$}awsM1%@$=L zNgTEU67o+QoE>YPc%U)Z`owiZZzKKLvP_q;8qF<=rr&1*0z?s#Y!UCw=4>zHLi{+5 zHcZ;m<=&^qO1)dGO^xEeD^L3LezEQ~>dvGOy%(yq*wW>hnlWOX8)Jn@k91l3w6D~= z#p+(8yfYgm009U<00Izz00bZa0SG_<0uX=z1Rwwb2=GRL59CLz&mB>0`Tzg`07*qo IM6N<$g4OJo`Tzg` diff --git a/assets/images/vaults/warehouse_2x2.png b/assets/images/vaults/warehouse_2x2.png index c45793a9bdf099d16b393de510e1815341c45014..fb87695152acef111a7a1c4d5770e6f3e2684949 100644 GIT binary patch delta 436 zcmbQiHko~bBnKM<1H-D!!h0JPH!*UEF-mJFZF}^8@@+F!Z)XJdH7kg? z>M!Ab#dm87H}f~s$rjo(ZeE<--^HYqRPRyqeWse*!^Q43&lfc|D=6r*o2EYQ>~L^d zur}}Gli&PW*^D1{X|pHv1o9kk%-ze_a!OV;z=*r%?M8UT+3c0*V)ywlL@} zIL0=6uY*aWnDmXJdVPV}3qTD0fTjhk8H{hmcI@Ca6Vvc5D3#992{1db`t||2zb{wc zZV+3*%CKYppGEcORxe_C{5(EL=-2UU&wg@p^dDsSBiyjM^mpI&q?zTPM7usO+qiyS z>!)37PnXvUG1=Z{y}G_Zjlr{jgW38ozh-XSTDkFI5o-^hQzYwVje4!s$7^(7-ucM+ zt!M4ZYyR&yExp%^5=`O3Liz^zI*l}#+6k=c$z>P9i)gKrBa`#ft Tfrm$d@x$Qh>gTe~DWM4f@%*~j delta 312 zcmbQtK7(z7BnJ}%1A{8}v(Al*n;1Dn#g%0H&o43D3`L3Q;(%sQva4^be&lASDm(SiU zV$j>3?XCU6&OVye{l#*Hosox}A~?8Xbyc1&y5m$N+}gT9Kx|c{#}U_5ZqBo!VnEpy z84DjzyLV=raM5DL*Yz@MHl2z*u}XNVRld}xa|aJw9#`x8+uPRpcm8Y>_Crds_gg<6 zo*d?U?IhQi$Q70UGEY5OsWIn=#oV(WOxNyNpIfjcwEyzi6|elaEWBtIJK<#N%{}ra z%RRJ~hNj)KniJ~VX4Opi7!C>rG$3BF%+5f+?zKwv$^RfJPgg&ebxsLQ E0K(^sKmY&$ diff --git a/lib/algorithms/kruskal.dart b/lib/algorithms/kruskal.dart new file mode 100644 index 0000000..1f1fe9e --- /dev/null +++ b/lib/algorithms/kruskal.dart @@ -0,0 +1,33 @@ +import 'package:dartterm/algorithms/union_find.dart'; + +class Edge { + 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> kruskal(int nRegions, List> srcEdges) { + var edges = List.from(srcEdges); // copy so we can mutate it + edges.sort((e0, e1) => -e0.score.compareTo(e1.score)); + + List> 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; +} diff --git a/lib/algorithms/regionalize.dart b/lib/algorithms/regionalize.dart index f4ad9a3..85d754e 100644 --- a/lib/algorithms/regionalize.dart +++ b/lib/algorithms/regionalize.dart @@ -26,7 +26,8 @@ class Region { } } -List regionalize(geo.Rect rect, bool Function(int, int) isAccessible) { +(List, 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 regionalize(geo.Rect rect, bool Function(int, int) isAccessible) { } } } - return _toExplicit(regions, nextRegion); + return (_toExplicit(regions, nextRegion), regions); } List _toExplicit(Map<(int, int), int> pointRegions, int nRegions) { diff --git a/lib/algorithms/union_find.dart b/lib/algorithms/union_find.dart new file mode 100644 index 0000000..6d944f3 --- /dev/null +++ b/lib/algorithms/union_find.dart @@ -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 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 + } + } +} diff --git a/lib/game.dart b/lib/game.dart index 6a396ae..f74790e 100644 --- a/lib/game.dart +++ b/lib/game.dart @@ -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; diff --git a/lib/gen/generator.dart b/lib/gen/generator.dart index 1a886c8..f34310d 100644 --- a/lib/gen/generator.dart +++ b/lib/gen/generator.dart @@ -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> 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; +} diff --git a/lib/gen/vaults.dart b/lib/gen/vaults.dart index f7b9bc5..b992cfd 100644 --- a/lib/gen/vaults.dart +++ b/lib/gen/vaults.dart @@ -14,7 +14,8 @@ class Vaults { static Future 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: