dartterm/lib/gen/generator.dart

217 lines
6.3 KiB
Dart

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';
part 'direction.dart';
part 'direction_set.dart';
part 'orientation.dart';
part 'requirement.dart';
part 'vault.dart';
part 'vaults.dart';
const vaultTries = 10;
class Generator {
final math.Random _random;
final Vaults _vaults;
List<Vault> _queue = [];
Generator(this._random, this._vaults);
Vault generate(Requirement requirement, bool canBeVault) {
if (canBeVault) {
Vault? suggested = _suggest(vaultTries, requirement);
if (suggested != null) {
return suggested;
}
}
// First of all: randomize orientation
// This way we only have to consider one kind of spilt
var orientation = randomOrientation(_random);
// Try to make vx the long axis if possible
var req2 = unReorientRequirement(requirement, orientation);
if (req2.vyMax > (req2.vxMax - 2) * 3 / 2) {
orientation = (orientation + 2) % 8; // rotate once more
}
// if only one of "left" and "right" needs to be smooth, prioritize right
// as left is generated first
req2 = unReorientRequirement(requirement, orientation);
if (req2.smooth.directions.contains(Direction.left) &&
req2.smooth.directions.contains(Direction.right)) {
orientation = (orientation + 4) % 8;
}
req2 = unReorientRequirement(requirement, orientation);
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}");
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;
}
Vault _generate(Requirement req) {
var vxMin = req.vxMin;
var vyMin = req.vyMin;
var vxMax = req.vxMax;
var vyMax = req.vyMax;
var smoothUp = req.smooth.directions.contains(Direction.up);
var smoothDown = req.smooth.directions.contains(Direction.down);
var smoothUpDown = smoothUp && smoothDown;
// var vxRand = _random.nextInt(vxMax - vxMin) + vxMin;
var vyRand = _random.nextInt(vyMax + 1 - vyMin) + vyMin;
if (vxMax < 2 || vyMax < 2) {
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, VaultTile.bspfloor, req.smooth.clone());
var v = Vault.blank(vxMax, vyMax, VaultTile.wall, req.smooth.clone());
v.blitFrom(v2, 1, 1);
return v;
} else {
var leftReq = Requirement(
math.max(vxMin - 4, 2), vxMax - 4, vyMin, vyMax, req.smooth.clone());
leftReq.smooth.directions.add(Direction.right);
var leftChild = generate(leftReq, true);
var vyMinRight = vyMin;
var vyMaxRight = vyMax;
if (smoothUpDown) {
vyMaxRight = vyMinRight = leftChild.size.dy;
}
var rightReq = Requirement(
vxMin - (leftChild.vx - 1),
vxMax - (leftChild.vx - 1),
vyMinRight,
vyMaxRight,
req.smooth.clone(),
);
rightReq.smooth.directions.add(Direction.left);
var rightChild = generate(rightReq, true);
var vxTotal = leftChild.vx + rightChild.vx - 1;
var vyTotal = math.max(leftChild.vy, rightChild.vy);
if (smoothUp) {
var v =
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, VaultTile.wall, req.smooth.clone());
v.blitFrom(leftChild, 0, vyTotal - leftChild.vy);
v.blitFrom(rightChild, leftChild.vx - 1, vyTotal - rightChild.vy);
return v;
}
// no smoothing reqs
// min: ensure some overlap
var vyTMax = math.min(vyMax, leftChild.vy + rightChild.vy - 3);
if (vyTMax > vyTotal) {
vyTotal += _random.nextInt(vyTMax - vyTotal);
}
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);
} else {
v.blitFrom(leftChild, 0, vyTotal - leftChild.vy);
v.blitFrom(rightChild, leftChild.vx - 1, 0);
}
return v;
}
}
Vault? _suggest(int tries, Requirement req) {
for (var i = 0; i < tries; i++) {
var sugg = _popSuggestion();
if (sugg == null) {
return null;
}
sugg = reorientVault(sugg, randomOrientation(_random));
sugg = _tidy(sugg, req);
if (sugg != null) {
return sugg;
}
}
return null;
}
Vault? _popSuggestion() {
if (_queue.isEmpty) {
_queue = _vaults.randomFlight(_random);
}
if (_queue.isEmpty) {
return null;
}
return _queue.removeLast();
}
Vault? _tidy(Vault vault, Requirement req) {
if (vault.vx > req.vxMax || vault.vy > req.vyMax) {
return null;
}
if (vault.vx < req.vxMin || vault.vy < req.vyMin) {
return null;
}
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;
}
}