diff --git a/assets/images/fonts/font_big.png b/assets/images/fonts/font_big.png index fc5b5c1..37072e9 100644 Binary files a/assets/images/fonts/font_big.png and b/assets/images/fonts/font_big.png differ diff --git a/lib/colors.dart b/lib/colors.dart index 826dce2..56fcb35 100644 --- a/lib/colors.dart +++ b/lib/colors.dart @@ -5,4 +5,7 @@ class Palette { static const defaultFg = Colors.white; static const subtitle = Colors.red; + + static const demoDoor = Colors.red; + static const demoExit = Colors.red; } diff --git a/lib/game.dart b/lib/game.dart index 892b789..ba64938 100644 --- a/lib/game.dart +++ b/lib/game.dart @@ -6,6 +6,7 @@ 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; @@ -31,39 +32,32 @@ void main() async { while (true) { clear(); - Vault output = Generator(math.Random(seed), vaults).generate(Requirement( - 16, - 32, - 16, - 24, - DirectionSet({ - Direction.up, - Direction.down, - Direction.left, - Direction.right, - }))); + Level output = + Generator(math.Random(seed), vaults).generateLevel(Requirement( + 16, + 32, + 16, + 18, + DirectionSet({ + Direction.up, + Direction.down, + Direction.left, + Direction.right, + }))); var geo.Size(dx: w, dy: h) = output.size; for (var y = 0; y < h; y++) { for (var x = 0; x < w; x++) { var cursor = at(x * 2, y * 2).big(); switch (output.tiles.get(x, y)) { - case VaultTile.bspfloor: - case VaultTile.floor: + case LevelTile.floor: + case LevelTile.openDoor: cursor.puts(" "); - case VaultTile.doorpronefloor: - cursor.puts("-"); - case VaultTile.door: - cursor.fg(Palette.subtitle).puts("+"); - case VaultTile.wall: - case VaultTile.defaultwall: + case LevelTile.closedDoor: + cursor.fg(Palette.demoDoor).puts("+"); + case LevelTile.exit: + cursor.fg(Palette.demoExit).puts("X"); + case LevelTile.wall: cursor.puts("#"); - case VaultTile.archpronewall: - cursor.puts("%"); - case VaultTile.archwall: - cursor.puts("\$"); - case VaultTile.exit: - cursor.puts("X"); - case VaultTile.meta0: case null: cursor.puts("?"); } diff --git a/lib/gen/generator.dart b/lib/gen/generator.dart index f34310d..4ed9c33 100644 --- a/lib/gen/generator.dart +++ b/lib/gen/generator.dart @@ -5,6 +5,7 @@ import 'package:dartterm/algorithms/regionalize.dart'; import 'package:dartterm/algorithms/kruskal.dart'; import 'package:dartterm/bitmap.dart'; import 'package:dartterm/skreek.dart'; +import 'package:dartterm/world/level.dart'; part 'direction.dart'; part 'direction_set.dart'; @@ -22,11 +23,19 @@ class Generator { Generator(this._random, this._vaults); - Vault generate(Requirement requirement) { + Level generateLevel(Requirement requirement) { var out = _generateOriented(requirement, false); return _finalize(out); } + /* + Vault generateVault(Requirement requirement) { + var out = _generateOriented(requirement, false); + var (vault, (_, _)) = _finalize(out); + return vault; + } + */ + Vault _generateOriented(Requirement requirement, bool canBeVault) { if (canBeVault) { Vault? suggested = _suggest(vaultTries, requirement); @@ -248,7 +257,7 @@ class Generator { return vault; } - Vault _finalize(Vault subj) { + Level _finalize(Vault subj) { var vx = subj.vx, vy = subj.vy; var orthoOffsets = [(0, -1), (0, 1), (-1, 0), (1, 0)]; @@ -303,11 +312,28 @@ class Generator { return walkable(subj.tiles.get(x, y)); }); + // generate one fake region for the exit doors to be in + Set<(int, int)> exitRegion = {}; + for (var x = -2; x < subj.vx + 2; x++) { + exitRegion.add((x, -1)); + exitRegion.add((x, subj.vy)); + } + for (var y = -2; y < subj.vy + 2; y++) { + exitRegion.add((-1, y)); + exitRegion.add((subj.vx, y)); + } + + int exitRegionId = regions.length; + for (var (x, y) in exitRegion) { + toRegion[(x, y)] = exitRegionId; + } + regions.add(Region.fromNonEmptySet(exitRegion)); + + // OK: now build the doors 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++) { @@ -348,14 +374,22 @@ class Generator { regions[region1].points.length, ); - possibleDoors.add(Edge(region0, region1, (x, y), - doorScore(points, roomSize, _random.nextDouble()))); + possibleDoors.add(Edge( + region0, + region1, + (x, y), + doorScore(region0 != exitRegionId && region1 != exitRegionId, + points, roomSize, _random.nextDouble()))); } } + List> exitDoors = []; var minimalDoors = kruskal(regions.length, possibleDoors); for (var d in minimalDoors) { var (x, y) = d.value; subj.tiles.set(x, y, VaultTile.door); + if (d.dst == exitRegionId || d.src == exitRegionId) { + exitDoors.add(d); + } } for (var x = 0; x < subj.vx; x++) { @@ -365,19 +399,68 @@ class Generator { } } } - return subj; + + if (exitDoors.length != 1) { + throw Exception("should be exactly one exit door"); + } + + // == Build the exit area == + var (exitX, exitY) = exitDoors[0].value; + int exitVaultX, exitVaultY; + Vault finalVault; + int vaultBlitX, vaultBlitY; + if (exitX == 0 || exitX == vx - 1) { + finalVault = + Vault.blank(vx + 3, vy, VaultTile.defaultwall, DirectionSet({})); + vaultBlitX = exitX == 0 ? 3 : 0; + vaultBlitY = 0; + exitVaultX = exitX == 0 ? 1 : vx + 1; + exitVaultY = exitY; + } else if (exitY == 0 || exitY == vy - 1) { + finalVault = + Vault.blank(vx, vy + 3, VaultTile.defaultwall, DirectionSet({})); + vaultBlitX = 0; + vaultBlitY = exitY == 0 ? 3 : 0; + exitVaultX = exitX; + exitVaultY = exitY == 0 ? 1 : vy + 1; + } else { + throw Exception("exit door in invalid position $exitX $exitY $vx $vy"); + } + + for (var x = exitVaultX - 1; x <= exitVaultX + 1; x++) { + for (var y = exitVaultY - 1; y <= exitVaultY + 1; y++) { + finalVault.tiles.set(x, y, VaultTile.exit); + if (x == exitVaultX && y == exitVaultY || + _manhattan(x, y, vaultBlitX + exitX, vaultBlitY + exitY) == 1) { + finalVault.tiles.set(x, y, VaultTile.floor); + } + } + } + finalVault.blitFrom(subj, vaultBlitX, vaultBlitY); + + return Level( + Bitmap.blankWith(finalVault.vx, finalVault.vy, + (x, y) => flattenVaultTile(finalVault.tiles.get(x, y)!)), + geo.Offset(exitVaultX, exitVaultY)); } } // components: +// - is not exit (exit should be placed last so it doesn't get more than one door) // - points for placement // - size of the underlying room // - random factor -double doorScore(double pointsForPlacement, int roomSize, double randomFactor) { +double doorScore(bool isNotExit, 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 + + return (isNotExit ? 1.0 : 0.0) * 1000000 + + pointsForPlacement * 100000 + (100000 - roomSize).toDouble() + randomFactor; } + +int _manhattan(int x0, int y0, int x1, int y1) { + return (x1 - x0).abs() + (y1 - y0).abs(); +} diff --git a/lib/gen/vault.dart b/lib/gen/vault.dart index 215d472..583828f 100644 --- a/lib/gen/vault.dart +++ b/lib/gen/vault.dart @@ -52,3 +52,24 @@ VaultTile mergeVaultTile(VaultTile bottom, VaultTile top) { } return top; } + +LevelTile flattenVaultTile(VaultTile vt) { + switch (vt) { + case VaultTile.meta0: + case VaultTile.defaultwall: + case VaultTile.archpronewall: + case VaultTile.archwall: + case VaultTile.wall: + return LevelTile.wall; + + case VaultTile.exit: + return LevelTile.exit; + case VaultTile.door: + return LevelTile.closedDoor; + + case VaultTile.doorpronefloor: + case VaultTile.bspfloor: + case VaultTile.floor: + return LevelTile.floor; + } +} diff --git a/lib/world/level.dart b/lib/world/level.dart index faac8b1..1be562f 100644 --- a/lib/world/level.dart +++ b/lib/world/level.dart @@ -1,3 +1,23 @@ +import 'package:dartterm/bitmap.dart'; +import 'package:dartterm/algorithms/geometry.dart' as geo; + class Level { - Set<(int, int)> openCells = {}; + Bitmap tiles; + geo.Offset spawn; + + geo.Size get size => tiles.size; + + Level(this.tiles, this.spawn) { + assert(tiles.rect.containsPoint(spawn)); + } +} + +enum LevelTile { + exit, + + floor, + wall, + + closedDoor, + openDoor, }