From c4c2b26653b8a9421c44cf7d1f31aa883b3afc00 Mon Sep 17 00:00:00 2001 From: Nyeogmi Date: Wed, 20 Sep 2023 20:56:30 -0700 Subject: [PATCH] Well, it's sort of working --- assets/images/vaults/house1.png | Bin 844 -> 735 bytes lib/algorithms/regionalize.dart | 53 ++++++++++++-------- lib/bitmap.dart | 46 +++++++++++++++++ lib/game.dart | 35 +++++++++---- lib/gen/generator.dart | 23 +++++---- lib/gen/requirement.dart | 14 ------ lib/gen/vault.dart | 36 ++++--------- lib/gen/vaults.dart | 86 ++++++++++++++++++++++++++++++-- lib/input.dart | 2 +- lib/skreek.dart | 4 ++ lib/terminal.dart | 3 +- lib/terminal/reexports.dart | 4 ++ lib/world/level.dart | 26 ---------- 13 files changed, 217 insertions(+), 115 deletions(-) create mode 100644 lib/bitmap.dart create mode 100644 lib/skreek.dart diff --git a/assets/images/vaults/house1.png b/assets/images/vaults/house1.png index 39f02f76c1f1a69d2679ffca17b4b380503c6a5b..224e20ddcc78954edb6032b9f6c85913b551bf7f 100644 GIT binary patch delta 277 zcmV+w0qXwD2HyoCiBL{Q4GJ0x0000DNk~Le0000W0000W2m$~A0Q?y3qp=~g0S5yl zH|9~kwv*cdCw~CPNkll!elIlAA;`Fb^Vuv9&4y z9Jer{JZKdI=@%f z;#my>V)8$!+2rJ9-oM!r^XBLWl*_Ds{oQmKMb%p$!lex-Ty_~DS823 bfP8%b(6U1Cu7y|%00000NkvXXu0mjf&){(= delta 387 zcmcc5dWKE0Gr-TCmrII^fq{Y7)59eQNH>5m2NRI=iTO5XqvB>pb|wx%p7mCjCf{aM zuV*yzba4!+xb^n>Nxnl0A`Tb7_x$($^|U11n#1sznCbI(E_Vx}+IobfZrCa%S@BGN ztFWY)Q7LQf>43C#?9=>nidux$O7 za8Gyjk)z4*}Q$iB}?Za;= diff --git a/lib/algorithms/regionalize.dart b/lib/algorithms/regionalize.dart index 9369dee..1e00222 100644 --- a/lib/algorithms/regionalize.dart +++ b/lib/algorithms/regionalize.dart @@ -4,17 +4,24 @@ class Region { final math.Rectangle rect; final Set<(int, int)> points; + bool get isRectangle => points.length == rect.width * rect.height; + Region(this.rect, this.points); - static fromNonEmptySet(Set<(int, int)> s) { + static Region fromNonEmptySet(Set<(int, int)> s) { assert(s.isNotEmpty); int xMin = s.map((xy) => xy.$1).reduce(math.min); int yMin = s.map((xy) => xy.$2).reduce(math.min); int xMax = s.map((xy) => xy.$1).reduce(math.max); int yMax = s.map((xy) => xy.$2).reduce(math.max); var rect = math.Rectangle.fromPoints( - math.Point(xMin, yMin), math.Point(xMax, yMax)); - Region(rect, s); + math.Point(xMin, yMin), math.Point(xMax + 1, yMax + 1)); + return Region(rect, s); + } + + @override + String toString() { + return "Region($rect, $points)"; } } @@ -23,22 +30,25 @@ List regionalize( int nextRegion = 0; Map<(int, int), int> regions = {}; - void floodfill(int x, int y, region) { + int floodfill(int x, int y) { + int workDone = 0; if (!rect.containsPoint(math.Point(x, y))) { - return; + return workDone; } if (regions[(x, y)] != null) { - return; + return workDone; } if (!isAccessible(x, y)) { - return; + return workDone; } - regions[(x, y)] = region; - floodfill(x - 1, y, region); - floodfill(x + 1, y, region); - floodfill(x, y - 1, region); - floodfill(x, y + 1, region); + regions[(x, y)] = nextRegion; + workDone += 1; + workDone += floodfill(x - 1, y); + workDone += floodfill(x + 1, y); + workDone += floodfill(x, y - 1); + workDone += floodfill(x, y + 1); + return workDone; } // TODO: This can be done more efficiently with a union/find data structure @@ -46,21 +56,20 @@ List regionalize( for (var y = rect.top; y < rect.bottom; y++) { for (var x = rect.left; x < rect.right; x++) { if (regions[(x, y)] == null) { - floodfill(x, y, nextRegion); - nextRegion += 1; + if (floodfill(x, y) > 0) { + nextRegion += 1; + } } } } - return _toExplicit(regions); + return _toExplicit(regions, nextRegion); } -List _toExplicit(Map<(int, int), int> regions) { - List> pointsOut = [ - for (var i = 0; i < regions.length; i++) Set() - ]; - for (var MapEntry(key: (x, y), value: id_) in regions.entries) { - pointsOut[id_].add((x, y)); +List _toExplicit(Map<(int, int), int> pointRegions, int nRegions) { + List> regionPoints = [for (var i = 0; i < nRegions; i++) {}]; + for (var MapEntry(key: (x, y), value: id_) in pointRegions.entries) { + regionPoints[id_].add((x, y)); } - return [for (var s in pointsOut) Region.fromNonEmptySet(s)]; + return [for (var s in regionPoints) Region.fromNonEmptySet(s)]; } diff --git a/lib/bitmap.dart b/lib/bitmap.dart new file mode 100644 index 0000000..1f3d8fd --- /dev/null +++ b/lib/bitmap.dart @@ -0,0 +1,46 @@ +import 'dart:math' as math; +import 'dart:typed_data'; +import 'dart:ui' as ui; + +import 'package:flutter/services.dart'; + +class Bitmap { + // This idiosyncratic usage of "bitmap" comes from some other technology. + // What I'm saying is "don't blame me" + final math.Rectangle rect; + final List data; + + Bitmap(this.rect, this.data) { + assert(this.data.length == this.rect.width * this.rect.height); + } + + static Future> load(String name, T Function(int) cb) async { + final assetImageByteData = await rootBundle.load(name); + final codec = + await ui.instantiateImageCodec(assetImageByteData.buffer.asUint8List()); + final image = (await codec.getNextFrame()).image; + + final bytedata = + (await image.toByteData(format: ui.ImageByteFormat.rawStraightRgba))!; + + final sx = image.width; + final sy = image.height; + + final List data = []; + for (var i = 0; i < sx * sy; i++) { + var pixel = bytedata.getUint32(i * 4, Endian.big); + data.add(cb(pixel)); + } + + return Bitmap(math.Rectangle(0, 0, sx, sy), data); + } + + T? get(int x, int y) { + var realX = x - rect.top; + var realY = y - rect.left; + if (realX < 0 || realY < 0 || realX > rect.width || realY > rect.height) { + return null; + } + return data[realY * rect.width + realX]; + } +} diff --git a/lib/game.dart b/lib/game.dart index 17e72ee..844b448 100644 --- a/lib/game.dart +++ b/lib/game.dart @@ -1,22 +1,22 @@ import 'dart:developer'; import 'dart:math' as math; import 'package:dartterm/assets.dart'; -import 'package:dartterm/colors.dart'; import 'package:dartterm/gen/generator.dart'; +import 'package:dartterm/input.dart'; +import 'package:dartterm/skreek.dart'; import 'package:dartterm/terminal.dart'; -import 'package:dartterm/world/level.dart'; void main() async { Vaults vaults; while (true) { - log("about to load template"); + skreek("about to load template"); at(0, 0).clear(); at(0, 0).puts("Loading template!"); Vaults? maybeVaults = - getVaultsIfAvailable("assets/images/wfc/bighouse2.png"); + getVaultsIfAvailable("assets/images/vaults/house1.png"); if (maybeVaults != null) { - log("wasn't null!"); + skreek("wasn't null!"); vaults = maybeVaults; break; } @@ -45,19 +45,32 @@ void main() async { for (var x = 0; x < w; x++) { var cursor = at(x * 2, y * 2).big(); switch (output.tiles[x + y * w]) { - case LevelTile.floor: + case VaultTile.bspfloor: cursor.puts(" "); - case LevelTile.door: + case VaultTile.floor: + cursor.puts("."); + case VaultTile.door: cursor.puts("d"); - case LevelTile.wall: + case VaultTile.wall: cursor.puts("#"); - case LevelTile.exit: + case VaultTile.exit: cursor.puts("X"); } } } - seed += 1; - await zzz(0.1); + inpLoop: + await for (var inp in rawInput()) { + print(inp); + switch (inp) { + case Keystroke(text: "a"): + seed -= 1; + break inpLoop; + case Keystroke(text: "d"): + seed += 1; + break inpLoop; + default: + } + } } } /* diff --git a/lib/gen/generator.dart b/lib/gen/generator.dart index 4d2465e..997d2dc 100644 --- a/lib/gen/generator.dart +++ b/lib/gen/generator.dart @@ -1,7 +1,8 @@ -import 'dart:developer'; import 'dart:math' as math; -import 'package:dartterm/world/level.dart'; +import 'package:dartterm/algorithms/regionalize.dart'; +import 'package:dartterm/bitmap.dart'; +import 'package:dartterm/skreek.dart'; part 'direction.dart'; part 'direction_set.dart'; @@ -23,7 +24,7 @@ class Generator { var vx = req.vx; var vy = req.vy; - var v = Vault.blank(vx, vy, req.smooth, LevelTile.wall); + var v = Vault.blank(vx, vy, req.smooth, VaultTile.wall); if (req.vx < 2 || req.vy < 2) { return v; @@ -54,7 +55,7 @@ class Generator { var out2 = _generate(req2); var out1 = reorientVault(out2, orientation); - log("$orientation ${requirement.vx} ${requirement.vy} ${req2.vx} ${req2.vy} ${out2.vx} ${out2.vy} ${out1.vx} ${out1.vy}"); + // log("$orientation ${requirement.vx} ${requirement.vy} ${req2.vx} ${req2.vy} ${out2.vx} ${out2.vy} ${out1.vx} ${out1.vy}"); assert(out1.vx == requirement.vx); assert(out1.vy == requirement.vy); return out1; @@ -63,10 +64,10 @@ class Generator { Vault _generate(Requirement req) { var vx = req.vx; var vy = req.vy; - var v = Vault.blank(vx, vy, req.smooth, LevelTile.wall); + var v = Vault.blank(vx, vy, req.smooth, VaultTile.wall); if (vx < 5 || vx * vy < 10) { - v.clear(LevelTile.floor); + v.clear(VaultTile.bspfloor); } else { // pick a split point var splitVx = _random.nextInt(vx - 4) + 2; @@ -118,6 +119,9 @@ class Generator { if (vault.vx < req.vx / 2 || vault.vy < req.vy / 2) { return null; } + if (!vault.smooth.directions.containsAll(req.smooth.directions)) { + return null; + } var rsd = req.smooth.directions; bool mustFillX = @@ -125,13 +129,12 @@ class Generator { if (vault.vx != req.vx && mustFillX) { return null; } - bool mustFillY = - rsd.contains(Direction.left) && rsd.contains(Direction.right); + bool mustFillY = rsd.contains(Direction.up) && rsd.contains(Direction.down); if (vault.vy != req.vy && mustFillY) { return null; } - Vault full = Vault.blank(req.vx, req.vy, req.smooth, LevelTile.wall); + Vault full = Vault.blank(req.vx, req.vy, req.smooth, VaultTile.wall); int vx = vault.vx; int dx; if (rsd.contains(Direction.left)) { @@ -149,7 +152,7 @@ class Generator { } else if (rsd.contains(Direction.down)) { dy = req.vy - vy; } else { - dy = _random.nextInt(req.vx - vx); + dy = _random.nextInt(req.vy - vy); } full.blitFrom(vault, dx, dy); return full; diff --git a/lib/gen/requirement.dart b/lib/gen/requirement.dart index a90fa35..326c730 100644 --- a/lib/gen/requirement.dart +++ b/lib/gen/requirement.dart @@ -17,18 +17,4 @@ class Requirement { Requirement rotateRight() { return Requirement(vy, vx, smooth.rotateRight()); } - - Requirement unReorient(int r) { - assert(r >= 0 && r < 8); - Requirement o = this; - if (r % 2 == 1) { - o = o.flip(); - r -= 1; - } - while (r >= 2) { - o = o.rotateLeft(); - r -= 2; - } - return o; - } } diff --git a/lib/gen/vault.dart b/lib/gen/vault.dart index 598877b..6a9b876 100644 --- a/lib/gen/vault.dart +++ b/lib/gen/vault.dart @@ -3,7 +3,7 @@ part of 'generator.dart'; class Vault { final int vx, vy; final DirectionSet smooth; - final List tiles; + final List tiles; Vault(this.vx, this.vy, this.smooth, this.tiles) { assert(tiles.length == vx * vy); @@ -14,7 +14,7 @@ class Vault { // presence or absence of walls // // In other words, this is wrong. - static Vault fromVaultData(int vx, int vy, List tiles) { + static Vault fromVaultData(int vx, int vy, List tiles) { assert(tiles.length == vx * vy); var smooth = { Direction.up, @@ -23,25 +23,25 @@ class Vault { Direction.right }; for (var x = 0; x < vx; x++) { - if (tiles[x + 0 * vx] == LevelTile.wall) { + if (tiles[x + 0 * vx] == VaultTile.wall) { smooth.remove(Direction.up); break; } } for (var x = 0; x < vx; x++) { - if (tiles[x + (vy - 1) * vx] == LevelTile.wall) { + if (tiles[x + (vy - 1) * vx] == VaultTile.wall) { smooth.remove(Direction.down); break; } } for (var y = 0; y < vy; y++) { - if (tiles[0 + y * vx] == LevelTile.wall) { + if (tiles[0 + y * vx] == VaultTile.wall) { smooth.remove(Direction.left); break; } } for (var y = 0; y < vy; y++) { - if (tiles[vx - 1 + y * vx] == LevelTile.wall) { + if (tiles[vx - 1 + y * vx] == VaultTile.wall) { smooth.remove(Direction.right); break; } @@ -50,7 +50,7 @@ class Vault { return Vault(vx, vy, DirectionSet(smooth), tiles); } - void clear(LevelTile lt) { + void clear(VaultTile lt) { for (var y = 0; y < vy; y++) { for (var x = 0; x < vx; x++) { tiles[y * vx + x] = lt; @@ -71,7 +71,7 @@ class Vault { } Vault flip() { - List tiles2 = [ + List tiles2 = [ for (var y = 0; y < vy; y++) for (var x = vx - 1; x >= 0; x--) tiles[y * vx + x] ]; @@ -82,7 +82,7 @@ class Vault { // TODO: Actually test this logic. // This worked in Python, so it might even be right! Vault rotateRight() { - List tiles2 = [ + List tiles2 = [ for (var x = 0; x < vx; x++) for (var y = 0; y < vy; y++) tiles[(vy - 1 - y) * vx + x] ]; @@ -91,7 +91,7 @@ class Vault { } Vault rotateLeft() { - List tiles2 = [ + List tiles2 = [ for (var x = vx - 1; x >= 0; x++) for (var y = vy - 1; y >= 0; y++) tiles[y * vx + (vx - 1 - x)] ]; @@ -99,21 +99,7 @@ class Vault { return Vault(vy, vx, smooth.rotateLeft(), tiles2); } - Vault reorient(int r) { - assert(r >= 0 && r < 8); - Vault o = this; - while (r >= 2) { - o = o.rotateRight(); - r -= 2; - } - if (r == 1) { - o = o.flip(); - r -= 1; - } - return o; - } - - static Vault blank(int vx, int vy, DirectionSet smooth, LevelTile lt) { + static Vault blank(int vx, int vy, DirectionSet smooth, VaultTile lt) { var tiles = [ for (var y = 0; y < vy; y++) for (var x = 0; x < vx; x++) lt diff --git a/lib/gen/vaults.dart b/lib/gen/vaults.dart index c5973e1..8c843d3 100644 --- a/lib/gen/vaults.dart +++ b/lib/gen/vaults.dart @@ -3,11 +3,6 @@ part of 'generator.dart'; class Vaults { final List _primitive = []; - static Future load(String filename) async { - // TODO - return Vaults(); - } - List randomFlight(math.Random rng) { // TODO: There are many more efficient ways to do this! List list2 = []; @@ -15,4 +10,85 @@ class Vaults { list2.shuffle(rng); return list2; } + + 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 vs = Vaults(); + + for (var region in regions) { + Vault v = loadVault(region, basis); + vs._primitive.add(v); + } + + return vs; + } + + static Vault loadVault(Region r, Bitmap b) { + skreek("Loading vault: $r"); + var tiles = [ + for (var y = r.rect.top; y < r.rect.bottom; y++) + for (var x = r.rect.left; x < r.rect.right; x++) + b.get(x, y) ?? VaultTile.wall + ]; + DirectionSet smooth = DirectionSet( + {Direction.up, Direction.left, Direction.right, Direction.down}); + for (var x = r.rect.left; x < r.rect.right; x++) { + if (b.get(x, r.rect.top) == null) { + smooth.directions.remove(Direction.up); + break; + } + } + for (var x = r.rect.left; x < r.rect.right; x++) { + if (b.get(x, r.rect.bottom - 1) == null) { + smooth.directions.remove(Direction.down); + break; + } + } + for (var y = r.rect.top; y < r.rect.bottom; y++) { + if (b.get(r.rect.left, y) == null) { + smooth.directions.remove(Direction.left); + break; + } + } + for (var y = r.rect.top; y < r.rect.bottom; y++) { + if (b.get(r.rect.right - 1, y) == null) { + smooth.directions.remove(Direction.right); + break; + } + } + + return Vault(r.rect.width, r.rect.height, smooth, tiles); + } +} + +enum VaultTile { + exit, + door, + bspfloor, + floor, + wall, +} + +VaultTile? colorToVaultTile(int c) { + switch (c) { + // RGBA + case 0x000000FF: + case 0x707070FF: + return VaultTile.wall; + case 0xFFFFFFFF: + case 0xFFFF00FF: + case 0xFF00FFFF: + return VaultTile.floor; + case 0x0087FFFF: + return VaultTile.door; + case 0xFF0000FF: + return VaultTile.exit; + case 0x007F00FF: + return null; + default: + throw Exception("unrecognized pixel: $c"); + } } diff --git a/lib/input.dart b/lib/input.dart index 962fc6b..ede0ae2 100644 --- a/lib/input.dart +++ b/lib/input.dart @@ -18,7 +18,7 @@ class Keystroke extends Input { const Keystroke(this.key, this._text); - String? text() => _text; + String? get text => _text; } enum Button { left, right } diff --git a/lib/skreek.dart b/lib/skreek.dart new file mode 100644 index 0000000..7403dbb --- /dev/null +++ b/lib/skreek.dart @@ -0,0 +1,4 @@ +void skreek(String msg) { + // ignore: avoid_print + print("[skreek] $msg"); +} diff --git a/lib/terminal.dart b/lib/terminal.dart index f3d2e3b..4ce09bf 100644 --- a/lib/terminal.dart +++ b/lib/terminal.dart @@ -8,6 +8,7 @@ import 'package:dartterm/colors.dart'; import 'package:dartterm/cp437.dart'; import 'package:dartterm/fonts.dart'; import 'package:dartterm/input.dart'; +import 'package:dartterm/skreek.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; @@ -71,7 +72,7 @@ class Terminal { } void _notifyInput(Input i) { - log("Input: $i $_lastSeenMouse"); + skreek("Input: $i $_lastSeenMouse"); _inputSink.add(i); } diff --git a/lib/terminal/reexports.dart b/lib/terminal/reexports.dart index 4855095..3cf1f4b 100644 --- a/lib/terminal/reexports.dart +++ b/lib/terminal/reexports.dart @@ -17,6 +17,10 @@ void notifyScreenDimensions(ScreenDimensions sd) { _terminal._notifyScreenDimensions(sd); } +Stream rawInput() { + return _terminal.rawInput(); +} + void clear() { at(0, 0).clear(); } diff --git a/lib/world/level.dart b/lib/world/level.dart index b128ee9..faac8b1 100644 --- a/lib/world/level.dart +++ b/lib/world/level.dart @@ -1,29 +1,3 @@ -enum LevelTile { - exit, - door, - floor, - wall, -} - class Level { Set<(int, int)> openCells = {}; } - -LevelTile colorToTile(int c) { - switch (c) { - // ABGR - case 0xFF000000: - case 0xFF707070: - return LevelTile.wall; - case 0xFFFFFFFF: - case 0xFF00FFFF: - case 0xFFFF00FF: - return LevelTile.floor; - case 0xFFFF8700: - return LevelTile.door; - case 0xFF0000FF: - return LevelTile.exit; - default: - throw Exception("unrecognized pixel: $c"); - } -}