Vault/BSP based level generator, part 1
This commit is contained in:
		@@ -1,7 +1,6 @@
 | 
				
			|||||||
import 'dart:ui' as ui;
 | 
					import 'dart:ui' as ui;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import 'package:dartterm/wfc/template.dart';
 | 
					import 'package:dartterm/gen/generator.dart';
 | 
				
			||||||
import 'package:dartterm/world/level.dart';
 | 
					 | 
				
			||||||
import 'package:flutter/services.dart';
 | 
					import 'package:flutter/services.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Assets {
 | 
					class Assets {
 | 
				
			||||||
@@ -14,15 +13,14 @@ class Assets {
 | 
				
			|||||||
    return image;
 | 
					    return image;
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  final _Table<WfcTemplate<LevelTile>> _wfcLevelTemplates =
 | 
					  final _Table<Vaults> _vaults = _Table(Vaults.load);
 | 
				
			||||||
      _Table(loadLevelWfcAsync);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ui.Image? getImageIfAvailable(String name) {
 | 
					  ui.Image? getImageIfAvailable(String name) {
 | 
				
			||||||
    return _images.getIfAvailable(name);
 | 
					    return _images.getIfAvailable(name);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  WfcTemplate<LevelTile>? getWfcLevelTemplateIfAvailable(String name) {
 | 
					  Vaults? getVaultsIfAvailable(String name) {
 | 
				
			||||||
    return _wfcLevelTemplates.getIfAvailable(name);
 | 
					    return _vaults.getIfAvailable(name);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -53,6 +51,6 @@ ui.Image? getImageIfAvailable(String name) {
 | 
				
			|||||||
  return assets.getImageIfAvailable(name);
 | 
					  return assets.getImageIfAvailable(name);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
WfcTemplate<LevelTile>? getWfcLevelTemplateIfAvailable(String name) {
 | 
					Vaults? getVaultsIfAvailable(String name) {
 | 
				
			||||||
  return assets.getWfcLevelTemplateIfAvailable(name);
 | 
					  return assets.getVaultsIfAvailable(name);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,41 +1,48 @@
 | 
				
			|||||||
import 'dart:developer';
 | 
					import 'dart:developer';
 | 
				
			||||||
 | 
					import 'dart:math' as math;
 | 
				
			||||||
import 'package:dartterm/assets.dart';
 | 
					import 'package:dartterm/assets.dart';
 | 
				
			||||||
import 'package:dartterm/colors.dart';
 | 
					import 'package:dartterm/colors.dart';
 | 
				
			||||||
 | 
					import 'package:dartterm/gen/generator.dart';
 | 
				
			||||||
import 'package:dartterm/terminal.dart';
 | 
					import 'package:dartterm/terminal.dart';
 | 
				
			||||||
import 'package:dartterm/wfc/template.dart';
 | 
					 | 
				
			||||||
import 'package:dartterm/world/level.dart';
 | 
					import 'package:dartterm/world/level.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void main() async {
 | 
					void main() async {
 | 
				
			||||||
  WfcTemplate<LevelTile> template;
 | 
					  Vaults vaults;
 | 
				
			||||||
  while (true) {
 | 
					  while (true) {
 | 
				
			||||||
    log("about to load template");
 | 
					    log("about to load template");
 | 
				
			||||||
    at(0, 0).clear();
 | 
					    at(0, 0).clear();
 | 
				
			||||||
    at(0, 0).puts("Loading template!");
 | 
					    at(0, 0).puts("Loading template!");
 | 
				
			||||||
    WfcTemplate<LevelTile>? maybeTemplate =
 | 
					    Vaults? maybeVaults =
 | 
				
			||||||
        getWfcLevelTemplateIfAvailable("assets/images/wfc/bighouse2.png");
 | 
					        getVaultsIfAvailable("assets/images/wfc/bighouse2.png");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (maybeTemplate != null) {
 | 
					    if (maybeVaults != null) {
 | 
				
			||||||
      log("wasn't null!");
 | 
					      log("wasn't null!");
 | 
				
			||||||
      template = maybeTemplate;
 | 
					      vaults = maybeVaults;
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    await zzz(0.1);
 | 
					    await zzz(0.1);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  at(0, 0).clear();
 | 
					  at(0, 0).clear();
 | 
				
			||||||
  at(0, 0).puts("Loaded! $template");
 | 
					  at(0, 0).puts("Loaded! $vaults");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  var W = 16;
 | 
					  Vault output = vaults.generateBoxed(
 | 
				
			||||||
  var H = 16;
 | 
					      math.Random(2),
 | 
				
			||||||
 | 
					      Requirement(
 | 
				
			||||||
  var wfc = Wfc(template, W, H);
 | 
					          16,
 | 
				
			||||||
  wfc.run(1, -1);
 | 
					          16,
 | 
				
			||||||
  var output = wfc.extractPartial();
 | 
					          DirectionSet({
 | 
				
			||||||
  for (var y = 0; y < W; y++) {
 | 
					            Direction.up,
 | 
				
			||||||
    for (var x = 0; x < H; x++) {
 | 
					            Direction.left,
 | 
				
			||||||
 | 
					            Direction.right,
 | 
				
			||||||
 | 
					            Direction.down
 | 
				
			||||||
 | 
					          })));
 | 
				
			||||||
 | 
					  var w = output.vx;
 | 
				
			||||||
 | 
					  var h = output.vy;
 | 
				
			||||||
 | 
					  for (var y = 0; y < w; y++) {
 | 
				
			||||||
 | 
					    for (var x = 0; x < h; x++) {
 | 
				
			||||||
      var cursor = at(x * 2, y * 2).big();
 | 
					      var cursor = at(x * 2, y * 2).big();
 | 
				
			||||||
      switch (output[x + y * W]) {
 | 
					      switch (output.tiles[x + y * w]) {
 | 
				
			||||||
        case LevelTile.floor:
 | 
					        case LevelTile.floor:
 | 
				
			||||||
          cursor.puts(" ");
 | 
					          cursor.puts(" ");
 | 
				
			||||||
        case LevelTile.door:
 | 
					        case LevelTile.door:
 | 
				
			||||||
@@ -44,8 +51,6 @@ void main() async {
 | 
				
			|||||||
          cursor.puts("#");
 | 
					          cursor.puts("#");
 | 
				
			||||||
        case LevelTile.exit:
 | 
					        case LevelTile.exit:
 | 
				
			||||||
          cursor.puts("X");
 | 
					          cursor.puts("X");
 | 
				
			||||||
        case null:
 | 
					 | 
				
			||||||
          cursor.puts("?");
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										301
									
								
								lib/gen/generator.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										301
									
								
								lib/gen/generator.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,301 @@
 | 
				
			|||||||
 | 
					import 'dart:developer';
 | 
				
			||||||
 | 
					import 'dart:math' as math;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'package:dartterm/world/level.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum Direction {
 | 
				
			||||||
 | 
					  up,
 | 
				
			||||||
 | 
					  left,
 | 
				
			||||||
 | 
					  down,
 | 
				
			||||||
 | 
					  right,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Vaults {
 | 
				
			||||||
 | 
					  List<Vault> _primitive = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static Future<Vaults> load(String filename) async {
 | 
				
			||||||
 | 
					    // TODO
 | 
				
			||||||
 | 
					    return Vaults();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Vault generateBoxed(math.Random random, Requirement req) {
 | 
				
			||||||
 | 
					    var vx = req.vx;
 | 
				
			||||||
 | 
					    var vy = req.vy;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var tiles = [
 | 
				
			||||||
 | 
					      for (var y = 0; y < vy; y++)
 | 
				
			||||||
 | 
					        for (var x = 0; x < vx; x++) LevelTile.wall
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					    var v = Vault(tiles, vx, vy, req.smooth);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (req.vx < 2 || req.vy < 2) {
 | 
				
			||||||
 | 
					      return v;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    var req2 = Requirement(vx - 2, vy - 2, req.smooth);
 | 
				
			||||||
 | 
					    var inner = generate(random, req2);
 | 
				
			||||||
 | 
					    v.blitFrom(inner, 1, 1);
 | 
				
			||||||
 | 
					    return v;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Vault generate(math.Random random, Requirement requirement) {
 | 
				
			||||||
 | 
					    // TODO: Pick a relevant vault from the vaults file if possible
 | 
				
			||||||
 | 
					    //
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 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 = requirement.unReorient(orientation);
 | 
				
			||||||
 | 
					    if (req2.vy > (req2.vx - 2) * 3 / 2) {
 | 
				
			||||||
 | 
					      orientation = (orientation + 2) % 8; // rotate once more
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    req2 = requirement.unReorient(orientation);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var out2 = _generate(random, req2);
 | 
				
			||||||
 | 
					    var out1 = out2.reorient(orientation);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    log("$orientation ${requirement.vx} ${requirement.vy} ${req2.vx} ${req2.vy} ${out2.vx} ${out2.vy} ${out1.vx} ${out1.vy}");
 | 
				
			||||||
 | 
					    assert(out1.vx == requirement.vx);
 | 
				
			||||||
 | 
					    assert(out1.vy == requirement.vy);
 | 
				
			||||||
 | 
					    return out1;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Vault _generate(math.Random random, Requirement req) {
 | 
				
			||||||
 | 
					    var vx = req.vx;
 | 
				
			||||||
 | 
					    var vy = req.vy;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var tiles = [
 | 
				
			||||||
 | 
					      for (var y = 0; y < vy; y++)
 | 
				
			||||||
 | 
					        for (var x = 0; x < vx; x++) LevelTile.wall
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					    var v = Vault(tiles, vx, vy, req.smooth);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (vx < 3 || vx * vy < 10) {
 | 
				
			||||||
 | 
					      v.clear(LevelTile.floor);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      // pick a split point
 | 
				
			||||||
 | 
					      var splitVx = random.nextInt(vx - 2) + 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      var reqLeft = Requirement(splitVx, vy, req.smooth.clone());
 | 
				
			||||||
 | 
					      reqLeft.smooth.directions.add(Direction.right);
 | 
				
			||||||
 | 
					      var reqRight = Requirement((vx - splitVx - 1), vy, req.smooth.clone());
 | 
				
			||||||
 | 
					      reqRight.smooth.directions.add(Direction.left);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      var vaultLeft = generate(random, reqLeft);
 | 
				
			||||||
 | 
					      var vaultRight = generate(random, reqRight);
 | 
				
			||||||
 | 
					      v.blitFrom(vaultLeft, 0, 0);
 | 
				
			||||||
 | 
					      v.blitFrom(vaultRight, splitVx + 1, 0);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return v;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TODO: There are many more efficient ways to do this
 | 
				
			||||||
 | 
					class DirectionSet {
 | 
				
			||||||
 | 
					  final Set<Direction> directions;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  DirectionSet(this.directions);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  DirectionSet flip() {
 | 
				
			||||||
 | 
					    var ds2 = DirectionSet({});
 | 
				
			||||||
 | 
					    for (var i in directions) {
 | 
				
			||||||
 | 
					      switch (i) {
 | 
				
			||||||
 | 
					        case Direction.up:
 | 
				
			||||||
 | 
					          ds2.directions.add(Direction.up);
 | 
				
			||||||
 | 
					        case Direction.left:
 | 
				
			||||||
 | 
					          ds2.directions.add(Direction.right);
 | 
				
			||||||
 | 
					        case Direction.down:
 | 
				
			||||||
 | 
					          ds2.directions.add(Direction.down);
 | 
				
			||||||
 | 
					        case Direction.right:
 | 
				
			||||||
 | 
					          ds2.directions.add(Direction.left);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return ds2;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  DirectionSet rotateLeft() {
 | 
				
			||||||
 | 
					    var ds2 = DirectionSet({});
 | 
				
			||||||
 | 
					    for (var i in directions) {
 | 
				
			||||||
 | 
					      switch (i) {
 | 
				
			||||||
 | 
					        case Direction.up:
 | 
				
			||||||
 | 
					          ds2.directions.add(Direction.left);
 | 
				
			||||||
 | 
					        case Direction.left:
 | 
				
			||||||
 | 
					          ds2.directions.add(Direction.down);
 | 
				
			||||||
 | 
					        case Direction.down:
 | 
				
			||||||
 | 
					          ds2.directions.add(Direction.right);
 | 
				
			||||||
 | 
					        case Direction.right:
 | 
				
			||||||
 | 
					          ds2.directions.add(Direction.up);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return ds2;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  DirectionSet rotateRight() {
 | 
				
			||||||
 | 
					    var ds2 = DirectionSet({});
 | 
				
			||||||
 | 
					    for (var i in directions) {
 | 
				
			||||||
 | 
					      switch (i) {
 | 
				
			||||||
 | 
					        case Direction.up:
 | 
				
			||||||
 | 
					          ds2.directions.add(Direction.right);
 | 
				
			||||||
 | 
					        case Direction.right:
 | 
				
			||||||
 | 
					          ds2.directions.add(Direction.down);
 | 
				
			||||||
 | 
					        case Direction.down:
 | 
				
			||||||
 | 
					          ds2.directions.add(Direction.left);
 | 
				
			||||||
 | 
					        case Direction.left:
 | 
				
			||||||
 | 
					          ds2.directions.add(Direction.up);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return ds2;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  DirectionSet clone() {
 | 
				
			||||||
 | 
					    var ds2 = DirectionSet({});
 | 
				
			||||||
 | 
					    ds2.directions.addAll(directions);
 | 
				
			||||||
 | 
					    return ds2;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Requirement {
 | 
				
			||||||
 | 
					  final int vx, vy;
 | 
				
			||||||
 | 
					  final DirectionSet smooth;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Requirement(this.vx, this.vy, this.smooth);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Requirement flip() {
 | 
				
			||||||
 | 
					    return Requirement(vx, vy, smooth.flip());
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Requirement rotateLeft() {
 | 
				
			||||||
 | 
					    return Requirement(vy, vx, smooth.rotateLeft());
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Requirement rotateRight() {
 | 
				
			||||||
 | 
					    return Requirement(vy, vx, smooth.rotateRight());
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Requirement unReorient(int r) {
 | 
				
			||||||
 | 
					    assert(r >= 0 && r < 8);
 | 
				
			||||||
 | 
					    Requirement o = this;
 | 
				
			||||||
 | 
					    if (r % 2 == 1) {
 | 
				
			||||||
 | 
					      o = o.flip();
 | 
				
			||||||
 | 
					      r -= 1;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    while (r >= 2) {
 | 
				
			||||||
 | 
					      o = o.rotateLeft();
 | 
				
			||||||
 | 
					      r -= 2;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return o;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Vault {
 | 
				
			||||||
 | 
					  final List<LevelTile> tiles;
 | 
				
			||||||
 | 
					  final int vx, vy;
 | 
				
			||||||
 | 
					  final DirectionSet smooth;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Vault(this.tiles, this.vx, this.vy, this.smooth) {
 | 
				
			||||||
 | 
					    assert(tiles.length == vx * vy);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static Vault fromVaultData(List<LevelTile> tiles, int vx, int vy) {
 | 
				
			||||||
 | 
					    assert(tiles.length == vx * vy);
 | 
				
			||||||
 | 
					    var smooth = {
 | 
				
			||||||
 | 
					      Direction.up,
 | 
				
			||||||
 | 
					      Direction.left,
 | 
				
			||||||
 | 
					      Direction.down,
 | 
				
			||||||
 | 
					      Direction.right
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    for (var x = 0; x < vx; x++) {
 | 
				
			||||||
 | 
					      if (tiles[x + 0 * vx] == LevelTile.wall) {
 | 
				
			||||||
 | 
					        smooth.remove(Direction.up);
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    for (var x = 0; x < vx; x++) {
 | 
				
			||||||
 | 
					      if (tiles[x + (vy - 1) * vx] == LevelTile.wall) {
 | 
				
			||||||
 | 
					        smooth.remove(Direction.down);
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    for (var y = 0; y < vy; y++) {
 | 
				
			||||||
 | 
					      if (tiles[0 + y * vx] == LevelTile.wall) {
 | 
				
			||||||
 | 
					        smooth.remove(Direction.left);
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    for (var y = 0; y < vy; y++) {
 | 
				
			||||||
 | 
					      if (tiles[vx - 1 + y * vx] == LevelTile.wall) {
 | 
				
			||||||
 | 
					        smooth.remove(Direction.right);
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return Vault(tiles, vx, vy, DirectionSet(smooth));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void clear(LevelTile lt) {
 | 
				
			||||||
 | 
					    for (var y = 0; y < vy; y++) {
 | 
				
			||||||
 | 
					      for (var x = 0; x < vx; x++) {
 | 
				
			||||||
 | 
					        tiles[y * vx + x] = 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];
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Vault flip() {
 | 
				
			||||||
 | 
					    List<LevelTile> tiles2 = [
 | 
				
			||||||
 | 
					      for (var y = 0; y < vy; y++)
 | 
				
			||||||
 | 
					        for (var x = vx - 1; x >= 0; x--) tiles[y * vx + x]
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return Vault(tiles2, vx, vy, smooth.flip());
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Vault rotateRight() {
 | 
				
			||||||
 | 
					    List<LevelTile> tiles2 = [
 | 
				
			||||||
 | 
					      for (var x = 0; x < vx; x++)
 | 
				
			||||||
 | 
					        for (var y = 0; y < vy; y++) tiles[y * vx + x]
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return Vault(tiles2, vy, vx, smooth.rotateRight());
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Vault rotateLeft() {
 | 
				
			||||||
 | 
					    List<LevelTile> tiles2 = [
 | 
				
			||||||
 | 
					      for (var x = vx - 1; x >= 0; x++)
 | 
				
			||||||
 | 
					        for (var y = vy - 1; y >= 0; y++) tiles[y * vx + x]
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return Vault(tiles2, vy, vx, smooth.rotateLeft());
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Vault reorient(int r) {
 | 
				
			||||||
 | 
					    assert(r >= 0 && r < 8);
 | 
				
			||||||
 | 
					    Vault o = this;
 | 
				
			||||||
 | 
					    while (r >= 2) {
 | 
				
			||||||
 | 
					      o = o.rotateRight();
 | 
				
			||||||
 | 
					      r -= 2;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (r == 1) {
 | 
				
			||||||
 | 
					      o = o.flip();
 | 
				
			||||||
 | 
					      r -= 1;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return o;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int randomOrientation(math.Random random) {
 | 
				
			||||||
 | 
					  return random.nextInt(8);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,281 +0,0 @@
 | 
				
			|||||||
import 'dart:math';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
abstract class Model {
 | 
					 | 
				
			||||||
  static var dx = [-1, 0, 1, 0];
 | 
					 | 
				
			||||||
  static var dy = [0, 1, 0, -1];
 | 
					 | 
				
			||||||
  static var opposite = [2, 3, 0, 1];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  bool _initialized = false;
 | 
					 | 
				
			||||||
  List<List<bool>> _wave = [];
 | 
					 | 
				
			||||||
  List<List<List<int>>> propagator = [];
 | 
					 | 
				
			||||||
  List<List<List<int>>> _compatible = [];
 | 
					 | 
				
			||||||
  List<int?> _observed = [];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  List<(int, int)> _stack = [];
 | 
					 | 
				
			||||||
  int _stacksize = 0, _observedSoFar = 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  int cMx = 0, cMy = 0, cT = 0, cN = 0;
 | 
					 | 
				
			||||||
  bool _periodic = false;
 | 
					 | 
				
			||||||
  bool ground = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  List<double> weights = [];
 | 
					 | 
				
			||||||
  List<double> _weightLogWeights = [], _distribution = [];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  List<int> _sumsOfOnes = [];
 | 
					 | 
				
			||||||
  double _sumOfWeights = 0.0,
 | 
					 | 
				
			||||||
      _sumOfWeightLogWeights = 0.0,
 | 
					 | 
				
			||||||
      _startingEntropy = 0.0;
 | 
					 | 
				
			||||||
  List<double> _sumsOfWeights = [],
 | 
					 | 
				
			||||||
      _sumsOfWeightLogWeights = [],
 | 
					 | 
				
			||||||
      _entropies = [];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Heuristic _heuristic = Heuristic.Entropy;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Model(int width, int height, int n, bool periodic, Heuristic heuristic) {
 | 
					 | 
				
			||||||
    cMx = width;
 | 
					 | 
				
			||||||
    cMy = height;
 | 
					 | 
				
			||||||
    cN = n;
 | 
					 | 
				
			||||||
    _periodic = periodic;
 | 
					 | 
				
			||||||
    _heuristic = heuristic;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  void _init() {
 | 
					 | 
				
			||||||
    _initialized = true;
 | 
					 | 
				
			||||||
    _wave = [
 | 
					 | 
				
			||||||
      for (var r = 0; r < cMx * cMy; r++) [for (var t = 0; t < cT; t++) false]
 | 
					 | 
				
			||||||
    ];
 | 
					 | 
				
			||||||
    _compatible = [
 | 
					 | 
				
			||||||
      for (var r = 0; r < cMx * cMy; r++)
 | 
					 | 
				
			||||||
        [
 | 
					 | 
				
			||||||
          for (var t = 0; t < cT; t++) [0, 0, 0, 0]
 | 
					 | 
				
			||||||
        ]
 | 
					 | 
				
			||||||
    ];
 | 
					 | 
				
			||||||
    _distribution = [for (var t = 0; t < cT; t++) 0.0];
 | 
					 | 
				
			||||||
    _observed = [for (var r = 0; r < cMx * cMy; r++) null];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    _weightLogWeights = [
 | 
					 | 
				
			||||||
      for (var t = 0; t < cT; t++) weights[t] * log(weights[t])
 | 
					 | 
				
			||||||
    ];
 | 
					 | 
				
			||||||
    _sumOfWeights = 0;
 | 
					 | 
				
			||||||
    _sumOfWeightLogWeights = 0.0;
 | 
					 | 
				
			||||||
    for (var t = 0; t < cT; t++) {
 | 
					 | 
				
			||||||
      _sumOfWeights += weights[t];
 | 
					 | 
				
			||||||
      _sumOfWeightLogWeights += _weightLogWeights[t];
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    _startingEntropy =
 | 
					 | 
				
			||||||
        log(_sumOfWeights) - _sumOfWeightLogWeights / _sumOfWeights;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    _sumsOfOnes = [for (var r = 0; r < cMx * cMy; r++) 0];
 | 
					 | 
				
			||||||
    _sumsOfWeights = [for (var r = 0; r < cMx * cMy; r++) 0.0];
 | 
					 | 
				
			||||||
    _sumsOfWeightLogWeights = [for (var r = 0; r < cMx * cMy; r++) 0.0];
 | 
					 | 
				
			||||||
    _entropies = [for (var r = 0; r < cMx * cMy; r++) 0.0];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    _stack = [for (var r = 0; r < _wave.length * cT; r++) (0, 0)];
 | 
					 | 
				
			||||||
    _stacksize = 0;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  bool run(int? seed, int limit) {
 | 
					 | 
				
			||||||
    if (!_initialized) {
 | 
					 | 
				
			||||||
      _init();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    clear();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    var random = Random(seed);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    for (var l = 0; l < limit || limit < 0; l++) {
 | 
					 | 
				
			||||||
      var node = _nextUnobservedNode(random);
 | 
					 | 
				
			||||||
      if (node >= 0) {
 | 
					 | 
				
			||||||
        _observe(node, random);
 | 
					 | 
				
			||||||
        var success = _propagate();
 | 
					 | 
				
			||||||
        if (!success) {
 | 
					 | 
				
			||||||
          return false;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        for (var i = 0; i < _wave.length; i++) {
 | 
					 | 
				
			||||||
          for (var t = 0; t < cT; t++) {
 | 
					 | 
				
			||||||
            if (_wave[i][t]) {
 | 
					 | 
				
			||||||
              _observed[i] = t;
 | 
					 | 
				
			||||||
              break;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return true;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return true;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  int _nextUnobservedNode(Random random) {
 | 
					 | 
				
			||||||
    if (_heuristic == Heuristic.Scanline) {
 | 
					 | 
				
			||||||
      for (var i = _observedSoFar; i < _wave.length; i++) {
 | 
					 | 
				
			||||||
        if (!_periodic && (i % cMx + cN > cMx || i ~/ cMx + cN > cMy)) {
 | 
					 | 
				
			||||||
          continue;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if (_sumsOfOnes[i] > 1) {
 | 
					 | 
				
			||||||
          _observedSoFar = i + 1;
 | 
					 | 
				
			||||||
          return i;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      return -1;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    double min = 1E+4;
 | 
					 | 
				
			||||||
    int argmin = -1;
 | 
					 | 
				
			||||||
    for (var i = 0; i < _wave.length; i++) {
 | 
					 | 
				
			||||||
      if (!_periodic && (i % cMx + cN > cMx || i ~/ cMx + cN > cMy)) {
 | 
					 | 
				
			||||||
        continue;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      var remainingValues = _sumsOfOnes[i];
 | 
					 | 
				
			||||||
      double entropy = _heuristic == Heuristic.Entropy
 | 
					 | 
				
			||||||
          ? _entropies[i]
 | 
					 | 
				
			||||||
          : remainingValues.toDouble();
 | 
					 | 
				
			||||||
      if (remainingValues > 1 && entropy <= min) {
 | 
					 | 
				
			||||||
        double noise = 1E-6 * random.nextDouble();
 | 
					 | 
				
			||||||
        if (entropy + noise < min) {
 | 
					 | 
				
			||||||
          min = entropy + noise;
 | 
					 | 
				
			||||||
          argmin = i;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return argmin;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  void _observe(int node, Random random) {
 | 
					 | 
				
			||||||
    var w = _wave[node];
 | 
					 | 
				
			||||||
    for (var t = 0; t < cT; t++) {
 | 
					 | 
				
			||||||
      _distribution[t] = w[t] ? weights[t] : 0.0;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    int r = _chooseRandom(random, _distribution);
 | 
					 | 
				
			||||||
    for (var t = 0; t < cT; t++) {
 | 
					 | 
				
			||||||
      if (w[t] != (t == r)) {
 | 
					 | 
				
			||||||
        _ban(node, t);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  bool _propagate() {
 | 
					 | 
				
			||||||
    while (_stacksize > 0) {
 | 
					 | 
				
			||||||
      int i1, t1;
 | 
					 | 
				
			||||||
      (i1, t1) = _stack[_stacksize - 1];
 | 
					 | 
				
			||||||
      _stacksize--;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      int x1 = i1 % cMx;
 | 
					 | 
				
			||||||
      int y1 = i1 % cMy;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      for (int d = 0; d < 4; d++) {
 | 
					 | 
				
			||||||
        int x2 = x1 + dx[d];
 | 
					 | 
				
			||||||
        int y2 = y1 + dy[d];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (!_periodic &&
 | 
					 | 
				
			||||||
            (x2 < 0 || y2 < 0 || x2 + cN > cMx || y2 + cN > cMy)) {
 | 
					 | 
				
			||||||
          continue;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (x2 < 0) {
 | 
					 | 
				
			||||||
          x2 += cMx;
 | 
					 | 
				
			||||||
        } else if (x2 >= cMx) {
 | 
					 | 
				
			||||||
          x2 -= cMx;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (y2 < 0) {
 | 
					 | 
				
			||||||
          y2 += cMy;
 | 
					 | 
				
			||||||
        } else if (y2 >= cMy) {
 | 
					 | 
				
			||||||
          y2 -= cMy;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        int i2 = x2 + y2 * cMx;
 | 
					 | 
				
			||||||
        var p = propagator[d][t1];
 | 
					 | 
				
			||||||
        var compat = _compatible[i2];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for (var l = 0; l < p.length; l++) {
 | 
					 | 
				
			||||||
          var t2 = p[l];
 | 
					 | 
				
			||||||
          var comp = compat[t2];
 | 
					 | 
				
			||||||
          comp[d]--;
 | 
					 | 
				
			||||||
          if (comp[d] == 0) {
 | 
					 | 
				
			||||||
            _ban(i2, t2);
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return _sumsOfOnes[0] > 0;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  void _ban(int i, int t) {
 | 
					 | 
				
			||||||
    _wave[i][t] = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    var comp = _compatible[i][t];
 | 
					 | 
				
			||||||
    for (var d = 0; d < 4; d++) {
 | 
					 | 
				
			||||||
      comp[d] = 0;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    _stack[_stacksize] = (i, t);
 | 
					 | 
				
			||||||
    _stacksize++;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    _sumsOfOnes[i] -= 1;
 | 
					 | 
				
			||||||
    _sumsOfWeights[i] -= weights[t];
 | 
					 | 
				
			||||||
    _sumsOfWeightLogWeights[i] -= _weightLogWeights[t];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    var sum = _sumsOfWeights[i];
 | 
					 | 
				
			||||||
    _entropies[i] = log(sum) - _sumsOfWeightLogWeights[i] / sum;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  void clear() {
 | 
					 | 
				
			||||||
    for (var i = 0; i < _wave.length; i++) {
 | 
					 | 
				
			||||||
      for (var t = 0; t < cT; t++) {
 | 
					 | 
				
			||||||
        _wave[i][t] = true;
 | 
					 | 
				
			||||||
        for (var d = 0; d < 4; d++) {
 | 
					 | 
				
			||||||
          _compatible[i][t][d] = propagator[opposite[d]][t].length;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      _sumsOfOnes[i] = weights.length;
 | 
					 | 
				
			||||||
      _sumsOfWeights[i] = _sumOfWeights;
 | 
					 | 
				
			||||||
      _sumsOfWeightLogWeights[i] = _sumOfWeightLogWeights;
 | 
					 | 
				
			||||||
      _entropies[i] = _startingEntropy;
 | 
					 | 
				
			||||||
      _observed[i] = -1;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    _observedSoFar = 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (ground) {
 | 
					 | 
				
			||||||
      for (var x = 0; x < cMx; x++) {
 | 
					 | 
				
			||||||
        for (var t = 0; t < cT - 1; t++) {
 | 
					 | 
				
			||||||
          _ban(x + (cMy - 1) * cMx, t);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        for (var y = 0; y < cMy - 1; y++) {
 | 
					 | 
				
			||||||
          _ban(x + y * cMx, cT - 1);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      _propagate();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
int _chooseRandom(Random rand, List<double> distribution) {
 | 
					 | 
				
			||||||
  if (distribution.isEmpty) {
 | 
					 | 
				
			||||||
    throw Exception("can't sample empty distribution");
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  var sum = 0.0;
 | 
					 | 
				
			||||||
  for (var i = 0; i < distribution.length; i++) {
 | 
					 | 
				
			||||||
    sum += distribution[i];
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (sum == 0.0) {
 | 
					 | 
				
			||||||
    return rand.nextInt(distribution.length);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  var rnd = rand.nextDouble() * sum;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  var i = 0;
 | 
					 | 
				
			||||||
  while (rnd > 0) {
 | 
					 | 
				
			||||||
    rnd -= distribution[i];
 | 
					 | 
				
			||||||
    if (rnd < 0) {
 | 
					 | 
				
			||||||
      return i;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    i += 1;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  return distribution.length - 1;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
enum Heuristic { Entropy, MRV, Scanline }
 | 
					 | 
				
			||||||
@@ -1,467 +0,0 @@
 | 
				
			|||||||
import 'dart:developer';
 | 
					 | 
				
			||||||
import 'dart:math' as math;
 | 
					 | 
				
			||||||
import 'dart:typed_data';
 | 
					 | 
				
			||||||
import 'dart:ui' as ui;
 | 
					 | 
				
			||||||
import 'dart:ui';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import 'package:flutter/services.dart';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Wfc<T> {
 | 
					 | 
				
			||||||
  // parameters
 | 
					 | 
				
			||||||
  final WfcTemplate<T> _template;
 | 
					 | 
				
			||||||
  final int _mx, _my;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // constants
 | 
					 | 
				
			||||||
  int get _n => _mx * _my;
 | 
					 | 
				
			||||||
  int get _nShingles => _template._shingles.n;
 | 
					 | 
				
			||||||
  int get _order => _template._order;
 | 
					 | 
				
			||||||
  double _weight(int shingleIx) =>
 | 
					 | 
				
			||||||
      _template._shingles._shingleWeights[shingleIx];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // overall algo state
 | 
					 | 
				
			||||||
  List<List<bool>> _wave = [];
 | 
					 | 
				
			||||||
  List<List<List<int>>> _compatible = [];
 | 
					 | 
				
			||||||
  List<int?> _observed = [];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // computationally expensive stuff that we keep in an incremental way
 | 
					 | 
				
			||||||
  List<int> _sumsOfOnes = [];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // temporaries
 | 
					 | 
				
			||||||
  List<double> _distribution = [];
 | 
					 | 
				
			||||||
  List<(int, int)> _stack = [];
 | 
					 | 
				
			||||||
  int _stacksize = 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Wfc(this._template, this._mx, this._my) {
 | 
					 | 
				
			||||||
    _wave = [
 | 
					 | 
				
			||||||
      for (var r = 0; r < _n; r++) [for (var t = 0; t < _nShingles; t++) false]
 | 
					 | 
				
			||||||
    ];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    _compatible = [
 | 
					 | 
				
			||||||
      for (var r = 0; r < _n; r++)
 | 
					 | 
				
			||||||
        [
 | 
					 | 
				
			||||||
          for (var t = 0; t < _nShingles; t++) [0, 0, 0, 0]
 | 
					 | 
				
			||||||
        ]
 | 
					 | 
				
			||||||
    ];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    _distribution = [for (var t = 0; t < _nShingles; t++) 0.0];
 | 
					 | 
				
			||||||
    _observed = [for (var r = 0; r < _n; r++) null];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    _sumsOfOnes = [for (var r = 0; r < _n; r++) 0];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    _stack = [for (var r = 0; r < _n * _nShingles; r++) (0, 0)];
 | 
					 | 
				
			||||||
    _stacksize = 0;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  void clear() {
 | 
					 | 
				
			||||||
    for (var i = 0; i < _wave.length; i++) {
 | 
					 | 
				
			||||||
      for (var t = 0; t < _nShingles; t++) {
 | 
					 | 
				
			||||||
        _wave[i][t] = true;
 | 
					 | 
				
			||||||
        for (var d = 0; d < 4; d++) {
 | 
					 | 
				
			||||||
          _compatible[i][t][d] = _template
 | 
					 | 
				
			||||||
              ._shingles._metadata._propagators[_opposite[d]][t].length;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      _sumsOfOnes[i] = _nShingles;
 | 
					 | 
				
			||||||
      _observed[i] = null;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  bool run(int? seed, int limit) {
 | 
					 | 
				
			||||||
    clear();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    var random = math.Random(seed);
 | 
					 | 
				
			||||||
    for (var l = 0; l < limit || limit < 0; l++) {
 | 
					 | 
				
			||||||
      var node = _nextUnobservedNode(random);
 | 
					 | 
				
			||||||
      if (node != null) {
 | 
					 | 
				
			||||||
        _observe(node, random);
 | 
					 | 
				
			||||||
        var success = _propagate();
 | 
					 | 
				
			||||||
        if (!success) {
 | 
					 | 
				
			||||||
          return false;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        for (var i = 0; i < _n; i++) {
 | 
					 | 
				
			||||||
          for (var t = 0; t < _nShingles; t++) {
 | 
					 | 
				
			||||||
            if (_wave[i][t]) {
 | 
					 | 
				
			||||||
              _observed[i] = t;
 | 
					 | 
				
			||||||
              break;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return true;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return true;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  List<T>? extract() {
 | 
					 | 
				
			||||||
    var partial = extractPartial();
 | 
					 | 
				
			||||||
    List<T> out = [];
 | 
					 | 
				
			||||||
    for (var i in partial) {
 | 
					 | 
				
			||||||
      if (i == null) {
 | 
					 | 
				
			||||||
        return null;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      out.add(i);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return out;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  List<T?> extractPartial() {
 | 
					 | 
				
			||||||
    List<T?> result = [];
 | 
					 | 
				
			||||||
    for (int i = 0; i < _n; i++) {
 | 
					 | 
				
			||||||
      result.add(null);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    for (int y = 0; y < _my; y++) {
 | 
					 | 
				
			||||||
      var dy = y < _my - _order + 1 ? 0 : _order - 1;
 | 
					 | 
				
			||||||
      for (int x = 0; x < _mx; x++) {
 | 
					 | 
				
			||||||
        var dx = x < _mx - _order + 1 ? 0 : _order - 1;
 | 
					 | 
				
			||||||
        var shingleIx = _observed[x - dx + (y - dy) * _mx];
 | 
					 | 
				
			||||||
        if (shingleIx != null) {
 | 
					 | 
				
			||||||
          var shingle = _template._shingles._shingleValues[shingleIx];
 | 
					 | 
				
			||||||
          var content = shingle.content[dx + dy * _order];
 | 
					 | 
				
			||||||
          var real = _template._embedding.decode(content);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          result[x + y * _mx] = real;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return result;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  int? _nextUnobservedNode(math.Random random) {
 | 
					 | 
				
			||||||
    double min = 1E+10;
 | 
					 | 
				
			||||||
    int? argmin;
 | 
					 | 
				
			||||||
    for (var i = 0; i < _n; i++) {
 | 
					 | 
				
			||||||
      if (i % _mx + _order > _mx || i ~/ _mx + _order > _my) {
 | 
					 | 
				
			||||||
        continue;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      var remainingValues = _sumsOfOnes[i];
 | 
					 | 
				
			||||||
      double entropy = remainingValues.toDouble();
 | 
					 | 
				
			||||||
      if (remainingValues > 1 && entropy <= min) {
 | 
					 | 
				
			||||||
        double noise = 1E-6 * random.nextDouble();
 | 
					 | 
				
			||||||
        if (entropy + noise < min) {
 | 
					 | 
				
			||||||
          min = entropy + noise;
 | 
					 | 
				
			||||||
          argmin = i;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return argmin;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  void _observe(int node, math.Random random) {
 | 
					 | 
				
			||||||
    var w = _wave[node];
 | 
					 | 
				
			||||||
    for (var t = 0; t < _nShingles; t++) {
 | 
					 | 
				
			||||||
      _distribution[t] = w[t] ? _weight(t) : 0.0;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    int r = _chooseRandom(random, _distribution);
 | 
					 | 
				
			||||||
    for (var t = 0; t < _nShingles; t++) {
 | 
					 | 
				
			||||||
      if (w[t] != (t == r)) {
 | 
					 | 
				
			||||||
        _ban(node, t);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  bool _propagate() {
 | 
					 | 
				
			||||||
    while (_stacksize > 0) {
 | 
					 | 
				
			||||||
      int i1, t1;
 | 
					 | 
				
			||||||
      (i1, t1) = _stack[_stacksize - 1];
 | 
					 | 
				
			||||||
      _stacksize--;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      int x1 = i1 % _mx;
 | 
					 | 
				
			||||||
      int y1 = i1 ~/ _mx;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      for (int d = 0; d < 4; d++) {
 | 
					 | 
				
			||||||
        var x2 = x1 + _dx[d];
 | 
					 | 
				
			||||||
        var y2 = y1 + _dy[d];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (x2 < 0 || y2 < 0 || x2 + _order > _mx || y2 + _order > _my) {
 | 
					 | 
				
			||||||
          continue;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        int i2 = x2 + y2 * _mx;
 | 
					 | 
				
			||||||
        var p = _template._shingles._metadata._propagators[d][t1];
 | 
					 | 
				
			||||||
        var compat = _compatible[i2];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for (var t2 in p) {
 | 
					 | 
				
			||||||
          var comp = compat[t2];
 | 
					 | 
				
			||||||
          comp[d]--;
 | 
					 | 
				
			||||||
          if (comp[d] == 0) {
 | 
					 | 
				
			||||||
            _ban(i2, t2);
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return _sumsOfOnes[0] > 0;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  void _ban(int i, int t) {
 | 
					 | 
				
			||||||
    _wave[i][t] = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    var comp = _compatible[i][t];
 | 
					 | 
				
			||||||
    for (var d = 0; d < 4; d++) {
 | 
					 | 
				
			||||||
      comp[d] = 0;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    _stack[_stacksize] = (i, t);
 | 
					 | 
				
			||||||
    _stacksize++;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    _sumsOfOnes[i] -= 1;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class WfcTemplate<T> {
 | 
					 | 
				
			||||||
  final Shingles _shingles;
 | 
					 | 
				
			||||||
  final Embedding<T> _embedding;
 | 
					 | 
				
			||||||
  final int _order;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  WfcTemplate(this._shingles, this._embedding, this._order);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  static Future<WfcTemplate<T>> loadAsync<T>(
 | 
					 | 
				
			||||||
      String name, int order, T Function(int) cb) async {
 | 
					 | 
				
			||||||
    final assetImageByteData = await rootBundle.load(name);
 | 
					 | 
				
			||||||
    final codec =
 | 
					 | 
				
			||||||
        await ui.instantiateImageCodec(assetImageByteData.buffer.asUint8List());
 | 
					 | 
				
			||||||
    final image = (await codec.getNextFrame()).image;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    final bytedata =
 | 
					 | 
				
			||||||
        (await image.toByteData(format: ImageByteFormat.rawStraightRgba))!;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    final sx = image.width;
 | 
					 | 
				
			||||||
    final sy = image.height;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    final List<T> bitmap = [];
 | 
					 | 
				
			||||||
    for (var i = 0; i < sx * sy; i++) {
 | 
					 | 
				
			||||||
      var pixel = bytedata.getUint32(i * 4, Endian.little);
 | 
					 | 
				
			||||||
      bitmap.add(cb(pixel));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return loadBitmap(bitmap, sx, sy, order);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  static WfcTemplate<T> loadBitmap<T>(
 | 
					 | 
				
			||||||
      List<T> bitmap, int sx, int sy, int order) {
 | 
					 | 
				
			||||||
    if (bitmap.length != sx * sy) {
 | 
					 | 
				
			||||||
      throw Exception("malformed bitmap");
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    var embedding = Embedding<T>();
 | 
					 | 
				
			||||||
    List<int> sample = [
 | 
					 | 
				
			||||||
      for (var i = 0; i < bitmap.length; i++) embedding.encode(bitmap[i])
 | 
					 | 
				
			||||||
    ];
 | 
					 | 
				
			||||||
    embedding.freeze();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    var shingles = Shingles();
 | 
					 | 
				
			||||||
    var xmax = sx - order + 1;
 | 
					 | 
				
			||||||
    var ymax = sy - order + 1;
 | 
					 | 
				
			||||||
    for (var y = 0; y < ymax; y++) {
 | 
					 | 
				
			||||||
      for (var x = 0; x < xmax; x++) {
 | 
					 | 
				
			||||||
        var ps = [
 | 
					 | 
				
			||||||
          Shingle(order, embedding.c,
 | 
					 | 
				
			||||||
              (dx, dy) => sample[(x + dx) % sx + (y + dy) % sy * sx])
 | 
					 | 
				
			||||||
        ];
 | 
					 | 
				
			||||||
        ps.add(ps[0].reflect());
 | 
					 | 
				
			||||||
        ps.add(ps[0].rotate());
 | 
					 | 
				
			||||||
        ps.add(ps[2].reflect());
 | 
					 | 
				
			||||||
        ps.add(ps[2].rotate());
 | 
					 | 
				
			||||||
        ps.add(ps[4].reflect());
 | 
					 | 
				
			||||||
        ps.add(ps[4].rotate());
 | 
					 | 
				
			||||||
        ps.add(ps[6].reflect());
 | 
					 | 
				
			||||||
        for (var p in ps) {
 | 
					 | 
				
			||||||
          shingles.observe(p, 1.0);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    shingles.freeze();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return WfcTemplate(shingles, embedding, order);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Shingles {
 | 
					 | 
				
			||||||
  bool _frozen = false;
 | 
					 | 
				
			||||||
  final Map<int, int> _shingleIndices = {};
 | 
					 | 
				
			||||||
  final List<Shingle> _shingleValues = [];
 | 
					 | 
				
			||||||
  final List<double> _shingleWeights = [];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  late ShingleMetadata _metadata;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  int get n {
 | 
					 | 
				
			||||||
    if (!_frozen) {
 | 
					 | 
				
			||||||
      throw StateError("can't use Shingles#get n until frozen");
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return _shingleValues.length;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  void freeze() {
 | 
					 | 
				
			||||||
    if (_frozen) {
 | 
					 | 
				
			||||||
      throw StateError("can't freeze when already frozen");
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    _frozen = true;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    _metadata = ShingleMetadata(this);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  void observe(Shingle s, double n) {
 | 
					 | 
				
			||||||
    if (_frozen) {
 | 
					 | 
				
			||||||
      throw StateError("can't observe when already frozen");
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // double n: weights can be fractional
 | 
					 | 
				
			||||||
    var index = _shingleIndices[s.hashCode];
 | 
					 | 
				
			||||||
    if (index == null) {
 | 
					 | 
				
			||||||
      index = _shingleValues.length;
 | 
					 | 
				
			||||||
      _shingleValues.add(s);
 | 
					 | 
				
			||||||
      _shingleWeights.add(n);
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      _shingleWeights[index] += n;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class ShingleMetadata {
 | 
					 | 
				
			||||||
  // [direction][source] => list of agreeing items
 | 
					 | 
				
			||||||
  List<List<List<int>>> _propagators = [];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  ShingleMetadata(Shingles s) {
 | 
					 | 
				
			||||||
    _propagators = [
 | 
					 | 
				
			||||||
      for (var d = 0; d < 4; d++)
 | 
					 | 
				
			||||||
        [
 | 
					 | 
				
			||||||
          for (var t = 0; t < s.n; t++)
 | 
					 | 
				
			||||||
            [
 | 
					 | 
				
			||||||
              for (var t2 = 0; t2 < s.n; t2++)
 | 
					 | 
				
			||||||
                if (s._shingleValues[t]
 | 
					 | 
				
			||||||
                    .agrees(s._shingleValues[t2], _dx[d], _dy[d]))
 | 
					 | 
				
			||||||
                  t2
 | 
					 | 
				
			||||||
            ]
 | 
					 | 
				
			||||||
        ]
 | 
					 | 
				
			||||||
    ];
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Shingle {
 | 
					 | 
				
			||||||
  int order;
 | 
					 | 
				
			||||||
  int c;
 | 
					 | 
				
			||||||
  List<int> content = [];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @override
 | 
					 | 
				
			||||||
  int hashCode = 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Shingle(this.order, this.c, int Function(int, int) f) {
 | 
					 | 
				
			||||||
    content = [
 | 
					 | 
				
			||||||
      for (var y = 0; y < order; y++)
 | 
					 | 
				
			||||||
        for (var x = 0; x < order; x++) f(x, y)
 | 
					 | 
				
			||||||
    ];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    int result = 0, power = 1;
 | 
					 | 
				
			||||||
    for (var i = 0; i < content.length; i++) {
 | 
					 | 
				
			||||||
      result += content[content.length - 1 - i] * power;
 | 
					 | 
				
			||||||
      power *= c;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    hashCode = result;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Shingle rotate() {
 | 
					 | 
				
			||||||
    return Shingle(order, c, (x, y) => content[order - 1 - y + x * order]);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Shingle reflect() {
 | 
					 | 
				
			||||||
    return Shingle(order, c, (x, y) => content[order - 1 - x + y * order]);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  bool agrees(Shingle other, int dx, int dy) {
 | 
					 | 
				
			||||||
    var p1 = content;
 | 
					 | 
				
			||||||
    var p2 = other.content;
 | 
					 | 
				
			||||||
    var n = order;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    int xmin = dx < 0 ? 0 : dx;
 | 
					 | 
				
			||||||
    int xmax = dx < 0 ? dx + n : n;
 | 
					 | 
				
			||||||
    int ymin = dy < 0 ? 0 : dy;
 | 
					 | 
				
			||||||
    int ymax = dy < 0 ? dy + n : n;
 | 
					 | 
				
			||||||
    for (var y = ymin; y < ymax; y++) {
 | 
					 | 
				
			||||||
      for (var x = xmin; x < xmax; x++) {
 | 
					 | 
				
			||||||
        if (p1[x + n * y] != p2[x - dx + n * (y - dy)]) {
 | 
					 | 
				
			||||||
          return false;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return true;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @override
 | 
					 | 
				
			||||||
  bool operator ==(Object other) {
 | 
					 | 
				
			||||||
    return (other is Shingle) &&
 | 
					 | 
				
			||||||
        other.hashCode == hashCode &&
 | 
					 | 
				
			||||||
        other.order == order &&
 | 
					 | 
				
			||||||
        other.c == c;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Embedding<T> {
 | 
					 | 
				
			||||||
  bool _frozen = false;
 | 
					 | 
				
			||||||
  final List<T> _colorOf = [];
 | 
					 | 
				
			||||||
  final Map<T, int> _codeOf = {};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  void freeze() {
 | 
					 | 
				
			||||||
    if (_frozen) {
 | 
					 | 
				
			||||||
      throw StateError("can't freeze when already frozen");
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    _frozen = true;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  int get c {
 | 
					 | 
				
			||||||
    if (!_frozen) {
 | 
					 | 
				
			||||||
      throw StateError("can't use Embedding#get c until frozen");
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return _colorOf.length;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  int encode(T t) {
 | 
					 | 
				
			||||||
    var code = _codeOf[t];
 | 
					 | 
				
			||||||
    if (code == null) {
 | 
					 | 
				
			||||||
      if (_frozen) {
 | 
					 | 
				
			||||||
        throw StateError("can't create new code when frozen");
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      code = _colorOf.length;
 | 
					 | 
				
			||||||
      _codeOf[t] = code;
 | 
					 | 
				
			||||||
      _colorOf.add(t);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return code;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  T decode(int i) {
 | 
					 | 
				
			||||||
    return _colorOf[i]!;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
int _chooseRandom(math.Random rand, List<double> distribution) {
 | 
					 | 
				
			||||||
  if (distribution.isEmpty) {
 | 
					 | 
				
			||||||
    throw Exception("can't sample empty distribution");
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  var sum = 0.0;
 | 
					 | 
				
			||||||
  for (var i = 0; i < distribution.length; i++) {
 | 
					 | 
				
			||||||
    sum += distribution[i];
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (sum == 0.0) {
 | 
					 | 
				
			||||||
    return rand.nextInt(distribution.length);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  var rnd = rand.nextDouble() * sum;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  var i = 0;
 | 
					 | 
				
			||||||
  while (rnd > 0) {
 | 
					 | 
				
			||||||
    rnd -= distribution[i];
 | 
					 | 
				
			||||||
    if (rnd < 0) {
 | 
					 | 
				
			||||||
      return i;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    i += 1;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  return distribution.length - 1;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
final List<int> _dx = [-1, 0, 1, 0];
 | 
					 | 
				
			||||||
final List<int> _dy = [0, 1, 0, -1];
 | 
					 | 
				
			||||||
final List<int> _opposite = [2, 3, 0, 1];
 | 
					 | 
				
			||||||
@@ -1,5 +1,3 @@
 | 
				
			|||||||
import 'package:dartterm/wfc/template.dart';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
enum LevelTile {
 | 
					enum LevelTile {
 | 
				
			||||||
  exit,
 | 
					  exit,
 | 
				
			||||||
  door,
 | 
					  door,
 | 
				
			||||||
@@ -11,23 +9,21 @@ class Level {
 | 
				
			|||||||
  Set<(int, int)> openCells = {};
 | 
					  Set<(int, int)> openCells = {};
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Future<WfcTemplate<LevelTile>> loadLevelWfcAsync(String name) async {
 | 
					LevelTile colorToTile(int c) {
 | 
				
			||||||
  return WfcTemplate.loadAsync(name, 3, (c) {
 | 
					  switch (c) {
 | 
				
			||||||
    switch (c) {
 | 
					    // ABGR
 | 
				
			||||||
      // ABGR
 | 
					    case 0xFF000000:
 | 
				
			||||||
      case 0xFF000000:
 | 
					    case 0xFF707070:
 | 
				
			||||||
      case 0xFF707070:
 | 
					      return LevelTile.wall;
 | 
				
			||||||
        return LevelTile.wall;
 | 
					    case 0xFFFFFFFF:
 | 
				
			||||||
      case 0xFFFFFFFF:
 | 
					    case 0xFF00FFFF:
 | 
				
			||||||
      case 0xFF00FFFF:
 | 
					    case 0xFFFF00FF:
 | 
				
			||||||
      case 0xFFFF00FF:
 | 
					      return LevelTile.floor;
 | 
				
			||||||
        return LevelTile.floor;
 | 
					    case 0xFFFF8700:
 | 
				
			||||||
      case 0xFFFF8700:
 | 
					      return LevelTile.door;
 | 
				
			||||||
        return LevelTile.door;
 | 
					    case 0xFF0000FF:
 | 
				
			||||||
      case 0xFF0000FF:
 | 
					      return LevelTile.exit;
 | 
				
			||||||
        return LevelTile.exit;
 | 
					    default:
 | 
				
			||||||
      default:
 | 
					      throw Exception("unrecognized pixel: $c");
 | 
				
			||||||
        throw Exception("unrecognized pixel: $c");
 | 
					  }
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user