Add and pervasively use grid type (Bitmap)

This commit is contained in:
Pyrex 2023-09-21 15:37:12 -07:00
parent 5de409515e
commit ae0c62b010
7 changed files with 220 additions and 102 deletions

View File

@ -0,0 +1,47 @@
// Dart has a habit of using double-inclusive bounds,
// and I strongly prefer half-open bounds
//
// So: Here's a reimplementation of the geometry I need
class Size {
final int dx;
final int dy;
Size(this.dx, this.dy) {
assert(dx >= 0);
assert(dy >= 0);
}
}
class Offset {
final int x;
final int y;
const Offset(this.x, this.y);
}
class Rect {
final int x0;
final int y0;
final int dx;
final int dy;
int get x1 => x0 + dx;
int get y1 => y0 + dy;
Rect(this.x0, this.y0, this.dx, this.dy) {
assert(dx >= 0);
assert(dy >= 0);
}
bool contains(int x, int y) {
return x0 <= x && x < x1 && y0 <= y && y < y1;
}
bool containsPoint(Offset xy) {
return contains(xy.x, xy.y);
}
bool containsRect(Rect rect) {
return x0 <= rect.x0 && y0 <= rect.y0 && rect.x1 <= x1 && rect.y1 <= y1;
}
}

View File

@ -1,5 +1,7 @@
import 'dart:math' as math;
import 'package:dartterm/algorithms/geometry.dart' as geo;
class Region {
final math.Rectangle<int> rect;
final Set<(int, int)> points;
@ -25,14 +27,13 @@ class Region {
}
}
List<Region> regionalize(
math.Rectangle<int> rect, bool Function(int, int) isAccessible) {
List<Region> regionalize(geo.Rect rect, bool Function(int, int) isAccessible) {
int nextRegion = 0;
Map<(int, int), int> regions = {};
int floodfill(int x, int y) {
int workDone = 0;
if (!rect.containsPoint(math.Point(x, y))) {
if (!rect.containsPoint(geo.Offset(x, y))) {
return workDone;
}
if (regions[(x, y)] != null) {
@ -53,8 +54,8 @@ List<Region> regionalize(
// TODO: This can be done more efficiently with a union/find data structure
// But this is an easy implementation to understand
for (var y = rect.top; y < rect.bottom; y++) {
for (var x = rect.left; x < rect.right; x++) {
for (var y = rect.y0; y < rect.y1; y++) {
for (var x = rect.x0; x < rect.x1; x++) {
if (regions[(x, y)] == null) {
if (floodfill(x, y) > 0) {
nextRegion += 1;

View File

@ -1,17 +1,35 @@
import 'dart:math' as math;
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:dartterm/algorithms/geometry.dart' as geo;
import 'package:flutter/services.dart';
class Bitmap<T> {
// This idiosyncratic usage of "bitmap" comes from some other technology.
// What I'm saying is "don't blame me"
final math.Rectangle<int> rect;
final geo.Size size;
final List<T> data;
Bitmap(this.rect, this.data) {
assert(this.data.length == this.rect.width * this.rect.height);
geo.Rect get rect => geo.Rect(0, 0, size.dx, size.dy);
Bitmap(this.size, this.data) {
assert(data.length == size.dx * size.dy);
}
static Bitmap<T> blankWith<T>(int dx, int dy, T Function(int, int) lt) {
var data = [
for (var y = 0; y < dy; y++)
for (var x = 0; x < dx; x++) lt(x, y)
];
return Bitmap(geo.Size(dx, dy), data);
}
static Bitmap<T> blank<T>(int dx, int dy, T lt) {
var data = [
for (var y = 0; y < dy; y++)
for (var x = 0; x < dx; x++) lt
];
return Bitmap(geo.Size(dx, dy), data);
}
static Future<Bitmap<T>> load<T>(String name, T Function(int) cb) async {
@ -32,15 +50,87 @@ class Bitmap<T> {
data.add(cb(pixel));
}
return Bitmap(math.Rectangle(0, 0, sx, sy), data);
return Bitmap(geo.Size(sx, sy), data);
}
void clearWith(T Function(int, int) t) {
for (var y = 0; y < size.dy; y++) {
for (var x = 0; x < size.dx; x++) {
data[y * size.dx + x] = t(x, y);
}
}
}
void clear(T t) {
for (var y = 0; y < size.dy; y++) {
for (var x = 0; x < size.dx; x++) {
data[y * size.dx + x] = t;
}
}
}
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) {
if (x < 0 || y < 0 || x >= size.dx || y >= size.dy) {
return null;
}
return data[realY * rect.width + realX];
return data[y * size.dx + x];
}
T unsafeGet(int x, int y) {
assert(x < 0 || y < 0 || x >= size.dx || y >= size.dy);
return data[y * size.dx + x];
}
void set(int x, int y, T value) {
assert(x < 0 || y < 0 || x >= size.dx || y >= size.dy);
data[y * size.dx + x] = value;
}
void blitFrom(Bitmap<T> other, int dx, int dy) {
assert(rect.containsRect(geo.Rect(dx, dy, other.size.dx, other.size.dy)));
var x0 = other.rect.x0;
var y0 = other.rect.x0;
var x1 = other.rect.x1;
var y1 = other.rect.y1;
var myW = size.dx;
var otW = other.size.dx;
for (var x = x0; x < x1; x++) {
for (var y = y0; y < y1; y++) {
data[(y + dy) * myW + (x + dx)] = other.data[y * otW + x];
}
}
}
Bitmap<T> flip() {
var geo.Size(:dx, :dy) = size;
List<T> data2 = [
for (var y = 0; y < size.dy; y++)
for (var x = size.dx - 1; x >= 0; x--) data[y * dx + x]
];
return Bitmap(geo.Size(dx, dy), data2);
}
Bitmap<T> rotateRight() {
var geo.Size(:dx, :dy) = size;
List<T> data2 = [
for (var x = 0; x < dx; x++)
for (var y = 0; y < dy; y++) data[(dy - 1 - y) * dx + x]
];
return Bitmap(geo.Size(dy, dx), data2);
}
Bitmap<T> rotateLeft() {
var geo.Size(:dx, :dy) = size;
List<T> data2 = [
for (var x = dx - 1; x >= 0; x++)
for (var y = dy - 1; y >= 0; y++) data[y * dx + (dx - 1 - x)]
];
return Bitmap(geo.Size(dy, dx), data2);
}
}

View File

@ -1,5 +1,6 @@
import 'dart:math' as math;
import 'package:dartterm/assets.dart';
import 'package:dartterm/algorithms/geometry.dart' as geo;
import 'package:dartterm/gen/generator.dart';
import 'package:dartterm/input.dart';
import 'package:dartterm/skreek.dart';
@ -31,13 +32,13 @@ void main() async {
clear();
Vault output = Generator(math.Random(seed), vaults)
.generate(Requirement(16, 32, 16, 24, DirectionSet({})), false);
var w = output.vx;
var h = output.vy;
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[x + y * w]) {
switch (output.tiles.get(x, y)) {
case VaultTile.bspfloor:
case null:
cursor.puts(" ");
case VaultTile.floor:
cursor.puts(".");

View File

@ -1,5 +1,6 @@
import 'dart:math' as math;
import 'package:dartterm/algorithms/geometry.dart' as geo;
import 'package:dartterm/algorithms/regionalize.dart';
import 'package:dartterm/bitmap.dart';
import 'package:dartterm/skreek.dart';
@ -50,8 +51,9 @@ class Generator {
var out1 = reorientVault(out2, orientation);
// log("$orientation ${requirement.vx} ${requirement.vy} ${req2.vx} ${req2.vy} ${out2.vx} ${out2.vy} ${out1.vx} ${out1.vy}");
assert(out1.vx >= requirement.vxMin && out1.vx <= requirement.vxMax);
assert(out1.vy >= requirement.vyMin && out1.vy <= requirement.vyMax);
var geo.Size(:dx, :dy) = out1.size;
assert(dx >= requirement.vxMin && dx <= requirement.vxMax);
assert(dy >= requirement.vyMin && dy <= requirement.vyMax);
assert(out1.smooth.directions.containsAll(requirement.smooth.directions));
return out1;
}
@ -70,11 +72,11 @@ class Generator {
var vyRand = _random.nextInt(vyMax + 1 - vyMin) + vyMin;
if (vxMax < 2 || vyMax < 2) {
return Vault.blank(vxMax, vyRand, req.smooth, VaultTile.wall);
return Vault.blank(vxMax, vyRand, VaultTile.wall, req.smooth);
} else if (vxMax < 9 || (vxMax - 2) * (vyMax - 2) < 12) {
var v2 =
Vault.blank(vxMax - 2, vyMax - 2, req.smooth, VaultTile.bspfloor);
var v = Vault.blank(vxMax, vyMax, req.smooth, VaultTile.wall);
var v2 = Vault.blank(
vxMax - 2, vyMax - 2, VaultTile.bspfloor, req.smooth.clone());
var v = Vault.blank(vxMax, vyMax, VaultTile.wall, req.smooth.clone());
v.blitFrom(v2, 1, 1);
return v;
} else {
@ -87,8 +89,7 @@ class Generator {
var vyMaxRight = vyMax;
if (smoothUpDown) {
vyMinRight = leftChild.vy;
vyMaxRight = leftChild.vy;
vyMaxRight = vyMinRight = leftChild.size.dy;
}
var rightReq = Requirement(
@ -106,14 +107,14 @@ class Generator {
if (smoothUp) {
var v =
Vault.blank(vxTotal, vyTotal, req.smooth.clone(), VaultTile.wall);
Vault.blank(vxTotal, vyTotal, VaultTile.wall, req.smooth.clone());
v.blitFrom(leftChild, 0, 0);
v.blitFrom(rightChild, leftChild.vx - 1, 0);
return v;
}
if (smoothDown) {
var v =
Vault.blank(vxTotal, vyTotal, req.smooth.clone(), VaultTile.wall);
Vault.blank(vxTotal, vyTotal, VaultTile.wall, req.smooth.clone());
v.blitFrom(leftChild, 0, vyTotal - leftChild.vy);
v.blitFrom(rightChild, leftChild.vx - 1, vyTotal - rightChild.vy);
return v;
@ -125,7 +126,7 @@ class Generator {
if (vyTMax > vyTotal) {
vyTotal += _random.nextInt(vyTMax - vyTotal);
}
var v = Vault.blank(vxTotal, vyTotal, req.smooth.clone(), VaultTile.wall);
var v = Vault.blank(vxTotal, vyTotal, VaultTile.wall, req.smooth.clone());
if (_random.nextBool()) {
v.blitFrom(leftChild, 0, 0);
v.blitFrom(rightChild, leftChild.vx - 1, vyTotal - rightChild.vy);
@ -173,32 +174,43 @@ class Generator {
if (!vault.smooth.directions.containsAll(req.smooth.directions)) {
return null;
}
// NOTE: If the vault has metaBSP regions, and they touch the outer edge, then it should be possible
// to extend those regions
// Extending a metaBSP region results in _two_ metaBSP regions:
// a big version of the original, and a second one covering all the space left over
// in the set of rows or columns the metabsp region did not touch
//
// Ex:
// XXXX##
// XXXX #
// XXXX #
// # #
// ######
//
// becomes
//
// XXXZYY
// XXXZYY
// XXXZYY
// XXXX##
// XXXX #
// XXXX #
// # #
// ######
//
// (where the Zs are spaces that Xs and Ys both touch)
//
// Extension can happen more than once, on each axis:
//
// XXXXXZYY
// XXXXXZYY
// XXXXXZYY
// XXXXXX##
// XXXXXX #
// BBXXXX #
// AA# #
// AA######
//
return vault;
/*
var rsd = req.smooth.directions;
Vault full = Vault.blank(req.vx, req.vy, req.smooth, VaultTile.wall);
int vx = vault.vx;
int dx;
if (rsd.contains(Direction.left)) {
dx = 0;
} else if (rsd.contains(Direction.right)) {
dx = req.vx - vx;
} else {
dx = _random.nextInt(req.vx - vx);
}
int vy = vault.vy;
int dy;
if (rsd.contains(Direction.up)) {
dy = 0;
} else if (rsd.contains(Direction.down)) {
dy = req.vy - vy;
} else {
dy = _random.nextInt(req.vy - vy);
}
full.blitFrom(vault, dx, dy);
return full;
*/
}
}

View File

@ -1,69 +1,36 @@
part of 'generator.dart';
class Vault {
final int vx, vy;
final Bitmap<VaultTile> tiles;
final DirectionSet smooth;
final List<VaultTile> tiles;
Vault(this.vx, this.vy, this.smooth, this.tiles) {
assert(tiles.length == vx * vy);
geo.Size get size => tiles.size;
int get vx => size.dx;
int get vy => size.dy;
Vault(this.tiles, this.smooth);
static Vault blank(int vx, int vy, VaultTile lt, DirectionSet smooth) {
return Vault(Bitmap.blank(vx, vy, lt), smooth);
}
void clear(VaultTile lt) {
for (var y = 0; y < vy; y++) {
for (var x = 0; x < vx; x++) {
tiles[y * vx + x] = lt;
}
}
tiles.clear(lt);
}
void blitFrom(Vault other, int dx, int dy) {
assert(dx >= 0);
assert(dy >= 0);
assert(dx + other.vx <= vx);
assert(dy + other.vy <= vy);
for (var x = 0; x < other.vx; x++) {
for (var y = 0; y < other.vy; y++) {
tiles[(y + dy) * vx + x + dx] = other.tiles[y * other.vx + x];
}
}
tiles.blitFrom(other.tiles, dx, dy);
}
Vault flip() {
List<VaultTile> tiles2 = [
for (var y = 0; y < vy; y++)
for (var x = vx - 1; x >= 0; x--) tiles[y * vx + x]
];
return Vault(vx, vy, smooth.flip(), tiles2);
return Vault(tiles.flip(), smooth.flip());
}
// TODO: Actually test this logic.
// This worked in Python, so it might even be right!
Vault rotateRight() {
List<VaultTile> tiles2 = [
for (var x = 0; x < vx; x++)
for (var y = 0; y < vy; y++) tiles[(vy - 1 - y) * vx + x]
];
return Vault(vy, vx, smooth.rotateRight(), tiles2);
return Vault(tiles.rotateRight(), smooth.rotateRight());
}
Vault rotateLeft() {
List<VaultTile> tiles2 = [
for (var x = vx - 1; x >= 0; x++)
for (var y = vy - 1; y >= 0; y++) tiles[y * vx + (vx - 1 - x)]
];
return Vault(vy, vx, smooth.rotateLeft(), tiles2);
}
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
];
return Vault(vx, vy, smooth, tiles);
return Vault(tiles.rotateLeft(), smooth.rotateLeft());
}
}

View File

@ -62,7 +62,7 @@ class Vaults {
}
}
return Vault(r.rect.width, r.rect.height, smooth, tiles);
return Vault(Bitmap(geo.Size(r.rect.width, r.rect.height), tiles), smooth);
}
}