Add and pervasively use grid type (Bitmap)
This commit is contained in:
parent
5de409515e
commit
ae0c62b010
47
lib/algorithms/geometry.dart
Normal file
47
lib/algorithms/geometry.dart
Normal 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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
108
lib/bitmap.dart
108
lib/bitmap.dart
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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(".");
|
||||
|
@ -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;
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user