Exit vaults, level type

This commit is contained in:
Pyrex 2023-09-22 17:55:29 -07:00
parent d489810154
commit 1b4240e430
6 changed files with 156 additions and 35 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -5,4 +5,7 @@ class Palette {
static const defaultFg = Colors.white; static const defaultFg = Colors.white;
static const subtitle = Colors.red; static const subtitle = Colors.red;
static const demoDoor = Colors.red;
static const demoExit = Colors.red;
} }

View File

@ -6,6 +6,7 @@ import 'package:dartterm/gen/generator.dart';
import 'package:dartterm/input.dart'; import 'package:dartterm/input.dart';
import 'package:dartterm/skreek.dart'; import 'package:dartterm/skreek.dart';
import 'package:dartterm/terminal.dart'; import 'package:dartterm/terminal.dart';
import 'package:dartterm/world/level.dart';
void main() async { void main() async {
Vaults vaults; Vaults vaults;
@ -31,39 +32,32 @@ void main() async {
while (true) { while (true) {
clear(); clear();
Vault output = Generator(math.Random(seed), vaults).generate(Requirement( Level output =
16, Generator(math.Random(seed), vaults).generateLevel(Requirement(
32, 16,
16, 32,
24, 16,
DirectionSet({ 18,
Direction.up, DirectionSet({
Direction.down, Direction.up,
Direction.left, Direction.down,
Direction.right, Direction.left,
}))); Direction.right,
})));
var geo.Size(dx: w, dy: h) = output.size; var geo.Size(dx: w, dy: h) = output.size;
for (var y = 0; y < h; y++) { for (var y = 0; y < h; y++) {
for (var x = 0; x < w; x++) { for (var x = 0; x < w; x++) {
var cursor = at(x * 2, y * 2).big(); var cursor = at(x * 2, y * 2).big();
switch (output.tiles.get(x, y)) { switch (output.tiles.get(x, y)) {
case VaultTile.bspfloor: case LevelTile.floor:
case VaultTile.floor: case LevelTile.openDoor:
cursor.puts(" "); cursor.puts(" ");
case VaultTile.doorpronefloor: case LevelTile.closedDoor:
cursor.puts("-"); cursor.fg(Palette.demoDoor).puts("+");
case VaultTile.door: case LevelTile.exit:
cursor.fg(Palette.subtitle).puts("+"); cursor.fg(Palette.demoExit).puts("X");
case VaultTile.wall: case LevelTile.wall:
case VaultTile.defaultwall:
cursor.puts("#"); cursor.puts("#");
case VaultTile.archpronewall:
cursor.puts("%");
case VaultTile.archwall:
cursor.puts("\$");
case VaultTile.exit:
cursor.puts("X");
case VaultTile.meta0:
case null: case null:
cursor.puts("?"); cursor.puts("?");
} }

View File

@ -5,6 +5,7 @@ import 'package:dartterm/algorithms/regionalize.dart';
import 'package:dartterm/algorithms/kruskal.dart'; import 'package:dartterm/algorithms/kruskal.dart';
import 'package:dartterm/bitmap.dart'; import 'package:dartterm/bitmap.dart';
import 'package:dartterm/skreek.dart'; import 'package:dartterm/skreek.dart';
import 'package:dartterm/world/level.dart';
part 'direction.dart'; part 'direction.dart';
part 'direction_set.dart'; part 'direction_set.dart';
@ -22,11 +23,19 @@ class Generator {
Generator(this._random, this._vaults); Generator(this._random, this._vaults);
Vault generate(Requirement requirement) { Level generateLevel(Requirement requirement) {
var out = _generateOriented(requirement, false); var out = _generateOriented(requirement, false);
return _finalize(out); return _finalize(out);
} }
/*
Vault generateVault(Requirement requirement) {
var out = _generateOriented(requirement, false);
var (vault, (_, _)) = _finalize(out);
return vault;
}
*/
Vault _generateOriented(Requirement requirement, bool canBeVault) { Vault _generateOriented(Requirement requirement, bool canBeVault) {
if (canBeVault) { if (canBeVault) {
Vault? suggested = _suggest(vaultTries, requirement); Vault? suggested = _suggest(vaultTries, requirement);
@ -248,7 +257,7 @@ class Generator {
return vault; return vault;
} }
Vault _finalize(Vault subj) { Level _finalize(Vault subj) {
var vx = subj.vx, vy = subj.vy; var vx = subj.vx, vy = subj.vy;
var orthoOffsets = [(0, -1), (0, 1), (-1, 0), (1, 0)]; var orthoOffsets = [(0, -1), (0, 1), (-1, 0), (1, 0)];
@ -303,11 +312,28 @@ class Generator {
return walkable(subj.tiles.get(x, y)); 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) { double doorPoints(int x, int y) {
return subj.tiles.get(x, y) == VaultTile.doorpronefloor ? 0.5 : 0.0; return subj.tiles.get(x, y) == VaultTile.doorpronefloor ? 0.5 : 0.0;
} }
//
List<Edge<(int, int)>> possibleDoors = []; List<Edge<(int, int)>> possibleDoors = [];
for (var x = 0; x < subj.vx; x++) { for (var x = 0; x < subj.vx; x++) {
for (var y = 0; y < subj.vy; y++) { for (var y = 0; y < subj.vy; y++) {
@ -348,14 +374,22 @@ class Generator {
regions[region1].points.length, regions[region1].points.length,
); );
possibleDoors.add(Edge(region0, region1, (x, y), possibleDoors.add(Edge(
doorScore(points, roomSize, _random.nextDouble()))); region0,
region1,
(x, y),
doorScore(region0 != exitRegionId && region1 != exitRegionId,
points, roomSize, _random.nextDouble())));
} }
} }
List<Edge<(int, int)>> exitDoors = [];
var minimalDoors = kruskal(regions.length, possibleDoors); var minimalDoors = kruskal(regions.length, possibleDoors);
for (var d in minimalDoors) { for (var d in minimalDoors) {
var (x, y) = d.value; var (x, y) = d.value;
subj.tiles.set(x, y, VaultTile.door); 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++) { 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: // components:
// - is not exit (exit should be placed last so it doesn't get more than one door)
// - points for placement // - points for placement
// - size of the underlying room // - size of the underlying room
// - random factor // - 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(pointsForPlacement >= 0.0 && pointsForPlacement <= 1.0);
assert(roomSize >= 0 && roomSize < 100000); assert(roomSize >= 0 && roomSize < 100000);
assert(randomFactor >= 0.0 && randomFactor < 1.0); assert(randomFactor >= 0.0 && randomFactor < 1.0);
return pointsForPlacement * 100000 + return (isNotExit ? 1.0 : 0.0) * 1000000 +
pointsForPlacement * 100000 +
(100000 - roomSize).toDouble() + (100000 - roomSize).toDouble() +
randomFactor; randomFactor;
} }
int _manhattan(int x0, int y0, int x1, int y1) {
return (x1 - x0).abs() + (y1 - y0).abs();
}

View File

@ -52,3 +52,24 @@ VaultTile mergeVaultTile(VaultTile bottom, VaultTile top) {
} }
return 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;
}
}

View File

@ -1,3 +1,23 @@
import 'package:dartterm/bitmap.dart';
import 'package:dartterm/algorithms/geometry.dart' as geo;
class Level { class Level {
Set<(int, int)> openCells = {}; Bitmap<LevelTile> 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,
} }