public void MirrorSetup() { var trb = new TileRotationBuilder(4, true, TileRotationTreatment.Missing); var tile1 = new Tile(1); var tile2 = new Tile(2); var tile3 = new Tile(3); var tile4 = new Tile(4); var tile5 = new Tile(5); var tiles = new[] { tile1, tile2, tile3, tile4 }; var reflectX = new Rotation(0, true); trb.Add(tile1, reflectX, tile2); trb.Add(tile3, reflectX, tile3); trb.Add(tile5, reflectX, tile5); var model = new AdjacentModel(DirectionSet.Cartesian2d); model.AddAdjacency(tiles, tiles, Direction.XPlus); model.AddAdjacency(new[] { tile5 }, tiles, Direction.XPlus); model.AddAdjacency(new[] { tile5 }, tiles, Direction.XMinus); model.AddAdjacency(tiles, tiles, Direction.YPlus); model.SetUniformFrequency(); model.SetFrequency(tile5, 0.0); var tr = trb.Build(); var constraints = new[] { new MirrorXConstraint { TileRotation = tr } }; // NB: It's important that width is an odd number var topology = new GridTopology(31, 31, false); var options = new TilePropagatorOptions { Constraints = constraints, }; propagatorMirror = new TilePropagator(model, topology, options); }
public void Export(TileModel model, TilePropagator tilePropagator, string filename, DeBroglieConfig config, ExportOptions exportOptions) { var tiledExportOptions = exportOptions as TiledExportOptions; if (tiledExportOptions == null) { throw new System.Exception($"Cannot export from {exportOptions.TypeDescription} to .tmx"); } var map = tiledExportOptions.Template; var srcFilename = tiledExportOptions.SrcFileName; var layerArray = tilePropagator.ToArray(); map.Layers = new BaseLayer[layerArray.Topology.Depth]; for (var z = 0; z < layerArray.Topology.Depth; z++) { map.Layers[z] = TiledUtil.MakeTileLayer(map, layerArray, z); } map.Width = map.Layers[0].Width; map.Height = map.Layers[0].Height; TiledUtil.Save(filename, map); // Check for any external files that may also need copying foreach (var tileset in map.Tilesets) { if (tileset is ExternalTileset e) { var srcPath = Path.Combine(Path.GetDirectoryName(srcFilename), e.source); var destPath = Path.Combine(Path.GetDirectoryName(filename), e.source); if (File.Exists(srcPath) && !File.Exists(destPath)) { File.Copy(srcPath, destPath); } } if (tileset.ImagePath != null) { var srcImagePath = Path.Combine(Path.GetDirectoryName(srcFilename), tileset.ImagePath); var destImagePath = Path.Combine(Path.GetDirectoryName(filename), tileset.ImagePath); if (File.Exists(srcImagePath) && !File.Exists(destImagePath)) { File.Copy(srcImagePath, destImagePath); } } } }
public void Check(TilePropagator propagator) { if (nearbyTracker.NewlyVisited.Count == 0) { return; } var newlyVisited = nearbyTracker.NewlyVisited; nearbyTracker.NewlyVisited = new HashSet <int>(); foreach (var index in newlyVisited) { propagator.Topology.GetCoord(index, out var x, out var y, out var z); propagator.Ban(x, y, z, tileset); } }
public static void Export(TileModel model, TilePropagator tilePropagator, string filename, DeBroglieConfig config, ExportOptions exportOptions) { var exporter = GetExporter(filename); // Handle conversions if (exporter is BitmapExporter && exportOptions is TiledExportOptions) { if (tilePropagator.Topology.Directions.Type != Topo.DirectionsType.Cartesian2d) { throw new NotSupportedException("Converting from Tiled format to bitmaps only supported for square grids."); } exportOptions = ConvertToBitmaps(exportOptions as TiledExportOptions); } exporter.Export(model, tilePropagator, filename, config, exportOptions); }
public void TestSeparationConstraint() { var model = new AdjacentModel(DirectionSet.Cartesian2d); var tile1 = new Tile(1); var tile2 = new Tile(2); var tiles = new[] { tile1, tile2 }; model.AddAdjacency(tiles, tiles, Direction.XPlus); model.AddAdjacency(tiles, tiles, Direction.YPlus); model.SetUniformFrequency(); var separationConstraint = new SeparationConstraint { Tiles = new[] { tile1 }.ToHashSet(), MinDistance = 3, }; var countConstraint = new CountConstraint { Tiles = new[] { tile1 }.ToHashSet(), Count = 2, Comparison = CountComparison.Exactly, }; var topology = new GridTopology(4, 1, false); var options = new TilePropagatorOptions { Constraints = new ITileConstraint[] { separationConstraint, countConstraint }, BacktrackType = BacktrackType.Backtrack, }; var propagator = new TilePropagator(model, topology, options); propagator.Run(); Assert.AreEqual(Resolution.Decided, propagator.Status); var r = propagator.ToArray(); // Only possible solution given the constraints Assert.AreEqual(tile1, r.Get(0)); Assert.AreEqual(tile2, r.Get(1)); Assert.AreEqual(tile2, r.Get(2)); Assert.AreEqual(tile1, r.Get(3)); }
public void TestParityConstraint() { var w = 10; var h = 10; var topology = new GridTopology(10, 10, false); var pathModel = new PathModel(forks: false); var constraint = new ParityConstraint { PathSpec = new EdgedPathSpec { Exits = pathModel.Exits }, }; var options = new TilePropagatorOptions { BackTrackDepth = -1, Constraints = new[] { constraint }, }; var propagator = new TilePropagator(pathModel.Model, topology, options); for (var x = 0; x < w; x++) { for (var y = 0; y < h; y++) { void Select(Tile t) => propagator.Select(x, y, 0, t); if (x == 0 && y == 1) { Select(pathModel.Straight2); continue; } if (x == 0 || y == 0 || x == w - 1 || y == h - 1) { Select(pathModel.Empty); } } } propagator.Step(); Assert.AreEqual(Resolution.Contradiction, propagator.Status); }
public void FreeSetup() { var tileCount = 10; var topology = new GridTopology(10, 10, 10, false); var model = new AdjacentModel(DirectionSet.Cartesian3d); var tiles = Enumerable.Range(0, tileCount).Select(x => new Tile(x)).ToList();; model.AddAdjacency(tiles, tiles, Direction.XPlus); model.AddAdjacency(tiles, tiles, Direction.YPlus); model.AddAdjacency(tiles, tiles, Direction.ZPlus); model.SetUniformFrequency(); propagator1 = new TilePropagator(model, topology, new TilePropagatorOptions { }); }
public void Init(TilePropagator propagator) { pathTileSet = propagator.CreateTileSet(Exits.Keys); pathSelectedTracker = propagator.CreateSelectedTracker(pathTileSet); endPointTileSet = EndPointTiles != null?propagator.CreateTileSet(EndPointTiles) : null; endPointSelectedTracker = EndPointTiles != null?propagator.CreateSelectedTracker(endPointTileSet) : null; graph = CreateEdgedGraph(propagator.Topology); if (TileRotation != null) { actualExits = new Dictionary <Tile, ISet <Direction> >(); foreach (var kv in Exits) { foreach (var rot in TileRotation.RotationGroup) { if (TileRotation.Rotate(kv.Key, rot, out var rtile)) { Direction Rotate(Direction d) { return(TopoArrayUtils.RotateDirection(propagator.Topology.AsGridTopology().Directions, d, rot)); } var rexits = new HashSet <Direction>(kv.Value.Select(Rotate)); actualExits[rtile] = rexits; } } } } else { actualExits = Exits; } tilesByExit = actualExits .SelectMany(kv => kv.Value.Select(e => Tuple.Create(kv.Key, e))) .GroupBy(x => x.Item2, x => x.Item1) .ToDictionary(g => g.Key, propagator.CreateTileSet); trackerByExit = tilesByExit .ToDictionary(kv => kv.Key, kv => propagator.CreateSelectedTracker(kv.Value)); }
internal IPickHeuristic GetHeuristic( IRandomPicker randomPicker, Func <double> randomDouble, TilePropagator propagator, TileModelMapping tileModelMapping, IPickHeuristic fallbackHeuristic) { pathView = PathSpec.MakeView(propagator); pathViewIsFresh = true; if (pathView is EdgedPathView epv) { return(new FollowPathHeuristic( randomPicker, randomDouble, propagator, tileModelMapping, fallbackHeuristic, epv)); } else { throw new NotImplementedException(); } }
public void Init(TilePropagator propagator) { tileset = propagator.CreateTileSet(Tiles); nearbyTracker = new NearbyTracker { MinDistance = MinDistance, Topology = propagator.Topology }; changeTracker = propagator.CreateSelectedChangeTracker(tileset, nearbyTracker); // Review the initial state foreach (var index in propagator.Topology.GetIndices()) { if (changeTracker.GetTristate(index).IsYes()) { nearbyTracker.VisitNearby(index, false); } } Check(propagator); }
public void TestDoubleCountConstraint() { var model = new AdjacentModel(DirectionSet.Cartesian2d); var tile1 = new Tile(1); var tile2 = new Tile(2); var tile3 = new Tile(3); var tiles = new[] { tile1, tile2, tile3 }; model.AddAdjacency(new[] { tile2 }, new[] { tile1 }, Direction.XPlus); model.AddAdjacency(new[] { tile1 }, new[] { tile3 }, Direction.XPlus); model.AddAdjacency(new[] { tile3 }, new[] { tile3 }, Direction.XPlus); model.AddAdjacency(new[] { tile3 }, new[] { tile2 }, Direction.XPlus); model.AddAdjacency(tiles, tiles, Direction.YPlus); model.SetUniformFrequency(); var topology = new GridTopology(10, 10, false); var count = 10; var options = new TilePropagatorOptions { Constraints = new[] { new CountConstraint { Tiles = new[] { tile1, tile2 }.ToHashSet(), Count = count, Comparison = CountComparison.Exactly, Eager = true, } } }; var propagator = new TilePropagator(model, topology, options); propagator.Run(); Assert.AreEqual(Resolution.Decided, propagator.Status); var actualCount = propagator.ToValueArray <int>().ToArray2d().OfType <int>().Count(x => x == 1 || x == 2); Assert.AreEqual(count, actualCount); }
public Point GetRandomPoint(TilePropagator propagator, TilePropagatorTileSet tileSet) { var topology = propagator.Topology; var points = new List <Point>(); for (var z = 0; z < topology.Depth; z++) { for (var y = 0; y < topology.Height; y++) { for (var x = 0; x < topology.Width; x++) { if (topology.Mask != null) { var index = topology.GetIndex(x, y, z); if (!topology.Mask[index]) { continue; } } propagator.GetBannedSelected(x, y, z, tileSet, out var isBanned, out var _); if (isBanned) { continue; } points.Add(new Point(x, y, z)); } } } // Choose a random point to select if (points.Count == 0) { throw new System.Exception($"No legal placement of {tileSet}"); } var i = (int)(propagator.RandomDouble() * points.Count); return(points[i]); }
public void TestDirtyIndexPicker() { var t1 = new Tile(1); var t2 = new Tile(2); var t3 = new Tile(3); var model = new AdjacentModel(DirectionSet.Cartesian2d); model.AddAdjacency(t1, t1, Direction.XPlus); model.AddAdjacency(t1, t2, Direction.XPlus); model.AddAdjacency(t2, t2, Direction.XPlus); model.AddAdjacency(t2, t3, Direction.XPlus); model.AddAdjacency(t3, t3, Direction.XPlus); model.AddAdjacency(t3, t2, Direction.XPlus); model.AddAdjacency(t2, t1, Direction.XPlus); model.SetUniformFrequency(); var topology = new GridTopology(6, 1, false); var options = new TilePropagatorOptions { IndexPickerType = IndexPickerType.Dirty, TilePickerType = TilePickerType.Ordered, CleanTiles = TopoArray.FromConstant(t1, topology), }; var propagator = new TilePropagator(model, topology, options); propagator.Select(3, 0, 0, t3); propagator.Run(); var a = propagator.ToValueArray <int?>(); Assert.AreEqual(null, a.Get(0, 0)); Assert.AreEqual(null, a.Get(1, 0)); Assert.AreEqual(2, a.Get(2, 0)); Assert.AreEqual(3, a.Get(3, 0)); Assert.AreEqual(2, a.Get(4, 0)); Assert.AreEqual(null, a.Get(5, 0)); }
public PathView(PathSpec spec, TilePropagator propagator) { if (spec.TileRotation != null) { tiles = new HashSet <Tile>(spec.TileRotation.RotateAll(spec.Tiles)); endPointTiles = spec.RelevantTiles == null ? null : new HashSet <Tile>(spec.TileRotation.RotateAll(spec.RelevantTiles)); } else { tiles = spec.Tiles; endPointTiles = spec.RelevantTiles; } tileSet = propagator.CreateTileSet(tiles); selectedTracker = propagator.CreateSelectedTracker(tileSet); Graph = PathConstraintUtils.CreateGraph(propagator.Topology); this.propagator = propagator; CouldBePath = new bool[propagator.Topology.IndexCount]; MustBePath = new bool[propagator.Topology.IndexCount]; hasEndPoints = spec.RelevantCells != null || spec.RelevantTiles != null; if (hasEndPoints) { CouldBeRelevant = new bool[propagator.Topology.IndexCount]; MustBeRelevant = new bool[propagator.Topology.IndexCount]; endPointIndices = spec.RelevantCells == null ? null : spec.RelevantCells.Select(p => propagator.Topology.GetIndex(p.X, p.Y, p.Z)).ToList(); endPointTileSet = spec.RelevantTiles != null?propagator.CreateTileSet(endPointTiles) : null; endPointSelectedTracker = spec.RelevantTiles != null?propagator.CreateSelectedTracker(endPointTileSet) : null; } else { CouldBeRelevant = CouldBePath; MustBeRelevant = MustBePath; endPointTileSet = tileSet; } }
public void Init(TilePropagator propagator) { var tiles = propagator.CreateTileSet(Tiles); var width = propagator.Topology.Width; var height = propagator.Topology.Height; var depth = propagator.Topology.Depth; for (var x = 0; x < width; x++) { var xmin = x == 0; var xmax = x == width - 1; for (var y = 0; y < height; y++) { var ymin = y == 0; var ymax = y == height - 1; for (var z = 0; z < depth; z++) { var zmin = z == 0; var zmax = z == depth - 1; var match = (Match(Sides, xmin, xmax, ymin, ymax, zmin, zmax) && !Match(ExcludeSides, xmin, xmax, ymin, ymax, zmin, zmax)) != InvertArea; if (match) { if (Ban) { propagator.Ban(x, y, z, tiles); } else { propagator.Select(x, y, z, tiles); } } } } } }
public void Check(TilePropagator propagator) { var topology = propagator.Topology; foreach (var i in topology.Indicies) { topology.GetCoord(i, out var x, out var y, out var z); var x2 = topology.Width - 1 - x; foreach (var tile in propagator.TileModel.Tiles) { if (TileRotation.Rotate(tile, reflectX, out var tile2)) { if (propagator.IsBanned(x, y, z, tile) && !propagator.IsBanned(x2, y, z, tile2)) { propagator.Ban(x2, y, z, tile2); } } } } }
public void PathSetup() { var tileCount = 10; var topology = new GridTopology(20, 20, false); var model = new AdjacentModel(DirectionSet.Cartesian2d); var tiles = Enumerable.Range(0, tileCount).Select(x => new Tile(x)).ToList();; model.AddAdjacency(tiles, tiles, Direction.XPlus); model.AddAdjacency(tiles, tiles, Direction.YPlus); model.SetUniformFrequency(); var pathConstraint = new PathConstraint(tiles.Skip(1).ToHashSet()); propagator5 = new TilePropagator(model, topology, new TilePropagatorOptions { BackTrackDepth = -1, Constraints = new[] { pathConstraint }, }); }
public void ChessSetup() { var topology = new GridTopology(10, 10, 10, false); var model = new AdjacentModel(DirectionSet.Cartesian3d); var t1 = new Tile(1); var t2 = new Tile(2); model.AddAdjacency(t1, t2, Direction.XPlus); model.AddAdjacency(t2, t1, Direction.XPlus); model.AddAdjacency(t1, t2, Direction.YPlus); model.AddAdjacency(t2, t1, Direction.YPlus); model.AddAdjacency(t1, t2, Direction.ZPlus); model.AddAdjacency(t2, t1, Direction.ZPlus); model.SetUniformFrequency(); propagator2 = new TilePropagator(model, topology, new TilePropagatorOptions { }); }
private Resolution Run(TilePropagator propagator) { var next = DateTime.Now + TimeSpan.FromMinutes(1); while (true) { for (var i = 0; i < 100; i++) { var status = propagator.Step(); if (status != Resolution.Undecided) { return(status); } } if (DateTime.Now > next) { System.Console.WriteLine($"Progress {propagator.GetProgress():p2}"); next = DateTime.Now + TimeSpan.FromMinutes(1); } } }
public void TestDirectionality() { var model = new AdjacentModel(DirectionSet.Cartesian2d); model.AddAdjacency(new Tile(1), new Tile(2), 1, 0, 0); model.SetUniformFrequency(); var topology = new GridTopology(2, 1, false); var up = Direction.YPlus; var down = Direction.YMinus; var seed = Environment.TickCount; var r = new Random(seed); Console.WriteLine("Seed {0}", seed); var constraint = new ConnectedConstraint { PathSpec = new EdgedPathSpec { Exits = new Dictionary <Tile, ISet <Direction> >() { { new Tile(1), new[] { up, down }.ToHashSet() }, { new Tile(2), new[] { up, down }.ToHashSet() }, } } }; var propagator = new TilePropagator(model, topology, new TilePropagatorOptions { RandomDouble = r.NextDouble, Constraints = new[] { constraint } }); propagator.Run(); Assert.AreEqual(Resolution.Contradiction, propagator.Status); }
public void ProcessItem() { if (config.Dest == null) { throw new ConfigurationException("Dest attribute must be set"); } var directory = config.BaseDirectory; Dest = Path.Combine(directory, config.Dest); Contdest = Path.ChangeExtension(Dest, ".contradiction" + Path.GetExtension(Dest)); // TODO: Neat way to do this without mutability? factory.TilesByName = new Dictionary <string, Tile>(); if (config.SrcType == SrcType.Sample) { SampleSet = LoadSample(); factory.TilesByName = SampleSet.TilesByName ?? factory.TilesByName; } else { SampleSet = LoadFileSet(); } var directions = SampleSet.Directions; Topology = factory.GetOutputTopology(directions); TileRotation = factory.GetTileRotation(config.RotationTreatment, Topology); Model = factory.GetModel(directions, SampleSet, TileRotation); Constraints = factory.GetConstraints(directions, TileRotation); System.Console.WriteLine($"Processing {Dest}"); Propagator = new TilePropagator(Model, Topology, config.Backtrack, constraints: Constraints.ToArray()); //NewMethod(dest, contdest, sampleSet, model, propagator); }
public void PathSetup() { var tileCount = 10; var topology = new GridTopology(20, 20, false); var model = new AdjacentModel(DirectionSet.Cartesian2d); var tiles = Enumerable.Range(0, tileCount).Select(x => new Tile(x)).ToList();; model.AddAdjacency(tiles, tiles, Direction.XPlus); model.AddAdjacency(tiles, tiles, Direction.YPlus); model.SetUniformFrequency(); #pragma warning disable CS0618 // Type or member is obsolete var pathConstraint = new PathConstraint(tiles.Skip(1).ToHashSet()); #pragma warning restore CS0618 // Type or member is obsolete propagatorPath = new TilePropagator(model, topology, new TilePropagatorOptions { BacktrackType = BacktrackType.Backtrack, Constraints = new[] { pathConstraint }, }); }
public static void WriteSteps(TilePropagator propagator) { Write(propagator); System.Console.WriteLine(); while (true) { var prevBacktrackCount = propagator.BacktrackCount; var status = propagator.Step(); Write(propagator); if (propagator.BacktrackCount != prevBacktrackCount) { System.Console.WriteLine("Backtracked!"); } System.Console.WriteLine(); if (status != Resolution.Undecided) { System.Console.WriteLine(status); break; } } }
public void Init(TilePropagator propagator) { if (PathSpec is PathSpec pathSpec) { // Convert PathSpec to EdgedPathSpec // As we have a bug with PathSpec ignoring paths of length 2. // (probably should use bridge edges instead of articulation points) ISet <Direction> allDirections = new HashSet <Direction>(Enumerable.Range(0, propagator.Topology.DirectionsCount).Cast <Direction>()); var edgedPathSpec = new EdgedPathSpec { Exits = pathSpec.Tiles.ToDictionary(x => x, _ => allDirections), RelevantCells = pathSpec.RelevantCells, RelevantTiles = pathSpec.RelevantTiles, TileRotation = pathSpec.TileRotation, }; pathView = edgedPathSpec.MakeView(propagator); } else { pathView = PathSpec.MakeView(propagator); } pathView.Init(); }
public void TestToTopArray() { var a = new int[, ] { { 1, 0 }, { 0, 1 }, }; var model = OverlappingModel.Create(a, 2, false, 8); var propagator = new TilePropagator(model, new GridTopology(4, 4, false)); propagator.Select(0, 0, 0, new Tile(1)); var status = propagator.Run(); Assert.AreEqual(Resolution.Decided, status); var result = propagator.ToValueArray <int>().ToArray2d(); Assert.AreEqual(4, result.GetLength(0)); Assert.AreEqual(4, result.GetLength(1)); Assert.AreEqual(1, result[0, 0]); Assert.AreEqual(1, result[3, 3]); }
public void Check(TilePropagator propagator) { pathView.Update(); var info = PathConstraintUtils.GetArticulationPoints(pathView.Graph, pathView.CouldBePath, pathView.MustBeRelevant); var isArticulation = info.IsArticulation; if (info.ComponentCount > 1) { propagator.SetContradiction(); return; } // All articulation points must be paths, // So ban any other possibilities for (var i = 0; i < pathView.Graph.NodeCount; i++) { if (isArticulation[i] && !pathView.MustBePath[i]) { pathView.SelectPath(i); } } // Any path tiles / EndPointTiles not in the connected component aren't safe to add. // Disabled for now, unclear exactly when it is needed if (info.ComponentCount > 0) { var component = info.Component; for (int i = 0; i < pathView.Graph.NodeCount; i++) { if (component[i] == null && pathView.CouldBeRelevant[i]) { pathView.BanRelevant(i); } } } }
public void TestBorderConstraint() { var a = new int[, ] { { 1, 0, 0 }, { 0, 1, 1 }, { 0, 1, 1 }, }; var model = AdjacentModel.Create(a, true); var propagator = new TilePropagator(model, new GridTopology(10, 10, false), true, constraints: new[] { new BorderConstraint { Tiles = new [] { new Tile(0) }, } }); var status = propagator.Run(); Assert.AreEqual(Resolution.Decided, status); var result = propagator.ToValueArray <int>().ToArray2d(); Assert.AreEqual(0, result[0, 0]); Assert.AreEqual(0, result[9, 0]); Assert.AreEqual(0, result[0, 9]); Assert.AreEqual(0, result[9, 9]); }
public void Check(TilePropagator propagator) { var topology = propagator.Topology; foreach (var i in changeTracker.GetChangedIndices()) { if (TryMapIndex(propagator, i, out var i2)) { topology.GetCoord(i, out var x, out var y, out var z); topology.GetCoord(i2, out var x2, out var y2, out var z2); foreach (var tile in propagator.TileModel.Tiles) { if (TryMapTile(tile, out var tile2)) { if (propagator.IsBanned(x, y, z, tile) && !propagator.IsBanned(x2, y, z, tile2)) { propagator.Ban(x2, y, z, tile2); } } } } } }
public void TestUnassignableEager() { var model = new AdjacentModel(DirectionSet.Cartesian2d); var tile1 = new Tile(1); var tile2 = new Tile(2); var tiles = new[] { tile1, tile2 }; model.AddAdjacency(tiles, tiles, Direction.XPlus); model.AddAdjacency(tiles, tiles, Direction.YPlus); model.SetUniformFrequency(); var topology = new GridTopology(3, 1, false); var count = 3; var options = new TilePropagatorOptions { Constraints = new[] { new CountConstraint { Tiles = new[] { tile1, }.ToHashSet(), Count = count, Comparison = CountComparison.Exactly, Eager = true, } } }; var propagator = new TilePropagator(model, topology, options); propagator.Select(1, 0, 0, tile2); propagator.Run(); Assert.AreEqual(Resolution.Contradiction, propagator.Status); }
public void TestDirectionality2() { var model = new AdjacentModel(DirectionSet.Cartesian2d); model.AddAdjacency(new Tile(1), new Tile(2), 1, 0, 0); model.SetUniformFrequency(); var topology = new Topology(2, 1, false); var left = Direction.XMinus; var right = Direction.XPlus; var edgedPathConstraint = new EdgedPathConstraint( new Dictionary <Tile, ISet <Direction> >() { { new Tile(1), new[] { left, right }.ToHashSet() }, { new Tile(2), new[] { left, right }.ToHashSet() }, } ); var propagator = new TilePropagator(model, topology, constraints: new[] { edgedPathConstraint }); propagator.Run(); }