diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..3a576ec
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1 @@
+github: [mxgmn]
diff --git a/CITATION.cff b/CITATION.cff
new file mode 100644
index 0000000..ec6022e
--- /dev/null
+++ b/CITATION.cff
@@ -0,0 +1,14 @@
+cff-version: 1.1.0
+message: "citation"
+authors:
+ - family-names: Gumin
+ given-names: Maxim
+title: MarkovJunior, a probabilistic programming language based on pattern matching and constraint propagation
+version: 1.0
+date-released: 2022-06-01
+keywords:
+ - probabilistic programming
+ - procedural generation
+ - constraint satisfaction
+ - language
+url: https://github.com/mxgmn/MarkovJunior
diff --git a/MarkovJunior.csproj b/MarkovJunior.csproj
new file mode 100644
index 0000000..0e28c68
--- /dev/null
+++ b/MarkovJunior.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net6.0
+ false
+ false
+
+
+
+
+
+
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..e450d09
--- /dev/null
+++ b/README.md
@@ -0,0 +1,242 @@
+# MarkovJunior
+MarkovJunior is a probabilistic programming language where programs are combinations of rewrite rules and inference is performed via constraint propagation. MarkovJunior is named after mathematician [Andrey Andreyevich Markov](https://en.wikipedia.org/wiki/Andrey_Markov,_Jr.), who defined and studied what is now called [Markov algorithms](https://en.wikipedia.org/wiki/Markov_algorithm).
+
+
+
+
+
+In its basic form, a MarkovJunior program is an ordered list of rewrite rules. For example, [MazeBacktracker](models/MazeBacktracker.xml) (animation on the left below) is a list of 2 rewrite rules:
+1. `RBB=GGR` or "replace red-black-black with green-green-red".
+2. `RGG=WWR` or "replace red-green-green with white-white-red".
+
+On each execution step MJ interpreter finds the first rule in the list that has a match on the grid, finds all matches for that rule and applies that rule for a random match. In the [maze backtracker](https://en.wikipedia.org/wiki/Maze_generation_algorithm#Depth-first_search) example, interpreter first applies a bunch of `RBB=GGR` rules. But eventually the green self-avoiding walk gets stuck. At this point the first rule has no matches, so interpreter applies the second rule `RGG=WWR` until the walk gets unstuck. Then it can apply the first rule again, and so on. Interpreter stops when there are no matches for any rule.
+
+
+
+
+
+Probabilistic inference in MarkovJunior allows to impose constraints on the future state, and generate only those runs that lead to the constrained future. For example, inference in Sokoban rules `{RWB=BRW RB=BR}` makes a group of (red) agents organize (white) crates into specified shapes.
+
+Using these ideas, we construct [many probabilistic generators](models/) of dungeons, architecture, puzzles and fun simulations.
+
+
+Higher resolution screenshots and more seeds: [ModernHouse](https://github.com/mxgmn/Blog/blob/master/ModernHouse.md), [SeaVilla](https://github.com/mxgmn/Blog/blob/master/SeaVilla.md), [Apartemazements](https://github.com/mxgmn/Blog/blob/master/Apartemazements.md), [CarmaTower](https://github.com/mxgmn/Blog/blob/master/CarmaTower.md), [Escheresque](https://github.com/mxgmn/Blog/blob/master/Escheresque.md), [PillarsOfEternity](https://github.com/mxgmn/Blog/blob/master/PillarsOfEternity.md), [Surface](https://github.com/mxgmn/Blog/blob/master/RandomSurface.md), [Knots](https://twitter.com/ExUtumno/status/895688856304992256).
+
+
+
+## Markov algorithms
+A Markov algorithm over an alphabet `A` is an ordered list of rules. Each rule is a string of the form `x=y`, where `x` and `y` are words in `A`, and some rules may be marked as halt rules. Application of a Markov algorithm to a word `w` proceeds as follows:
+1. Find the first rule `x=y` where `x` is a substring of `w`. If there are no such rules, then halt.
+2. Replace the leftmost `x` in `w` by `y`.
+3. If the found rule was a halt rule, then halt. Otherwise, go to step 1.
+
+For example, consider this Markov algorithm in the alphabet `{0, 1, x}` (ε is the empty word):
+```
+1=0x
+x0=0xx
+0=ε
+```
+If we apply it to the string `110` we get this sequence of strings:
+```
+110 -> 0x10 -> 0x0x0 -> 00xxx0 -> 00xx0xx -> 00x0xxxx -> 000xxxxxx -> 00xxxxxx -> 0xxxxxx -> xxxxxx
+```
+In general, this algorithm converts a binary representation of a number into its unary representation.
+
+Markov's student [Vilnis Detlovs](https://lv.wikipedia.org/wiki/Vilnis_Detlovs) [proved](http://www.mathnet.ru/php/archive.phtml?wshow=paper&jrnid=tm&paperid=1293) that for any Turing machine there exists a Markov algorithm that computes the same function. In comparison, grammars are unordered sets of rewrite rules and L-systems are rewrite rules that are applied in parallel. For more interesting examples of Markov algorithms check [Markov's book](http://www.mathnet.ru/links/1543dd6e347b444e6f3e108fafaf9f2a/tm1178.pdf) or see the greatest common divisor example in the [comment section](https://github.com/mxgmn/test#comments) or [multiplication example](https://en.wikipedia.org/wiki/Markov_algorithm#Description) on Wikipedia.
+
+How would one generalize Markov algorithms to multiple dimensions? First, in multiple dimensions there are no natural ways to insert a string into another string, so the lefts and rights of our rewrite rules should have the same size. Second, there are no natural ways to choose *the leftmost* match. Possible options are:
+* Choose a random match. This is what MJ's `(exists)` nodes do.
+* Choose all matches. There is a problem with this option however because different matches can overlap and have conflicts. Possible solutions are:
+ * Greedily choose a maximal subset of non-conflicting matches. This is what MJ's `{forall}` nodes do.
+ * Consider all matches in superposition. That is, instead of separate values, keep waves in each grid cell - boolean vectors that tell which spacetime patterns are forbidden and which are not. And this is how MJ performs inference.
+
+We lose Turing completeness because our new procedure is not deterministic, but practice shows that this formalism still allows to describe a huge range of interesting random processes.
+
+
+
+## Rewrite rules
+The simplest MarkovJunior program is probably `(B=W)`. It contains just a single rule `B=W`. On each turn, this program converts a random black square into a white square.
+
+
+
+
+
+(B=W) | (WB=WW) | (WBB=WAW) | (WBB=WAW)
+
+
+[Growth](models/Growth.xml) model `(WB=WW)` is more interesting. On each turn it replaces a black-white pair of adjacent cells `BW` with a white-white pair `WW`. In other words, on each turn it picks a random black cell adjacent to some white cell and color it into white. This model is almost identical to the [Eden growth model](http://digitalassets.lib.berkeley.edu/math/ucb/text/math_s4_v4_article-15.pdf): on each turn both models choose among the same set of black cells. They differ only in probability distributions: a uniform distribution over black cells adjacent to white cells is not the same as a uniform distribution over pairs of adjacent black and white cells.
+
+Model `(WBB=WAW)` generates a maze, with a single line of code! Compare it with an [implementation](https://bl.ocks.org/mbostock/70a28267db0354261476) in a conventional language. Any MarkovJunior model can be run in any number of dimensions without changes. On the right you can see the end result of [MazeGrowth](models/MazeGrowth.xml) in 3d, rendered in [MagicaVoxel](https://ephtracy.github.io/). By default, we use [PICO-8 palette](resources/palette.xml):
+
+
+Model `(RBB=WWR)` is a [self-avoiding random walk](https://en.wikipedia.org/wiki/Self-avoiding_walk). Note that self-avoiding walks in 3d are longer on average than in 2d. In general, comparing the behaviors of similar random processes in different dimensions is a fascinating topic. A [classic result](https://sites.math.washington.edu/~morrow/336_19/papers19/Legrand.pdf) of George Pólya says that a random walk in 2d returns to its initial position with probability one, while in 3d this is no longer the case.
+
+
+
+
+(RBB=WWR) | LoopErasedWalk | (RB=WR RW=WR)
+
+
+We can put several rules into one **rulenode**. For example, `(RBB=WWR RBW=GWP PWG=PBU UWW=BBU UWP=BBR)` is a [loop-erased random walk](https://en.wikipedia.org/wiki/Loop-erased_random_walk). Trail model `(RB=WR RW=WR)` generates [decent connected caves](https://blog.jrheard.com/procedural-dungeon-generation-drunkards-walk-in-clojurescript).
+
+Model `(RBB=WWR R*W=W*R)` is known as the [Aldous-Broder maze generation algorithm](http://weblog.jamisbuck.org/2011/1/17/maze-generation-aldous-broder-algorithm). The **wildcard** symbol `*` in the input means that *any* color is allowed to be in the square. The wildcard symbol in the output means that the color doesn't change after the application of the rule. Aldous-Broder algorithm takes much more turns on average to generate a maze than MazeGrowth, for example, but it has a nice property that MazeGrowth doesn't have: each maze has the same probability to be generated. In other words, MazeTrail is an unbiased maze generation algorithm, or it samples mazes (or spanning trees) with the uniform distribution. [Wilson's algorithm](http://web.stanford.edu/~yuvalwig/math/teaching/UniformSpanningTrees.pdf) is a more efficient unbiased maze generation algorithm. Compare its [MarkovJunior](images/Wilson.gif) [implementation](models/Wilson.xml) with an [implementation](https://bl.ocks.org/mbostock/11357811) in a conventional language!
+
+
+
+## Combining rulenodes
+We can put several rulenodes into a **sequence node**, to be run one after the other. In the [River](models/River.xml) model we first construct a stochastic [Voronoi](models/Voronoi.xml) diagram with 2 sources, and use the boundary between the formed regions as a base for a river. Then we spawn a couple more Voronoi seeds to grow forests and simultaneously grow grass from the river. As a result, we get random river valleys!
+
+
+
+
+
+In [Apartemazements](models/Apartemazements.xml) we start with a WFC node and then do constructive postprocessing with rulenodes:
+1. Prepare constraints: mark bottom cells with a separate bottom color, mark the remaining border cells (sides and top) with a separate border color. Border cells should map to Empty, bottom cells should map to all tiles except Down.
+2. Run WFC [Paths](resources/tilesets/Path.xml) tileset to generate closed stairy cycles.
+3. Randomize light sources.
+4. Drop columns from corners of flat tiles.
+5. Retract double columns, columns that touch ground and columns that touch stairs, except columns growing from corners of the Turn tiles.
+6. Grow windows between neighboring columns.
+7. Merge windows into bigger rectangles. We do this in several steps:
+ 1. Detect uneven patterns of windows when window corners touch window midpoints.
+ 2. Mark these patterns and propagate the markings through the whole lengths of window sides.
+ 3. Merge unmarked pairs of window sides.
+8. Turn the remaining 1x1 windows into walls.
+
+A more interesting way to combine nodes is to put them into a **Markov node**. Markov nodes substantially expand what we can do, because they allow to return to past nodes. When a Markov node is active, interpreter finds its first child node that matches and applies it. On the next turn, it finds the first matching node in the list again, and so on. The simplest example of the Markov node use is [MazeBacktracker](MazeBacktracker.xml) explained in the top section.
+
+
+
+
+
+
+One of my favorite examples that motivated the development of MarkovJunior is [Bob Nystrom's dungeon generation algorithm](https://journal.stuffwithstuff.com/2014/12/21/rooms-and-mazes/). It goes as follows:
+1. Draw a grid `{PBB=**P}`.
+2. Spawn a bunch of rooms `(room.png)`.
+3. Generate a maze on the rest of the grid. We can use any maze generation algorithm, but [MazeBacktracker](MazeBacktracker.xml) is preferred because it produces fewer branching points.
+4. Make the resulting configuration of rooms and corridors connected. This can be elegantly done with a Markov node `({GWW=**G}(GBW=*WG))`.
+5. Make some additional connections `(GBG=*W* #5)`, so the resulting dungeon has cycles. Dungeons without cycles are pretty boring, since the player has to return through already explored zones.
+6. Retract dead ends `{BBB/BWB=BBB/BBB}`.
+
+
+
+
+
+
+Like in REFAL, Markov nodes can be nested: once we go into a child node, we ignore outer nodes until the child branch completes.
+
+
+
+## Inference
+Probabilistic inference in MarkovJunior allows to impose constraints on the future state, and generate only those runs that lead to the constrained future. In other words, inference connects 2 given states (or partially observed states) with a chain of rewrite rules.
+
+The simplest example of inference use is connecting 2 points with a path. In the self-avoiding walk model `(RBB=WWR)` we can **observe** a given square on the grid to become `R` red. Then the interpreter would generate only those walks that lead to the observed square. We can set the interpreter to follow the goal more strictly or less strictly by varying the **temperature** parameter. By default, temperature is set to zero.
+
+
+
+
+
+Coldest | Cold | Hot | Hottest
+
+
+Another thing we can do is to observe *all* odd grid squares becoming white or red. Then the interpreter would generate self-avoiding walks that cover the entire grid.
+
+
+
+
+
+
+We can engage inference for any rewrite rules. For example, inference for [stair-drawing rules](models/StairsPath3D.xml) connects 2 points with a stairy path. Inference for rule `R**/**B=B**/**R` generates paths that a chess knight can take. Inference in the [CrossCountry](models/CrossCountry.xml) model connects 2 points with a path taking terrain costs into account. Inference for the Sokoban ruleset `{RB=BR RWB=BRW}` solves Sokoban puzzles or even [multiagent Sokoban puzzles](images/multisokoban.gif)!
+
+
+Inference in MarkovJunior is done via unidirectional (fast) or bidirectional (slow, but more powerful) constraint propagation. Unidirectional constraint propagation for rewrite rules can be described equivalently in terms of **rule propagation** fields which generalize Dijkstra fields for arbitrary rewrite rules. Dijkstra fields is a popular technique in grid-based procedural generation ([1](https://groups.google.com/forum/#!topic/rec.games.roguelike.development/6yNIuhSerpM), [2](http://www.roguebasin.com/index.php?title=The_Incredible_Power_of_Dijkstra_Maps), [3](http://www.roguebasin.com/index.php?title=Dijkstra_Maps_Visualized)). They in turn generalize [distance fields](https://iquilezles.org/www/articles/distfunctions/distfunctions.htm) used in computer graphics.
+
+If constraint propagation completes it doesn't necessarily mean that the goal state is achievable. But if the propagation fails then we know for sure that the goal is not achievable. This allows to catch states where a crate is pushed to the wrong wall in Sokoban, or where the grid-covering walk splits the grid into 2 disconnected parts. In addition to this boolean heuristic, it's worth looking at the minimal number of turns required for constraint propagation to complete. This integer-valued heuristic is [admissible](https://en.wikipedia.org/wiki/Admissible_heuristic), and we use it in A\* search to sample paths made of rewrite rules between 2 given states.
+
+
+
+## Open problems
+1. **Program synthesis for procedural generation**. William Chyr's talk ["Level Design in Impossible Geometry"](https://youtu.be/ed2zmmcEryw?t=1298) is not at all about procedural generation, yet I find [one slide](images/ps-for-procgen.jpg) to be very characteristic for pcg practice. William compares his earlier and later approaches to level design. The earlier one produced chaotic levels, while the later approach produced more structured, more intentional levels based on one central idea. Later levels weren't simpler, yet they were more memorable and easier for players to perceive. To me, the left level looks like it was generated procedurally! It has a very similar feel to my [procedural voxel puzzles](https://twitter.com/ExUtumno/status/971031987304763393). Can we make generators that produce levels that are more like the one on the right? This problem may seem AI-complete. But I'd argue it is very similar to classic genetic programming problems like [Koza's lawnmower problem](https://pdfs.semanticscholar.org/555e/13cc2dd246e3d63ceb00590605f3ff59593d.pdf). For example, take a simple procgen task of [generating Hamiltonian paths on the grid](models/CompleteSAW.xml). Even for small grid sizes like 29x29 this task is already computationally demanding. But do we really need to sample from all possible paths in practice? If we give this task to a human, they would probably draw a spiral or a zigzag curve - these are much more memorable and intentional designs than a random Hamiltonian path, plus they generalize to any grid sizes. To summarize, we can ask the system either to find a random Hamiltonian path or to find a short program that generates Hamiltonian paths. In the first case the result would look like the left level on the slide, and in the second case like the right level. Solving the latter program synthesis problem would create more memorable and intentional generators.
+2. **Model synthesis from examples**. Markov algorithms seem to be a perfect environment for program/model synthesis: no variables, ifs or whiles, nodes can be easily moved around without breaking correctness, models are easy to make differentiable. Random MJ programs are often fun and can produce human-relatable results and behaviors.
+ 1. Can we synthesize a MJ model from a result, or a set of results?
+ 2. Given a maze, is it possible to determine (or assign probabilities) whether it was generated by [MazeGrowth](models/MazeGrowth.xml) or [MazeBacktracker](models/MazeBacktracker.xml)?
+ 3. Solve the [Abstraction and Reasoning Challenge](https://www.kaggle.com/c/abstraction-and-reasoning-challenge) by inferring MarkovJunior models. Adjoint problem: use insights from the ARC challenge to build a better DSL for procedural generation on a grid.
+3. **Custom algorithms that run in the wave space**. To unite the advantages of constructive and constrained-based procedural generation. Related: custom algorithms (MJ rewrite rules) with custom energy functions like Ising energy or ConvChain energy.
+4. Generalize the notion of a pattern.
+5. Investigate MJ-like processes on other (possibly nonregular) grids or arbitrary graphs.
+6. Experiment with interactive extensions of Markov algorithms. It's possible to turn any MJ model into a game by assigning specific rewrite rules or nodes to key presses.
+7. Push the state of the art in grid-based procedural generation. [ModernHouse](https://twitter.com/ExUtumno/status/1141354217774428160) does not yet reach the structural variety of human-designed houses like [Sims 2 houses](https://www.thesimsresource.com/downloads/browse/category/sims2-lots/featured/1/search/modern%20house/). Use more subtle constraints.
+
+
+
+## Comments
+Compared to Turing machines and lambda calculus, Markov algorithms is probably the shortest and simplest way to rigorously define what an algorithm is.
+
+Exercise: prove that the following Markov algorithm finds the greatest common divisor of 2 numbers written in a unary representation. For example, if we apply it to `111111*1111111111` we get `11`.
+```
+1a=a1
+1*1=a*
+1*=*b
+b=1
+a=c
+c=1
+*=ε (halt)
+```
+
+Fast pattern matching. MarkovJunior interpreter samples matches uniformly, but it doesn't scan the whole grid every turn. To keep pattern matching fast, the interpreter remembers previously found matches and searches only around the places that got changed. When a rulenode is encountered for the first time, MJ interpreter uses a multidimensional version of the [Boyer–Moore algorithm](https://en.wikipedia.org/wiki/Boyer%E2%80%93Moore_string-search_algorithm).
+
+Stochastic relaxation. Markov nodes have a very nice representations as limits of differentiable nodes. Consider an unordered set of rewrite rules where each rule `r` is assigned a weight `w(r)`. On each step the interpreter finds all matches for all rules and chooses a random match according to the Boltzmann distribution `p(r) ~ exp(-w(r)/t)`. Then in the freezing limit `t->0` we get a Markov node, ordered by weights. What's good about this construction, is that for any `t>0` and for a typical score function, score's average on multiple runs would be a continuous (and smooth for practical purposes) function of weights. This means that one can find the optimal weights by gradient descent and then freeze the system to get the final discrete program.
+
+Read this [essay](https://www.jstor.org/stable/27641983) by [Boris Kushner](https://en.wikipedia.org/wiki/Boris_Kushner_(mathematician)) about A. A. Markov and his work in constructive mathematics.
+
+
+
+## Used work
+Main used work:
+1. Andrey A. Markov, [The Theory of Algorithms](http://www.mathnet.ru/php/archive.phtml?wshow=paper&jrnid=tm&paperid=1117&option_lang=eng), 1951. Markov used these ideas earlier in 1947 in his proof of the algorithmic undecidability of the word problem in semigroups. See also a later [book](http://www.mathnet.ru/php/archive.phtml?wshow=paper&jrnid=tm&paperid=1178&option_lang=eng) with a more detailed treatment. I would be grateful for links to English translations in open access.
+2. Guilherme S. Tows, [Imagegram](https://zaratustra.itch.io/imagegram), 2009. MarkovJunior takes forall-nodes from Imagegram.
+3. Valentin Turchin, [REFAL language](http://fprog.ru/2011/issue7/practice-fp-7-screen.pdf), 1968. MJ takes the idea of nested Markov nodes from REFAL.
+4. Brian Walker et al., [The incredible power of Dijkstra maps](https://groups.google.com/forum/#!topic/rec.games.roguelike.development/6yNIuhSerpM), 2010. A discussion in the the roguelike community that contains many techniques of using Dijkstra maps/distance fields for procedural generation and NPC AI. Later writeups: [1](http://www.roguebasin.com/index.php?title=The_Incredible_Power_of_Dijkstra_Maps), [2](http://www.roguebasin.com/index.php?title=Dijkstra_Maps_Visualized). We generalize Dijkstra maps to arbitrary rewrite rules.
+5. Pavlos S. Efraimidis, Paul Spirakis, [Weighted Random Sampling](https://utopia.duth.gr/~pefraimi/research/data/2007EncOfAlg.pdf), 2005.
+6. Work used in custom nodes: [Model Synthesis](http://graphics.stanford.edu/~pmerrell/thesis.pdf), [Wave Function Collapse Algorithm](https://github.com/mxgmn/WaveFunctionCollapse), [ConvChain Algorithm](https://github.com/mxgmn/ConvChain).
+7. Classic algorithms: [constraint propagation](https://en.wikipedia.org/wiki/Local_consistency), [constraint solving algorithms](https://www.cs.ubc.ca/~mack/Publications/AI77.pdf), [graph traversal](https://en.wikipedia.org/wiki/Graph_traversal), [A* search](https://www.cs.auckland.ac.nz/courses/compsci709s2c/resources/Mike.d/astarNilsson.pdf).
+
+Related work:
+1. Daniel Ritchie, [Probabilistic Programming for Procedural Modeling and Design](https://dritchie.github.io/pdf/thesis.pdfv), 2016.
+2. Lingfeng Yang, [From Execution Traces to Specialized Inference](https://stacks.stanford.edu/file/druid:kq822ym0815/et2si-reduced-opt-augmented.pdf), 2015.
+
+Sources of examples:
+1. [BasicKeys](models/BasicKeys.xml) and [Keys](Keys.xml) are adaptations of graph grammars formulated by Joris Dormans, [Engineering Emergence: Applied Theory for Game Design](https://www.illc.uva.nl/Research/Publications/Dissertations/DS-2012-12.text.pdf), 2012. Which in turn are development of the earlier work by David Adams, [Automatic Generation of Dungeons for Computer Games](https://pdfs.semanticscholar.org/2502/0f8d955aee07b7dd49a3ec23b1f2a8cf1d06.pdf), 2002. I use a variation of these models to generate key-lock-bridge puzzles in [SeaVilla](models/SeaVilla.xml).
+1. [CarmaTower](models/CarmaTower.xml) is a proceduralization of a [voxel scene](https://twitter.com/Sir_carma/status/851883489628704768) by Antoine Lendrevie.
+1. The [NystromDungeon](models/NystromDungeon.xml) model is a MarkovJunior port of [Bob Nystrom's dungeon generator](https://journal.stuffwithstuff.com/2014/12/21/rooms-and-mazes/).
+1. [HamiltonianPath](models/HamiltonianPath.xml) algorithm is adapted from [this](http://aip.scitation.org/doi/pdf/10.1063/1.443937) article. Compare it with an [implementation](http://clisby.net/projects/hamiltonian_path/hamiltonian_path_v1.html) in a conventional language.
+1. Room shapes in [DungeonGrowth](models/DungeonGrowth.xml) are taken from the [r/proceduralgeneration post](https://old.reddit.com/r/proceduralgeneration/comments/3pa8a1/my_take_at_a_roguelike_level_generator_ft/). Note that MJ interpreter automatically performs the optimizations described in the post.
+1. The [Wilson](models/Wilson.xml) model is a rewrite rule formulation of the [Wilson's algorithm](https://en.wikipedia.org/wiki/Loop-erased_random_walk#Uniform_spanning_tree). Compare it with an [implementation](https://bl.ocks.org/mbostock/11357811) in a conventional language.
+1. [MazeGrowth](models/MazeGrowth.xml) model is also known as maze generation via random traversal. Compare it with an [implementation](https://bl.ocks.org/mbostock/70a28267db0354261476) in a conventional language.
+1. [Growth](models/Growth.xml) is closely related to the [Eden growth model](http://digitalassets.lib.berkeley.edu/math/ucb/text/math_s4_v4_article-15.pdf).
+1. [BernoulliPercolation](models/BernoulliPercolation.xml) is a well studied model in a [percolation theory](https://en.wikipedia.org/wiki/Percolation_theory).
+1. [NestedGrowth](models/NestedGrowth.xml) is taken from [Imagegram](https://zaratustra.itch.io/imagegram).
+1. [SmoothTrail](models/SmoothTrail.xml) is adapted from [128_mhz's tweet](https://twitter.com/128_mhz/status/953847394403205120).
+1. [SokobanLevel1](models/SokobanLevel1.xml) seems to be the first level from Hiroyuki Imabayashi's Sokoban puzzle. [SokobanLevel2](models/SokobanLevel2.xml) is the [level 452](https://www.sokobanonline.com/play/web-archive/razorflame/ionic-catalysts-xi/58022_ionic-catalysts-xi-452) from Ionic Catalysts XI set.
+
+Voxel scenes were rendered in [MagicaVoxel](https://ephtracy.github.io/) by [ephtracy](https://github.com/ephtracy). Special thanks to [Brian Bucklew](https://github.com/unormal) for demonstrating the power of Dijkstra fields to me in roguelike level generation and [Kevin Chapelier](https://github.com/kchapelier) for a number of good suggestions. The font used in GUI is [Tamzen](https://github.com/sunaku/tamzen-font).
+
+
+
+## How to build
+MarkovJunior interpreter is a console application that depends only on the standard library. Get [.NET Core](https://dotnet.microsoft.com/download) for Windows, Linux or macOS and run
+```
+dotnet run --configuration Release MarkovJunior.csproj
+```
+Alternatively, download and run the latest [release](https://github.com/mxgmn/MarkovJunior/releases) for Windows.
+
+Generated results are put into the `output` folder. Edit `models.xml` to change model parameters. Open `.vox` files with [MagicaVoxel](https://ephtracy.github.io/).
+
+
+
+## Funding
+MarkovJunior development was [funded](https://github.com/users/mxgmn/sponsorship) by
+1. **[Embark Studios](https://www.embark-studios.com/)**
+2. [Oskar Stålberg](https://twitter.com/OskSta)
+3. [Freehold Games](https://store.steampowered.com/app/333640/Caves_of_Qud/)
+4. [Bob Burrough](https://bobburrough.com/)
diff --git a/images/Apartemazements.gif b/images/Apartemazements.gif
new file mode 100644
index 0000000..4ffc1c9
Binary files /dev/null and b/images/Apartemazements.gif differ
diff --git a/images/Basic.gif b/images/Basic.gif
new file mode 100644
index 0000000..b85d462
Binary files /dev/null and b/images/Basic.gif differ
diff --git a/images/Circuit.gif b/images/Circuit.gif
new file mode 100644
index 0000000..6d1125c
Binary files /dev/null and b/images/Circuit.gif differ
diff --git a/images/CompleteSAW.gif b/images/CompleteSAW.gif
new file mode 100644
index 0000000..e85d328
Binary files /dev/null and b/images/CompleteSAW.gif differ
diff --git a/images/DungeonGrowth.gif b/images/DungeonGrowth.gif
new file mode 100644
index 0000000..85d4700
Binary files /dev/null and b/images/DungeonGrowth.gif differ
diff --git a/images/Flowers.gif b/images/Flowers.gif
new file mode 100644
index 0000000..d2843ae
Binary files /dev/null and b/images/Flowers.gif differ
diff --git a/images/Growth.gif b/images/Growth.gif
new file mode 100644
index 0000000..521932c
Binary files /dev/null and b/images/Growth.gif differ
diff --git a/images/LoopErasedWalk.gif b/images/LoopErasedWalk.gif
new file mode 100644
index 0000000..ecc7320
Binary files /dev/null and b/images/LoopErasedWalk.gif differ
diff --git a/images/MazeBacktracker.gif b/images/MazeBacktracker.gif
new file mode 100644
index 0000000..0e7f5a0
Binary files /dev/null and b/images/MazeBacktracker.gif differ
diff --git a/images/MazeGrowth.gif b/images/MazeGrowth.gif
new file mode 100644
index 0000000..71f3558
Binary files /dev/null and b/images/MazeGrowth.gif differ
diff --git a/images/MazeGrowth.png b/images/MazeGrowth.png
new file mode 100644
index 0000000..1c631e9
Binary files /dev/null and b/images/MazeGrowth.png differ
diff --git a/images/NystromDungeon.gif b/images/NystromDungeon.gif
new file mode 100644
index 0000000..30e0544
Binary files /dev/null and b/images/NystromDungeon.gif differ
diff --git a/images/RegularSAW.gif b/images/RegularSAW.gif
new file mode 100644
index 0000000..f9bef9c
Binary files /dev/null and b/images/RegularSAW.gif differ
diff --git a/images/River.gif b/images/River.gif
new file mode 100644
index 0000000..6a797e0
Binary files /dev/null and b/images/River.gif differ
diff --git a/images/SokobanLevel1.gif b/images/SokobanLevel1.gif
new file mode 100644
index 0000000..a60d0b6
Binary files /dev/null and b/images/SokobanLevel1.gif differ
diff --git a/images/StairsPath.gif b/images/StairsPath.gif
new file mode 100644
index 0000000..09ace26
Binary files /dev/null and b/images/StairsPath.gif differ
diff --git a/images/Trail.gif b/images/Trail.gif
new file mode 100644
index 0000000..039cf1c
Binary files /dev/null and b/images/Trail.gif differ
diff --git a/images/Wilson.gif b/images/Wilson.gif
new file mode 100644
index 0000000..7bd1987
Binary files /dev/null and b/images/Wilson.gif differ
diff --git a/images/cold.gif b/images/cold.gif
new file mode 100644
index 0000000..deba93a
Binary files /dev/null and b/images/cold.gif differ
diff --git a/images/coldest.gif b/images/coldest.gif
new file mode 100644
index 0000000..5310197
Binary files /dev/null and b/images/coldest.gif differ
diff --git a/images/hot.gif b/images/hot.gif
new file mode 100644
index 0000000..1861d3f
Binary files /dev/null and b/images/hot.gif differ
diff --git a/images/hottest.gif b/images/hottest.gif
new file mode 100644
index 0000000..76cc515
Binary files /dev/null and b/images/hottest.gif differ
diff --git a/images/multisokoban.gif b/images/multisokoban.gif
new file mode 100644
index 0000000..2ab813e
Binary files /dev/null and b/images/multisokoban.gif differ
diff --git a/images/palette.png b/images/palette.png
new file mode 100644
index 0000000..33505ef
Binary files /dev/null and b/images/palette.png differ
diff --git a/images/ps-for-procgen.jpg b/images/ps-for-procgen.jpg
new file mode 100644
index 0000000..de4e1b7
Binary files /dev/null and b/images/ps-for-procgen.jpg differ
diff --git a/images/top-1764.png b/images/top-1764.png
new file mode 100644
index 0000000..1bf415d
Binary files /dev/null and b/images/top-1764.png differ
diff --git a/images/top-882.png b/images/top-882.png
new file mode 100644
index 0000000..58dbe23
Binary files /dev/null and b/images/top-882.png differ
diff --git a/images/top-iso.gif b/images/top-iso.gif
new file mode 100644
index 0000000..6ef9414
Binary files /dev/null and b/images/top-iso.gif differ
diff --git a/images/top-mv.gif b/images/top-mv.gif
new file mode 100644
index 0000000..835ea54
Binary files /dev/null and b/images/top-mv.gif differ
diff --git a/models.xml b/models.xml
new file mode 100644
index 0000000..68b665a
--- /dev/null
+++ b/models.xml
@@ -0,0 +1,167 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ gifs
+
+
+
+
+
+
+
+
+
+
+ benchmarks
+
+
+
+
+
+
diff --git a/models/Apartemazements.xml b/models/Apartemazements.xml
new file mode 100644
index 0000000..fe1f114
--- /dev/null
+++ b/models/Apartemazements.xml
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/Backtracker.xml b/models/Backtracker.xml
new file mode 100644
index 0000000..f31a118
--- /dev/null
+++ b/models/Backtracker.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
diff --git a/models/BacktrackerCycle.xml b/models/BacktrackerCycle.xml
new file mode 100644
index 0000000..d5a90f8
--- /dev/null
+++ b/models/BacktrackerCycle.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/Basic.xml b/models/Basic.xml
new file mode 100644
index 0000000..09dfe73
--- /dev/null
+++ b/models/Basic.xml
@@ -0,0 +1 @@
+
diff --git a/models/BasicBrickWall.xml b/models/BasicBrickWall.xml
new file mode 100644
index 0000000..b65ddea
--- /dev/null
+++ b/models/BasicBrickWall.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/BasicDijkstraDungeon.xml b/models/BasicDijkstraDungeon.xml
new file mode 100644
index 0000000..c2a316e
--- /dev/null
+++ b/models/BasicDijkstraDungeon.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/models/BasicDijkstraFill.xml b/models/BasicDijkstraFill.xml
new file mode 100644
index 0000000..6245b35
--- /dev/null
+++ b/models/BasicDijkstraFill.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/models/BasicDungeonGrowth.xml b/models/BasicDungeonGrowth.xml
new file mode 100644
index 0000000..c5c84a1
--- /dev/null
+++ b/models/BasicDungeonGrowth.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/BasicKeys.xml b/models/BasicKeys.xml
new file mode 100644
index 0000000..9551022
--- /dev/null
+++ b/models/BasicKeys.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/BasicPartitioning.xml b/models/BasicPartitioning.xml
new file mode 100644
index 0000000..5e19669
--- /dev/null
+++ b/models/BasicPartitioning.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/models/BasicSkyline.xml b/models/BasicSkyline.xml
new file mode 100644
index 0000000..8f0af2a
--- /dev/null
+++ b/models/BasicSkyline.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/models/BasicSnake.xml b/models/BasicSnake.xml
new file mode 100644
index 0000000..a78ef68
--- /dev/null
+++ b/models/BasicSnake.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/BernoulliPercolation.xml b/models/BernoulliPercolation.xml
new file mode 100644
index 0000000..a94d320
--- /dev/null
+++ b/models/BernoulliPercolation.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/models/BiasedGrowth.xml b/models/BiasedGrowth.xml
new file mode 100644
index 0000000..af80f7e
--- /dev/null
+++ b/models/BiasedGrowth.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/models/BiasedGrowthContraction.xml b/models/BiasedGrowthContraction.xml
new file mode 100644
index 0000000..81547df
--- /dev/null
+++ b/models/BiasedGrowthContraction.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/BiasedMazeGrowth.xml b/models/BiasedMazeGrowth.xml
new file mode 100644
index 0000000..90cb2a7
--- /dev/null
+++ b/models/BiasedMazeGrowth.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/models/BiasedVoronoi.xml b/models/BiasedVoronoi.xml
new file mode 100644
index 0000000..7034ca1
--- /dev/null
+++ b/models/BiasedVoronoi.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/BishopParity.xml b/models/BishopParity.xml
new file mode 100644
index 0000000..adb0dc1
--- /dev/null
+++ b/models/BishopParity.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/BlueNoise.xml b/models/BlueNoise.xml
new file mode 100644
index 0000000..8fccf19
--- /dev/null
+++ b/models/BlueNoise.xml
@@ -0,0 +1,4 @@
+ periodic="True"
+
+
+
diff --git a/models/CarmaTower.xml b/models/CarmaTower.xml
new file mode 100644
index 0000000..4556132
--- /dev/null
+++ b/models/CarmaTower.xml
@@ -0,0 +1,323 @@
+
+
+
+
+
+
+
+
+
+
+
+ z
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/models/Cave.xml b/models/Cave.xml
new file mode 100644
index 0000000..caeb4c4
--- /dev/null
+++ b/models/Cave.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/models/CaveContour.xml b/models/CaveContour.xml
new file mode 100644
index 0000000..44486b7
--- /dev/null
+++ b/models/CaveContour.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/CentralCrawlers.xml b/models/CentralCrawlers.xml
new file mode 100644
index 0000000..f295d46
--- /dev/null
+++ b/models/CentralCrawlers.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/models/CentralSAW.xml b/models/CentralSAW.xml
new file mode 100644
index 0000000..9a585fa
--- /dev/null
+++ b/models/CentralSAW.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/models/ChainDungeon.xml b/models/ChainDungeon.xml
new file mode 100644
index 0000000..b2c4b6d
--- /dev/null
+++ b/models/ChainDungeon.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/ChainDungeonMaze.xml b/models/ChainDungeonMaze.xml
new file mode 100644
index 0000000..25b555b
--- /dev/null
+++ b/models/ChainDungeonMaze.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/ChainMaze.xml b/models/ChainMaze.xml
new file mode 100644
index 0000000..b73c17b
--- /dev/null
+++ b/models/ChainMaze.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/models/Chase.xml b/models/Chase.xml
new file mode 100644
index 0000000..8112ce0
--- /dev/null
+++ b/models/Chase.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/Circuit.xml b/models/Circuit.xml
new file mode 100644
index 0000000..4a5b76b
--- /dev/null
+++ b/models/Circuit.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/ClosedSurface.xml b/models/ClosedSurface.xml
new file mode 100644
index 0000000..69e55e1
--- /dev/null
+++ b/models/ClosedSurface.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/models/ColoredKnots.xml b/models/ColoredKnots.xml
new file mode 100644
index 0000000..6ef96fc
--- /dev/null
+++ b/models/ColoredKnots.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/CompleteSAW.xml b/models/CompleteSAW.xml
new file mode 100644
index 0000000..47f1527
--- /dev/null
+++ b/models/CompleteSAW.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/CompleteSAWSmart.xml b/models/CompleteSAWSmart.xml
new file mode 100644
index 0000000..338f862
--- /dev/null
+++ b/models/CompleteSAWSmart.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/ConnectedCaves.xml b/models/ConnectedCaves.xml
new file mode 100644
index 0000000..3c74c88
--- /dev/null
+++ b/models/ConnectedCaves.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/ConstrainedCaves.xml b/models/ConstrainedCaves.xml
new file mode 100644
index 0000000..a53a349
--- /dev/null
+++ b/models/ConstrainedCaves.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/Counting.xml b/models/Counting.xml
new file mode 100644
index 0000000..6947e41
--- /dev/null
+++ b/models/Counting.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/models/Coupling.xml b/models/Coupling.xml
new file mode 100644
index 0000000..a905948
--- /dev/null
+++ b/models/Coupling.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/Crawlers.xml b/models/Crawlers.xml
new file mode 100644
index 0000000..54cb5f6
--- /dev/null
+++ b/models/Crawlers.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/models/CrawlersChase.xml b/models/CrawlersChase.xml
new file mode 100644
index 0000000..d385e1d
--- /dev/null
+++ b/models/CrawlersChase.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/CrossCountry.xml b/models/CrossCountry.xml
new file mode 100644
index 0000000..2605ef7
--- /dev/null
+++ b/models/CrossCountry.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/Cycles.xml b/models/Cycles.xml
new file mode 100644
index 0000000..352dc1f
--- /dev/null
+++ b/models/Cycles.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/models/DenseSAW.xml b/models/DenseSAW.xml
new file mode 100644
index 0000000..e082411
--- /dev/null
+++ b/models/DenseSAW.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/models/DiagonalPath.xml b/models/DiagonalPath.xml
new file mode 100644
index 0000000..0842dac
--- /dev/null
+++ b/models/DiagonalPath.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/Digger.xml b/models/Digger.xml
new file mode 100644
index 0000000..454b121
--- /dev/null
+++ b/models/Digger.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/models/DijkstraDungeon.xml b/models/DijkstraDungeon.xml
new file mode 100644
index 0000000..12e40b6
--- /dev/null
+++ b/models/DijkstraDungeon.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/DualRetraction.xml b/models/DualRetraction.xml
new file mode 100644
index 0000000..c11268c
--- /dev/null
+++ b/models/DualRetraction.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/DualRetraction3D.xml b/models/DualRetraction3D.xml
new file mode 100644
index 0000000..a5ff3b6
--- /dev/null
+++ b/models/DualRetraction3D.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/DungeonGrowth.xml b/models/DungeonGrowth.xml
new file mode 100644
index 0000000..506fdd9
--- /dev/null
+++ b/models/DungeonGrowth.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/DwarfPath.xml b/models/DwarfPath.xml
new file mode 100644
index 0000000..cf17005
--- /dev/null
+++ b/models/DwarfPath.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/Dwarves.xml b/models/Dwarves.xml
new file mode 100644
index 0000000..ed24a9a
--- /dev/null
+++ b/models/Dwarves.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/Escher.xml b/models/Escher.xml
new file mode 100644
index 0000000..ee91be7
--- /dev/null
+++ b/models/Escher.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/EscherSurface.xml b/models/EscherSurface.xml
new file mode 100644
index 0000000..17b5acc
--- /dev/null
+++ b/models/EscherSurface.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/EuclideanPath.xml b/models/EuclideanPath.xml
new file mode 100644
index 0000000..a529a56
--- /dev/null
+++ b/models/EuclideanPath.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/FindLongCycle.xml b/models/FindLongCycle.xml
new file mode 100644
index 0000000..b28d0f1
--- /dev/null
+++ b/models/FindLongCycle.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/FireNoise.xml b/models/FireNoise.xml
new file mode 100644
index 0000000..2556906
--- /dev/null
+++ b/models/FireNoise.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/Flowers.xml b/models/Flowers.xml
new file mode 100644
index 0000000..9725be3
--- /dev/null
+++ b/models/Flowers.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/ForestFire.xml b/models/ForestFire.xml
new file mode 100644
index 0000000..75ae8c6
--- /dev/null
+++ b/models/ForestFire.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
diff --git a/models/ForestFireCA.xml b/models/ForestFireCA.xml
new file mode 100644
index 0000000..3128f38
--- /dev/null
+++ b/models/ForestFireCA.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/models/GameOfLife.xml b/models/GameOfLife.xml
new file mode 100644
index 0000000..30e6d6e
--- /dev/null
+++ b/models/GameOfLife.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/models/GoTo.xml b/models/GoTo.xml
new file mode 100644
index 0000000..a84f767
--- /dev/null
+++ b/models/GoTo.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/models/GoToGradient.xml b/models/GoToGradient.xml
new file mode 100644
index 0000000..1b6c318
--- /dev/null
+++ b/models/GoToGradient.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/models/GrowTo.xml b/models/GrowTo.xml
new file mode 100644
index 0000000..d8fc095
--- /dev/null
+++ b/models/GrowTo.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/models/Growth.xml b/models/Growth.xml
new file mode 100644
index 0000000..595a6ae
--- /dev/null
+++ b/models/Growth.xml
@@ -0,0 +1 @@
+
diff --git a/models/GrowthCompetition.xml b/models/GrowthCompetition.xml
new file mode 100644
index 0000000..1e758e0
--- /dev/null
+++ b/models/GrowthCompetition.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/models/GrowthContraction.xml b/models/GrowthContraction.xml
new file mode 100644
index 0000000..ec5026d
--- /dev/null
+++ b/models/GrowthContraction.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/models/GrowthWalk.xml b/models/GrowthWalk.xml
new file mode 100644
index 0000000..0e82752
--- /dev/null
+++ b/models/GrowthWalk.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/models/HamiltonianPath.xml b/models/HamiltonianPath.xml
new file mode 100644
index 0000000..10e2312
--- /dev/null
+++ b/models/HamiltonianPath.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/HamiltonianPaths.xml b/models/HamiltonianPaths.xml
new file mode 100644
index 0000000..1584848
--- /dev/null
+++ b/models/HamiltonianPaths.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/Hills.xml b/models/Hills.xml
new file mode 100644
index 0000000..4552bbf
--- /dev/null
+++ b/models/Hills.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/models/IrregularMazeGrowth.xml b/models/IrregularMazeGrowth.xml
new file mode 100644
index 0000000..4f5fe4d
--- /dev/null
+++ b/models/IrregularMazeGrowth.xml
@@ -0,0 +1 @@
+
diff --git a/models/IrregularSAW.xml b/models/IrregularSAW.xml
new file mode 100644
index 0000000..2fadd59
--- /dev/null
+++ b/models/IrregularSAW.xml
@@ -0,0 +1 @@
+
diff --git a/models/Keys.xml b/models/Keys.xml
new file mode 100644
index 0000000..e5c3b77
--- /dev/null
+++ b/models/Keys.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/KnightPatrol.xml b/models/KnightPatrol.xml
new file mode 100644
index 0000000..bf00042
--- /dev/null
+++ b/models/KnightPatrol.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/models/Knots2D.xml b/models/Knots2D.xml
new file mode 100644
index 0000000..5ffbc24
--- /dev/null
+++ b/models/Knots2D.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/models/Knots3D.xml b/models/Knots3D.xml
new file mode 100644
index 0000000..139a4c1
--- /dev/null
+++ b/models/Knots3D.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/models/Laplace.xml b/models/Laplace.xml
new file mode 100644
index 0000000..c26f7a2
--- /dev/null
+++ b/models/Laplace.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/models/Lightning.xml b/models/Lightning.xml
new file mode 100644
index 0000000..e925dbc
--- /dev/null
+++ b/models/Lightning.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/LoopErasedWalk.xml b/models/LoopErasedWalk.xml
new file mode 100644
index 0000000..a9032c1
--- /dev/null
+++ b/models/LoopErasedWalk.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/models/LoopGrowth.xml b/models/LoopGrowth.xml
new file mode 100644
index 0000000..e16d5be
--- /dev/null
+++ b/models/LoopGrowth.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/models/MarchingSquares.xml b/models/MarchingSquares.xml
new file mode 100644
index 0000000..7bb0b0d
--- /dev/null
+++ b/models/MarchingSquares.xml
@@ -0,0 +1,11 @@
+
+
+
+
diff --git a/models/MazeBacktracker.xml b/models/MazeBacktracker.xml
new file mode 100644
index 0000000..d468d8e
--- /dev/null
+++ b/models/MazeBacktracker.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
diff --git a/models/MazeGrowth.xml b/models/MazeGrowth.xml
new file mode 100644
index 0000000..52d736d
--- /dev/null
+++ b/models/MazeGrowth.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
diff --git a/models/MazeMap.xml b/models/MazeMap.xml
new file mode 100644
index 0000000..d440a8a
--- /dev/null
+++ b/models/MazeMap.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
diff --git a/models/MazeTrail.xml b/models/MazeTrail.xml
new file mode 100644
index 0000000..029cfb7
--- /dev/null
+++ b/models/MazeTrail.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
diff --git a/models/ModernHouse.xml b/models/ModernHouse.xml
new file mode 100644
index 0000000..644dacb
--- /dev/null
+++ b/models/ModernHouse.xml
@@ -0,0 +1,510 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/models/MultiSokoban8.xml b/models/MultiSokoban8.xml
new file mode 100644
index 0000000..edc6331
--- /dev/null
+++ b/models/MultiSokoban8.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/MultiSokoban9.xml b/models/MultiSokoban9.xml
new file mode 100644
index 0000000..98bce0e
--- /dev/null
+++ b/models/MultiSokoban9.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/NestedGrowth.xml b/models/NestedGrowth.xml
new file mode 100644
index 0000000..5de08c9
--- /dev/null
+++ b/models/NestedGrowth.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/NoDeadEnds.xml b/models/NoDeadEnds.xml
new file mode 100644
index 0000000..1e08981
--- /dev/null
+++ b/models/NoDeadEnds.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
diff --git a/models/Noise.xml b/models/Noise.xml
new file mode 100644
index 0000000..cc66b74
--- /dev/null
+++ b/models/Noise.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/models/NystromDungeon.xml b/models/NystromDungeon.xml
new file mode 100644
index 0000000..2f313a4
--- /dev/null
+++ b/models/NystromDungeon.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/OddScale.xml b/models/OddScale.xml
new file mode 100644
index 0000000..9812d87
--- /dev/null
+++ b/models/OddScale.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
diff --git a/models/OddScale3D.xml b/models/OddScale3D.xml
new file mode 100644
index 0000000..8711b4b
--- /dev/null
+++ b/models/OddScale3D.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/OpenCave.xml b/models/OpenCave.xml
new file mode 100644
index 0000000..749b0cb
--- /dev/null
+++ b/models/OpenCave.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/models/OpenCave3D.xml b/models/OpenCave3D.xml
new file mode 100644
index 0000000..507d156
--- /dev/null
+++ b/models/OpenCave3D.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/models/OrganicMechanic.xml b/models/OrganicMechanic.xml
new file mode 100644
index 0000000..f74358f
--- /dev/null
+++ b/models/OrganicMechanic.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/OrientedEscher.xml b/models/OrientedEscher.xml
new file mode 100644
index 0000000..fe9980c
--- /dev/null
+++ b/models/OrientedEscher.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/PaintCompetition.xml b/models/PaintCompetition.xml
new file mode 100644
index 0000000..ceff7b9
--- /dev/null
+++ b/models/PaintCompetition.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/ParallelGrowth.xml b/models/ParallelGrowth.xml
new file mode 100644
index 0000000..31e58c1
--- /dev/null
+++ b/models/ParallelGrowth.xml
@@ -0,0 +1 @@
+
diff --git a/models/ParallelMazeGrowth.xml b/models/ParallelMazeGrowth.xml
new file mode 100644
index 0000000..822114b
--- /dev/null
+++ b/models/ParallelMazeGrowth.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/models/ParallelWalk.xml b/models/ParallelWalk.xml
new file mode 100644
index 0000000..7fcacd7
--- /dev/null
+++ b/models/ParallelWalk.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/models/Partitioning.xml b/models/Partitioning.xml
new file mode 100644
index 0000000..5d7eae5
--- /dev/null
+++ b/models/Partitioning.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/models/Percolation.xml b/models/Percolation.xml
new file mode 100644
index 0000000..4b86991
--- /dev/null
+++ b/models/Percolation.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/models/PeriodicEscher.xml b/models/PeriodicEscher.xml
new file mode 100644
index 0000000..6d30904
--- /dev/null
+++ b/models/PeriodicEscher.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/PillarsOfEternity.xml b/models/PillarsOfEternity.xml
new file mode 100644
index 0000000..3fda72d
--- /dev/null
+++ b/models/PillarsOfEternity.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/Push.xml b/models/Push.xml
new file mode 100644
index 0000000..aac0030
--- /dev/null
+++ b/models/Push.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/models/PutColoredLs.xml b/models/PutColoredLs.xml
new file mode 100644
index 0000000..70683b2
--- /dev/null
+++ b/models/PutColoredLs.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/PutLs.xml b/models/PutLs.xml
new file mode 100644
index 0000000..6b7b50d
--- /dev/null
+++ b/models/PutLs.xml
@@ -0,0 +1 @@
+
diff --git a/models/RandomWalk.xml b/models/RandomWalk.xml
new file mode 100644
index 0000000..0496b20
--- /dev/null
+++ b/models/RandomWalk.xml
@@ -0,0 +1 @@
+
diff --git a/models/Rectangle.xml b/models/Rectangle.xml
new file mode 100644
index 0000000..f63827b
--- /dev/null
+++ b/models/Rectangle.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/models/RegularPath.xml b/models/RegularPath.xml
new file mode 100644
index 0000000..dadeb7a
--- /dev/null
+++ b/models/RegularPath.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/RegularSAW.xml b/models/RegularSAW.xml
new file mode 100644
index 0000000..c44b8cd
--- /dev/null
+++ b/models/RegularSAW.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/models/RegularSAWRestart.xml b/models/RegularSAWRestart.xml
new file mode 100644
index 0000000..8e0af02
--- /dev/null
+++ b/models/RegularSAWRestart.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/models/River.xml b/models/River.xml
new file mode 100644
index 0000000..e936198
--- /dev/null
+++ b/models/River.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/SAWRestart.xml b/models/SAWRestart.xml
new file mode 100644
index 0000000..87748b3
--- /dev/null
+++ b/models/SAWRestart.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
diff --git a/models/SeaVilla.xml b/models/SeaVilla.xml
new file mode 100644
index 0000000..7d70216
--- /dev/null
+++ b/models/SeaVilla.xml
@@ -0,0 +1,434 @@
+
+
+
+
+
+ 31 values
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ не уверен насчет Fb
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/SelectLargeCaves.xml b/models/SelectLargeCaves.xml
new file mode 100644
index 0000000..3dd127e
--- /dev/null
+++ b/models/SelectLargeCaves.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/SelectLongKnots.xml b/models/SelectLongKnots.xml
new file mode 100644
index 0000000..286f8ea
--- /dev/null
+++ b/models/SelectLongKnots.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/SelfAvoidingWalk.xml b/models/SelfAvoidingWalk.xml
new file mode 100644
index 0000000..2e1c299
--- /dev/null
+++ b/models/SelfAvoidingWalk.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/models/SequentialSnake.xml b/models/SequentialSnake.xml
new file mode 100644
index 0000000..3bd3c9d
--- /dev/null
+++ b/models/SequentialSnake.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/SequentialSokoban.xml b/models/SequentialSokoban.xml
new file mode 100644
index 0000000..bf1c235
--- /dev/null
+++ b/models/SequentialSokoban.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/Sewers.xml b/models/Sewers.xml
new file mode 100644
index 0000000..0c5b98e
--- /dev/null
+++ b/models/Sewers.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/SmartSAW.xml b/models/SmartSAW.xml
new file mode 100644
index 0000000..6432436
--- /dev/null
+++ b/models/SmartSAW.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/SmarterDigger.xml b/models/SmarterDigger.xml
new file mode 100644
index 0000000..cff1d32
--- /dev/null
+++ b/models/SmarterDigger.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
diff --git a/models/SmoothTrail.xml b/models/SmoothTrail.xml
new file mode 100644
index 0000000..3c5d8ec
--- /dev/null
+++ b/models/SmoothTrail.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/Snake.xml b/models/Snake.xml
new file mode 100644
index 0000000..a409481
--- /dev/null
+++ b/models/Snake.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/SnellLaw.xml b/models/SnellLaw.xml
new file mode 100644
index 0000000..606e9a7
--- /dev/null
+++ b/models/SnellLaw.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/SoftPath.xml b/models/SoftPath.xml
new file mode 100644
index 0000000..3dffcf2
--- /dev/null
+++ b/models/SoftPath.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/SokobanLevel1.xml b/models/SokobanLevel1.xml
new file mode 100644
index 0000000..b7916cf
--- /dev/null
+++ b/models/SokobanLevel1.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/SokobanLevel2.xml b/models/SokobanLevel2.xml
new file mode 100644
index 0000000..fc06c95
--- /dev/null
+++ b/models/SokobanLevel2.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/StableCrawlers.xml b/models/StableCrawlers.xml
new file mode 100644
index 0000000..d531e46
--- /dev/null
+++ b/models/StableCrawlers.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/models/StairsPath.xml b/models/StairsPath.xml
new file mode 100644
index 0000000..26a2757
--- /dev/null
+++ b/models/StairsPath.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/StochasticVoronoi.xml b/models/StochasticVoronoi.xml
new file mode 100644
index 0000000..4ea7a94
--- /dev/null
+++ b/models/StochasticVoronoi.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/models/StormySnellLaw.xml b/models/StormySnellLaw.xml
new file mode 100644
index 0000000..9d4deed
--- /dev/null
+++ b/models/StormySnellLaw.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/StrangeDungeon.xml b/models/StrangeDungeon.xml
new file mode 100644
index 0000000..71ec794
--- /dev/null
+++ b/models/StrangeDungeon.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/StrangeGrowth.xml b/models/StrangeGrowth.xml
new file mode 100644
index 0000000..ae39a2a
--- /dev/null
+++ b/models/StrangeGrowth.xml
@@ -0,0 +1 @@
+
diff --git a/models/StrangeNoise.xml b/models/StrangeNoise.xml
new file mode 100644
index 0000000..bd28cec
--- /dev/null
+++ b/models/StrangeNoise.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/models/SubmergedKnots.xml b/models/SubmergedKnots.xml
new file mode 100644
index 0000000..a4660f7
--- /dev/null
+++ b/models/SubmergedKnots.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/Surface.xml b/models/Surface.xml
new file mode 100644
index 0000000..711541f
--- /dev/null
+++ b/models/Surface.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/models/Tetris.xml b/models/Tetris.xml
new file mode 100644
index 0000000..935d605
--- /dev/null
+++ b/models/Tetris.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/TileDungeon.xml b/models/TileDungeon.xml
new file mode 100644
index 0000000..855f8c6
--- /dev/null
+++ b/models/TileDungeon.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/Trail.xml b/models/Trail.xml
new file mode 100644
index 0000000..ee5dae0
--- /dev/null
+++ b/models/Trail.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/models/Voronoi.xml b/models/Voronoi.xml
new file mode 100644
index 0000000..f85cdb3
--- /dev/null
+++ b/models/Voronoi.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/models/WaveBrickWall.xml b/models/WaveBrickWall.xml
new file mode 100644
index 0000000..40e2cb6
--- /dev/null
+++ b/models/WaveBrickWall.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/models/WaveDungeon.xml b/models/WaveDungeon.xml
new file mode 100644
index 0000000..3984bb4
--- /dev/null
+++ b/models/WaveDungeon.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/WaveFlowers.xml b/models/WaveFlowers.xml
new file mode 100644
index 0000000..33cefe3
--- /dev/null
+++ b/models/WaveFlowers.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/models/Wilson.xml b/models/Wilson.xml
new file mode 100644
index 0000000..ae65109
--- /dev/null
+++ b/models/Wilson.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/models/WolfBasedApproach.xml b/models/WolfBasedApproach.xml
new file mode 100644
index 0000000..eb06962
--- /dev/null
+++ b/models/WolfBasedApproach.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/fonts/Tamzen8x16b.png b/resources/fonts/Tamzen8x16b.png
new file mode 100644
index 0000000..c59dca0
Binary files /dev/null and b/resources/fonts/Tamzen8x16b.png differ
diff --git a/resources/fonts/Tamzen8x16r.png b/resources/fonts/Tamzen8x16r.png
new file mode 100644
index 0000000..b180e98
Binary files /dev/null and b/resources/fonts/Tamzen8x16r.png differ
diff --git a/resources/palette.xml b/resources/palette.xml
new file mode 100644
index 0000000..5c57b31
--- /dev/null
+++ b/resources/palette.xml
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/rules/BasicDijkstraRoom.png b/resources/rules/BasicDijkstraRoom.png
new file mode 100644
index 0000000..1408d08
Binary files /dev/null and b/resources/rules/BasicDijkstraRoom.png differ
diff --git a/resources/rules/BasicDijkstraRoom.vox b/resources/rules/BasicDijkstraRoom.vox
new file mode 100644
index 0000000..7a5b86d
Binary files /dev/null and b/resources/rules/BasicDijkstraRoom.vox differ
diff --git a/resources/rules/CarmaTower/block_1_5x3.vox b/resources/rules/CarmaTower/block_1_5x3.vox
new file mode 100644
index 0000000..8dbbdcc
Binary files /dev/null and b/resources/rules/CarmaTower/block_1_5x3.vox differ
diff --git a/resources/rules/CarmaTower/block_2_4x3.vox b/resources/rules/CarmaTower/block_2_4x3.vox
new file mode 100644
index 0000000..987bcf2
Binary files /dev/null and b/resources/rules/CarmaTower/block_2_4x3.vox differ
diff --git a/resources/rules/CarmaTower/bracing_h_1.vox b/resources/rules/CarmaTower/bracing_h_1.vox
new file mode 100644
index 0000000..04318ab
Binary files /dev/null and b/resources/rules/CarmaTower/bracing_h_1.vox differ
diff --git a/resources/rules/CarmaTower/bracing_h_2.vox b/resources/rules/CarmaTower/bracing_h_2.vox
new file mode 100644
index 0000000..a95438e
Binary files /dev/null and b/resources/rules/CarmaTower/bracing_h_2.vox differ
diff --git a/resources/rules/CarmaTower/bracing_h_3.vox b/resources/rules/CarmaTower/bracing_h_3.vox
new file mode 100644
index 0000000..10e426d
Binary files /dev/null and b/resources/rules/CarmaTower/bracing_h_3.vox differ
diff --git a/resources/rules/CarmaTower/bracing_v_1.vox b/resources/rules/CarmaTower/bracing_v_1.vox
new file mode 100644
index 0000000..fc5cb02
Binary files /dev/null and b/resources/rules/CarmaTower/bracing_v_1.vox differ
diff --git a/resources/rules/CarmaTower/bracing_v_2.vox b/resources/rules/CarmaTower/bracing_v_2.vox
new file mode 100644
index 0000000..4974877
Binary files /dev/null and b/resources/rules/CarmaTower/bracing_v_2.vox differ
diff --git a/resources/rules/CarmaTower/bracing_v_3.vox b/resources/rules/CarmaTower/bracing_v_3.vox
new file mode 100644
index 0000000..de7da35
Binary files /dev/null and b/resources/rules/CarmaTower/bracing_v_3.vox differ
diff --git a/resources/rules/CarmaTower/cut_1.vox b/resources/rules/CarmaTower/cut_1.vox
new file mode 100644
index 0000000..240569d
Binary files /dev/null and b/resources/rules/CarmaTower/cut_1.vox differ
diff --git a/resources/rules/CarmaTower/cut_2.vox b/resources/rules/CarmaTower/cut_2.vox
new file mode 100644
index 0000000..f9556a6
Binary files /dev/null and b/resources/rules/CarmaTower/cut_2.vox differ
diff --git a/resources/rules/CarmaTower/holes_2.vox b/resources/rules/CarmaTower/holes_2.vox
new file mode 100644
index 0000000..3af83cd
Binary files /dev/null and b/resources/rules/CarmaTower/holes_2.vox differ
diff --git a/resources/rules/CarmaTower/holes_4.vox b/resources/rules/CarmaTower/holes_4.vox
new file mode 100644
index 0000000..4fade8d
Binary files /dev/null and b/resources/rules/CarmaTower/holes_4.vox differ
diff --git a/resources/rules/CarmaTower/holes_h_4.vox b/resources/rules/CarmaTower/holes_h_4.vox
new file mode 100644
index 0000000..03419ae
Binary files /dev/null and b/resources/rules/CarmaTower/holes_h_4.vox differ
diff --git a/resources/rules/CarmaTower/holes_v_3.vox b/resources/rules/CarmaTower/holes_v_3.vox
new file mode 100644
index 0000000..945637b
Binary files /dev/null and b/resources/rules/CarmaTower/holes_v_3.vox differ
diff --git a/resources/rules/CarmaTower/roof_3x5.vox b/resources/rules/CarmaTower/roof_3x5.vox
new file mode 100644
index 0000000..ac412b7
Binary files /dev/null and b/resources/rules/CarmaTower/roof_3x5.vox differ
diff --git a/resources/rules/CarmaTower/roof_5x9.vox b/resources/rules/CarmaTower/roof_5x9.vox
new file mode 100644
index 0000000..8aa0d24
Binary files /dev/null and b/resources/rules/CarmaTower/roof_5x9.vox differ
diff --git a/resources/rules/CarmaTower/roof_7x11.vox b/resources/rules/CarmaTower/roof_7x11.vox
new file mode 100644
index 0000000..0362400
Binary files /dev/null and b/resources/rules/CarmaTower/roof_7x11.vox differ
diff --git a/resources/rules/CarmaTower/roof_7x7.vox b/resources/rules/CarmaTower/roof_7x7.vox
new file mode 100644
index 0000000..3c6b393
Binary files /dev/null and b/resources/rules/CarmaTower/roof_7x7.vox differ
diff --git a/resources/rules/CarmaTower/roof_thing_1.vox b/resources/rules/CarmaTower/roof_thing_1.vox
new file mode 100644
index 0000000..87cf2ee
Binary files /dev/null and b/resources/rules/CarmaTower/roof_thing_1.vox differ
diff --git a/resources/rules/CarmaTower/roof_thing_2.vox b/resources/rules/CarmaTower/roof_thing_2.vox
new file mode 100644
index 0000000..fc06cbc
Binary files /dev/null and b/resources/rules/CarmaTower/roof_thing_2.vox differ
diff --git a/resources/rules/CarmaTower/roof_thing_5x9.vox b/resources/rules/CarmaTower/roof_thing_5x9.vox
new file mode 100644
index 0000000..2a48a94
Binary files /dev/null and b/resources/rules/CarmaTower/roof_thing_5x9.vox differ
diff --git a/resources/rules/CarmaTower/roof_thing_7x11.vox b/resources/rules/CarmaTower/roof_thing_7x11.vox
new file mode 100644
index 0000000..7d2d84c
Binary files /dev/null and b/resources/rules/CarmaTower/roof_thing_7x11.vox differ
diff --git a/resources/rules/CarmaTower/side_11x7.vox b/resources/rules/CarmaTower/side_11x7.vox
new file mode 100644
index 0000000..8c98071
Binary files /dev/null and b/resources/rules/CarmaTower/side_11x7.vox differ
diff --git a/resources/rules/CarmaTower/side_13x5.vox b/resources/rules/CarmaTower/side_13x5.vox
new file mode 100644
index 0000000..9148db3
Binary files /dev/null and b/resources/rules/CarmaTower/side_13x5.vox differ
diff --git a/resources/rules/CarmaTower/side_3x5.vox b/resources/rules/CarmaTower/side_3x5.vox
new file mode 100644
index 0000000..745a2ec
Binary files /dev/null and b/resources/rules/CarmaTower/side_3x5.vox differ
diff --git a/resources/rules/CarmaTower/side_3x5_fake.vox b/resources/rules/CarmaTower/side_3x5_fake.vox
new file mode 100644
index 0000000..559a194
Binary files /dev/null and b/resources/rules/CarmaTower/side_3x5_fake.vox differ
diff --git a/resources/rules/CarmaTower/side_5x13.vox b/resources/rules/CarmaTower/side_5x13.vox
new file mode 100644
index 0000000..e2d0916
Binary files /dev/null and b/resources/rules/CarmaTower/side_5x13.vox differ
diff --git a/resources/rules/CarmaTower/side_5x3.vox b/resources/rules/CarmaTower/side_5x3.vox
new file mode 100644
index 0000000..c1ac93d
Binary files /dev/null and b/resources/rules/CarmaTower/side_5x3.vox differ
diff --git a/resources/rules/CarmaTower/side_5x3_fake.vox b/resources/rules/CarmaTower/side_5x3_fake.vox
new file mode 100644
index 0000000..e311c3d
Binary files /dev/null and b/resources/rules/CarmaTower/side_5x3_fake.vox differ
diff --git a/resources/rules/CarmaTower/side_7x11.vox b/resources/rules/CarmaTower/side_7x11.vox
new file mode 100644
index 0000000..ed561dd
Binary files /dev/null and b/resources/rules/CarmaTower/side_7x11.vox differ
diff --git a/resources/rules/CarmaTower/small_windows_2.vox b/resources/rules/CarmaTower/small_windows_2.vox
new file mode 100644
index 0000000..e11ade1
Binary files /dev/null and b/resources/rules/CarmaTower/small_windows_2.vox differ
diff --git a/resources/rules/CarmaTower/small_windows_3.vox b/resources/rules/CarmaTower/small_windows_3.vox
new file mode 100644
index 0000000..9ac167c
Binary files /dev/null and b/resources/rules/CarmaTower/small_windows_3.vox differ
diff --git a/resources/rules/CarmaTower/window_1.vox b/resources/rules/CarmaTower/window_1.vox
new file mode 100644
index 0000000..57a6d5c
Binary files /dev/null and b/resources/rules/CarmaTower/window_1.vox differ
diff --git a/resources/rules/CarmaTower/window_2.vox b/resources/rules/CarmaTower/window_2.vox
new file mode 100644
index 0000000..bb0475f
Binary files /dev/null and b/resources/rules/CarmaTower/window_2.vox differ
diff --git a/resources/rules/CarmaTower/window_3.vox b/resources/rules/CarmaTower/window_3.vox
new file mode 100644
index 0000000..990fce2
Binary files /dev/null and b/resources/rules/CarmaTower/window_3.vox differ
diff --git a/resources/rules/Chip.png b/resources/rules/Chip.png
new file mode 100644
index 0000000..58a1324
Binary files /dev/null and b/resources/rules/Chip.png differ
diff --git a/resources/rules/ConstrainedRoom.png b/resources/rules/ConstrainedRoom.png
new file mode 100644
index 0000000..6990d00
Binary files /dev/null and b/resources/rules/ConstrainedRoom.png differ
diff --git a/resources/rules/DijkstraRoom.png b/resources/rules/DijkstraRoom.png
new file mode 100644
index 0000000..98470c6
Binary files /dev/null and b/resources/rules/DijkstraRoom.png differ
diff --git a/resources/rules/DualRetraction/Room1.png b/resources/rules/DualRetraction/Room1.png
new file mode 100644
index 0000000..ba6c4cc
Binary files /dev/null and b/resources/rules/DualRetraction/Room1.png differ
diff --git a/resources/rules/DualRetraction/Room1.vox b/resources/rules/DualRetraction/Room1.vox
new file mode 100644
index 0000000..333243e
Binary files /dev/null and b/resources/rules/DualRetraction/Room1.vox differ
diff --git a/resources/rules/DualRetraction/Room2.png b/resources/rules/DualRetraction/Room2.png
new file mode 100644
index 0000000..dbb9ff8
Binary files /dev/null and b/resources/rules/DualRetraction/Room2.png differ
diff --git a/resources/rules/DualRetraction/Room2.vox b/resources/rules/DualRetraction/Room2.vox
new file mode 100644
index 0000000..46f2d3c
Binary files /dev/null and b/resources/rules/DualRetraction/Room2.vox differ
diff --git a/resources/rules/DungeonGrowth/Room1.png b/resources/rules/DungeonGrowth/Room1.png
new file mode 100644
index 0000000..e4e6fef
Binary files /dev/null and b/resources/rules/DungeonGrowth/Room1.png differ
diff --git a/resources/rules/DungeonGrowth/Room10.png b/resources/rules/DungeonGrowth/Room10.png
new file mode 100644
index 0000000..0b950cf
Binary files /dev/null and b/resources/rules/DungeonGrowth/Room10.png differ
diff --git a/resources/rules/DungeonGrowth/Room11.png b/resources/rules/DungeonGrowth/Room11.png
new file mode 100644
index 0000000..8774ada
Binary files /dev/null and b/resources/rules/DungeonGrowth/Room11.png differ
diff --git a/resources/rules/DungeonGrowth/Room12.png b/resources/rules/DungeonGrowth/Room12.png
new file mode 100644
index 0000000..fecd85f
Binary files /dev/null and b/resources/rules/DungeonGrowth/Room12.png differ
diff --git a/resources/rules/DungeonGrowth/Room13.png b/resources/rules/DungeonGrowth/Room13.png
new file mode 100644
index 0000000..dd3c7ac
Binary files /dev/null and b/resources/rules/DungeonGrowth/Room13.png differ
diff --git a/resources/rules/DungeonGrowth/Room14.png b/resources/rules/DungeonGrowth/Room14.png
new file mode 100644
index 0000000..92bb254
Binary files /dev/null and b/resources/rules/DungeonGrowth/Room14.png differ
diff --git a/resources/rules/DungeonGrowth/Room15.png b/resources/rules/DungeonGrowth/Room15.png
new file mode 100644
index 0000000..1c86395
Binary files /dev/null and b/resources/rules/DungeonGrowth/Room15.png differ
diff --git a/resources/rules/DungeonGrowth/Room16.png b/resources/rules/DungeonGrowth/Room16.png
new file mode 100644
index 0000000..1e7b964
Binary files /dev/null and b/resources/rules/DungeonGrowth/Room16.png differ
diff --git a/resources/rules/DungeonGrowth/Room17.png b/resources/rules/DungeonGrowth/Room17.png
new file mode 100644
index 0000000..2782a1a
Binary files /dev/null and b/resources/rules/DungeonGrowth/Room17.png differ
diff --git a/resources/rules/DungeonGrowth/Room18.png b/resources/rules/DungeonGrowth/Room18.png
new file mode 100644
index 0000000..04cf9e5
Binary files /dev/null and b/resources/rules/DungeonGrowth/Room18.png differ
diff --git a/resources/rules/DungeonGrowth/Room2.png b/resources/rules/DungeonGrowth/Room2.png
new file mode 100644
index 0000000..ffb4bc0
Binary files /dev/null and b/resources/rules/DungeonGrowth/Room2.png differ
diff --git a/resources/rules/DungeonGrowth/Room3.png b/resources/rules/DungeonGrowth/Room3.png
new file mode 100644
index 0000000..1da6745
Binary files /dev/null and b/resources/rules/DungeonGrowth/Room3.png differ
diff --git a/resources/rules/DungeonGrowth/Room4.png b/resources/rules/DungeonGrowth/Room4.png
new file mode 100644
index 0000000..6db6d24
Binary files /dev/null and b/resources/rules/DungeonGrowth/Room4.png differ
diff --git a/resources/rules/DungeonGrowth/Room5.png b/resources/rules/DungeonGrowth/Room5.png
new file mode 100644
index 0000000..2cb0307
Binary files /dev/null and b/resources/rules/DungeonGrowth/Room5.png differ
diff --git a/resources/rules/DungeonGrowth/Room6.png b/resources/rules/DungeonGrowth/Room6.png
new file mode 100644
index 0000000..a2ce3d6
Binary files /dev/null and b/resources/rules/DungeonGrowth/Room6.png differ
diff --git a/resources/rules/DungeonGrowth/Room7.png b/resources/rules/DungeonGrowth/Room7.png
new file mode 100644
index 0000000..6c9ce17
Binary files /dev/null and b/resources/rules/DungeonGrowth/Room7.png differ
diff --git a/resources/rules/DungeonGrowth/Room8.png b/resources/rules/DungeonGrowth/Room8.png
new file mode 100644
index 0000000..83b316f
Binary files /dev/null and b/resources/rules/DungeonGrowth/Room8.png differ
diff --git a/resources/rules/DungeonGrowth/Room9.png b/resources/rules/DungeonGrowth/Room9.png
new file mode 100644
index 0000000..ea7c7f6
Binary files /dev/null and b/resources/rules/DungeonGrowth/Room9.png differ
diff --git a/resources/rules/ModernHouse/Bridge.vox b/resources/rules/ModernHouse/Bridge.vox
new file mode 100644
index 0000000..8633e73
Binary files /dev/null and b/resources/rules/ModernHouse/Bridge.vox differ
diff --git a/resources/rules/ModernHouse/ColumnBottom.vox b/resources/rules/ModernHouse/ColumnBottom.vox
new file mode 100644
index 0000000..488e3f9
Binary files /dev/null and b/resources/rules/ModernHouse/ColumnBottom.vox differ
diff --git a/resources/rules/ModernHouse/ColumnTop.vox b/resources/rules/ModernHouse/ColumnTop.vox
new file mode 100644
index 0000000..fe04b3c
Binary files /dev/null and b/resources/rules/ModernHouse/ColumnTop.vox differ
diff --git a/resources/rules/ModernHouse/ContactBottom.vox b/resources/rules/ModernHouse/ContactBottom.vox
new file mode 100644
index 0000000..23a1e3f
Binary files /dev/null and b/resources/rules/ModernHouse/ContactBottom.vox differ
diff --git a/resources/rules/ModernHouse/ContactTop.vox b/resources/rules/ModernHouse/ContactTop.vox
new file mode 100644
index 0000000..2b5242d
Binary files /dev/null and b/resources/rules/ModernHouse/ContactTop.vox differ
diff --git a/resources/rules/ModernHouse/Dir.vox b/resources/rules/ModernHouse/Dir.vox
new file mode 100644
index 0000000..66f11a0
Binary files /dev/null and b/resources/rules/ModernHouse/Dir.vox differ
diff --git a/resources/rules/ModernHouse/Down.vox b/resources/rules/ModernHouse/Down.vox
new file mode 100644
index 0000000..543895a
Binary files /dev/null and b/resources/rules/ModernHouse/Down.vox differ
diff --git a/resources/rules/ModernHouse/Edge.vox b/resources/rules/ModernHouse/Edge.vox
new file mode 100644
index 0000000..abdcb8a
Binary files /dev/null and b/resources/rules/ModernHouse/Edge.vox differ
diff --git a/resources/rules/ModernHouse/FrameDir.vox b/resources/rules/ModernHouse/FrameDir.vox
new file mode 100644
index 0000000..d119ecc
Binary files /dev/null and b/resources/rules/ModernHouse/FrameDir.vox differ
diff --git a/resources/rules/ModernHouse/FrameVertex.vox b/resources/rules/ModernHouse/FrameVertex.vox
new file mode 100644
index 0000000..88b371e
Binary files /dev/null and b/resources/rules/ModernHouse/FrameVertex.vox differ
diff --git a/resources/rules/ModernHouse/GlassColumnBottom.vox b/resources/rules/ModernHouse/GlassColumnBottom.vox
new file mode 100644
index 0000000..131c52c
Binary files /dev/null and b/resources/rules/ModernHouse/GlassColumnBottom.vox differ
diff --git a/resources/rules/ModernHouse/GlassColumnMid.vox b/resources/rules/ModernHouse/GlassColumnMid.vox
new file mode 100644
index 0000000..55df87a
Binary files /dev/null and b/resources/rules/ModernHouse/GlassColumnMid.vox differ
diff --git a/resources/rules/ModernHouse/GlassColumnTop.vox b/resources/rules/ModernHouse/GlassColumnTop.vox
new file mode 100644
index 0000000..94695e4
Binary files /dev/null and b/resources/rules/ModernHouse/GlassColumnTop.vox differ
diff --git a/resources/rules/ModernHouse/Grass.vox b/resources/rules/ModernHouse/Grass.vox
new file mode 100644
index 0000000..a881ef2
Binary files /dev/null and b/resources/rules/ModernHouse/Grass.vox differ
diff --git a/resources/rules/ModernHouse/In.vox b/resources/rules/ModernHouse/In.vox
new file mode 100644
index 0000000..6d6f71d
Binary files /dev/null and b/resources/rules/ModernHouse/In.vox differ
diff --git a/resources/rules/ModernHouse/Node.vox b/resources/rules/ModernHouse/Node.vox
new file mode 100644
index 0000000..4e5b974
Binary files /dev/null and b/resources/rules/ModernHouse/Node.vox differ
diff --git a/resources/rules/ModernHouse/Out.vox b/resources/rules/ModernHouse/Out.vox
new file mode 100644
index 0000000..f5e89bb
Binary files /dev/null and b/resources/rules/ModernHouse/Out.vox differ
diff --git a/resources/rules/ModernHouse/SecondaryDir.vox b/resources/rules/ModernHouse/SecondaryDir.vox
new file mode 100644
index 0000000..1469b6e
Binary files /dev/null and b/resources/rules/ModernHouse/SecondaryDir.vox differ
diff --git a/resources/rules/ModernHouse/SecondaryNode.vox b/resources/rules/ModernHouse/SecondaryNode.vox
new file mode 100644
index 0000000..86d77d2
Binary files /dev/null and b/resources/rules/ModernHouse/SecondaryNode.vox differ
diff --git a/resources/rules/ModernHouse/StyleBottom.vox b/resources/rules/ModernHouse/StyleBottom.vox
new file mode 100644
index 0000000..4980646
Binary files /dev/null and b/resources/rules/ModernHouse/StyleBottom.vox differ
diff --git a/resources/rules/ModernHouse/StyleTop.vox b/resources/rules/ModernHouse/StyleTop.vox
new file mode 100644
index 0000000..d4ff2c1
Binary files /dev/null and b/resources/rules/ModernHouse/StyleTop.vox differ
diff --git a/resources/rules/ModernHouse/TertiaryDir.vox b/resources/rules/ModernHouse/TertiaryDir.vox
new file mode 100644
index 0000000..7785f13
Binary files /dev/null and b/resources/rules/ModernHouse/TertiaryDir.vox differ
diff --git a/resources/rules/ModernHouse/Up.vox b/resources/rules/ModernHouse/Up.vox
new file mode 100644
index 0000000..58c6f91
Binary files /dev/null and b/resources/rules/ModernHouse/Up.vox differ
diff --git a/resources/rules/ModernHouse/Wall.vox b/resources/rules/ModernHouse/Wall.vox
new file mode 100644
index 0000000..f318bed
Binary files /dev/null and b/resources/rules/ModernHouse/Wall.vox differ
diff --git a/resources/rules/ModernHouse/WallBottom.vox b/resources/rules/ModernHouse/WallBottom.vox
new file mode 100644
index 0000000..27c761b
Binary files /dev/null and b/resources/rules/ModernHouse/WallBottom.vox differ
diff --git a/resources/rules/ModernHouse/WallTop.vox b/resources/rules/ModernHouse/WallTop.vox
new file mode 100644
index 0000000..35f10ec
Binary files /dev/null and b/resources/rules/ModernHouse/WallTop.vox differ
diff --git a/resources/rules/ModernHouse/WindowBottom.vox b/resources/rules/ModernHouse/WindowBottom.vox
new file mode 100644
index 0000000..7a83561
Binary files /dev/null and b/resources/rules/ModernHouse/WindowBottom.vox differ
diff --git a/resources/rules/ModernHouse/WindowMid.vox b/resources/rules/ModernHouse/WindowMid.vox
new file mode 100644
index 0000000..c387efd
Binary files /dev/null and b/resources/rules/ModernHouse/WindowMid.vox differ
diff --git a/resources/rules/ModernHouse/WindowTop.vox b/resources/rules/ModernHouse/WindowTop.vox
new file mode 100644
index 0000000..ca4bba8
Binary files /dev/null and b/resources/rules/ModernHouse/WindowTop.vox differ
diff --git a/resources/rules/ModernHouse/WoodBottom.vox b/resources/rules/ModernHouse/WoodBottom.vox
new file mode 100644
index 0000000..0770580
Binary files /dev/null and b/resources/rules/ModernHouse/WoodBottom.vox differ
diff --git a/resources/rules/ModernHouse/WoodTop.vox b/resources/rules/ModernHouse/WoodTop.vox
new file mode 100644
index 0000000..ae021b0
Binary files /dev/null and b/resources/rules/ModernHouse/WoodTop.vox differ
diff --git a/resources/rules/SeaVilla/BridgeWood.vox b/resources/rules/SeaVilla/BridgeWood.vox
new file mode 100644
index 0000000..0939e8f
Binary files /dev/null and b/resources/rules/SeaVilla/BridgeWood.vox differ
diff --git a/resources/rules/SeaVilla/ContactWood.vox b/resources/rules/SeaVilla/ContactWood.vox
new file mode 100644
index 0000000..e948d2b
Binary files /dev/null and b/resources/rules/SeaVilla/ContactWood.vox differ
diff --git a/resources/rules/SeaVilla/Cross.vox b/resources/rules/SeaVilla/Cross.vox
new file mode 100644
index 0000000..ba9d441
Binary files /dev/null and b/resources/rules/SeaVilla/Cross.vox differ
diff --git a/resources/rules/SeaVilla/CrossX.vox b/resources/rules/SeaVilla/CrossX.vox
new file mode 100644
index 0000000..87fd95e
Binary files /dev/null and b/resources/rules/SeaVilla/CrossX.vox differ
diff --git a/resources/rules/SeaVilla/Dir.vox b/resources/rules/SeaVilla/Dir.vox
new file mode 100644
index 0000000..1009940
Binary files /dev/null and b/resources/rules/SeaVilla/Dir.vox differ
diff --git a/resources/rules/SeaVilla/DrawPier.vox b/resources/rules/SeaVilla/DrawPier.vox
new file mode 100644
index 0000000..1e186e7
Binary files /dev/null and b/resources/rules/SeaVilla/DrawPier.vox differ
diff --git a/resources/rules/SeaVilla/Edge.vox b/resources/rules/SeaVilla/Edge.vox
new file mode 100644
index 0000000..2418195
Binary files /dev/null and b/resources/rules/SeaVilla/Edge.vox differ
diff --git a/resources/rules/SeaVilla/Goal.vox b/resources/rules/SeaVilla/Goal.vox
new file mode 100644
index 0000000..d301960
Binary files /dev/null and b/resources/rules/SeaVilla/Goal.vox differ
diff --git a/resources/rules/SeaVilla/In.vox b/resources/rules/SeaVilla/In.vox
new file mode 100644
index 0000000..5fc5387
Binary files /dev/null and b/resources/rules/SeaVilla/In.vox differ
diff --git a/resources/rules/SeaVilla/KeyK.vox b/resources/rules/SeaVilla/KeyK.vox
new file mode 100644
index 0000000..c94581f
Binary files /dev/null and b/resources/rules/SeaVilla/KeyK.vox differ
diff --git a/resources/rules/SeaVilla/KeyL.vox b/resources/rules/SeaVilla/KeyL.vox
new file mode 100644
index 0000000..aa73c39
Binary files /dev/null and b/resources/rules/SeaVilla/KeyL.vox differ
diff --git a/resources/rules/SeaVilla/KeyR.vox b/resources/rules/SeaVilla/KeyR.vox
new file mode 100644
index 0000000..144a10e
Binary files /dev/null and b/resources/rules/SeaVilla/KeyR.vox differ
diff --git a/resources/rules/SeaVilla/Ladder.vox b/resources/rules/SeaVilla/Ladder.vox
new file mode 100644
index 0000000..27b1c23
Binary files /dev/null and b/resources/rules/SeaVilla/Ladder.vox differ
diff --git a/resources/rules/SeaVilla/LadderBottom.vox b/resources/rules/SeaVilla/LadderBottom.vox
new file mode 100644
index 0000000..5ac926e
Binary files /dev/null and b/resources/rules/SeaVilla/LadderBottom.vox differ
diff --git a/resources/rules/SeaVilla/LadderTop.vox b/resources/rules/SeaVilla/LadderTop.vox
new file mode 100644
index 0000000..9cd06c2
Binary files /dev/null and b/resources/rules/SeaVilla/LadderTop.vox differ
diff --git a/resources/rules/SeaVilla/Line.vox b/resources/rules/SeaVilla/Line.vox
new file mode 100644
index 0000000..f9d96af
Binary files /dev/null and b/resources/rules/SeaVilla/Line.vox differ
diff --git a/resources/rules/SeaVilla/LockK.vox b/resources/rules/SeaVilla/LockK.vox
new file mode 100644
index 0000000..32e463b
Binary files /dev/null and b/resources/rules/SeaVilla/LockK.vox differ
diff --git a/resources/rules/SeaVilla/LockL.vox b/resources/rules/SeaVilla/LockL.vox
new file mode 100644
index 0000000..0e83931
Binary files /dev/null and b/resources/rules/SeaVilla/LockL.vox differ
diff --git a/resources/rules/SeaVilla/LockR.vox b/resources/rules/SeaVilla/LockR.vox
new file mode 100644
index 0000000..634cb60
Binary files /dev/null and b/resources/rules/SeaVilla/LockR.vox differ
diff --git a/resources/rules/SeaVilla/Out.vox b/resources/rules/SeaVilla/Out.vox
new file mode 100644
index 0000000..5e60214
Binary files /dev/null and b/resources/rules/SeaVilla/Out.vox differ
diff --git a/resources/rules/SeaVilla/OutIn.vox b/resources/rules/SeaVilla/OutIn.vox
new file mode 100644
index 0000000..32d2850
Binary files /dev/null and b/resources/rules/SeaVilla/OutIn.vox differ
diff --git a/resources/rules/SeaVilla/OutWall.vox b/resources/rules/SeaVilla/OutWall.vox
new file mode 100644
index 0000000..369f642
Binary files /dev/null and b/resources/rules/SeaVilla/OutWall.vox differ
diff --git a/resources/rules/SeaVilla/Pier.vox b/resources/rules/SeaVilla/Pier.vox
new file mode 100644
index 0000000..6ac6646
Binary files /dev/null and b/resources/rules/SeaVilla/Pier.vox differ
diff --git a/resources/rules/SeaVilla/Room.vox b/resources/rules/SeaVilla/Room.vox
new file mode 100644
index 0000000..cbe4e79
Binary files /dev/null and b/resources/rules/SeaVilla/Room.vox differ
diff --git a/resources/rules/SeaVilla/RoomDouble.vox b/resources/rules/SeaVilla/RoomDouble.vox
new file mode 100644
index 0000000..2f1d656
Binary files /dev/null and b/resources/rules/SeaVilla/RoomDouble.vox differ
diff --git a/resources/rules/SeaVilla/RoomIn.vox b/resources/rules/SeaVilla/RoomIn.vox
new file mode 100644
index 0000000..9d6bb36
Binary files /dev/null and b/resources/rules/SeaVilla/RoomIn.vox differ
diff --git a/resources/rules/SeaVilla/RoomOut.vox b/resources/rules/SeaVilla/RoomOut.vox
new file mode 100644
index 0000000..c3fb5f4
Binary files /dev/null and b/resources/rules/SeaVilla/RoomOut.vox differ
diff --git a/resources/rules/SeaVilla/RoomWall.vox b/resources/rules/SeaVilla/RoomWall.vox
new file mode 100644
index 0000000..75c5c8a
Binary files /dev/null and b/resources/rules/SeaVilla/RoomWall.vox differ
diff --git a/resources/rules/SeaVilla/StairsBottom.vox b/resources/rules/SeaVilla/StairsBottom.vox
new file mode 100644
index 0000000..ac4c5ef
Binary files /dev/null and b/resources/rules/SeaVilla/StairsBottom.vox differ
diff --git a/resources/rules/SeaVilla/StairsTop.vox b/resources/rules/SeaVilla/StairsTop.vox
new file mode 100644
index 0000000..b4a80d6
Binary files /dev/null and b/resources/rules/SeaVilla/StairsTop.vox differ
diff --git a/resources/rules/SeaVilla/Stone.vox b/resources/rules/SeaVilla/Stone.vox
new file mode 100644
index 0000000..a3495cc
Binary files /dev/null and b/resources/rules/SeaVilla/Stone.vox differ
diff --git a/resources/rules/SeaVilla/TopContactWood.vox b/resources/rules/SeaVilla/TopContactWood.vox
new file mode 100644
index 0000000..2c55419
Binary files /dev/null and b/resources/rules/SeaVilla/TopContactWood.vox differ
diff --git a/resources/rules/SeaVilla/TopCross.vox b/resources/rules/SeaVilla/TopCross.vox
new file mode 100644
index 0000000..a10329e
Binary files /dev/null and b/resources/rules/SeaVilla/TopCross.vox differ
diff --git a/resources/rules/SeaVilla/TopDir.vox b/resources/rules/SeaVilla/TopDir.vox
new file mode 100644
index 0000000..9f4815a
Binary files /dev/null and b/resources/rules/SeaVilla/TopDir.vox differ
diff --git a/resources/rules/SeaVilla/TopEdge.vox b/resources/rules/SeaVilla/TopEdge.vox
new file mode 100644
index 0000000..786a87b
Binary files /dev/null and b/resources/rules/SeaVilla/TopEdge.vox differ
diff --git a/resources/rules/SeaVilla/TopIn.vox b/resources/rules/SeaVilla/TopIn.vox
new file mode 100644
index 0000000..cccf925
Binary files /dev/null and b/resources/rules/SeaVilla/TopIn.vox differ
diff --git a/resources/rules/SeaVilla/TopOut.vox b/resources/rules/SeaVilla/TopOut.vox
new file mode 100644
index 0000000..2883174
Binary files /dev/null and b/resources/rules/SeaVilla/TopOut.vox differ
diff --git a/resources/rules/SeaVilla/TopStone.vox b/resources/rules/SeaVilla/TopStone.vox
new file mode 100644
index 0000000..c864f36
Binary files /dev/null and b/resources/rules/SeaVilla/TopStone.vox differ
diff --git a/resources/rules/SeaVilla/TopWall.vox b/resources/rules/SeaVilla/TopWall.vox
new file mode 100644
index 0000000..573b3e1
Binary files /dev/null and b/resources/rules/SeaVilla/TopWall.vox differ
diff --git a/resources/rules/SeaVilla/Wall.vox b/resources/rules/SeaVilla/Wall.vox
new file mode 100644
index 0000000..5e691fc
Binary files /dev/null and b/resources/rules/SeaVilla/Wall.vox differ
diff --git a/resources/rules/SeaVilla/Water.vox b/resources/rules/SeaVilla/Water.vox
new file mode 100644
index 0000000..cd02143
Binary files /dev/null and b/resources/rules/SeaVilla/Water.vox differ
diff --git a/resources/rules/SeaVilla/WaterWall.vox b/resources/rules/SeaVilla/WaterWall.vox
new file mode 100644
index 0000000..ca020c2
Binary files /dev/null and b/resources/rules/SeaVilla/WaterWall.vox differ
diff --git a/resources/rules/Sokoban1.png b/resources/rules/Sokoban1.png
new file mode 100644
index 0000000..0ca23b1
Binary files /dev/null and b/resources/rules/Sokoban1.png differ
diff --git a/resources/rules/Sokoban2.png b/resources/rules/Sokoban2.png
new file mode 100644
index 0000000..e82e6d9
Binary files /dev/null and b/resources/rules/Sokoban2.png differ
diff --git a/resources/rules/Stairs/Down.vox b/resources/rules/Stairs/Down.vox
new file mode 100644
index 0000000..58a1b56
Binary files /dev/null and b/resources/rules/Stairs/Down.vox differ
diff --git a/resources/rules/Stairs/End.vox b/resources/rules/Stairs/End.vox
new file mode 100644
index 0000000..b85948e
Binary files /dev/null and b/resources/rules/Stairs/End.vox differ
diff --git a/resources/rules/Stairs/Line.vox b/resources/rules/Stairs/Line.vox
new file mode 100644
index 0000000..b2b5682
Binary files /dev/null and b/resources/rules/Stairs/Line.vox differ
diff --git a/resources/rules/Stairs/Turn.vox b/resources/rules/Stairs/Turn.vox
new file mode 100644
index 0000000..d76e7a4
Binary files /dev/null and b/resources/rules/Stairs/Turn.vox differ
diff --git a/resources/rules/Stairs/Up.vox b/resources/rules/Stairs/Up.vox
new file mode 100644
index 0000000..14e7046
Binary files /dev/null and b/resources/rules/Stairs/Up.vox differ
diff --git a/resources/samples/BrickWall.png b/resources/samples/BrickWall.png
new file mode 100644
index 0000000..33205e5
Binary files /dev/null and b/resources/samples/BrickWall.png differ
diff --git a/resources/samples/Dungeon.png b/resources/samples/Dungeon.png
new file mode 100644
index 0000000..64ce90d
Binary files /dev/null and b/resources/samples/Dungeon.png differ
diff --git a/resources/samples/Flowers.png b/resources/samples/Flowers.png
new file mode 100644
index 0000000..20aef5c
Binary files /dev/null and b/resources/samples/Flowers.png differ
diff --git a/resources/samples/Maze.png b/resources/samples/Maze.png
new file mode 100644
index 0000000..c6c9bd4
Binary files /dev/null and b/resources/samples/Maze.png differ
diff --git a/resources/samples/Room.png b/resources/samples/Room.png
new file mode 100644
index 0000000..efbaf83
Binary files /dev/null and b/resources/samples/Room.png differ
diff --git a/resources/samples/Sewers.png b/resources/samples/Sewers.png
new file mode 100644
index 0000000..e266f3b
Binary files /dev/null and b/resources/samples/Sewers.png differ
diff --git a/resources/samples/Skew1.png b/resources/samples/Skew1.png
new file mode 100644
index 0000000..65f27ea
Binary files /dev/null and b/resources/samples/Skew1.png differ
diff --git a/resources/samples/Skew2.png b/resources/samples/Skew2.png
new file mode 100644
index 0000000..8c60b39
Binary files /dev/null and b/resources/samples/Skew2.png differ
diff --git a/resources/tilesets/Dungeon.xml b/resources/tilesets/Dungeon.xml
new file mode 100644
index 0000000..70485e6
--- /dev/null
+++ b/resources/tilesets/Dungeon.xml
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/tilesets/Dungeon/Door.vox b/resources/tilesets/Dungeon/Door.vox
new file mode 100644
index 0000000..cd98272
Binary files /dev/null and b/resources/tilesets/Dungeon/Door.vox differ
diff --git a/resources/tilesets/Dungeon/Empty.vox b/resources/tilesets/Dungeon/Empty.vox
new file mode 100644
index 0000000..4117aee
Binary files /dev/null and b/resources/tilesets/Dungeon/Empty.vox differ
diff --git a/resources/tilesets/Dungeon/In.vox b/resources/tilesets/Dungeon/In.vox
new file mode 100644
index 0000000..a34e071
Binary files /dev/null and b/resources/tilesets/Dungeon/In.vox differ
diff --git a/resources/tilesets/Dungeon/Line.vox b/resources/tilesets/Dungeon/Line.vox
new file mode 100644
index 0000000..591d46c
Binary files /dev/null and b/resources/tilesets/Dungeon/Line.vox differ
diff --git a/resources/tilesets/Dungeon/Out.vox b/resources/tilesets/Dungeon/Out.vox
new file mode 100644
index 0000000..f8ae868
Binary files /dev/null and b/resources/tilesets/Dungeon/Out.vox differ
diff --git a/resources/tilesets/Dungeon/Stone.vox b/resources/tilesets/Dungeon/Stone.vox
new file mode 100644
index 0000000..ccdb3d6
Binary files /dev/null and b/resources/tilesets/Dungeon/Stone.vox differ
diff --git a/resources/tilesets/Dungeon/T.vox b/resources/tilesets/Dungeon/T.vox
new file mode 100644
index 0000000..071dde6
Binary files /dev/null and b/resources/tilesets/Dungeon/T.vox differ
diff --git a/resources/tilesets/Dungeon/Turn.vox b/resources/tilesets/Dungeon/Turn.vox
new file mode 100644
index 0000000..e170d8b
Binary files /dev/null and b/resources/tilesets/Dungeon/Turn.vox differ
diff --git a/resources/tilesets/Dungeon/Wall.vox b/resources/tilesets/Dungeon/Wall.vox
new file mode 100644
index 0000000..54e649b
Binary files /dev/null and b/resources/tilesets/Dungeon/Wall.vox differ
diff --git a/resources/tilesets/Escher.xml b/resources/tilesets/Escher.xml
new file mode 100644
index 0000000..60c8c2a
--- /dev/null
+++ b/resources/tilesets/Escher.xml
@@ -0,0 +1,101 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/tilesets/Escher/Empty.vox b/resources/tilesets/Escher/Empty.vox
new file mode 100644
index 0000000..a4eb457
Binary files /dev/null and b/resources/tilesets/Escher/Empty.vox differ
diff --git a/resources/tilesets/Escher/Line.vox b/resources/tilesets/Escher/Line.vox
new file mode 100644
index 0000000..10627a1
Binary files /dev/null and b/resources/tilesets/Escher/Line.vox differ
diff --git a/resources/tilesets/Escher/Stairs.vox b/resources/tilesets/Escher/Stairs.vox
new file mode 100644
index 0000000..902ee48
Binary files /dev/null and b/resources/tilesets/Escher/Stairs.vox differ
diff --git a/resources/tilesets/Escher/Turn.vox b/resources/tilesets/Escher/Turn.vox
new file mode 100644
index 0000000..2d4dcb9
Binary files /dev/null and b/resources/tilesets/Escher/Turn.vox differ
diff --git a/resources/tilesets/Escher/X.vox b/resources/tilesets/Escher/X.vox
new file mode 100644
index 0000000..d387d1a
Binary files /dev/null and b/resources/tilesets/Escher/X.vox differ
diff --git a/resources/tilesets/EscherSurface.xml b/resources/tilesets/EscherSurface.xml
new file mode 100644
index 0000000..9adc4ba
--- /dev/null
+++ b/resources/tilesets/EscherSurface.xml
@@ -0,0 +1,141 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/tilesets/EscherSurface/Empty.vox b/resources/tilesets/EscherSurface/Empty.vox
new file mode 100644
index 0000000..adbb1dd
Binary files /dev/null and b/resources/tilesets/EscherSurface/Empty.vox differ
diff --git a/resources/tilesets/EscherSurface/Line.vox b/resources/tilesets/EscherSurface/Line.vox
new file mode 100644
index 0000000..4e863b3
Binary files /dev/null and b/resources/tilesets/EscherSurface/Line.vox differ
diff --git a/resources/tilesets/EscherSurface/Stairs.vox b/resources/tilesets/EscherSurface/Stairs.vox
new file mode 100644
index 0000000..c6e2bc7
Binary files /dev/null and b/resources/tilesets/EscherSurface/Stairs.vox differ
diff --git a/resources/tilesets/EscherSurface/Turn.vox b/resources/tilesets/EscherSurface/Turn.vox
new file mode 100644
index 0000000..51ec155
Binary files /dev/null and b/resources/tilesets/EscherSurface/Turn.vox differ
diff --git a/resources/tilesets/EscherSurface/X.vox b/resources/tilesets/EscherSurface/X.vox
new file mode 100644
index 0000000..d070233
Binary files /dev/null and b/resources/tilesets/EscherSurface/X.vox differ
diff --git a/resources/tilesets/Knots2D.xml b/resources/tilesets/Knots2D.xml
new file mode 100644
index 0000000..4b133c1
--- /dev/null
+++ b/resources/tilesets/Knots2D.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/tilesets/Knots2D/Cross.vox b/resources/tilesets/Knots2D/Cross.vox
new file mode 100644
index 0000000..7630f45
Binary files /dev/null and b/resources/tilesets/Knots2D/Cross.vox differ
diff --git a/resources/tilesets/Knots2D/Empty.vox b/resources/tilesets/Knots2D/Empty.vox
new file mode 100644
index 0000000..972a4b7
Binary files /dev/null and b/resources/tilesets/Knots2D/Empty.vox differ
diff --git a/resources/tilesets/Knots2D/Line.vox b/resources/tilesets/Knots2D/Line.vox
new file mode 100644
index 0000000..b5b1f37
Binary files /dev/null and b/resources/tilesets/Knots2D/Line.vox differ
diff --git a/resources/tilesets/Knots2D/Turn.vox b/resources/tilesets/Knots2D/Turn.vox
new file mode 100644
index 0000000..52ca58a
Binary files /dev/null and b/resources/tilesets/Knots2D/Turn.vox differ
diff --git a/resources/tilesets/Knots3D.xml b/resources/tilesets/Knots3D.xml
new file mode 100644
index 0000000..dd14df7
--- /dev/null
+++ b/resources/tilesets/Knots3D.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/tilesets/Knots3D/3/Empty.vox b/resources/tilesets/Knots3D/3/Empty.vox
new file mode 100644
index 0000000..466a609
Binary files /dev/null and b/resources/tilesets/Knots3D/3/Empty.vox differ
diff --git a/resources/tilesets/Knots3D/3/Line.vox b/resources/tilesets/Knots3D/3/Line.vox
new file mode 100644
index 0000000..9352312
Binary files /dev/null and b/resources/tilesets/Knots3D/3/Line.vox differ
diff --git a/resources/tilesets/Knots3D/3/Turn.vox b/resources/tilesets/Knots3D/3/Turn.vox
new file mode 100644
index 0000000..a12b996
Binary files /dev/null and b/resources/tilesets/Knots3D/3/Turn.vox differ
diff --git a/resources/tilesets/Knots3D/4/Empty.vox b/resources/tilesets/Knots3D/4/Empty.vox
new file mode 100644
index 0000000..2c3e032
Binary files /dev/null and b/resources/tilesets/Knots3D/4/Empty.vox differ
diff --git a/resources/tilesets/Knots3D/4/Line.vox b/resources/tilesets/Knots3D/4/Line.vox
new file mode 100644
index 0000000..761e637
Binary files /dev/null and b/resources/tilesets/Knots3D/4/Line.vox differ
diff --git a/resources/tilesets/Knots3D/4/Turn.vox b/resources/tilesets/Knots3D/4/Turn.vox
new file mode 100644
index 0000000..988317e
Binary files /dev/null and b/resources/tilesets/Knots3D/4/Turn.vox differ
diff --git a/resources/tilesets/Knots3D/5/Empty.vox b/resources/tilesets/Knots3D/5/Empty.vox
new file mode 100644
index 0000000..b789103
Binary files /dev/null and b/resources/tilesets/Knots3D/5/Empty.vox differ
diff --git a/resources/tilesets/Knots3D/5/Line.vox b/resources/tilesets/Knots3D/5/Line.vox
new file mode 100644
index 0000000..0973584
Binary files /dev/null and b/resources/tilesets/Knots3D/5/Line.vox differ
diff --git a/resources/tilesets/Knots3D/5/Turn.vox b/resources/tilesets/Knots3D/5/Turn.vox
new file mode 100644
index 0000000..48981ed
Binary files /dev/null and b/resources/tilesets/Knots3D/5/Turn.vox differ
diff --git a/resources/tilesets/Knots3D/Tubes/Empty.vox b/resources/tilesets/Knots3D/Tubes/Empty.vox
new file mode 100644
index 0000000..adbb1dd
Binary files /dev/null and b/resources/tilesets/Knots3D/Tubes/Empty.vox differ
diff --git a/resources/tilesets/Knots3D/Tubes/Line.vox b/resources/tilesets/Knots3D/Tubes/Line.vox
new file mode 100644
index 0000000..a85b893
Binary files /dev/null and b/resources/tilesets/Knots3D/Tubes/Line.vox differ
diff --git a/resources/tilesets/Knots3D/Tubes/Turn.vox b/resources/tilesets/Knots3D/Tubes/Turn.vox
new file mode 100644
index 0000000..986a154
Binary files /dev/null and b/resources/tilesets/Knots3D/Tubes/Turn.vox differ
diff --git a/resources/tilesets/MarchingHills.xml b/resources/tilesets/MarchingHills.xml
new file mode 100644
index 0000000..04cff5a
--- /dev/null
+++ b/resources/tilesets/MarchingHills.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ - new!
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/tilesets/MarchingHills/Empty.vox b/resources/tilesets/MarchingHills/Empty.vox
new file mode 100644
index 0000000..2d4f345
Binary files /dev/null and b/resources/tilesets/MarchingHills/Empty.vox differ
diff --git a/resources/tilesets/MarchingHills/In.vox b/resources/tilesets/MarchingHills/In.vox
new file mode 100644
index 0000000..2b2b6f6
Binary files /dev/null and b/resources/tilesets/MarchingHills/In.vox differ
diff --git a/resources/tilesets/MarchingHills/Out.vox b/resources/tilesets/MarchingHills/Out.vox
new file mode 100644
index 0000000..34a695d
Binary files /dev/null and b/resources/tilesets/MarchingHills/Out.vox differ
diff --git a/resources/tilesets/MarchingHills/Stone.vox b/resources/tilesets/MarchingHills/Stone.vox
new file mode 100644
index 0000000..73e06c4
Binary files /dev/null and b/resources/tilesets/MarchingHills/Stone.vox differ
diff --git a/resources/tilesets/MarchingHills/Wall.vox b/resources/tilesets/MarchingHills/Wall.vox
new file mode 100644
index 0000000..05523a4
Binary files /dev/null and b/resources/tilesets/MarchingHills/Wall.vox differ
diff --git a/resources/tilesets/OrientedEscher.xml b/resources/tilesets/OrientedEscher.xml
new file mode 100644
index 0000000..0f4cc1d
--- /dev/null
+++ b/resources/tilesets/OrientedEscher.xml
@@ -0,0 +1,95 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/tilesets/OrientedEscher/Cube.vox b/resources/tilesets/OrientedEscher/Cube.vox
new file mode 100644
index 0000000..d369ec5
Binary files /dev/null and b/resources/tilesets/OrientedEscher/Cube.vox differ
diff --git a/resources/tilesets/OrientedEscher/Empty.vox b/resources/tilesets/OrientedEscher/Empty.vox
new file mode 100644
index 0000000..a4eb457
Binary files /dev/null and b/resources/tilesets/OrientedEscher/Empty.vox differ
diff --git a/resources/tilesets/OrientedEscher/Stairs.vox b/resources/tilesets/OrientedEscher/Stairs.vox
new file mode 100644
index 0000000..0a55412
Binary files /dev/null and b/resources/tilesets/OrientedEscher/Stairs.vox differ
diff --git a/resources/tilesets/OrientedEscher/T.vox b/resources/tilesets/OrientedEscher/T.vox
new file mode 100644
index 0000000..47097b5
Binary files /dev/null and b/resources/tilesets/OrientedEscher/T.vox differ
diff --git a/resources/tilesets/OrientedEscher/Turn.vox b/resources/tilesets/OrientedEscher/Turn.vox
new file mode 100644
index 0000000..8729dcb
Binary files /dev/null and b/resources/tilesets/OrientedEscher/Turn.vox differ
diff --git a/resources/tilesets/OrientedStairs.xml b/resources/tilesets/OrientedStairs.xml
new file mode 100644
index 0000000..5137a5b
--- /dev/null
+++ b/resources/tilesets/OrientedStairs.xml
@@ -0,0 +1,94 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/tilesets/OrientedStairs/Cube.vox b/resources/tilesets/OrientedStairs/Cube.vox
new file mode 100644
index 0000000..abeb79c
Binary files /dev/null and b/resources/tilesets/OrientedStairs/Cube.vox differ
diff --git a/resources/tilesets/OrientedStairs/Empty.vox b/resources/tilesets/OrientedStairs/Empty.vox
new file mode 100644
index 0000000..a4eb457
Binary files /dev/null and b/resources/tilesets/OrientedStairs/Empty.vox differ
diff --git a/resources/tilesets/OrientedStairs/StairsB.vox b/resources/tilesets/OrientedStairs/StairsB.vox
new file mode 100644
index 0000000..589d449
Binary files /dev/null and b/resources/tilesets/OrientedStairs/StairsB.vox differ
diff --git a/resources/tilesets/OrientedStairs/StairsT.vox b/resources/tilesets/OrientedStairs/StairsT.vox
new file mode 100644
index 0000000..597a158
Binary files /dev/null and b/resources/tilesets/OrientedStairs/StairsT.vox differ
diff --git a/resources/tilesets/OrientedStairs/T1.vox b/resources/tilesets/OrientedStairs/T1.vox
new file mode 100644
index 0000000..68e1f69
Binary files /dev/null and b/resources/tilesets/OrientedStairs/T1.vox differ
diff --git a/resources/tilesets/OrientedStairs/T2.vox b/resources/tilesets/OrientedStairs/T2.vox
new file mode 100644
index 0000000..8d5e56c
Binary files /dev/null and b/resources/tilesets/OrientedStairs/T2.vox differ
diff --git a/resources/tilesets/OrientedStairs/Turn.vox b/resources/tilesets/OrientedStairs/Turn.vox
new file mode 100644
index 0000000..01fe0b5
Binary files /dev/null and b/resources/tilesets/OrientedStairs/Turn.vox differ
diff --git a/resources/tilesets/Partition.xml b/resources/tilesets/Partition.xml
new file mode 100644
index 0000000..8a21fce
--- /dev/null
+++ b/resources/tilesets/Partition.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/tilesets/Partition/I.vox b/resources/tilesets/Partition/I.vox
new file mode 100644
index 0000000..c8c3936
Binary files /dev/null and b/resources/tilesets/Partition/I.vox differ
diff --git a/resources/tilesets/Partition/Nothing.vox b/resources/tilesets/Partition/Nothing.vox
new file mode 100644
index 0000000..2177cc8
Binary files /dev/null and b/resources/tilesets/Partition/Nothing.vox differ
diff --git a/resources/tilesets/Partition/T.vox b/resources/tilesets/Partition/T.vox
new file mode 100644
index 0000000..1394a95
Binary files /dev/null and b/resources/tilesets/Partition/T.vox differ
diff --git a/resources/tilesets/PartitionedEdges.xml b/resources/tilesets/PartitionedEdges.xml
new file mode 100644
index 0000000..57b10cb
--- /dev/null
+++ b/resources/tilesets/PartitionedEdges.xml
@@ -0,0 +1,215 @@
+
+
+ 1
+ 2
+ 4
+ 1
+ 4
+ 4
+ 4
+ 1
+ 21
+
+ 2
+ 2
+ 2
+ 2
+ 4
+ 4
+ 4
+ 20
+
+ 1
+ 2
+ 4
+ 1
+ 8
+
+ 49
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/tilesets/PartitionedEdges/00.vox b/resources/tilesets/PartitionedEdges/00.vox
new file mode 100644
index 0000000..dbcc272
Binary files /dev/null and b/resources/tilesets/PartitionedEdges/00.vox differ
diff --git a/resources/tilesets/PartitionedEdges/01.vox b/resources/tilesets/PartitionedEdges/01.vox
new file mode 100644
index 0000000..b906daf
Binary files /dev/null and b/resources/tilesets/PartitionedEdges/01.vox differ
diff --git a/resources/tilesets/PartitionedEdges/10.vox b/resources/tilesets/PartitionedEdges/10.vox
new file mode 100644
index 0000000..7a2ac9d
Binary files /dev/null and b/resources/tilesets/PartitionedEdges/10.vox differ
diff --git a/resources/tilesets/PartitionedEdges/11.vox b/resources/tilesets/PartitionedEdges/11.vox
new file mode 100644
index 0000000..cc7b45a
Binary files /dev/null and b/resources/tilesets/PartitionedEdges/11.vox differ
diff --git a/resources/tilesets/PartitionedEdges/Bridge.vox b/resources/tilesets/PartitionedEdges/Bridge.vox
new file mode 100644
index 0000000..715de3c
Binary files /dev/null and b/resources/tilesets/PartitionedEdges/Bridge.vox differ
diff --git a/resources/tilesets/PartitionedEdges/ContactDown.vox b/resources/tilesets/PartitionedEdges/ContactDown.vox
new file mode 100644
index 0000000..96b1f33
Binary files /dev/null and b/resources/tilesets/PartitionedEdges/ContactDown.vox differ
diff --git a/resources/tilesets/PartitionedEdges/ContactUp.vox b/resources/tilesets/PartitionedEdges/ContactUp.vox
new file mode 100644
index 0000000..185f4ff
Binary files /dev/null and b/resources/tilesets/PartitionedEdges/ContactUp.vox differ
diff --git a/resources/tilesets/PartitionedEdges/Cross.vox b/resources/tilesets/PartitionedEdges/Cross.vox
new file mode 100644
index 0000000..bccf166
Binary files /dev/null and b/resources/tilesets/PartitionedEdges/Cross.vox differ
diff --git a/resources/tilesets/PartitionedEdges/Down.vox b/resources/tilesets/PartitionedEdges/Down.vox
new file mode 100644
index 0000000..9abb0c8
Binary files /dev/null and b/resources/tilesets/PartitionedEdges/Down.vox differ
diff --git a/resources/tilesets/PartitionedEdges/Empty.vox b/resources/tilesets/PartitionedEdges/Empty.vox
new file mode 100644
index 0000000..a4af27d
Binary files /dev/null and b/resources/tilesets/PartitionedEdges/Empty.vox differ
diff --git a/resources/tilesets/PartitionedEdges/I.vox b/resources/tilesets/PartitionedEdges/I.vox
new file mode 100644
index 0000000..1560615
Binary files /dev/null and b/resources/tilesets/PartitionedEdges/I.vox differ
diff --git a/resources/tilesets/PartitionedEdges/Line.vox b/resources/tilesets/PartitionedEdges/Line.vox
new file mode 100644
index 0000000..2999974
Binary files /dev/null and b/resources/tilesets/PartitionedEdges/Line.vox differ
diff --git a/resources/tilesets/PartitionedEdges/Nothing.vox b/resources/tilesets/PartitionedEdges/Nothing.vox
new file mode 100644
index 0000000..0745d7e
Binary files /dev/null and b/resources/tilesets/PartitionedEdges/Nothing.vox differ
diff --git a/resources/tilesets/PartitionedEdges/T.vox b/resources/tilesets/PartitionedEdges/T.vox
new file mode 100644
index 0000000..f4e37b4
Binary files /dev/null and b/resources/tilesets/PartitionedEdges/T.vox differ
diff --git a/resources/tilesets/PartitionedEdges/Turn.vox b/resources/tilesets/PartitionedEdges/Turn.vox
new file mode 100644
index 0000000..214ae75
Binary files /dev/null and b/resources/tilesets/PartitionedEdges/Turn.vox differ
diff --git a/resources/tilesets/PartitionedEdges/Up0.vox b/resources/tilesets/PartitionedEdges/Up0.vox
new file mode 100644
index 0000000..304061b
Binary files /dev/null and b/resources/tilesets/PartitionedEdges/Up0.vox differ
diff --git a/resources/tilesets/PartitionedEdges/Up1.vox b/resources/tilesets/PartitionedEdges/Up1.vox
new file mode 100644
index 0000000..450f0f8
Binary files /dev/null and b/resources/tilesets/PartitionedEdges/Up1.vox differ
diff --git a/resources/tilesets/PartitionedEdges/X.vox b/resources/tilesets/PartitionedEdges/X.vox
new file mode 100644
index 0000000..b263256
Binary files /dev/null and b/resources/tilesets/PartitionedEdges/X.vox differ
diff --git a/resources/tilesets/PartitionedEdges/o.vox b/resources/tilesets/PartitionedEdges/o.vox
new file mode 100644
index 0000000..32a6d3d
Binary files /dev/null and b/resources/tilesets/PartitionedEdges/o.vox differ
diff --git a/resources/tilesets/Paths.xml b/resources/tilesets/Paths.xml
new file mode 100644
index 0000000..47d1780
--- /dev/null
+++ b/resources/tilesets/Paths.xml
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ~
+
+
+
+
+
+
+
+
+
+
+
+
+ ~
+
+
+
+
+
+
+
+
+
+
+
+
+ ~
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ~
+
+
diff --git a/resources/tilesets/Paths/Down.vox b/resources/tilesets/Paths/Down.vox
new file mode 100644
index 0000000..70d6e87
Binary files /dev/null and b/resources/tilesets/Paths/Down.vox differ
diff --git a/resources/tilesets/Paths/Empty.vox b/resources/tilesets/Paths/Empty.vox
new file mode 100644
index 0000000..44cf9d2
Binary files /dev/null and b/resources/tilesets/Paths/Empty.vox differ
diff --git a/resources/tilesets/Paths/Line.vox b/resources/tilesets/Paths/Line.vox
new file mode 100644
index 0000000..f59d0aa
Binary files /dev/null and b/resources/tilesets/Paths/Line.vox differ
diff --git a/resources/tilesets/Paths/Turn.vox b/resources/tilesets/Paths/Turn.vox
new file mode 100644
index 0000000..a267372
Binary files /dev/null and b/resources/tilesets/Paths/Turn.vox differ
diff --git a/resources/tilesets/Paths/Up.vox b/resources/tilesets/Paths/Up.vox
new file mode 100644
index 0000000..6e60a63
Binary files /dev/null and b/resources/tilesets/Paths/Up.vox differ
diff --git a/resources/tilesets/Paths/X.vox b/resources/tilesets/Paths/X.vox
new file mode 100644
index 0000000..d65c4d5
Binary files /dev/null and b/resources/tilesets/Paths/X.vox differ
diff --git a/resources/tilesets/Surface.xml b/resources/tilesets/Surface.xml
new file mode 100644
index 0000000..1f99707
--- /dev/null
+++ b/resources/tilesets/Surface.xml
@@ -0,0 +1,62 @@
+
+
+ 1
+ 3
+ 12
+ 8
+ 24
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/tilesets/Surface/Angle.vox b/resources/tilesets/Surface/Angle.vox
new file mode 100644
index 0000000..b4eb6be
Binary files /dev/null and b/resources/tilesets/Surface/Angle.vox differ
diff --git a/resources/tilesets/Surface/Concave.vox b/resources/tilesets/Surface/Concave.vox
new file mode 100644
index 0000000..4208df7
Binary files /dev/null and b/resources/tilesets/Surface/Concave.vox differ
diff --git a/resources/tilesets/Surface/Corner.vox b/resources/tilesets/Surface/Corner.vox
new file mode 100644
index 0000000..ac7ee8a
Binary files /dev/null and b/resources/tilesets/Surface/Corner.vox differ
diff --git a/resources/tilesets/Surface/Empty.vox b/resources/tilesets/Surface/Empty.vox
new file mode 100644
index 0000000..13e3b31
Binary files /dev/null and b/resources/tilesets/Surface/Empty.vox differ
diff --git a/resources/tilesets/Surface/Plane.vox b/resources/tilesets/Surface/Plane.vox
new file mode 100644
index 0000000..6d8bd5b
Binary files /dev/null and b/resources/tilesets/Surface/Plane.vox differ
diff --git a/source/AllNode.cs b/source/AllNode.cs
new file mode 100644
index 0000000..399d87f
--- /dev/null
+++ b/source/AllNode.cs
@@ -0,0 +1,108 @@
+// Copyright (C) 2022 Maxim Gumin, The MIT License (MIT)
+
+using System;
+using System.Linq;
+using System.Xml.Linq;
+using System.Collections.Generic;
+
+class AllNode : RuleNode
+{
+ override protected bool Load(XElement xelem, bool[] parentSymmetry, Grid grid)
+ {
+ if (!base.Load(xelem, parentSymmetry, grid)) return false;
+ matches = new List<(int, int, int, int)>();
+ matchMask = AH.Array2D(rules.Length, grid.state.Length, false);
+ return true;
+ }
+
+ void Fit(int r, int x, int y, int z, bool[] newstate, int MX, int MY)
+ {
+ Rule rule = rules[r];
+ for (int dz = 0; dz < rule.OMZ; dz++) for (int dy = 0; dy < rule.OMY; dy++) for (int dx = 0; dx < rule.OMX; dx++)
+ {
+ byte value = rule.output[dx + dy * rule.OMX + dz * rule.OMX * rule.OMY];
+ if (value != 0xff && newstate[x + dx + (y + dy) * MX + (z + dz) * MX * MY]) return;
+ }
+ last[r] = true;
+ for (int dz = 0; dz < rule.OMZ; dz++) for (int dy = 0; dy < rule.OMY; dy++) for (int dx = 0; dx < rule.OMX; dx++)
+ {
+ byte newvalue = rule.output[dx + dy * rule.OMX + dz * rule.OMX * rule.OMY];
+ if (newvalue != 0xff)
+ {
+ int sx = x + dx, sy = y + dy, sz = z + dz;
+ int i = sx + sy * MX + sz * MX * MY;
+ newstate[i] = true;
+ grid.state[i] = newvalue;
+ ip.changes.Add((sx, sy, sz));
+ }
+ }
+ }
+
+ override public bool Go()
+ {
+ if (!base.Go()) return false;
+ lastMatchedTurn = ip.counter;
+
+ if (trajectory != null)
+ {
+ if (counter >= trajectory.Length) return false;
+ Array.Copy(trajectory[counter], grid.state, grid.state.Length);
+ counter++;
+ return true;
+ }
+
+ if (matchCount == 0) return false;
+
+ int MX = grid.MX, MY = grid.MY;
+ if (potentials != null)
+ {
+ double firstHeuristic = 0;
+ bool firstHeuristicComputed = false;
+
+ List<(int, double)> list = new();
+ for (int m = 0; m < matchCount; m++)
+ {
+ var (r, x, y, z) = matches[m];
+ double? heuristic = Field.DeltaPointwise(grid.state, rules[r], x, y, z, fields, potentials, grid.MX, grid.MY);
+ if (heuristic != null)
+ {
+ double h = (double)heuristic;
+ if (!firstHeuristicComputed)
+ {
+ firstHeuristic = h;
+ firstHeuristicComputed = true;
+ }
+ double u = ip.random.NextDouble();
+ list.Add((m, temperature > 0 ? Math.Pow(u, Math.Exp((h - firstHeuristic) / temperature)) : -h + 0.001 * u));
+ }
+ }
+ (int, double)[] ordered = list.OrderBy(pair => -pair.Item2).ToArray();
+ for (int k = 0; k < ordered.Length; k++)
+ {
+ var (r, x, y, z) = matches[ordered[k].Item1];
+ matchMask[r][x + y * MX + z * MX * MY] = false;
+ Fit(r, x, y, z, grid.mask, MX, MY);
+ }
+ }
+ else
+ {
+ int[] shuffle = new int[matchCount];
+ shuffle.Shuffle(ip.random);
+ for (int k = 0; k < shuffle.Length; k++)
+ {
+ var (r, x, y, z) = matches[shuffle[k]];
+ matchMask[r][x + y * MX + z * MX * MY] = false;
+ Fit(r, x, y, z, grid.mask, MX, MY);
+ }
+ }
+
+ for (int n = ip.first[lastMatchedTurn]; n < ip.changes.Count; n++)
+ {
+ var (x, y, z) = ip.changes[n];
+ grid.mask[x + y * MX + z * MX * MY] = false;
+ }
+ counter++;
+ matchCount = 0;
+ return true;
+ }
+}
diff --git a/source/ArrayHelper.cs b/source/ArrayHelper.cs
new file mode 100644
index 0000000..a1dffae
--- /dev/null
+++ b/source/ArrayHelper.cs
@@ -0,0 +1,85 @@
+// Copyright (C) 2022 Maxim Gumin, The MIT License (MIT)
+
+using System;
+
+static class AH
+{
+ public static T[][][] Array3D(int MX, int MY, int MZ, T value)
+ {
+ T[][][] result = new T[MX][][];
+ for (int x = 0; x < result.Length; x++)
+ {
+ result[x] = new T[MY][];
+ T[][] resultx = result[x];
+ for (int y = 0; y < resultx.Length; y++)
+ {
+ resultx[y] = new T[MZ];
+ T[] resultxy = resultx[y];
+ for (int z = 0; z < resultxy.Length; z++) resultxy[z] = value;
+ }
+ }
+ return result;
+ }
+
+ public static T[][] Array2D(int MX, int MY, Func f)
+ {
+ T[][] result = new T[MX][];
+ for (int x = 0; x < result.Length; x++)
+ {
+ result[x] = new T[MY];
+ T[] resultx = result[x];
+ for (int y = 0; y < resultx.Length; y++) resultx[y] = f(x, y);
+ }
+ return result;
+ }
+ public static T[][] Array2D(int MX, int MY, T value)
+ {
+ T[][] result = new T[MX][];
+ for (int x = 0; x < result.Length; x++)
+ {
+ result[x] = new T[MY];
+ T[] resultx = result[x];
+ for (int y = 0; y < resultx.Length; y++) resultx[y] = value;
+ }
+ return result;
+ }
+
+ public static T[] Array1D(int length, Func f)
+ {
+ T[] result = new T[length];
+ for (int i = 0; i < result.Length; i++) result[i] = f(i);
+ return result;
+ }
+
+ public static T[] Array1D(int length, T value)
+ {
+ T[] result = new T[length];
+ for (int i = 0; i < result.Length; i++) result[i] = value;
+ return result;
+ }
+
+ public static T[] FlatArray3D(int MX, int MY, int MZ, Func f)
+ {
+ T[] result = new T[MX * MY * MZ];
+ for (int z = 0; z < MZ; z++) for (int y = 0; y < MY; y++) for (int x = 0; x < MX; x++) result[z * MX * MY + y * MX + x] = f(x, y, z);
+ return result;
+ }
+
+ public static void Set2D(this T[][] a, T value)
+ {
+ for (int y = 0; y < a.Length; y++)
+ {
+ T[] ay = a[y];
+ for (int x = 0; x < ay.Length; x++) ay[x] = value;
+ }
+ }
+
+ public static void CopyFrom2D(this T[][] a, T[][] b) { for (int j = 0; j < a.Length; j++) Array.Copy(b[j], a[j], b[j].Length); }
+
+ public static bool Same(byte[] t1, byte[] t2)
+ {
+ if (t1.Length != t2.Length) return false;
+ for (int i = 0; i < t1.Length; i++) if (t1[i] != t2[i]) return false;
+ return true;
+ }
+}
diff --git a/source/ConvChain.cs b/source/ConvChain.cs
new file mode 100644
index 0000000..3222a4c
--- /dev/null
+++ b/source/ConvChain.cs
@@ -0,0 +1,136 @@
+// Copyright (C) 2022 Maxim Gumin, The MIT License (MIT)
+
+using System;
+using System.Xml.Linq;
+
+class ConvChainNode : Node
+{
+ int N;
+ double temperature;
+ double[] weights;
+
+ public byte c0, c1;
+ bool[] substrate;
+ byte substrateColor;
+ int counter, steps;
+
+ //public string name;
+ public bool[] sample;
+ public int SMX, SMY;
+
+ override protected bool Load(XElement xelem, bool[] symmetry, Grid grid)
+ {
+ if (grid.MZ != 1)
+ {
+ Interpreter.WriteLine("convchain currently works only for 2d");
+ return false;
+ }
+
+ string name = xelem.Get("sample");
+ string filename = $"resources/samples/{name}.png";
+ int[] bitmap;
+ (bitmap, SMX, SMY, _) = Graphics.LoadBitmap(filename);
+ if (bitmap == null)
+ {
+ Interpreter.WriteLine($"couldn't load ConvChain sample {filename}");
+ return false;
+ }
+ /*bool[]*/ sample = new bool[bitmap.Length];
+ for (int i = 0; i < sample.Length; i++) sample[i] = bitmap[i] == -1;
+
+ N = xelem.Get("n", 3);
+ steps = xelem.Get("steps", -1);
+ temperature = xelem.Get("temperature", 1.0);
+ c0 = grid.values[xelem.Get("black")];
+ c1 = grid.values[xelem.Get("white")];
+ substrateColor = grid.values[xelem.Get("on")];
+
+ substrate = new bool[grid.state.Length];
+
+ static bool[] pattern(int N, Func f)
+ {
+ bool[] result = new bool[N * N];
+ for (int y = 0; y < N; y++) for (int x = 0; x < N; x++) result[x + y * N] = f(x, y);
+ return result;
+ };
+ static bool[] rotate(int N, bool[] p) => pattern(N, (x, y) => p[N - 1 - y + x * N]);
+ static bool[] reflect(int N, bool[] p) => pattern(N, (x, y) => p[N - 1 - x + y * N]);
+
+ weights = new double[1 << (N * N)];
+ for (int y = 0; y < SMY; y++) for (int x = 0; x < SMX; x++)
+ {
+ bool[] p = pattern(N, (dx, dy) => sample[(x + dx) % SMX + (y + dy) % SMY * SMX]);
+ var symmetries = SymmetryHelper.SquareSymmetries(p, q => rotate(N, q), q => reflect(N, q), (q1, q2) => false);
+ foreach (bool[] q in symmetries) weights[q.Index()] += 1;
+ }
+
+ for (int k = 0; k < weights.Length; k++) if (weights[k] <= 0) weights[k] = 0.1;
+ return true;
+ }
+
+ void Toggle(byte[] state, int i) => state[i] = state[i] == c0 ? c1 : c0;
+ override public bool Go()
+ {
+ if (steps > 0 && counter >= steps) return false;
+
+ int MX = grid.MX, MY = grid.MY;
+ byte[] state = grid.state;
+
+ if (counter == 0)
+ {
+ bool anySubstrate = false;
+ for (int i = 0; i < substrate.Length; i++) if (state[i] == substrateColor)
+ {
+ state[i] = ip.random.Next(2) == 0 ? c0 : c1;
+ substrate[i] = true;
+ anySubstrate = true;
+ }
+ counter++;
+ return anySubstrate;
+ }
+
+ for (int k = 0; k < state.Length; k++)
+ {
+ int r = ip.random.Next(state.Length);
+ if (!substrate[r]) continue;
+
+ int x = r % MX, y = r / MX;
+ double q = 1;
+
+ for (int sy = y - N + 1; sy <= y + N - 1; sy++) for (int sx = x - N + 1; sx <= x + N - 1; sx++)
+ {
+ int ind = 0, difference = 0;
+ for (int dy = 0; dy < N; dy++) for (int dx = 0; dx < N; dx++)
+ {
+ int X = sx + dx;
+ if (X < 0) X += MX;
+ else if (X >= MX) X -= MX;
+
+ int Y = sy + dy;
+ if (Y < 0) Y += MY;
+ else if (Y >= MY) Y -= MY;
+
+ bool value = state[X + Y * MX] == c1;
+ int power = 1 << (dy * N + dx);
+ ind += value ? power : 0;
+ if (X == x && Y == y) difference = value ? power : -power;
+ }
+
+ q *= weights[ind - difference] / weights[ind];
+ }
+
+ if (q >= 1) { Toggle(state, r); continue; }
+ if (temperature != 1) q = Math.Pow(q, 1.0 / temperature);
+ if (q > ip.random.NextDouble()) Toggle(state, r);
+ }
+
+ counter++;
+ return true;
+ }
+
+ override public void Reset()
+ {
+ for (int i = 0; i < substrate.Length; i++) substrate[i] = false;
+ counter = 0;
+ }
+}
diff --git a/source/Convolution.cs b/source/Convolution.cs
new file mode 100644
index 0000000..0e1b18a
--- /dev/null
+++ b/source/Convolution.cs
@@ -0,0 +1,170 @@
+// Copyright (C) 2022 Maxim Gumin, The MIT License (MIT)
+
+using System.Linq;
+using System.Xml.Linq;
+using System.Collections.Generic;
+
+class ConvolutionNode : Node
+{
+ ConvolutionRule[] rules;
+ int[] kernel;
+ bool periodic;
+ public int counter, steps;
+
+ int[][] sumfield;
+
+ static readonly Dictionary kernels2d = new()
+ {
+ ["VonNeumann"] = new int[9] { 0, 1, 0, 1, 0, 1, 0, 1, 0 },
+ ["Moore"] = new int[9] { 1, 1, 1, 1, 0, 1, 1, 1, 1 },
+ };
+ static readonly Dictionary kernels3d = new()
+ {
+ ["VonNeumann"] = new int[27] { 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 },
+ ["NoCorners"] = new int[27] { 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0 },
+ };
+
+ override protected bool Load(XElement xelem, bool[] parentSymmetry, Grid grid)
+ {
+ string neighborhood = xelem.Get("neighborhood");
+ kernel = grid.MZ == 1 ? kernels2d[neighborhood] : kernels3d[neighborhood];
+
+ rules = xelem.Elements("rule").Select(x => new ConvolutionRule(x, grid)).ToArray();
+ steps = xelem.Get("steps", -1);
+ periodic = xelem.Get("periodic", false);
+
+ sumfield = AH.Array2D(grid.state.Length, grid.C, 0);
+ return true;
+ }
+
+ override public void Reset()
+ {
+ counter = 0;
+ }
+
+ override public bool Go()
+ {
+ if (steps > 0 && counter >= steps) return false;
+ int MX = grid.MX, MY = grid.MY, MZ = grid.MZ;
+
+ sumfield.Set2D(0);
+ if (MZ == 1)
+ {
+ for (int y = 0; y < MY; y++) for (int x = 0; x < MX; x++)
+ {
+ int[] sums = sumfield[x + y * MX];
+ for (int dy = -1; dy <= 1; dy++) for (int dx = -1; dx <= 1; dx++)
+ {
+ int sx = x + dx;
+ int sy = y + dy;
+
+ if (periodic)
+ {
+ if (sx < 0) sx += MX;
+ else if (sx >= MX) sx -= MX;
+ if (sy < 0) sy += MY;
+ else if (sy >= MY) sy -= MY;
+ }
+ else if (sx < 0 || sy < 0 || sx >= MX || sy >= MY) continue;
+
+ sums[grid.state[sx + sy * MX]] += kernel[dx + 1 + (dy + 1) * 3];
+ }
+ }
+ }
+ else
+ {
+ for (int z = 0; z < MZ; z++) for (int y = 0; y < MY; y++) for (int x = 0; x < MX; x++)
+ {
+ int[] sums = sumfield[x + y * MX + z * MX * MY];
+ for (int dz = -1; dz <= 1; dz++) for (int dy = -1; dy <= 1; dy++) for (int dx = -1; dx <= 1; dx++)
+ {
+ int sx = x + dx;
+ int sy = y + dy;
+ int sz = z + dz;
+
+ if (periodic)
+ {
+ if (sx < 0) sx += MX;
+ else if (sx >= MX) sx -= MX;
+ if (sy < 0) sy += MY;
+ else if (sy >= MY) sy -= MY;
+ if (sz < 0) sz += MZ;
+ else if (sz >= MZ) sz -= MZ;
+ }
+ else if (sx < 0 || sy < 0 || sz < 0 || sx >= MX || sy >= MY || sz >= MZ) continue;
+
+ sums[grid.state[sx + sy * MX + sz * MX * MY]] += kernel[dx + 1 + (dy + 1) * 3 + (dz + 1) * 9];
+ }
+ }
+ }
+
+ bool change = false;
+ for (int i = 0; i < sumfield.Length; i++)
+ {
+ int[] sums = sumfield[i];
+ byte input = grid.state[i];
+ for (int r = 0; r < rules.Length; r++)
+ {
+ ConvolutionRule rule = rules[r];
+ if (input == rule.input && rule.output != grid.state[i] && (rule.p == 1.0 || ip.random.Next() < rule.p * int.MaxValue))
+ {
+ bool success = true;
+ if (rule.sums != null)
+ {
+ int sum = 0;
+ for (int c = 0; c < rule.values.Length; c++) sum += sums[rule.values[c]];
+ success = rule.sums[sum];
+ }
+ if (success)
+ {
+ grid.state[i] = rule.output;
+ change = true;
+ break;
+ }
+ }
+ }
+ }
+
+ counter++;
+ return change;
+ }
+
+ class ConvolutionRule
+ {
+ public byte input, output;
+ public byte[] values;
+ public bool[] sums;
+ public double p;
+
+ public ConvolutionRule(XElement xelem, Grid grid)
+ {
+ input = grid.values[xelem.Get("in")];
+ output = grid.values[xelem.Get("out")];
+ string valueString = xelem.Get("values", null);
+ if (valueString != null) values = valueString.Select(c => grid.values[c]).ToArray();
+ p = xelem.Get("p", 1.0);
+
+ static int[] interval(string s)
+ {
+ if (s.Contains('.'))
+ {
+ string[] bounds = s.Split("..");
+ int min = int.Parse(bounds[0]);
+ int max = int.Parse(bounds[1]);
+ int[] result = new int[max - min + 1];
+ for (int i = 0; i < result.Length; i++) result[i] = min + i;
+ return result;
+ }
+ else return new int[1] { int.Parse(s) };
+ };
+
+ string sumsText = xelem.Get("sum", null);
+ if (sumsText != null)
+ {
+ sums = new bool[28];
+ string[] intervals = sumsText.Split(',');
+ foreach (string s in intervals) foreach (int i in interval(s)) sums[i] = true;
+ }
+ }
+ }
+}
diff --git a/source/Field.cs b/source/Field.cs
new file mode 100644
index 0000000..115e883
--- /dev/null
+++ b/source/Field.cs
@@ -0,0 +1,119 @@
+// Copyright (C) 2022 Maxim Gumin, The MIT License (MIT)
+
+using System.Linq;
+using System.Xml.Linq;
+using System.Collections.Generic;
+
+class Field
+{
+ public bool recompute, inversed, essential;
+ public int zero, substrate;
+
+ public Field(XElement xelem, Grid grid)
+ {
+ recompute = xelem.Get("recompute", false);
+ essential = xelem.Get("essential", false);
+ string on = xelem.Get("on");
+ substrate = grid.Wave(on);
+
+ string zeroSymbols = xelem.Get("from", null);
+ if (zeroSymbols != null) inversed = true;
+ else zeroSymbols = xelem.Get("to");
+ zero = grid.Wave(zeroSymbols);
+ }
+
+ public bool Compute(int[] potential, Grid grid)
+ {
+ int MX = grid.MX, MY = grid.MY, MZ = grid.MZ;
+ var front = new Queue<(int, int, int, int)>();
+
+ int ix = 0, iy = 0, iz = 0;
+ for (int i = 0; i < grid.state.Length; i++)
+ {
+ potential[i] = -1;
+ byte value = grid.state[i];
+ if ((zero & 1 << value) != 0)
+ {
+ potential[i] = 0;
+ front.Enqueue((0, ix, iy, iz));
+ }
+
+ ix++;
+ if (ix == MX)
+ {
+ ix = 0; iy++;
+ if (iy == MY) { iy = 0; iz++; }
+ }
+ }
+
+ if (!front.Any()) return false;
+ while (front.Any())
+ {
+ var (t, x, y, z) = front.Dequeue();
+ var neighbors = Neighbors(x, y, z, MX, MY, MZ);
+ for (int n = 0; n < neighbors.Count; n++)
+ {
+ var (nx, ny, nz) = neighbors[n];
+ int i = nx + ny * grid.MX + nz * grid.MX * grid.MY;
+ byte v = grid.state[i];
+ if (potential[i] == -1 && (substrate & 1 << v) != 0)
+ {
+ front.Enqueue((t + 1, nx, ny, nz));
+ potential[i] = t + 1;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ static List<(int, int, int)> Neighbors(int x, int y, int z, int MX, int MY, int MZ)
+ {
+ List<(int, int, int)> result = new();
+
+ if (x > 0) result.Add((x - 1, y, z));
+ if (x < MX - 1) result.Add((x + 1, y, z));
+ if (y > 0) result.Add((x, y - 1, z));
+ if (y < MY - 1) result.Add((x, y + 1, z));
+ if (z > 0) result.Add((x, y, z - 1));
+ if (z < MZ - 1) result.Add((x, y, z + 1));
+
+ return result;
+ }
+
+ public static int? DeltaPointwise(byte[] state, Rule rule, int x, int y, int z, Field[] fields, int[][] potentials, int MX, int MY)
+ {
+ int sum = 0;
+ int dz = 0, dy = 0, dx = 0;
+ for (int di = 0; di < rule.input.Length; di++)
+ {
+ byte newValue = rule.output[di];
+ if (newValue != 0xff && (rule.input[di] & 1 << newValue) == 0)
+ {
+ int i = x + dx + (y + dy) * MX + (z + dz) * MX * MY;
+ int newPotential = potentials[newValue][i];
+ if (newPotential == -1) return null;
+
+ byte oldValue = state[i];
+ int oldPotential = potentials[oldValue][i];
+ sum += newPotential - oldPotential;
+
+ if (fields != null)
+ {
+ Field oldField = fields[oldValue];
+ if (oldField != null && oldField.inversed) sum += 2 * oldPotential;
+ Field newField = fields[newValue];
+ if (newField != null && newField.inversed) sum -= 2 * newPotential;
+ }
+ }
+
+ dx++;
+ if (dx == rule.IMX)
+ {
+ dx = 0; dy++;
+ if (dy == rule.IMY) { dy = 0; dz++; }
+ }
+ }
+ return sum;
+ }
+}
diff --git a/source/GUI.cs b/source/GUI.cs
new file mode 100644
index 0000000..6f0b51a
--- /dev/null
+++ b/source/GUI.cs
@@ -0,0 +1,301 @@
+// Copyright (C) 2022 Maxim Gumin, The MIT License (MIT)
+
+using System;
+using System.Linq;
+using System.Collections.Generic;
+
+static class GUI
+{
+ const int S = 7, SMALL = 3, ZSHIFT = 2, LIMIT = 10;
+ const int HINDENT = 30, HGAP = 2, HARROW = 10, HLINE = 14;
+ const int VSKIP = 2, SMALLVSKIP = 2, FONTSHIFT = 2, AFTERFONT = 4;
+
+ const string FONT = "Tamzen8x16r", TITLEFONT = "Tamzen8x16b";
+ static readonly bool DENSE = true, D3 = true;
+
+ public const int BACKGROUND = (255 << 24) + (34 << 16) + (34 << 8) + 34;
+ const int IVALUE = 102, INACTIVE = (255 << 24) + (IVALUE << 16) + (IVALUE << 8) + IVALUE, ACTIVE = -1;
+
+ static (bool[], int FX, int FY)[] fonts;
+
+ static readonly char[] legend = "ABCDEFGHIJKLMNOPQRSTUVWXYZ 12345abcdefghijklmnopqrstuvwxyz\u03bb67890{}[]()<>$*-+=/#_%^@\\&|~?'\"`!,.;:".ToCharArray();
+ static readonly Dictionary map;
+ static GUI()
+ {
+ map = new Dictionary();
+ for (int i = 0; i < legend.Length; i++) map.Add(legend[i], (byte)i);
+ fonts = new (bool[], int, int)[2];
+
+ (int[] bitmap, int width, int height, _) = Graphics.LoadBitmap($"resources/fonts/{FONT}.png");
+ int b0 = bitmap[0];
+ int b1 = bitmap[width - 1];
+ fonts[0] = (bitmap.Select(argb => argb != b0 && argb != b1).ToArray(), width / 32, height / 3);
+
+ (bitmap, width, height, _) = Graphics.LoadBitmap($"resources/fonts/{TITLEFONT}.png");
+ b0 = bitmap[0];
+ b1 = bitmap[width - 1];
+ fonts[1] = (bitmap.Select(argb => argb != b0 && argb != b1).ToArray(), width / 32, height / 3);
+ }
+
+ public static void Draw(string name, Branch root, Branch current, int[] bitmap, int WIDTH, int HEIGHT, Dictionary palette)
+ {
+ void drawRectangle(int x, int y, int width, int height, int color)
+ {
+ if (y + height > HEIGHT) return;
+ for (int dy = 0; dy < height; dy++) for (int dx = 0; dx < width; dx++) bitmap[x + dx + (y + dy) * WIDTH] = color;
+ };
+ void drawSquare(int x, int y, int S, char c) => drawRectangle(x, y, S, S, palette[c]);
+ void drawShadedSquare(int x, int y, int S, int color)
+ {
+ drawRectangle(x, y, S, S, color);
+ drawRectangle(x + S, y, 1, S + 1, BACKGROUND);
+ drawRectangle(x, y + S, S + 1, 1, BACKGROUND);
+ };
+ void drawHLine(int x, int y, int length, int color, bool dashed = false)
+ {
+ if (length <= 0 || x < 0 || x + length >= WIDTH) return;
+ if (!dashed) drawRectangle(x, y, length, 1, color);
+ else
+ {
+ if (y >= HEIGHT) return;
+ int shift = length % 4 == 0 ? 1 : 0;
+ for (int dx = 0; dx < length; dx++) if ((dx + shift) / 2 % 2 == 0) bitmap[x + dx + y * WIDTH] = color;
+ }
+ }
+ void drawVLine(int x, int y, int height, int color)
+ {
+ if (x < 0) return;
+ int yend = Math.Min(y + height, HEIGHT);
+ drawRectangle(x, y, 1, yend - y, color);
+ }
+ int write(string s, int x, int y, int color, int font = 0)
+ {
+ int fontshift = font == 0 ? FONTSHIFT : 0;
+ var (f, FX, FY) = fonts[font];
+ if (y - FONTSHIFT + FY >= HEIGHT) return -1;
+ for (int i = 0; i < s.Length; i++)
+ {
+ int p = map[s[i]];
+ int px = p % 32, py = p / 32;
+ for (int dy = 0; dy < FY; dy++) for (int dx = 0; dx < FX; dx++)
+ if (f[px * FX + dx + (py * FY + dy) * FX * 32]) bitmap[x + i * FX + dx + (y + dy - fontshift) * WIDTH] = color;
+ }
+ return s.Length * FX;
+ };
+
+ Dictionary lh = new();
+ void drawDash(Node node, bool markov, bool active)
+ {
+ if (node == root) return;
+ (int level, int height) = lh[node];
+ int extra = markov ? 3 : 1;
+ drawHLine(level * HINDENT - HLINE - HGAP - extra, height + S / 2, (node is MarkovNode || node is SequenceNode ? HINDENT : HLINE) + extra, active ? ACTIVE : INACTIVE);
+ };
+ void drawBracket(Branch branch, int level, int n, bool active)
+ {
+ int first = lh[branch.nodes[0]].height;
+ int last = lh[branch.nodes[n]].height;
+ int x = (level + 1) * HINDENT - HGAP - HLINE;
+ int color = active ? ACTIVE : INACTIVE;
+ bool markov = branch is MarkovNode;
+ drawVLine(x, first + S / 2, last - first + 1, color);
+ drawVLine(x - (markov ? 3 : 1), first + S / 2, last - first + 1, color);
+ };
+ int drawArray(byte[] a, int x, int y, int MX, int MY, int MZ, char[] characters, int S)
+ {
+ for (int dz = 0; dz < MZ; dz++) for (int dy = 0; dy < MY; dy++) for (int dx = 0; dx < MX; dx++)
+ {
+ byte i = a[dx + dy * MX + dz * MX * MY];
+ int color = i != 0xff ? palette[characters[i]] : (D3 ? INACTIVE : BACKGROUND);
+ drawShadedSquare(x + dx * S + (MZ - dz - 1) * ZSHIFT, y + dy * S + (MZ - dz - 1) * ZSHIFT, S, color);
+ }
+ return MX * S + (MZ - 1) * ZSHIFT;
+ };
+ void drawSample(int x, int y, bool[] sample, int MX, int MY, byte c0, byte c1, char[] characters, int S)
+ {
+ for (int dy = 0; dy < MY; dy++) for (int dx = 0; dx < MX; dx++)
+ {
+ byte b = sample[dx + dy * MX] ? c1 : c0;
+ drawSquare(x + dx * S, y + dy * S, S, characters[b]);
+ }
+ };
+
+ int y = fonts[1].FY / 2;
+ write(name, 8, y, ACTIVE, 1);
+ y += (int)(AFTERFONT * fonts[1].FY / 2);
+
+ void draw(Node node, int level)
+ {
+ lh.Add(node, (level, y));
+ int x = level * HINDENT;
+ char[] characters = node.grid.characters;
+
+ if (node is Branch branch)
+ {
+ if (branch is WFCNode wfcnode)
+ {
+ write($"wfc {wfcnode.name}", x, y, branch == current && branch.n < 0 ? ACTIVE : INACTIVE);
+ y += fonts[0].FY + VSKIP;
+ }
+
+ bool markov = branch is MarkovNode;
+ bool sequence = branch is SequenceNode;
+ foreach (var child in branch.nodes)
+ {
+ draw(child, markov || sequence ? level + 1 : level);
+ drawDash(child, markov, false);
+ }
+ }
+ else
+ {
+ bool active = current != null && current.n >= 0 && current.nodes[current.n] == node;
+ int NODECOLOR = active ? ACTIVE : INACTIVE;
+ if (node is RuleNode rulenode)
+ {
+ for (int r = 0; r < rulenode.rules.Length && r < 40; r++)
+ {
+ Rule rule = rulenode.rules[r];
+ if (!rule.original) continue;
+ int s = rule.IMX * rule.IMY > LIMIT ? SMALL : S;
+
+ int LINECOLOR = (active && IsActive(rulenode, r)) ? ACTIVE : INACTIVE;
+ x += drawArray(rule.binput, x, y, rule.IMX, rule.IMY, rule.IMZ, characters, s) + HGAP;
+
+ drawHLine(x, y + S / 2, HARROW, LINECOLOR, rulenode is not OneNode);
+ x += HARROW + HGAP;
+ x += drawArray(rule.output, x, y, rule.OMX, rule.OMY, rule.OMZ, characters, s) + HGAP;
+
+ if (rulenode.steps > 0) write($" {rulenode.counter}/{rulenode.steps}", x, y, LINECOLOR);
+ y += rule.IMY * s + (rule.IMZ - 1) * ZSHIFT + SMALLVSKIP;
+ x = level * HINDENT;
+ }
+ if (rulenode.fields != null)
+ {
+ y += SMALLVSKIP;
+ for (int c = 0; c < rulenode.fields.Length; c++)
+ {
+ Field field = rulenode.fields[c];
+ if (field == null) continue;
+
+ if (!DENSE)
+ {
+ x += write("field for ", x, y, NODECOLOR);
+ drawSquare(x, y, S, characters[c]);
+ x += S;
+
+ x += write(field.inversed ? " from " : " to ", x, y, NODECOLOR);
+ byte[] zero = Helper.NonZeroPositions(field.zero);
+ for (int k = 0; k < zero.Length; k++, x += S) drawSquare(x, y, S, characters[zero[k]]);
+
+ x += write(" on ", x, y, NODECOLOR);
+ byte[] substrate = Helper.NonZeroPositions(field.substrate);
+ for (int k = 0; k < substrate.Length; k++, x += S) drawSquare(x, y, S, characters[substrate[k]]);
+ }
+ else
+ {
+ x += write("field ", x, y, NODECOLOR);
+ drawSquare(x, y, S, characters[c]);
+ x += S + HGAP;
+ byte[] zero = Helper.NonZeroPositions(field.zero);
+ for (int k = 0; k < zero.Length; k++, x += S) drawSquare(x, y, S, characters[zero[k]]);
+ x += HGAP;
+ byte[] substrate = Helper.NonZeroPositions(field.substrate);
+ for (int k = 0; k < substrate.Length; k++, x += S) drawSquare(x, y, S, characters[substrate[k]]);
+ }
+
+ x = level * HINDENT;
+ y += fonts[0].FY;
+ }
+ }
+ y += VSKIP;
+ }
+ else if (node is PathNode path)
+ {
+ int VSHIFT = (fonts[0].FY - FONTSHIFT - S) / 2;
+ if (!DENSE)
+ {
+ x += write("path from ", x, y, NODECOLOR);
+ byte[] start = Helper.NonZeroPositions(path.start);
+ for (int k = 0; k < start.Length; k++, x += S) drawSquare(x, y + VSHIFT, S, characters[start[k]]);
+
+ x += write(" to ", x, y, NODECOLOR);
+ byte[] finish = Helper.NonZeroPositions(path.finish);
+ for (int k = 0; k < finish.Length; k++, x += S) drawSquare(x, y + VSHIFT, S, characters[finish[k]]);
+
+ x += write(" on ", x, y, NODECOLOR);
+ byte[] on = Helper.NonZeroPositions(path.substrate);
+ for (int k = 0; k < on.Length; k++, x += S) drawSquare(x, y + VSHIFT, S, characters[on[k]]);
+
+ x += write(" colored ", x, y, NODECOLOR);
+ drawSquare(x, y + VSHIFT, S, characters[path.value]);
+ y += fonts[0].FY + VSKIP;
+ }
+ else
+ {
+ x += write("path ", x, y, NODECOLOR);
+ byte[] start = Helper.NonZeroPositions(path.start);
+ for (int k = 0; k < start.Length; k++, x += S) drawSquare(x, y + VSHIFT, S, characters[start[k]]);
+ x += HGAP;
+ byte[] finish = Helper.NonZeroPositions(path.finish);
+ for (int k = 0; k < finish.Length; k++, x += S) drawSquare(x, y + VSHIFT, S, characters[finish[k]]);
+ x += HGAP;
+ byte[] on = Helper.NonZeroPositions(path.substrate);
+ for (int k = 0; k < on.Length; k++, x += S) drawSquare(x, y + VSHIFT, S, characters[on[k]]);
+ x += HGAP;
+ drawSquare(x, y + VSHIFT, S, characters[path.value]);
+ y += fonts[0].FY + VSKIP;
+ }
+ }
+ else if (node is MapNode)
+ {
+ Console.WriteLine("ERROR! Map is Branch");
+ write("map", x, y, NODECOLOR);
+ y += fonts[0].FY + VSKIP;
+ }
+ else if (node is ConvolutionNode convnode)
+ {
+ string s = "convolution";
+ if (convnode.steps > 0) s += $" {convnode.counter}/{convnode.steps}";
+ write(s, x, y, NODECOLOR);
+ y += fonts[0].FY + VSKIP;
+ }
+ else if (node is ConvChainNode chainnode)
+ {
+ x += write($"convchain ", x, y, NODECOLOR);
+ drawSample(x, y, chainnode.sample, chainnode.SMX, chainnode.SMY, chainnode.c0, chainnode.c1, characters, 7);
+ y += fonts[0].FY + VSKIP;
+ }
+ }
+ };
+
+ void drawLine(Branch branch)
+ {
+ int childLevel = lh[branch.nodes[0]].level;
+ drawBracket(branch, childLevel - 1, branch.nodes.Length - 1, false);
+ foreach (Node child in branch.nodes) if (child is Branch childBranch) drawLine(childBranch);
+ };
+
+ draw(root, 0);
+ drawLine(root);
+ for (Branch b = current; b != null; b = b.parent)
+ {
+ if (b.n >= 0)
+ {
+ drawDash(b.nodes[b.n], b is MarkovNode, true);
+ drawBracket(b, lh[b.nodes[0]].level - 1, b.n, true);
+ }
+ }
+ }
+
+ static bool IsActive(RuleNode node, int index)
+ {
+ if (node.last[index]) return true;
+ for (int r = index + 1; r < node.rules.Length; r++)
+ {
+ Rule rule = node.rules[r];
+ if (rule.original) break;
+ if (node.last[r]) return true;
+ }
+ return false;
+ }
+}
diff --git a/source/Graphics.cs b/source/Graphics.cs
new file mode 100644
index 0000000..680fce4
--- /dev/null
+++ b/source/Graphics.cs
@@ -0,0 +1,221 @@
+// Copyright (C) 2022 Maxim Gumin, The MIT License (MIT)
+
+using System;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.Collections.Generic;
+
+static class Graphics
+{
+ public static (int[], int, int, int) LoadBitmap(string filename)
+ {
+ try
+ {
+ Bitmap bitmap = new(filename);
+ int width = bitmap.Width, height = bitmap.Height;
+ var bits = bitmap.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
+ int[] result = new int[bitmap.Width * bitmap.Height];
+ System.Runtime.InteropServices.Marshal.Copy(bits.Scan0, result, 0, result.Length);
+ bitmap.UnlockBits(bits);
+ bitmap.Dispose();
+ return (result, width, height, 1);
+ }
+ catch (Exception) { return (null, -1, -1, -1); }
+ }
+
+ public static void SaveBitmap(int[] data, int width, int height, string filename)
+ {
+ Bitmap result = new(width, height);
+ var bits = result.LockBits(new Rectangle(0, 0, result.Width, result.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
+ System.Runtime.InteropServices.Marshal.Copy(data, 0, bits.Scan0, data.Length);
+ result.UnlockBits(bits);
+ result.Save(filename);
+ }
+
+ public static (int[], int, int) Render(byte[] state, int MX, int MY, int MZ, int[] colors, int pixelsize, int MARGIN) => MZ == 1 ? BitmapRender(state, MX, MY, colors, pixelsize, MARGIN) : IsometricRender(state, MX, MY, MZ, colors, pixelsize, MARGIN);
+
+ public static (int[], int, int) BitmapRender(byte[] state, int MX, int MY, int[] colors, int pixelsize, int MARGIN)
+ {
+ int WIDTH = MARGIN + MX * pixelsize, HEIGHT = MY * pixelsize;
+ int TOTALWIDTH = WIDTH, TOTALHEIGHT = HEIGHT;
+ //int TOTALWIDTH = 189 + MARGIN, TOTALHEIGHT = 189;
+ int[] bitmap = new int[TOTALWIDTH * TOTALHEIGHT];
+ for (int i = 0; i < bitmap.Length; i++) bitmap[i] = GUI.BACKGROUND;
+ //for (int i = 0; i < bitmap.Length; i++) bitmap[i] = 255 << 24;
+
+ int DX = (TOTALWIDTH - WIDTH) / 2;
+ int DY = (TOTALHEIGHT - HEIGHT) / 2;
+
+ for (int y = 0; y < MY; y++) for (int x = 0; x < MX; x++)
+ {
+ int c = colors[state[x + y * MX]];
+ for (int dy = 0; dy < pixelsize; dy++) for (int dx = 0; dx < pixelsize; dx++)
+ {
+ int SX = DX + x * pixelsize + dx;
+ int SY = DY + y * pixelsize + dy;
+ if (SX < 0 || SX >= TOTALWIDTH - MARGIN || SY < 0 || SY >= TOTALHEIGHT) continue;
+ bitmap[MARGIN + SX + SY * TOTALWIDTH] = c;
+ }
+ }
+ return (bitmap, TOTALWIDTH, TOTALHEIGHT);
+ }
+
+ static readonly Dictionary sprites = new();
+ public static (int[], int, int) IsometricRender(byte[] state, int MX, int MY, int MZ, int[] colors, int blocksize, int MARGIN)
+ {
+ var voxels = new List[MX + MY + MZ - 2];
+ var visibleVoxels = new List[MX + MY + MZ - 2];
+ for (int i = 0; i < voxels.Length; i++)
+ {
+ voxels[i] = new List();
+ visibleVoxels[i] = new List();
+ }
+ bool[] visible = new bool[MX * MY * MZ]; //нужен для быстрой работы с transparent
+
+ for (int z = 0; z < MZ; z++) for (int y = 0; y < MY; y++) for (int x = 0; x < MX; x++)
+ {
+ int i = x + y * MX + z * MX * MY;
+ byte value = state[i];
+ visible[i] = value != 0;
+ if (value != 0) voxels[x + y + z].Add(new Voxel(colors[value], x, y, z));
+ }
+
+ bool[][] hash = AH.Array2D(MX + MY - 1, MX + MY + 2 * MZ - 3, false);
+ for (int i = voxels.Length - 1; i >= 0; i--)
+ {
+ List voxelsi = voxels[i];
+ for (int j = 0; j < voxelsi.Count; j++)
+ {
+ Voxel s = voxelsi[j];
+ int u = s.x - s.y + MY - 1, v = s.x + s.y - 2 * s.z + 2 * MZ - 2;
+ if (!hash[u][v])
+ {
+ bool X = s.x == 0 || !visible[(s.x - 1) + s.y * MX + s.z * MX * MY];
+ bool Y = s.y == 0 || !visible[s.x + (s.y - 1) * MX + s.z * MX * MY];
+ bool Z = s.z == 0 || !visible[s.x + s.y * MX + (s.z - 1) * MX * MY];
+
+ s.edges[0] = s.y == MY - 1 || !visible[s.x + (s.y + 1) * MX + s.z * MX * MY];
+ s.edges[1] = s.x == MX - 1 || !visible[s.x + 1 + s.y * MX + s.z * MX * MY];
+ s.edges[2] = X || (s.y != MY - 1 && visible[s.x - 1 + (s.y + 1) * MX + s.z * MX * MY]);
+ s.edges[3] = X || (s.z != MZ - 1 && visible[s.x - 1 + s.y * MX + (s.z + 1) * MX * MY]);
+ s.edges[4] = Y || (s.x != MX - 1 && visible[s.x + 1 + (s.y - 1) * MX + s.z * MX * MY]);
+ s.edges[5] = Y || (s.z != MZ - 1 && visible[s.x + (s.y - 1) * MX + (s.z + 1) * MX * MY]);
+ s.edges[6] = Z || (s.x != MX - 1 && visible[s.x + 1 + s.y * MX + (s.z - 1) * MX * MY]);
+ s.edges[7] = Z || (s.y != MY - 1 && visible[s.x + (s.y + 1) * MX + (s.z - 1) * MX * MY]);
+
+ visibleVoxels[i].Add(s);
+ hash[u][v] = true;
+ }
+ }
+ }
+
+ int FITWIDTH = (MX + MY) * blocksize, FITHEIGHT = ((MX + MY) / 2 + MZ) * blocksize;
+ int WIDTH = FITWIDTH + 2 * blocksize, HEIGHT = FITHEIGHT + 2 * blocksize;
+ //const int WIDTH = 330, HEIGHT = 330;
+
+ int[] screen = new int[(MARGIN + WIDTH) * HEIGHT];
+ for (int i = 0; i < screen.Length; i++) screen[i] = GUI.BACKGROUND;
+
+ void Blit(int[] sprite, int SX, int SY, int x, int y, int r, int g, int b)
+ {
+ for (int dy = 0; dy < SY; dy++) for (int dx = 0; dx < SX; dx++)
+ {
+ int grayscale = sprite[dx + dy * SX];
+ if (grayscale < 0) continue;
+ byte R = (byte)((float)r * (float)grayscale / 256.0f);
+ byte G = (byte)((float)g * (float)grayscale / 256.0f);
+ byte B = (byte)((float)b * (float)grayscale / 256.0f);
+ int X = x + dx;
+ int Y = y + dy;
+ if (MARGIN + X >= 0 && X < WIDTH && Y >= 0 && Y < HEIGHT) screen[MARGIN + X + Y * (MARGIN + WIDTH)] = Int(R, G, B);
+ }
+ };
+
+ bool success = sprites.TryGetValue(blocksize, out Sprite sprite);
+ if (!success)
+ {
+ sprite = new Sprite(blocksize);
+ sprites.Add(blocksize, sprite);
+ }
+
+ for (int i = 0; i < visibleVoxels.Length; i++) foreach (Voxel s in visibleVoxels[i])
+ {
+ int u = blocksize * (s.x - s.y);
+ int v = (blocksize * (s.x + s.y) / 2 - blocksize * s.z);
+ int positionx = WIDTH / 2 + u - blocksize;
+ //int positionx = WIDTH / 2 + u - 5 * blocksize;
+ int positiony = (HEIGHT - FITHEIGHT) / 2 + (MZ - 1) * blocksize + v;
+ var (r, g, b) = RGB(s.color);
+ Blit(sprite.cube, sprite.width, sprite.height, positionx, positiony, r, g, b);
+ for (int j = 0; j < 8; j++) if (s.edges[j]) Blit(sprite.edges[j], sprite.width, sprite.height, positionx, positiony, r, g, b);
+ }
+
+ return (screen, MARGIN + WIDTH, HEIGHT);
+ }
+
+ static int Int(byte r, byte g, byte b) => (0xff << 24) + (r << 16) + (g << 8) + b;
+ static (byte, byte, byte) RGB(int i)
+ {
+ byte r = (byte)((i & 0xff0000) >> 16);
+ byte g = (byte)((i & 0xff00) >> 8);
+ byte b = (byte)(i & 0xff);
+ return (r, g, b);
+ }
+}
+
+class Sprite
+{
+ public int[] cube;
+ public int[][] edges;
+ public int width, height;
+
+ const int c1 = 215, c2 = 143, c3 = 71, black = 0, transparent = -1;
+
+ public Sprite(int size)
+ {
+ width = 2 * size;
+ height = 2 * size - 1;
+
+ int[] texture(Func f)
+ {
+ int[] result = new int[width * height];
+ for (int j = 0; j < height; j++) for (int i = 0; i < width; i++) result[i + j * width] = f(i - size + 1, size - j - 1);
+ return result;
+ };
+
+ int f(int x, int y)
+ {
+ if (2 * y - x >= 2 * size || 2 * y + x > 2 * size || 2 * y - x < -2 * size || 2 * y + x <= -2 * size) return transparent;
+ else if (x > 0 && 2 * y < x) return c3;
+ else if (x <= 0 && 2 * y <= -x) return c2;
+ else return c1;
+ };
+
+ cube = texture(f);
+ edges = new int[8][];
+ edges[0] = texture((x, y) => x == 1 && y <= 0 ? c1 : transparent);
+ edges[1] = texture((x, y) => x == 0 && y <= 0 ? c1 : transparent);
+ edges[2] = texture((x, y) => x == 1 - size && 2 * y < size && 2 * y >= -size ? black : transparent);
+ edges[3] = texture((x, y) => x <= 0 && y == x / 2 + size - 1 ? black : transparent);
+ edges[4] = texture((x, y) => x == size && 2 * y < size && 2 * y >= -size ? black : transparent);
+ edges[5] = texture((x, y) => x > 0 && y == -(x + 1) / 2 + size ? black : transparent);
+ edges[6] = texture((x, y) => x > 0 && y == (x + 1) / 2 - size ? black : transparent);
+ edges[7] = texture((x, y) => x <= 0 && y == -x / 2 - size + 1 ? black : transparent);
+ }
+}
+
+struct Voxel
+{
+ public int color;
+ public int x, y, z;
+ public bool[] edges;
+
+ public Voxel(int color, int x, int y, int z)
+ {
+ this.color = color;
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ edges = new bool[8];
+ }
+}
diff --git a/source/Grid.cs b/source/Grid.cs
new file mode 100644
index 0000000..6c0bd94
--- /dev/null
+++ b/source/Grid.cs
@@ -0,0 +1,140 @@
+// Copyright (C) 2022 Maxim Gumin, The MIT License (MIT)
+
+using System;
+using System.Linq;
+using System.Xml.Linq;
+using System.Collections.Generic;
+
+class Grid
+{
+ public byte[] state;
+ public bool[] mask;
+ public int MX, MY, MZ;
+
+ public byte C;
+ public char[] characters;
+ public Dictionary values;
+ public Dictionary waves;
+ public string folder;
+
+ int transparent;
+ byte[] statebuffer;
+
+ public static Grid Load(XElement xelem, int MX, int MY, int MZ)
+ {
+ Grid g = new();
+ g.MX = MX;
+ g.MY = MY;
+ g.MZ = MZ;
+ string valueString = xelem.Get("values", null)?.Replace(" ", "");
+ if (valueString == null)
+ {
+ Interpreter.WriteLine("no values specified");
+ return null;
+ }
+
+ g.C = (byte)valueString.Length;
+ g.values = new Dictionary();
+ g.waves = new Dictionary();
+ g.characters = new char[g.C];
+ for (byte i = 0; i < g.C; i++)
+ {
+ char symbol = valueString[i];
+ if (g.values.ContainsKey(symbol))
+ {
+ Interpreter.WriteLine($"repeating value {symbol} at line {xelem.LineNumber()}");
+ return null;
+ }
+ else
+ {
+ g.characters[i] = symbol;
+ g.values.Add(symbol, i);
+ g.waves.Add(symbol, 1 << i);
+ }
+ }
+
+ string transparentString = xelem.Get("transparent", null);
+ if (transparentString != null) g.transparent = g.Wave(transparentString);
+
+ var xunions = xelem.MyDescendants("markov", "sequence", "union").Where(x => x.Name == "union");
+ g.waves.Add('*', (1 << g.C) - 1);
+ foreach (XElement xunion in xunions)
+ {
+ char symbol = xunion.Get("symbol");
+ if (g.waves.ContainsKey(symbol))
+ {
+ Interpreter.WriteLine($"repeating union type {symbol} at line {xunion.LineNumber()}");
+ return null;
+ }
+ else
+ {
+ int w = g.Wave(xunion.Get("values"));
+ g.waves.Add(symbol, w);
+ }
+ }
+
+ g.state = new byte[MX * MY * MZ];
+ g.statebuffer = new byte[MX * MY * MZ];
+ g.mask = new bool[MX * MY * MZ];
+ g.folder = xelem.Get("folder", null);
+ return g;
+ }
+
+ public void Clear(int origin)
+ {
+ for (int i = 0; i < state.Length; i++) state[i] = 0;
+ if (origin >= 0) state[origin] = 1;
+ }
+
+ public int Wave(string values)
+ {
+ int sum = 0;
+ for (int k = 0; k < values.Length; k++) sum += 1 << this.values[values[k]];
+ return sum;
+ }
+
+ /*static readonly int[] DX = { 1, 0, -1, 0, 0, 0 };
+ static readonly int[] DY = { 0, 1, 0, -1, 0, 0 };
+ static readonly int[] DZ = { 0, 0, 0, 0, 1, -1 };
+ public byte[] State()
+ {
+ int neighbors(int x, int y, int z)
+ {
+ int sum = 0;
+ for (int d = 0; d < 6; d++)
+ {
+ int X = x + DX[d], Y = y + DY[d], Z = z + DZ[d];
+ if (X < 0 || X >= MX || Y < 0 || Y >= MY || Z < 0 || Z >= MZ) continue;
+ if (state[X + Y * MX + Z * MX * MY] != 0) sum++;
+ }
+ return sum;
+ };
+
+ Array.Copy(state, statebuffer, state.Length);
+ for (int z = 0; z < MZ; z++) for (int y = 0; y < MY; y++) for (int x = 0; x < MX; x++)
+ {
+ int i = x + y * MX + z * MX * MY;
+ byte v = state[i];
+ int n = neighbors(x, y, z);
+ if (v == 0 || ((1 << v) & transparent) != 0 || n == 0 || (n == 1 && z == 1 && (state[i - MX * MY] == 11 || state[i - MX * MY] == 4))) statebuffer[i] = 0;
+ }
+ return statebuffer;
+ }*/
+
+ public bool Matches(Rule rule, int x, int y, int z)
+ {
+ int dz = 0, dy = 0, dx = 0;
+ for (int di = 0; di < rule.input.Length; di++)
+ {
+ if ((rule.input[di] & (1 << state[x + dx + (y + dy) * MX + (z + dz) * MX * MY])) == 0) return false;
+
+ dx++;
+ if (dx == rule.IMX)
+ {
+ dx = 0; dy++;
+ if (dy == rule.IMY) { dy = 0; dz++; }
+ }
+ }
+ return true;
+ }
+}
diff --git a/source/Helper.cs b/source/Helper.cs
new file mode 100644
index 0000000..caa2d72
--- /dev/null
+++ b/source/Helper.cs
@@ -0,0 +1,117 @@
+// Copyright (C) 2022 Maxim Gumin, The MIT License (MIT)
+
+using System;
+using System.Collections.Generic;
+
+static class Helper
+{
+ public static (byte[], int) Ords(this int[] data, List uniques = null)
+ {
+ byte[] result = new byte[data.Length];
+ if (uniques == null) uniques = new List();
+ for (int i = 0; i < data.Length; i++)
+ {
+ int d = data[i];
+ int ord = uniques.IndexOf(d);
+ if (ord == -1)
+ {
+ ord = uniques.Count;
+ uniques.Add(d);
+ }
+ result[i] = (byte)ord;
+ }
+ return (result, uniques.Count);
+ }
+
+ public static string[][] Split(this string s, char S1, char S2)
+ {
+ string[] split = s.Split(S1);
+ string[][] result = new string[split.Length][];
+ for (int k = 0; k < result.Length; k++) result[k] = split[k].Split(S2);
+ return result;
+ }
+
+ public static int Power(int a, int n)
+ {
+ int product = 1;
+ for (int i = 0; i < n; i++) product *= a;
+ return product;
+ }
+
+ public static int Index(this bool[] array)
+ {
+ int result = 0, power = 1;
+ for (int i = 0; i < array.Length; i++, power *= 2) if (array[i]) result += power;
+ return result;
+ }
+ public static long Index(this byte[] p, int C)
+ {
+ long result = 0, power = 1;
+ for (int i = 0; i < p.Length; i++, power *= C) result += p[p.Length - 1 - i] * power;
+ return result;
+ }
+
+ public static byte FirstNonZeroPosition(int w)
+ {
+ for (byte p = 0; p < 32; p++, w >>= 1) if ((w & 1) == 1) return p;
+ return 0xff;
+ }
+ public static byte[] NonZeroPositions(int w)
+ {
+ int amount = 0, wcopy = w;
+ for (byte p = 0; p < 32; p++, w >>= 1) if ((w & 1) == 1) amount++;
+ byte[] result = new byte[amount];
+ amount = 0;
+ for (byte p = 0; p < 32; p++, wcopy >>= 1) if ((wcopy & 1) == 1)
+ {
+ result[amount] = p;
+ amount++;
+ }
+ return result;
+ }
+
+ public static int MaxPositiveIndex(this int[] amounts)
+ {
+ int max = -1, argmax = -1;
+ for (int i = 0; i < amounts.Length; i++)
+ {
+ int amount = amounts[i];
+ if (amount > 0 && amount > max)
+ {
+ max = amount;
+ argmax = i;
+ }
+ }
+ return argmax;
+ }
+}
+
+static class RandomHelper
+{
+ public static T Random(this List list, Random random) => list[random.Next(list.Count)];
+
+ public static int Random(this double[] weights, double r)
+ {
+ double sum = 0;
+ for (int i = 0; i < weights.Length; i++) sum += weights[i];
+ double threshold = r * sum;
+
+ double partialSum = 0;
+ for (int i = 0; i < weights.Length; i++)
+ {
+ partialSum += weights[i];
+ if (partialSum >= threshold) return i;
+ }
+ return 0;
+ }
+
+ public static void Shuffle(this int[] array, Random random)
+ {
+ for (int i = 0; i < array.Length; i++)
+ {
+ int j = random.Next(i + 1);
+ array[i] = array[j];
+ array[j] = i;
+ }
+ }
+}
diff --git a/source/Interpreter.cs b/source/Interpreter.cs
new file mode 100644
index 0000000..e34515e
--- /dev/null
+++ b/source/Interpreter.cs
@@ -0,0 +1,101 @@
+// Copyright (C) 2022 Maxim Gumin, The MIT License (MIT)
+
+using System;
+using System.Xml.Linq;
+using System.Collections.Generic;
+
+class Interpreter
+{
+ public Branch root, current;
+ public Grid grid;
+ Grid startgrid;
+
+ Origin origin;
+ enum Origin { None, Random, Center };
+ public Random random;
+
+ public List<(int, int, int)> changes;
+ public List first;
+ public int counter;
+
+ public bool gif;
+
+ Interpreter() { }
+ public static Interpreter Load(XElement xelem, int MX, int MY, int MZ)
+ {
+ Interpreter ip = new();
+
+ string originName = xelem.Get("origin", null);
+ if (originName == "Random") ip.origin = Origin.Random;
+ else if (originName == "Center") ip.origin = Origin.Center;
+ else if (originName == null) ip.origin = Origin.None;
+ else
+ {
+ WriteLine($"unknown origin name {originName}");
+ return null;
+ }
+
+ ip.grid = Grid.Load(xelem, MX, MY, MZ);
+ if (ip.grid == null)
+ {
+ Console.WriteLine("failed to load grid");
+ return null;
+ }
+ ip.startgrid = ip.grid;
+
+ string symmetryString = xelem.Get("symmetry", null);
+ bool[] symmetry = SymmetryHelper.GetSymmetry(ip.grid.MZ == 1, symmetryString, AH.Array1D(ip.grid.MZ == 1 ? 8 : 48, true));
+ if (symmetry == null)
+ {
+ WriteLine($"unknown symmetry {symmetryString} at line {xelem.LineNumber()}");
+ return null;
+ }
+
+ Node topnode = Node.Factory(xelem, symmetry, ip, ip.grid);
+ if (topnode == null) return null;
+ ip.root = topnode is Branch ? topnode as Branch : new MarkovNode(topnode, ip);
+
+ ip.changes = new List<(int, int, int)>();
+ ip.first = new List();
+ return ip;
+ }
+
+ public IEnumerable<(byte[], char[], int, int, int)> Run(int seed, int steps, bool gif)
+ {
+ random = new Random(seed);
+ grid = startgrid;
+ int originIndex = origin switch
+ {
+ Origin.Random => random.Next(grid.MX * grid.MY * grid.MZ),
+ Origin.Center => grid.MX / 2 + (grid.MY / 2) * grid.MX + (grid.MZ / 2) * grid.MX * grid.MY,
+ _ => -1
+ };
+ grid.Clear(originIndex);
+ changes.Clear();
+ first.Clear();
+ first.Add(0);
+
+ root.Reset();
+ current = root;
+
+ this.gif = gif;
+ counter = 0;
+ while (current != null && (steps <= 0 || counter < steps))
+ {
+ if (gif)
+ {
+ Console.WriteLine($"[{counter}]");
+ yield return (grid.state, grid.characters, grid.MX, grid.MY, grid.MZ);
+ }
+
+ current.Go();
+ counter++;
+ first.Add(changes.Count);
+ }
+
+ yield return (grid.state, grid.characters, grid.MX, grid.MY, grid.MZ);
+ }
+
+ public static void WriteLine(string s) => Console.WriteLine(s);
+ public static void Write(string s) => Console.Write(s);
+}
diff --git a/source/Map.cs b/source/Map.cs
new file mode 100644
index 0000000..5ec1afa
--- /dev/null
+++ b/source/Map.cs
@@ -0,0 +1,114 @@
+// Copyright (C) 2022 Maxim Gumin, The MIT License (MIT)
+
+using System.Xml.Linq;
+using System.Collections.Generic;
+
+class MapNode : Branch
+{
+ Grid newgrid;
+ Rule[] rules;
+ int NX, NY, NZ, DX, DY, DZ;
+
+ override protected bool Load(XElement xelem, bool[] parentSymmetry, Grid grid)
+ {
+ string scalestring = xelem.Get("scale", null);
+ if (scalestring == null)
+ {
+ Interpreter.WriteLine($"scale should be specified in map node");
+ return false;
+ }
+ string[] scales = scalestring.Split(' ');
+ if (scales.Length != 3)
+ {
+ Interpreter.WriteLine($"scale attribute \"{scalestring}\" should have 3 components separated by space");
+ return false;
+ }
+
+ static (int numerator, int denominator) readScale(string s)
+ {
+ if (!s.Contains('/')) return (int.Parse(s), 1);
+ else
+ {
+ string[] nd = s.Split('/');
+ return (int.Parse(nd[0]), int.Parse(nd[1]));
+ }
+ };
+
+ (NX, DX) = readScale(scales[0]);
+ (NY, DY) = readScale(scales[1]);
+ (NZ, DZ) = readScale(scales[2]);
+
+ newgrid = Grid.Load(xelem, NX * grid.MX / DX, NY * grid.MY / DY, NZ * grid.MZ / DZ);
+ if (newgrid == null) return false;
+
+ if (!base.Load(xelem, parentSymmetry, newgrid)) return false;
+ bool[] symmetry = SymmetryHelper.GetSymmetry(grid.MZ == 1, xelem.Get("symmetry", null), parentSymmetry);
+
+ List ruleList = new();
+ foreach (XElement xrule in xelem.Elements("rule"))
+ {
+ Rule rule = Rule.Load(xrule, grid, newgrid);
+ if (rule == null) return false;
+ foreach (Rule r in rule.Symmetries(symmetry, grid.MZ == 1)) ruleList.Add(r);
+ }
+ rules = ruleList.ToArray();
+ return true;
+ }
+
+ static bool Matches(Rule rule, int x, int y, int z, byte[] state, int MX, int MY, int MZ)
+ {
+ for (int dz = 0; dz < rule.IMZ; dz++) for (int dy = 0; dy < rule.IMY; dy++) for (int dx = 0; dx < rule.IMX; dx++)
+ {
+ int sx = x + dx;
+ int sy = y + dy;
+ int sz = z + dz;
+
+ if (sx >= MX) sx -= MX;
+ if (sy >= MY) sy -= MY;
+ if (sz >= MZ) sz -= MZ;
+
+ int inputWave = rule.input[dx + dy * rule.IMX + dz * rule.IMX * rule.IMY];
+ if ((inputWave & (1 << state[sx + sy * MX + sz * MX * MY])) == 0) return false;
+ }
+
+ return true;
+ }
+
+ static void Apply(Rule rule, int x, int y, int z, byte[] state, int MX, int MY, int MZ)
+ {
+ for (int dz = 0; dz < rule.OMZ; dz++) for (int dy = 0; dy < rule.OMY; dy++) for (int dx = 0; dx < rule.OMX; dx++)
+ {
+ int sx = x + dx;
+ int sy = y + dy;
+ int sz = z + dz;
+
+ if (sx >= MX) sx -= MX;
+ if (sy >= MY) sy -= MY;
+ if (sz >= MZ) sz -= MZ;
+
+ byte output = rule.output[dx + dy * rule.OMX + dz * rule.OMX * rule.OMY];
+ if (output != 0xff) state[sx + sy * MX + sz * MX * MY] = output;
+ }
+ }
+
+ override public bool Go()
+ {
+ if (n >= 0) return base.Go();
+
+ newgrid.Clear(-1);
+ foreach (Rule rule in rules)
+ for (int z = 0; z < grid.MZ; z++) for (int y = 0; y < grid.MY; y++) for (int x = 0; x < grid.MX; x++)
+ if (Matches(rule, x, y, z, grid.state, grid.MX, grid.MY, grid.MZ))
+ Apply(rule, x * NX / DX, y * NY / DY, z * NZ / DZ, newgrid.state, newgrid.MX, newgrid.MY, newgrid.MZ);
+
+ ip.grid = newgrid;
+ n++;
+ return true;
+ }
+
+ override public void Reset()
+ {
+ base.Reset();
+ n = -1;
+ }
+}
diff --git a/source/Node.cs b/source/Node.cs
new file mode 100644
index 0000000..a4a6037
--- /dev/null
+++ b/source/Node.cs
@@ -0,0 +1,110 @@
+// Copyright (C) 2022 Maxim Gumin, The MIT License (MIT)
+
+using System;
+using System.Linq;
+using System.Xml.Linq;
+
+abstract class Node
+{
+ abstract protected bool Load(XElement xelem, bool[] symmetry, Grid grid);
+ abstract public void Reset();
+ abstract public bool Go();
+
+ protected Interpreter ip;
+ public Grid grid;
+
+ public static Node Factory(XElement xelem, bool[] symmetry, Interpreter ip, Grid grid)
+ {
+ if (!nodenames.Contains(xelem.Name.LocalName))
+ {
+ Interpreter.WriteLine($"unknown node type \"{xelem.Name}\" at line {xelem.LineNumber()}");
+ return null;
+ }
+
+ Node result = xelem.Name.LocalName switch
+ {
+ "one" => new OneNode(),
+ "all" => new AllNode(),
+ "prl" => new ParallelNode(),
+ "markov" => new MarkovNode(),
+ "sequence" => new SequenceNode(),
+ "path" => new PathNode(),
+ "map" => new MapNode(),
+ "convolution" => new ConvolutionNode(),
+ "convchain" => new ConvChainNode(),
+ "wfc" when xelem.Get("sample", null) != null => new OverlapNode(),
+ "wfc" when xelem.Get("tileset", null) != null => new TileNode(),
+ _ => null
+ };
+
+ result.ip = ip;
+ result.grid = grid;
+ bool success = result.Load(xelem, symmetry, grid);
+
+ if (!success) return null;
+ return result;
+ }
+
+ protected static string[] nodenames = new string[] { "one", "all", "prl", "markov", "sequence", "path", "map", "convolution", "convchain", "wfc" };
+}
+
+abstract class Branch : Node
+{
+ public Branch parent;
+ public Node[] nodes;
+ public int n;
+
+ override protected bool Load(XElement xelem, bool[] parentSymmetry, Grid grid)
+ {
+ string symmetryString = xelem.Get("symmetry", null);
+ bool[] symmetry = SymmetryHelper.GetSymmetry(ip.grid.MZ == 1, symmetryString, parentSymmetry);
+ if (symmetry == null)
+ {
+ Interpreter.WriteLine($"unknown symmetry {symmetryString} at line {xelem.LineNumber()}");
+ return false;
+ }
+
+ XElement[] xchildren = xelem.Elements(nodenames).ToArray();
+ nodes = new Node[xchildren.Length];
+ for (int c = 0; c < xchildren.Length; c++)
+ {
+ var child = Factory(xchildren[c], symmetry, ip, grid);
+ if (child == null) return false;
+ if (child is Branch branch) branch.parent = branch is MapNode || branch is WFCNode ? null : this;
+ nodes[c] = child;
+ }
+ return true;
+ }
+
+ override public bool Go()
+ {
+ for (; n < nodes.Length; n++)
+ {
+ Node node = nodes[n];
+ if (node is Branch branch) ip.current = branch;
+ if (node.Go()) return true;
+ }
+ ip.current = ip.current.parent;
+ Reset();
+ return false;
+ }
+
+ override public void Reset()
+ {
+ foreach (var node in nodes) node.Reset();
+ n = 0;
+ }
+}
+
+class SequenceNode : Branch { }
+class MarkovNode : Branch
+{
+ public MarkovNode() { }
+ public MarkovNode(Node child, Interpreter ip) { nodes = new Node[] { child }; this.ip = ip; grid = ip.grid; }
+
+ public override bool Go()
+ {
+ n = 0;
+ return base.Go();
+ }
+}
diff --git a/source/Observation.cs b/source/Observation.cs
new file mode 100644
index 0000000..b209b54
--- /dev/null
+++ b/source/Observation.cs
@@ -0,0 +1,184 @@
+// Copyright (C) 2022 Maxim Gumin, The MIT License (MIT)
+
+using System.Linq;
+using System.Collections.Generic;
+
+class Observation
+{
+ readonly byte from;
+ readonly int to;
+
+ public Observation(char from, string to, Grid grid)
+ {
+ this.from = grid.values[from];
+ this.to = grid.Wave(to);
+ }
+
+ public static bool ComputeFutureSetPresent(int[] future, byte[] state, Observation[] observations)
+ {
+ bool[] mask = new bool[observations.Length];
+ for (int k = 0; k < observations.Length; k++) if (observations[k] == null) mask[k] = true;
+
+ for (int i = 0; i < state.Length; i++)
+ {
+ byte value = state[i];
+ Observation obs = observations[value];
+ mask[value] = true;
+ if (obs != null)
+ {
+ future[i] = obs.to;
+ state[i] = obs.from;
+ }
+ else future[i] = 1 << value;
+ }
+
+ for (int k = 0; k < mask.Length; k++) if (!mask[k])
+ {
+ //Console.WriteLine($"observed value {k} not present on the grid, observe-node returning false");
+ return false;
+ }
+ return true;
+ }
+
+ public static void ComputeForwardPotentials(int[][] potentials, byte[] state, int MX, int MY, int MZ, Rule[] rules)
+ {
+ potentials.Set2D(-1);
+ for (int i = 0; i < state.Length; i++) potentials[state[i]][i] = 0;
+ ComputePotentials(potentials, MX, MY, MZ, rules, false);
+ }
+ public static void ComputeBackwardPotentials(int[][] potentials, int[] future, int MX, int MY, int MZ, Rule[] rules)
+ {
+ for (int c = 0; c < potentials.Length; c++)
+ {
+ int[] potential = potentials[c];
+ for (int i = 0; i < future.Length; i++) potential[i] = (future[i] & (1 << c)) != 0 ? 0 : -1;
+ }
+ ComputePotentials(potentials, MX, MY, MZ, rules, true);
+ }
+
+ static void ComputePotentials(int[][] potentials, int MX, int MY, int MZ, Rule[] rules, bool backwards)
+ {
+ Queue<(byte c, int x, int y, int z)> queue = new();
+ for (byte c = 0; c < potentials.Length; c++)
+ {
+ int[] potential = potentials[c];
+ for (int i = 0; i < potential.Length; i++) if (potential[i] == 0) queue.Enqueue((c, i % MX, (i % (MX * MY)) / MX, i / (MX * MY)));
+ }
+ bool[][] matchMask = AH.Array2D(rules.Length, potentials[0].Length, false);
+
+ while (queue.Any())
+ {
+ (byte value, int x, int y, int z) = queue.Dequeue();
+ int i = x + y * MX + z * MX * MY;
+ int t = potentials[value][i];
+ for (int r = 0; r < rules.Length; r++)
+ {
+ bool[] maskr = matchMask[r];
+ Rule rule = rules[r];
+ var shifts = backwards ? rule.oshifts[value] : rule.ishifts[value];
+ for (int l = 0; l < shifts.Length; l++)
+ {
+ var (shiftx, shifty, shiftz) = shifts[l];
+ int sx = x - shiftx;
+ int sy = y - shifty;
+ int sz = z - shiftz;
+
+ if (sx < 0 || sy < 0 || sz < 0 || sx + rule.IMX > MX || sy + rule.IMY > MY || sz + rule.IMZ > MZ) continue;
+ int si = sx + sy * MX + sz * MX * MY;
+ if (!maskr[si] && ForwardMatches(rule, sx, sy, sz, potentials, t, MX, MY, backwards))
+ {
+ maskr[si] = true;
+ ApplyForward(rule, sx, sy, sz, potentials, t, MX, MY, queue, backwards);
+ }
+ }
+ }
+ }
+ }
+
+ static bool ForwardMatches(Rule rule, int x, int y, int z, int[][] potentials, int t, int MX, int MY, bool backwards)
+ {
+ int dz = 0, dy = 0, dx = 0;
+ byte[] a = backwards ? rule.output : rule.binput;
+ for (int di = 0; di < a.Length; di++)
+ {
+ byte value = a[di];
+ if (value != 0xff)
+ {
+ int current = potentials[value][x + dx + (y + dy) * MX + (z + dz) * MX * MY];
+ if (current > t || current == -1) return false;
+ }
+ dx++;
+ if (dx == rule.IMX)
+ {
+ dx = 0; dy++;
+ if (dy == rule.IMY) { dy = 0; dz++; }
+ }
+ }
+ return true;
+ }
+
+ static void ApplyForward(Rule rule, int x, int y, int z, int[][] potentials, int t, int MX, int MY, Queue<(byte, int, int, int)> q, bool backwards)
+ {
+ byte[] a = backwards ? rule.binput : rule.output;
+ for (int dz = 0; dz < rule.IMZ; dz++)
+ {
+ int zdz = z + dz;
+ for (int dy = 0; dy < rule.IMY; dy++)
+ {
+ int ydy = y + dy;
+ for (int dx = 0; dx < rule.IMX; dx++)
+ {
+ int xdx = x + dx;
+ int idi = xdx + ydy * MX + zdz * MX * MY;
+ int di = dx + dy * rule.IMX + dz * rule.IMX * rule.IMY;
+ byte o = a[di];
+ if (o != 0xff && potentials[o][idi] == -1)
+ {
+ potentials[o][idi] = t + 1;
+ q.Enqueue((o, xdx, ydy, zdz));
+ }
+ }
+ }
+ }
+ }
+
+ public static bool IsGoalReached(byte[] present, int[] future)
+ {
+ for (int i = 0; i < present.Length; i++) if (((1 << present[i]) & future[i]) == 0) return false;
+ return true;
+ }
+
+ public static int ForwardPointwise(int[][] potentials, int[] future)
+ {
+ int sum = 0;
+ for (int i = 0; i < future.Length; i++)
+ {
+ int f = future[i];
+ int min = 1000, argmin = -1;
+ for (int c = 0; c < potentials.Length; c++, f >>= 1)
+ {
+ int potential = potentials[c][i];
+ if ((f & 1) == 1 && potential >= 0 && potential < min)
+ {
+ min = potential;
+ argmin = c;
+ }
+ }
+ if (argmin < 0) return -1;
+ sum += min;
+ }
+ return sum;
+ }
+
+ public static int BackwardPointwise(int[][] potentials, byte[] present)
+ {
+ int sum = 0;
+ for (int i = 0; i < present.Length; i++)
+ {
+ int potential = potentials[present[i]][i];
+ if (potential < 0) return -1;
+ sum += potential;
+ }
+ return sum;
+ }
+}
diff --git a/source/OneNode.cs b/source/OneNode.cs
new file mode 100644
index 0000000..5546d06
--- /dev/null
+++ b/source/OneNode.cs
@@ -0,0 +1,140 @@
+// Copyright (C) 2022 Maxim Gumin, The MIT License (MIT)
+
+using System;
+using System.Xml.Linq;
+using System.Collections.Generic;
+
+class OneNode : RuleNode
+{
+ override protected bool Load(XElement xelem, bool[] parentSymmetry, Grid grid)
+ {
+ if (!base.Load(xelem, parentSymmetry, grid)) return false;
+ matches = new List<(int, int, int, int)>();
+ matchMask = AH.Array2D(rules.Length, grid.state.Length, false);
+ return true;
+ }
+
+ override public void Reset()
+ {
+ base.Reset();
+ if (matchCount != 0)
+ {
+ matchMask.Set2D(false);
+ matchCount = 0;
+ }
+ }
+
+ void Apply(Rule rule, int x, int y, int z)
+ {
+ int MX = grid.MX, MY = grid.MY;
+ var changes = ip.changes;
+
+ for (int dz = 0; dz < rule.OMZ; dz++) for (int dy = 0; dy < rule.OMY; dy++) for (int dx = 0; dx < rule.OMX; dx++)
+ {
+ byte newValue = rule.output[dx + dy * rule.OMX + dz * rule.OMX * rule.OMY];
+ if (newValue != 0xff)
+ {
+ int sx = x + dx;
+ int sy = y + dy;
+ int sz = z + dz;
+ int si = sx + sy * MX + sz * MX * MY;
+ byte oldValue = grid.state[si];
+ if (newValue != oldValue)
+ {
+ grid.state[si] = newValue;
+ changes.Add((sx, sy, sz));
+ }
+ }
+ }
+ }
+
+ override public bool Go()
+ {
+ if (!base.Go()) return false;
+ lastMatchedTurn = ip.counter;
+
+ if (trajectory != null)
+ {
+ if (counter >= trajectory.Length) return false;
+ Array.Copy(trajectory[counter], grid.state, grid.state.Length);
+ counter++;
+ return true;
+ }
+
+ var (R, X, Y, Z) = RandomMatch(ip.random);
+ if (R < 0) return false;
+ else
+ {
+ last[R] = true;
+ Apply(rules[R], X, Y, Z);
+ counter++;
+ return true;
+ }
+ }
+
+ (int r, int x, int y, int z) RandomMatch(Random random)
+ {
+ if (potentials != null)
+ {
+ if (observations != null && Observation.IsGoalReached(grid.state, future))
+ {
+ futureComputed = false;
+ return (-1, -1, -1, -1);
+ }
+ double max = -1000.0;
+ int argmax = -1;
+
+ double firstHeuristic = 0.0;
+ bool firstHeuristicComputed = false;
+
+ for (int k = 0; k < matchCount; k++)
+ {
+ var (r, x, y, z) = matches[k];
+ int i = x + y * grid.MX + z * grid.MX * grid.MY;
+ if (!grid.Matches(rules[r], x, y, z))
+ {
+ matchMask[r][i] = false;
+ matches[k] = matches[matchCount - 1];
+ matchCount--;
+ k--;
+ }
+ else
+ {
+ double? heuristic = Field.DeltaPointwise(grid.state, rules[r], x, y, z, fields, potentials, grid.MX, grid.MY);
+ if (heuristic == null) continue;
+ double h = (double)heuristic;
+ if (!firstHeuristicComputed)
+ {
+ firstHeuristic = h;
+ firstHeuristicComputed = true;
+ }
+ double u = random.NextDouble();
+ double key = temperature > 0 ? Math.Pow(u, Math.Exp((h - firstHeuristic) / temperature)) : -h + 0.001 * u;
+ if (key > max)
+ {
+ max = key;
+ argmax = k;
+ }
+ }
+ }
+ return argmax >= 0 ? matches[argmax] : (-1, -1, -1, -1);
+ }
+ else
+ {
+ while (matchCount > 0)
+ {
+ int matchIndex = random.Next(matchCount);
+
+ var (r, x, y, z) = matches[matchIndex];
+ int i = x + y * grid.MX + z * grid.MX * grid.MY;
+
+ matchMask[r][i] = false;
+ matches[matchIndex] = matches[matchCount - 1];
+ matchCount--;
+
+ if (grid.Matches(rules[r], x, y, z)) return (r, x, y, z);
+ }
+ return (-1, -1, -1, -1);
+ }
+ }
+}
diff --git a/source/OverlapModel.cs b/source/OverlapModel.cs
new file mode 100644
index 0000000..8db4d9a
--- /dev/null
+++ b/source/OverlapModel.cs
@@ -0,0 +1,199 @@
+// Copyright (C) 2022 Maxim Gumin, The MIT License (MIT)
+
+using System;
+using System.Linq;
+using System.Xml.Linq;
+using System.Collections.Generic;
+
+class OverlapNode : WFCNode
+{
+ byte[][] patterns;
+
+ override protected bool Load(XElement xelem, bool[] parentSymmetry, Grid grid)
+ {
+ if (grid.MZ != 1)
+ {
+ Interpreter.WriteLine("overlapping model currently works only for 2d");
+ return false;
+ }
+ N = xelem.Get("n", 3);
+
+ string symmetryString = xelem.Get("symmetry", null);
+ bool[] symmetry = SymmetryHelper.GetSymmetry(true, symmetryString, parentSymmetry);
+ if (symmetry == null)
+ {
+ Interpreter.WriteLine($"unknown symmetry {symmetryString} at line {xelem.LineNumber()}");
+ return false;
+ }
+
+ bool periodicInput = xelem.Get("periodicInput", true);
+
+ newgrid = Grid.Load(xelem, grid.MX, grid.MY, grid.MZ);
+ if (newgrid == null) return false;
+ periodic = true;
+
+ /*string*/ name = xelem.Get("sample");
+ (int[] bitmap, int SMX, int SMY, _) = Graphics.LoadBitmap($"resources/samples/{name}.png");
+ if (bitmap == null)
+ {
+ Interpreter.WriteLine($"couldn't read sample {name}");
+ return false;
+ }
+ (byte[] sample, int C) = bitmap.Ords();
+ if (C > newgrid.C)
+ {
+ Interpreter.WriteLine($"there were more than {newgrid.C} colors in the sample");
+ return false;
+ }
+ long W = Helper.Power(C, N * N);
+
+ byte[] pattern(Func f)
+ {
+ byte[] result = new byte[N * N];
+ for (int y = 0; y < N; y++) for (int x = 0; x < N; x++) result[x + y * N] = f(x, y);
+ return result;
+ };
+
+ byte[] patternFromSample(int x, int y) => pattern((dx, dy) => sample[(x + dx) % SMX + ((y + dy) % SMY) * SMX]);
+ byte[] rotate(byte[] p) => pattern((x, y) => p[N - 1 - y + x * N]);
+ byte[] reflect(byte[] p) => pattern((x, y) => p[N - 1 - x + y * N]);
+
+ byte[] patternFromIndex(long ind)
+ {
+ long residue = ind, power = W;
+ byte[] result = new byte[N * N];
+ for (int i = 0; i < result.Length; i++)
+ {
+ power /= C;
+ int count = 0;
+ while (residue >= power)
+ {
+ residue -= power;
+ count++;
+ }
+ result[i] = (byte)count;
+ }
+ return result;
+ };
+
+ Dictionary weights = new();
+ List ordering = new();
+
+ int ymax = periodicInput ? grid.MY : grid.MY - N + 1;
+ int xmax = periodicInput ? grid.MX : grid.MX - N + 1;
+ for (int y = 0; y < ymax; y++) for (int x = 0; x < xmax; x++)
+ {
+ byte[][] ps = new byte[8][];
+
+ ps[0] = patternFromSample(x, y);
+ ps[1] = reflect(ps[0]);
+ ps[2] = rotate(ps[0]);
+ ps[3] = reflect(ps[2]);
+ ps[4] = rotate(ps[2]);
+ ps[5] = reflect(ps[4]);
+ ps[6] = rotate(ps[4]);
+ ps[7] = reflect(ps[6]);
+
+ for (int k = 0; k < 8; k++) if (symmetry[k])
+ {
+ long ind = ps[k].Index(C);
+ if (weights.ContainsKey(ind)) weights[ind]++;
+ else
+ {
+ weights.Add(ind, 1);
+ ordering.Add(ind);
+ }
+ }
+ }
+
+ P = weights.Count;
+ Console.WriteLine($"number of patterns P = {P}");
+
+ patterns = new byte[P][];
+ base.weights = new double[P];
+ int counter = 0;
+ foreach (long w in ordering)
+ {
+ patterns[counter] = patternFromIndex(w);
+ base.weights[counter] = weights[w];
+ counter++;
+ }
+
+ bool agrees(byte[] p1, byte[] p2, int dx, int dy)
+ {
+ int xmin = dx < 0 ? 0 : dx, xmax = dx < 0 ? dx + N : N, ymin = dy < 0 ? 0 : dy, ymax = dy < 0 ? dy + N : N;
+ for (int y = ymin; y < ymax; y++) for (int x = xmin; x < xmax; x++) if (p1[x + N * y] != p2[x - dx + N * (y - dy)]) return false;
+ return true;
+ };
+
+ propagator = new int[4][][];
+ for (int d = 0; d < 4; d++)
+ {
+ propagator[d] = new int[P][];
+ for (int t = 0; t < P; t++)
+ {
+ List list = new();
+ for (int t2 = 0; t2 < P; t2++) if (agrees(patterns[t], patterns[t2], DX[d], DY[d])) list.Add(t2);
+ propagator[d][t] = new int[list.Count];
+ for (int c = 0; c < list.Count; c++) propagator[d][t][c] = list[c];
+ }
+ }
+
+ map = new Dictionary();
+ foreach (XElement xrule in xelem.Elements("rule"))
+ {
+ char input = xrule.Get("in");
+ byte[] outputs = xrule.Get("out").Split('|').Select(s => newgrid.values[s[0]]).ToArray();
+ bool[] position = Enumerable.Range(0, P).Select(t => outputs.Contains(patterns[t][0])).ToArray();
+ map.Add(grid.values[input], position);
+ }
+ if (!map.ContainsKey(0)) map.Add(0, Enumerable.Repeat(true, P).ToArray());
+
+ return base.Load(xelem, parentSymmetry, grid);
+ }
+
+ protected override void UpdateState()
+ {
+ int MX = newgrid.MX, MY = newgrid.MY;
+ int[][] votes = AH.Array2D(newgrid.state.Length, newgrid.C, 0);
+ for (int i = 0; i < wave.data.Length; i++)
+ {
+ bool[] w = wave.data[i];
+ int x = i % MX, y = i / MX;
+ for (int p = 0; p < P; p++) if (w[p])
+ {
+ byte[] pattern = patterns[p];
+ for (int dy = 0; dy < N; dy++)
+ {
+ int ydy = y + dy;
+ if (ydy >= MY) ydy -= MY;
+ for (int dx = 0; dx < N; dx++)
+ {
+ int xdx = x + dx;
+ if (xdx >= MX) xdx -= MX;
+ byte value = pattern[dx + dy * N];
+ votes[xdx + ydy * MX][value]++;
+ }
+ }
+ }
+ }
+
+ Random r = new();
+ for (int i = 0; i < votes.Length; i++)
+ {
+ double max = -1.0;
+ byte argmax = 0xff;
+ int[] v = votes[i];
+ for (byte c = 0; c < v.Length; c++)
+ {
+ double value = v[c] + 0.1 * r.NextDouble();
+ if (value > max)
+ {
+ argmax = c;
+ max = value;
+ }
+ }
+ newgrid.state[i] = argmax;
+ }
+ }
+}
diff --git a/source/ParallelNode.cs b/source/ParallelNode.cs
new file mode 100644
index 0000000..edce793
--- /dev/null
+++ b/source/ParallelNode.cs
@@ -0,0 +1,50 @@
+// Copyright (C) 2022 Maxim Gumin, The MIT License (MIT)
+
+using System.Xml.Linq;
+
+class ParallelNode : RuleNode
+{
+ byte[] newstate;
+
+ override protected bool Load(XElement xelem, bool[] parentSymmetry, Grid grid)
+ {
+ if (!base.Load(xelem, parentSymmetry, grid)) return false;
+ newstate = new byte[grid.state.Length];
+ return true;
+ }
+
+ override protected void Add(int r, int x, int y, int z, bool[] maskr)
+ {
+ Rule rule = rules[r];
+ if (ip.random.NextDouble() > rule.p) return;
+ last[r] = true;
+ int MX = grid.MX, MY = grid.MY;
+
+ for (int dz = 0; dz < rule.OMZ; dz++) for (int dy = 0; dy < rule.OMY; dy++) for (int dx = 0; dx < rule.OMX; dx++)
+ {
+ byte newvalue = rule.output[dx + dy * rule.OMX + dz * rule.OMX * rule.OMY];
+ int idi = x + dx + (y + dy) * MX + (z + dz) * MX * MY;
+ if (newvalue != 0xff && newvalue != grid.state[idi])
+ {
+ newstate[idi] = newvalue;
+ ip.changes.Add((x + dx, y + dy, z + dz));
+ }
+ }
+ matchCount++;
+ }
+
+ public override bool Go()
+ {
+ if (!base.Go()) return false;
+
+ for (int n = ip.first[ip.counter]; n < ip.changes.Count; n++)
+ {
+ var (x, y, z) = ip.changes[n];
+ int i = x + y * grid.MX + z * grid.MX * grid.MY;
+ grid.state[i] = newstate[i];
+ }
+
+ counter++;
+ return matchCount > 0;
+ }
+}
diff --git a/source/Path.cs b/source/Path.cs
new file mode 100644
index 0000000..09ff285
--- /dev/null
+++ b/source/Path.cs
@@ -0,0 +1,228 @@
+// Copyright (C) 2022 Maxim Gumin, The MIT License (MIT)
+
+using System;
+using System.Linq;
+using System.Xml.Linq;
+using System.Collections.Generic;
+
+class PathNode : Node
+{
+ public int start, finish, substrate;
+ public byte value;
+ bool inertia, longest, edges, vertices;
+
+ override protected bool Load(XElement xelem, bool[] parentSymmetry, Grid grid)
+ {
+ string startSymbols = xelem.Get("from");
+ start = grid.Wave(startSymbols);
+ value = grid.values[xelem.Get("color", startSymbols[0])];
+ finish = grid.Wave(xelem.Get("to"));
+ inertia = xelem.Get("inertia", false);
+ longest = xelem.Get("longest", false);
+ edges = xelem.Get("edges", false);
+ vertices = xelem.Get("vertices", false);
+ substrate = grid.Wave(xelem.Get("on"));
+ return true;
+ }
+
+ public override void Reset() { }
+ public override bool Go()
+ {
+ Queue<(int, int, int, int)> frontier = new();
+ List<(int x, int y, int z)> startPositions = new();
+ int[] generations = AH.Array1D(grid.state.Length, -1);
+ int MX = grid.MX, MY = grid.MY, MZ = grid.MZ;
+
+ for (int z = 0; z < MZ; z++) for (int y = 0; y < MY; y++) for (int x = 0; x < MX; x++)
+ {
+ int i = x + y * MX + z * MX * MY;
+ generations[i] = -1;
+
+ byte s = grid.state[i];
+ if ((start & 1 << s) != 0) startPositions.Add((x, y, z));
+ if ((finish & 1 << s) != 0)
+ {
+ generations[i] = 0;
+ frontier.Enqueue((0, x, y, z));
+ }
+ }
+
+ if (!startPositions.Any() || !frontier.Any()) return false;
+
+ void push(int t, int x, int y, int z)
+ {
+ int i = x + y * MX + z * MX * MY;
+ byte v = grid.state[i];
+ if (generations[i] == -1 && ((substrate & 1 << v) != 0 || (start & 1 << v) != 0))
+ {
+ if ((substrate & 1 << v) != 0) frontier.Enqueue((t, x, y, z));
+ generations[i] = t;
+ }
+ };
+
+ while (frontier.Any())
+ {
+ var (t, x, y, z) = frontier.Dequeue();
+ foreach (var (dx, dy, dz) in Directions(x, y, z, MX, MY, MZ, edges, vertices)) push(t + 1, x + dx, y + dy, z + dz);
+ }
+
+ if (!startPositions.Where(p => generations[p.x + p.y * MX + p.z * MX * MY] > 0).Any()) return false;
+
+ Random localRandom = new(ip.random.Next());
+ double min = MX * MY * MZ, max = -2;
+ (int, int, int) argmin = (-1, -1, -1), argmax = (-1, -1, -1);
+
+ foreach (var p in startPositions)
+ {
+ int g = generations[p.x + p.y * MX + p.z * MX * MY];
+ if (g == -1) continue;
+ double dg = g;
+ double noise = 0.1 * localRandom.NextDouble();
+
+ if (dg + noise < min)
+ {
+ min = dg + noise;
+ argmin = p;
+ }
+
+ if (dg + noise > max)
+ {
+ max = dg + noise;
+ argmax = p;
+ }
+ }
+
+ var (penx, peny, penz) = longest ? argmax : argmin;
+ var (dirx, diry, dirz) = Direction(penx, peny, penz, 0, 0, 0, generations, localRandom);
+ penx += dirx;
+ peny += diry;
+ penz += dirz;
+
+ while (generations[penx + peny * MX + penz * MX * MY] != 0)
+ {
+ grid.state[penx + peny * MX + penz * MX * MY] = value;
+ ip.changes.Add((penx, peny, penz));
+ (dirx, diry, dirz) = Direction(penx, peny, penz, dirx, diry, dirz, generations, localRandom);
+ penx += dirx;
+ peny += diry;
+ penz += dirz;
+ }
+ return true;
+ }
+
+ (int, int, int) Direction(int x, int y, int z, int dx, int dy, int dz, int[] generations, Random random)
+ {
+ List<(int x, int y, int z)> candidates = new();
+ int MX = grid.MX, MY = grid.MY, MZ = grid.MZ;
+ int g = generations[x + y * MX + z * MX * MY];
+
+ void add(int DX, int DY, int DZ)
+ {
+ if (generations[x + DX + (y + DY) * MX + (z + DZ) * MX * MY] == g - 1) candidates.Add((DX, DY, DZ));
+ };
+
+ if (!vertices && !edges)
+ {
+ if (dx != 0 || dy != 0 || dz != 0)
+ {
+ int cx = x + dx, cy = y + dy, cz = z + dz;
+ if (inertia && cx >= 0 && cy >= 0 && cz >= 0 && cx < MX && cy < MY && cz < MZ && generations[cx + cy * MX + cz * MX * MY] == g - 1)
+ return (dx, dy, dz);
+ }
+
+ if (x > 0) add(-1, 0, 0);
+ if (x < MX - 1) add(1, 0, 0);
+ if (y > 0) add(0, -1, 0);
+ if (y < MY - 1) add(0, 1, 0);
+ if (z > 0) add(0, 0, -1);
+ if (z < MZ - 1) add(0, 0, 1);
+
+ return candidates.Random(random);
+ }
+ else
+ {
+ foreach (var p in Directions(x, y, z, MX, MY, MZ, edges, vertices)) add(p.x, p.y, p.z);
+ (int, int, int) result = (-1, -1, -1);
+
+ if (inertia && (dx != 0 || dy != 0 || dz != 0))
+ {
+ double maxScalar = -4;
+ foreach (var c in candidates)
+ {
+ double noise = 0.1 * random.NextDouble();
+ double cos = (c.x * dx + c.y * dy + c.z * dz) / Math.Sqrt((c.x * c.x + c.y * c.y + c.z * c.z) * (dx * dx + dy * dy + dz * dz));
+
+ if (cos + noise > maxScalar)
+ {
+ maxScalar = cos + noise;
+ result = c;
+ }
+ }
+ }
+ else result = candidates.Random(random);
+
+ return result;
+ }
+ }
+
+ static List<(int x, int y, int z)> Directions(int x, int y, int z, int MX, int MY, int MZ, bool edges, bool vertices)
+ {
+ List<(int, int, int)> result = new();
+ if (MZ == 1)
+ {
+ if (x > 0) result.Add((-1, 0, 0));
+ if (x < MX - 1) result.Add((1, 0, 0));
+ if (y > 0) result.Add((0, -1, 0));
+ if (y < MY - 1) result.Add((0, 1, 0));
+
+ if (edges)
+ {
+ if (x > 0 && y > 0) result.Add((-1, -1, 0));
+ if (x > 0 && y < MY - 1) result.Add((-1, 1, 0));
+ if (x < MX - 1 && y > 0) result.Add((1, -1, 0));
+ if (x < MX - 1 && y < MY - 1) result.Add((1, 1, 0));
+ }
+ }
+ else
+ {
+ if (x > 0) result.Add((-1, 0, 0));
+ if (x < MX - 1) result.Add((1, 0, 0));
+ if (y > 0) result.Add((0, -1, 0));
+ if (y < MY - 1) result.Add((0, 1, 0));
+ if (z > 0) result.Add((0, 0, -1));
+ if (z < MZ - 1) result.Add((0, 0, 1));
+
+ if (edges)
+ {
+ if (x > 0 && y > 0) result.Add((-1, -1, 0));
+ if (x > 0 && y < MY - 1) result.Add((-1, 1, 0));
+ if (x < MX - 1 && y > 0) result.Add((1, -1, 0));
+ if (x < MX - 1 && y < MY - 1) result.Add((1, 1, 0));
+
+ if (x > 0 && z > 0) result.Add((-1, 0, -1));
+ if (x > 0 && z < MZ - 1) result.Add((-1, 0, 1));
+ if (x < MX - 1 && z > 0) result.Add((1, 0, -1));
+ if (x < MX - 1 && z < MZ - 1) result.Add((1, 0, 1));
+
+ if (y > 0 && z > 0) result.Add((0, -1, -1));
+ if (y > 0 && z < MZ - 1) result.Add((0, -1, 1));
+ if (y < MY - 1 && z > 0) result.Add((0, 1, -1));
+ if (y < MY - 1 && z < MZ - 1) result.Add((0, 1, 1));
+ }
+
+ if (vertices)
+ {
+ if (x > 0 && y > 0 && z > 0) result.Add((-1, -1, -1));
+ if (x > 0 && y > 0 && z < MZ - 1) result.Add((-1, -1, 1));
+ if (x > 0 && y < MY - 1 && z > 0) result.Add((-1, 1, -1));
+ if (x > 0 && y < MY - 1 && z < MZ - 1) result.Add((-1, 1, 1));
+ if (x < MX - 1 && y > 0 && z > 0) result.Add((1, -1, -1));
+ if (x < MX - 1 && y > 0 && z < MZ - 1) result.Add((1, -1, 1));
+ if (x < MX - 1 && y < MY - 1 && z > 0) result.Add((1, 1, -1));
+ if (x < MX - 1 && y < MY - 1 && z < MZ - 1) result.Add((1, 1, 1));
+ }
+ }
+
+ return result;
+ }
+}
diff --git a/source/Program.cs b/source/Program.cs
new file mode 100644
index 0000000..342af33
--- /dev/null
+++ b/source/Program.cs
@@ -0,0 +1,77 @@
+// Copyright (C) 2022 Maxim Gumin, The MIT License (MIT)
+
+using System;
+using System.Linq;
+using System.Xml.Linq;
+using System.Diagnostics;
+using System.Collections.Generic;
+
+static class Program
+{
+ static void Main()
+ {
+ Stopwatch sw = Stopwatch.StartNew();
+ var folder = System.IO.Directory.CreateDirectory("output");
+ foreach (var file in folder.GetFiles()) file.Delete();
+
+ Dictionary palette = XDocument.Load("resources/palette.xml").Root.Elements("color").ToDictionary(x => x.Get("symbol"), x => Convert.ToInt32(x.Get("value"), 16) + (255 << 24));
+
+ Random meta = new();
+ XDocument xdoc = XDocument.Load("models.xml", LoadOptions.SetLineInfo);
+ foreach (XElement xmodel in xdoc.Root.Elements("model"))
+ {
+ string name = xmodel.Get("name");
+ int linearSize = xmodel.Get("size", -1);
+ int dimension = xmodel.Get("d", 2);
+ int MX = xmodel.Get("length", linearSize);
+ int MY = xmodel.Get("width", linearSize);
+ int MZ = xmodel.Get("height", dimension == 2 ? 1 : linearSize);
+
+ Console.Write($"{name} > ");
+ string filename = $"models/{name}.xml";
+ XDocument modeldoc;
+ try { modeldoc = XDocument.Load(filename, LoadOptions.SetLineInfo); }
+ catch (Exception)
+ {
+ Console.WriteLine($"ERROR: couldn't open xml file {filename}");
+ continue;
+ }
+
+ Interpreter interpreter = Interpreter.Load(modeldoc.Root, MX, MY, MZ);
+ if (interpreter == null)
+ {
+ Console.WriteLine("ERROR");
+ continue;
+ }
+
+ int amount = xmodel.Get("amount", 2);
+ int pixelsize = xmodel.Get("pixelsize", 4);
+ string seedString = xmodel.Get("seeds", null);
+ int[] seeds = seedString?.Split(' ').Select(s => int.Parse(s)).ToArray();
+ bool gif = xmodel.Get("gif", false);
+ bool iso = xmodel.Get("iso", false);
+ int steps = xmodel.Get("steps", gif ? 1000 : 50000);
+ int gui = xmodel.Get("gui", 0);
+ if (gif) amount = 1;
+
+ for (int k = 0; k < amount; k++)
+ {
+ int seed = seeds != null && k < seeds.Length ? seeds[k] : meta.Next();
+ foreach ((byte[] result, char[] legend, int FX, int FY, int FZ) in interpreter.Run(seed, steps, gif))
+ {
+ int[] colors = legend.Select(ch => palette[ch]).ToArray();
+ string outputname = gif ? $"output/{interpreter.counter}" : $"output/{name}_{seed}";
+ if (FZ == 1 || iso)
+ {
+ var (bitmap, WIDTH, HEIGHT) = Graphics.Render(result, FX, FY, FZ, colors, pixelsize, gui);
+ if (gui > 0) GUI.Draw(name, interpreter.root, interpreter.current, bitmap, WIDTH, HEIGHT, palette);
+ Graphics.SaveBitmap(bitmap, WIDTH, HEIGHT, outputname + ".png");
+ }
+ else VoxHelper.SaveVox(result, (byte)FX, (byte)FY, (byte)FZ, colors, outputname + ".vox");
+ }
+ Console.WriteLine("DONE");
+ }
+ }
+ Console.WriteLine($"time = {sw.ElapsedMilliseconds}");
+ }
+}
diff --git a/source/Rule.cs b/source/Rule.cs
new file mode 100644
index 0000000..29c5053
--- /dev/null
+++ b/source/Rule.cs
@@ -0,0 +1,283 @@
+// Copyright (C) 2022 Maxim Gumin, The MIT License (MIT)
+
+using System.Linq;
+using System.Xml.Linq;
+using System.Collections.Generic;
+
+class Rule
+{
+ public int IMX, IMY, IMZ, OMX, OMY, OMZ;
+ public int[] input;
+ public byte[] output, binput;
+
+ public double p;
+ public (int, int, int)[][] ishifts, oshifts;
+
+ public bool original;
+
+ public Rule(int[] input, int IMX, int IMY, int IMZ, byte[] output, int OMX, int OMY, int OMZ, int C, double p)
+ {
+ this.input = input;
+ this.output = output;
+ this.IMX = IMX;
+ this.IMY = IMY;
+ this.IMZ = IMZ;
+ this.OMX = OMX;
+ this.OMY = OMY;
+ this.OMZ = OMZ;
+
+ this.p = p;
+
+ List<(int, int, int)>[] lists = new List<(int, int, int)>[C];
+ for (int c = 0; c < C; c++) lists[c] = new List<(int, int, int)>();
+ for (int z = 0; z < IMZ; z++) for (int y = 0; y < IMY; y++) for (int x = 0; x < IMX; x++)
+ {
+ int w = input[x + y * IMX + z * IMX * IMY];
+ for (int c = 0; c < C; c++, w >>= 1) if ((w & 1) == 1) lists[c].Add((x, y, z));
+ }
+ ishifts = new (int, int, int)[C][];
+ for (int c = 0; c < C; c++) ishifts[c] = lists[c].ToArray();
+
+ if (OMX == IMX && OMY == IMY && OMZ == IMZ)
+ {
+ for (int c = 0; c < C; c++) lists[c].Clear();
+ for (int z = 0; z < OMZ; z++) for (int y = 0; y < OMY; y++) for (int x = 0; x < OMX; x++)
+ {
+ byte o = output[x + y * OMX + z * OMX * OMY];
+ if (o != 0xff) lists[o].Add((x, y, z));
+ else for (int c = 0; c < C; c++) lists[c].Add((x, y, z));
+ }
+ oshifts = new (int, int, int)[C][];
+ for (int c = 0; c < C; c++) oshifts[c] = lists[c].ToArray();
+ }
+
+ int wildcard = (1 << C) - 1;
+ binput = new byte[input.Length];
+ for (int i = 0; i < input.Length; i++)
+ {
+ int w = input[i];
+ binput[i] = w == wildcard ? (byte)0xff : Helper.FirstNonZeroPosition(w);
+ }
+ }
+
+ public Rule ZRotated()
+ {
+ int[] newinput = new int[input.Length];
+ for (int z = 0; z < IMZ; z++) for (int y = 0; y < IMX; y++) for (int x = 0; x < IMY; x++)
+ newinput[x + y * IMY + z * IMX * IMY] = input[IMX - 1 - y + x * IMX + z * IMX * IMY];
+
+ byte[] newoutput = new byte[output.Length];
+ for (int z = 0; z < OMZ; z++) for (int y = 0; y < OMX; y++) for (int x = 0; x < OMY; x++)
+ newoutput[x + y * OMY + z * OMX * OMY] = output[OMX - 1 - y + x * OMX + z * OMX * OMY];
+
+ return new Rule(newinput, IMY, IMX, IMZ, newoutput, OMY, OMX, OMZ, ishifts.Length, p);
+ }
+
+ public Rule YRotated()
+ {
+ int[] newinput = new int[input.Length];
+ for (int z = 0; z < IMX; z++) for (int y = 0; y < IMY; y++) for (int x = 0; x < IMZ; x++)
+ newinput[x + y * IMZ + z * IMZ * IMY] = input[IMX - 1 - z + y * IMX + x * IMX * IMY];
+
+ byte[] newoutput = new byte[output.Length];
+ for (int z = 0; z < OMX; z++) for (int y = 0; y < OMY; y++) for (int x = 0; x < OMZ; x++)
+ newoutput[x + y * OMZ + z * OMZ * OMY] = output[OMX - 1 - z + y * OMX + x * OMX * OMY];
+
+ return new Rule(newinput, IMZ, IMY, IMX, newoutput, OMZ, OMY, OMX, ishifts.Length, p);
+ }
+
+ public Rule Reflected()
+ {
+ int[] newinput = new int[input.Length];
+ for (int z = 0; z < IMZ; z++) for (int y = 0; y < IMY; y++) for (int x = 0; x < IMX; x++)
+ newinput[x + y * IMX + z * IMX * IMY] = input[IMX - 1 - x + y * IMX + z * IMX * IMY];
+
+ byte[] newoutput = new byte[output.Length];
+ for (int z = 0; z < OMZ; z++) for (int y = 0; y < OMY; y++) for (int x = 0; x < OMX; x++)
+ newoutput[x + y * OMX + z * OMX * OMY] = output[OMX - 1 - x + y * OMX + z * OMX * OMY];
+
+ return new Rule(newinput, IMX, IMY, IMZ, newoutput, OMX, OMY, OMZ, ishifts.Length, p);
+ }
+
+ public static bool Same(Rule a1, Rule a2)
+ {
+ if (a1.IMX != a2.IMX || a1.IMY != a2.IMY || a1.IMZ != a2.IMZ || a1.OMX != a2.OMX || a1.OMY != a2.OMY || a1.OMZ != a2.OMZ) return false;
+ for (int i = 0; i < a1.IMX * a1.IMY * a1.IMZ; i++) if (a1.input[i] != a2.input[i]) return false;
+ for (int i = 0; i < a1.OMX * a1.OMY * a1.OMZ; i++) if (a1.output[i] != a2.output[i]) return false;
+ return true;
+ }
+
+ public IEnumerable Symmetries(bool[] symmetry, bool d2)
+ {
+ if (d2) return SymmetryHelper.SquareSymmetries(this, r => r.ZRotated(), r => r.Reflected(), Same, symmetry);
+ else return SymmetryHelper.CubeSymmetries(this, r => r.ZRotated(), r => r.YRotated(), r => r.Reflected(), Same, symmetry);
+ }
+
+ public static (char[] data, int MX, int MY, int MZ) LoadResource(string filename, string legend, bool d2)
+ {
+ if (legend == null)
+ {
+ Interpreter.WriteLine($"no legend for {filename}");
+ return (null, -1, -1, -1);
+ }
+ (int[] data, int MX, int MY, int MZ) = d2 ? Graphics.LoadBitmap(filename) : VoxHelper.LoadVox(filename);
+ if (data == null)
+ {
+ Interpreter.WriteLine($"couldn't read {filename}");
+ return (null, MX, MY, MZ);
+ }
+ (byte[] ords, int amount) = data.Ords();
+ if (amount > legend.Length)
+ {
+ Interpreter.WriteLine($"the amount of colors {amount} in {filename} is more than {legend.Length}");
+ return (null, MX, MY, MZ);
+ }
+ return (ords.Select(o => legend[o]).ToArray(), MX, MY, MZ);
+ }
+
+ static (char[], int, int, int) Parse(string s)
+ {
+ string[][] lines = Helper.Split(s, ' ', '/');
+ int MX = lines[0][0].Length;
+ int MY = lines[0].Length;
+ int MZ = lines.Length;
+ char[] result = new char[MX * MY * MZ];
+
+ for (int z = 0; z < MZ; z++)
+ {
+ string[] linesz = lines[MZ - 1 - z];
+ if (linesz.Length != MY)
+ {
+ Interpreter.Write("non-rectangular pattern");
+ return (null, -1, -1, -1);
+ }
+ for (int y = 0; y < MY; y++)
+ {
+ string lineszy = linesz[y];
+ if (lineszy.Length != MX)
+ {
+ Interpreter.Write("non-rectangular pattern");
+ return (null, -1, -1, -1);
+ }
+ for (int x = 0; x < MX; x++) result[x + y * MX + z * MX * MY] = lineszy[x];
+ }
+ }
+
+ return (result, MX, MY, MZ);
+ }
+
+ public static Rule Load(XElement xelem, Grid gin, Grid gout)
+ {
+ int lineNumber = xelem.LineNumber();
+ string filepath(string name)
+ {
+ string result = "resources/rules/";
+ if (gout.folder != null) result += gout.folder + "/";
+ result += name;
+ result += gin.MZ == 1 ? ".png" : ".vox";
+ return result;
+ };
+
+ string inString = xelem.Get("in", null);
+ string outString = xelem.Get("out", null);
+ string finString = xelem.Get("fin", null);
+ string foutString = xelem.Get("fout", null);
+ string fileString = xelem.Get("file", null);
+ string legend = xelem.Get("legend", null);
+
+ char[] inRect, outRect;
+ int IMX = -1, IMY = -1, IMZ = -1, OMX = -1, OMY = -1, OMZ = -1;
+ if (fileString == null)
+ {
+ if (inString == null && finString == null)
+ {
+ Interpreter.WriteLine($"no input in a rule at line {lineNumber}");
+ return null;
+ }
+ if (outString == null && foutString == null)
+ {
+ Interpreter.WriteLine($"no output in a rule at line {lineNumber}");
+ return null;
+ }
+
+ (inRect, IMX, IMY, IMZ) = inString != null ? Parse(inString) : LoadResource(filepath(finString), legend, gin.MZ == 1);
+ if (inRect == null)
+ {
+ Interpreter.WriteLine($" in input at line {lineNumber}");
+ return null;
+ }
+
+ (outRect, OMX, OMY, OMZ) = outString != null ? Parse(outString) : LoadResource(filepath(foutString), legend, gin.MZ == 1);
+ if (outRect == null)
+ {
+ Interpreter.WriteLine($" in output at line {lineNumber}");
+ return null;
+ }
+
+ if (gin == gout && (OMZ != IMZ || OMY != IMY || OMX != IMX))
+ {
+ Interpreter.WriteLine($"non-matching pattern sizes at line {lineNumber}");
+ return null;
+ }
+ }
+ else
+ {
+ if (inString != null || finString != null || outString != null || foutString != null)
+ {
+ Interpreter.WriteLine($"rule at line {lineNumber} already contains a file attribute");
+ return null;
+ }
+ (char[] rect, int FX, int FY, int FZ) = LoadResource(filepath(fileString), legend, gin.MZ == 1);
+ if (rect == null)
+ {
+ Interpreter.WriteLine($" in a rule at line {lineNumber}");
+ return null;
+ }
+ if (FX % 2 != 0)
+ {
+ Interpreter.WriteLine($"odd width {FX} in {fileString}");
+ return null;
+ }
+
+ IMX = OMX = FX / 2;
+ IMY = OMY = FY;
+ IMZ = OMZ = FZ;
+
+ inRect = AH.FlatArray3D(FX / 2, FY, FZ, (x, y, z) => rect[x + y * FX + z * FX * FY]);
+ outRect = AH.FlatArray3D(FX / 2, FY, FZ, (x, y, z) => rect[x + FX / 2 + y * FX + z * FX * FY]);
+ }
+
+ int[] input = new int[inRect.Length];
+ for (int i = 0; i < inRect.Length; i++)
+ {
+ char c = inRect[i];
+ bool success = gin.waves.TryGetValue(c, out int value);
+ if (!success)
+ {
+ Interpreter.WriteLine($"input code {c} at line {lineNumber} is not found in codes");
+ return null;
+ }
+ input[i] = value;
+ }
+
+ byte[] output = new byte[outRect.Length];
+ for (int o = 0; o < outRect.Length; o++)
+ {
+ char c = outRect[o];
+ if (c == '*') output[o] = 0xff;
+ else
+ {
+ bool success = gout.values.TryGetValue(c, out byte value);
+ if (!success)
+ {
+ Interpreter.WriteLine($"output code {c} at line {lineNumber} is not found in codes");
+ return null;
+ }
+ output[o] = value;
+ }
+ }
+
+ double p = xelem.Get("p", 1.0);
+ return new Rule(input, IMX, IMY, IMZ, output, OMX, OMY, OMZ, gin.C, p);
+ }
+}
diff --git a/source/RuleNode.cs b/source/RuleNode.cs
new file mode 100644
index 0000000..51e63e0
--- /dev/null
+++ b/source/RuleNode.cs
@@ -0,0 +1,210 @@
+// Copyright (C) 2022 Maxim Gumin, The MIT License (MIT)
+
+using System;
+using System.Linq;
+using System.Xml.Linq;
+using System.Collections.Generic;
+
+abstract class RuleNode : Node
+{
+ public Rule[] rules;
+ public int counter, steps;
+
+ protected List<(int, int, int, int)> matches;
+ protected int matchCount, lastMatchedTurn;
+ protected bool[][] matchMask;
+
+ protected int[][] potentials;
+ public Field[] fields;
+ protected Observation[] observations;
+ protected double temperature;
+
+ protected bool search, futureComputed;
+ protected int[] future;
+ protected byte[][] trajectory;
+
+ int limit;
+ double depthCoefficient;
+
+ public bool[] last;
+
+ override protected bool Load(XElement xelem, bool[] parentSymmetry, Grid grid)
+ {
+ string symmetryString = xelem.Get("symmetry", null);
+ bool[] symmetry = SymmetryHelper.GetSymmetry(grid.MZ == 1, symmetryString, parentSymmetry);
+ if (symmetry == null)
+ {
+ Interpreter.WriteLine($"unknown symmetry {symmetryString} at line {xelem.LineNumber()}");
+ return false;
+ }
+
+ List ruleList = new();
+ XElement[] xrules = xelem.Elements("rule").ToArray();
+ XElement[] ruleElements = xrules.Length > 0 ? xrules : new XElement[] { xelem };
+ foreach (XElement xrule in ruleElements)
+ {
+ Rule rule = Rule.Load(xrule, grid, grid);
+ if (rule == null) return false;
+ rule.original = true;
+
+ string ruleSymmetryString = xrule.Get("symmetry", null);
+ bool[] ruleSymmetry = SymmetryHelper.GetSymmetry(grid.MZ == 1, ruleSymmetryString, symmetry);
+ if (ruleSymmetry == null)
+ {
+ Interpreter.WriteLine($"unknown symmetry {ruleSymmetryString} at line {xrule.LineNumber()}");
+ return false;
+ }
+ foreach (Rule r in rule.Symmetries(ruleSymmetry, grid.MZ == 1)) ruleList.Add(r);
+ }
+ rules = ruleList.ToArray();
+ last = new bool[rules.Length];
+
+ steps = xelem.Get("steps", 0);
+
+ temperature = xelem.Get("temperature", 0.0);
+ var xfields = xelem.Elements("field");
+ if (xfields.Any())
+ {
+ fields = new Field[grid.C];
+ foreach (XElement xfield in xfields) fields[grid.values[xfield.Get("for")]] = new Field(xfield, grid);
+ potentials = AH.Array2D(grid.C, grid.state.Length, 0);
+ }
+
+ var xobservations = xelem.Elements("observe");
+ if (xobservations.Any())
+ {
+ observations = new Observation[grid.C];
+ foreach (var x in xobservations)
+ {
+ byte value = grid.values[x.Get("value")];
+ observations[value] = new Observation(x.Get("from", grid.characters[value]), x.Get("to"), grid);
+ }
+
+ search = xelem.Get("search", false);
+ if (search)
+ {
+ limit = xelem.Get("limit", -1);
+ depthCoefficient = xelem.Get("depthCoefficient", 0.5);
+ }
+ else potentials = AH.Array2D(grid.C, grid.state.Length, 0);
+ future = new int[grid.state.Length];
+ }
+
+ return true;
+ }
+
+ override public void Reset()
+ {
+ lastMatchedTurn = -1;
+ counter = 0;
+ futureComputed = false;
+
+ for (int r = 0; r < last.Length; r++) last[r] = false;
+ }
+
+ protected virtual void Add(int r, int x, int y, int z, bool[] maskr)
+ {
+ maskr[x + y * grid.MX + z * grid.MX * grid.MY] = true;
+
+ var match = (r, x, y, z);
+ if (matchCount < matches.Count) matches[matchCount] = match;
+ else matches.Add(match);
+ matchCount++;
+ }
+
+ public override bool Go()
+ {
+ for (int r = 0; r < last.Length; r++) last[r] = false;
+
+ if (steps > 0 && counter >= steps) return false; //есть вариант вернуть false на том же ходу, на котором мы достигли предела
+
+ int MX = grid.MX, MY = grid.MY, MZ = grid.MZ;
+ if (observations != null && !futureComputed)
+ {
+ if (!Observation.ComputeFutureSetPresent(future, grid.state, observations)) return false;
+ else
+ {
+ futureComputed = true;
+ if (search)
+ {
+ trajectory = null;
+ int TRIES = limit < 0 ? 1 : 20;
+ for (int k = 0; k < TRIES && trajectory == null; k++) trajectory = Search.Run(grid.state, future, rules, grid.MX, grid.MY, grid.MZ, grid.C, this is AllNode, limit, depthCoefficient, ip.random.Next());
+ if (trajectory == null) Console.WriteLine("SEARCH RETURNED NULL");
+ }
+ else Observation.ComputeBackwardPotentials(potentials, future, MX, MY, MZ, rules);
+ }
+ }
+
+ if (lastMatchedTurn >= 0)
+ {
+ for (int n = ip.first[lastMatchedTurn]; n < ip.changes.Count; n++)
+ {
+ var (x, y, z) = ip.changes[n];
+ byte value = grid.state[x + y * MX + z * MX * MY];
+ for (int r = 0; r < rules.Length; r++)
+ {
+ Rule rule = rules[r];
+ bool[] maskr = matchMask[r];
+ (int x, int y, int z)[] shifts = rule.ishifts[value];
+ for (int l = 0; l < shifts.Length; l++)
+ {
+ var (shiftx, shifty, shiftz) = shifts[l];
+ int sx = x - shiftx;
+ int sy = y - shifty;
+ int sz = z - shiftz;
+
+ if (sx < 0 || sy < 0 || sz < 0 || sx + rule.IMX > MX || sy + rule.IMY > MY || sz + rule.IMZ > MZ) continue;
+ int si = sx + sy * MX + sz * MX * MY;
+
+ if (!maskr[si] && grid.Matches(rule, sx, sy, sz)) Add(r, sx, sy, sz, maskr);
+ }
+ }
+ }
+ }
+ else
+ {
+ matchCount = 0;
+ for (int r = 0; r < rules.Length; r++)
+ {
+ Rule rule = rules[r];
+ bool[] maskr = matchMask?[r];
+ for (int z = rule.IMZ - 1; z < MZ; z += rule.IMZ)
+ for (int y = rule.IMY - 1; y < MY; y += rule.IMY)
+ for (int x = rule.IMX - 1; x < MX; x += rule.IMX)
+ {
+ var shifts = rule.ishifts[grid.state[x + y * MX + z * MX * MY]];
+ for (int l = 0; l < shifts.Length; l++)
+ {
+ var (shiftx, shifty, shiftz) = shifts[l];
+ int sx = x - shiftx;
+ int sy = y - shifty;
+ int sz = z - shiftz;
+ if (sx < 0 || sy < 0 || sz < 0 || sx + rule.IMX > MX || sy + rule.IMY > MY || sz + rule.IMZ > MZ) continue;
+
+ if (grid.Matches(rule, sx, sy, sz)) Add(r, sx, sy, sz, maskr);
+ }
+ }
+ }
+ }
+
+ if (fields != null)
+ {
+ bool anysuccess = false, anycomputation = false;
+ for (int c = 0; c < fields.Length; c++)
+ {
+ Field field = fields[c];
+ if (field != null && (counter == 0 || field.recompute))
+ {
+ bool success = field.Compute(potentials[c], grid);
+ if (!success && field.essential) return false;
+ anysuccess |= success;
+ anycomputation = true;
+ }
+ }
+ if (anycomputation && !anysuccess) return false;
+ }
+
+ return true;
+ }
+}
diff --git a/source/Search.cs b/source/Search.cs
new file mode 100644
index 0000000..3761e55
--- /dev/null
+++ b/source/Search.cs
@@ -0,0 +1,295 @@
+// Copyright (C) 2022 Maxim Gumin, The MIT License (MIT)
+
+using System;
+using System.Linq;
+using System.Collections.Generic;
+
+static class Search
+{
+ public static byte[][] Run(byte[] present, int[] future, Rule[] rules, int MX, int MY, int MZ, int C, bool all, int limit, double depthCoefficient, int seed)
+ {
+ //Console.WriteLine("START SEARCH");
+ //present.Print(MX, MY);
+ int[][] bpotentials = AH.Array2D(C, present.Length, -1);
+ int[][] fpotentials = AH.Array2D(C, present.Length, -1);
+
+ Observation.ComputeBackwardPotentials(bpotentials, future, MX, MY, MZ, rules);
+ int rootBackwardEstimate = Observation.BackwardPointwise(bpotentials, present);
+ Observation.ComputeForwardPotentials(fpotentials, present, MX, MY, MZ, rules);
+ int rootForwardEstimate = Observation.ForwardPointwise(fpotentials, future);
+
+ if (rootBackwardEstimate < 0 || rootForwardEstimate < 0)
+ {
+ Console.WriteLine("INCORRECT PROBLEM");
+ return null;
+ }
+ Console.WriteLine($"root estimate = ({rootBackwardEstimate}, {rootForwardEstimate})");
+ if (rootBackwardEstimate == 0) return Array.Empty();
+ Board rootBoard = new(present, -1, 0, rootBackwardEstimate, rootForwardEstimate);
+
+ List database = new();
+ database.Add(rootBoard);
+ Dictionary visited = new(new StateComparer());
+ visited.Add(present, 0);
+
+ PriorityQueue frontier = new();
+ Random random = new(seed);
+ frontier.Enqueue(0, rootBoard.Rank(random, depthCoefficient));
+ int frontierLength = 1;
+
+ int record = rootBackwardEstimate + rootForwardEstimate;
+ while (frontierLength > 0 && (limit < 0 || database.Count < limit))
+ {
+ int parentIndex = frontier.Dequeue();
+ frontierLength--;
+ Board parentBoard = database[parentIndex];
+ //Console.WriteLine("-----------------------------------------------------------------------------------");
+ //Console.WriteLine($"extracting board at depth {parentBoard.depth} and estimate ({parentBoard.backwardEstimate}, {parentBoard.forwardEstimate}):");
+ //parentBoard.state.Print(MX, MY);
+
+ var children = all ? parentBoard.state.AllChildStates(MX, MY, rules) : parentBoard.state.OneChildStates(MX, MY, rules);
+ //Console.WriteLine($"this board has {children.Length} children");
+ foreach (var childState in children)
+ //for (int c = 0; c < children.Length; c++)
+ {
+ //byte[] childState = children[c];
+ bool success = visited.TryGetValue(childState, out int childIndex);
+ if (success)
+ {
+ Board oldBoard = database[childIndex];
+ if (parentBoard.depth + 1 < oldBoard.depth)
+ {
+ //Console.WriteLine($"found a shorter {parentBoard.depth + 1}-route to an existing {oldBoard.depth}-board of estimate ({oldBoard.backwardEstimate}, {oldBoard.forwardEstimate})");
+ oldBoard.depth = parentBoard.depth + 1;
+ oldBoard.parentIndex = parentIndex;
+
+ if (oldBoard.backwardEstimate >= 0 && oldBoard.forwardEstimate >= 0)
+ {
+ frontier.Enqueue(childIndex, oldBoard.Rank(random, depthCoefficient));
+ frontierLength++;
+ }
+ }
+ //else Console.WriteLine($"found a longer {parentBoard.depth + 1}-route to an existing {oldBoard.depth}-board of estimate ({oldBoard.backwardEstimate}, {oldBoard.forwardEstimate})");
+ }
+ else
+ {
+ int childBackwardEstimate = Observation.BackwardPointwise(bpotentials, childState);
+ Observation.ComputeForwardPotentials(fpotentials, childState, MX, MY, MZ, rules);
+ int childForwardEstimate = Observation.ForwardPointwise(fpotentials, future);
+
+ //Console.WriteLine($"child {c} has estimate ({childBackwardEstimate}, {childForwardEstimate}):");
+ //childState.Print(MX, MY);
+ if (childBackwardEstimate < 0 || childForwardEstimate < 0) continue;
+
+ Board childBoard = new(childState, parentIndex, parentBoard.depth + 1, childBackwardEstimate, childForwardEstimate);
+ database.Add(childBoard);
+ childIndex = database.Count - 1;
+ visited.Add(childBoard.state, childIndex);
+
+ if (childBoard.forwardEstimate == 0)
+ {
+ Console.WriteLine($"found a trajectory of length {parentBoard.depth + 1}, visited {visited.Count} states");
+ List trajectory = Board.Trajectory(childIndex, database);
+ trajectory.Reverse();
+ return trajectory.Select(b => b.state).ToArray();
+ }
+ else
+ {
+ if (limit < 0 && childBackwardEstimate + childForwardEstimate <= record)
+ {
+ record = childBackwardEstimate + childForwardEstimate;
+ Console.WriteLine($"found a state of record estimate {record} = {childBackwardEstimate} + {childForwardEstimate}");
+ childState.Print(MX, MY);
+ }
+ frontier.Enqueue(childIndex, childBoard.Rank(random, depthCoefficient));
+ frontierLength++;
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ static List OneChildStates(this byte[] state, int MX, int MY, Rule[] rules)
+ {
+ List result = new();
+ foreach (Rule rule in rules)
+ for (int y = 0; y < MY; y++) for (int x = 0; x < MX; x++)
+ if (Matches(rule, x, y, state, MX, MY)) result.Add(Applied(rule, x, y, state, MX));
+ return result;
+ }
+
+ static bool Matches(this Rule rule, int x, int y, byte[] state, int MX, int MY)
+ {
+ if (x + rule.IMX > MX || y + rule.IMY > MY) return false;
+
+ int dy = 0, dx = 0;
+ for (int di = 0; di < rule.input.Length; di++) //попробовать binput, но в этот раз здесь тоже заменить!
+ {
+ if ((rule.input[di] & (1 << state[x + dx + (y + dy) * MX])) == 0) return false;
+ dx++;
+ if (dx == rule.IMX) { dx = 0; dy++; }
+ }
+ return true;
+ }
+
+ static byte[] Applied(Rule rule, int x, int y, byte[] state, int MX)
+ {
+ byte[] result = new byte[state.Length];
+ Array.Copy(state, result, state.Length);
+ for (int dz = 0; dz < rule.OMZ; dz++) for (int dy = 0; dy < rule.OMY; dy++) for (int dx = 0; dx < rule.OMX; dx++)
+ {
+ byte newValue = rule.output[dx + dy * rule.OMX + dz * rule.OMX * rule.OMY];
+ if (newValue != 0xff) result[x + dx + (y + dy) * MX] = newValue;
+ }
+ return result;
+ }
+
+ static void Print(this byte[] state, int MX, int MY)
+ {
+ char[] characters = new[] { '.', 'R', 'W', '#', 'a', '!', '?', '%', '0', '1', '2', '3', '4', '5' };
+ for (int y = 0; y < MY; y++)
+ {
+ for (int x = 0; x < MX; x++) Console.Write($"{characters[state[x + y * MX]]} ");
+ Console.WriteLine();
+ }
+ }
+
+ public static bool IsInside(this (int x, int y) p, Rule rule, int x, int y) =>
+ x <= p.x && p.x < x + rule.IMX && y <= p.y && p.y < y + rule.IMY;
+ public static bool Overlap(Rule rule0, int x0, int y0, Rule rule1, int x1, int y1)
+ {
+ for (int dy = 0; dy < rule0.IMY; dy++) for (int dx = 0; dx < rule0.IMX; dx++)
+ if ((x0 + dx, y0 + dy).IsInside(rule1, x1, y1)) return true;
+ return false;
+ }
+
+ public static List AllChildStates(this byte[] state, int MX, int MY, Rule[] rules)
+ {
+ var list = new List<(Rule, int)>();
+ int[] amounts = new int[state.Length];
+ for (int i = 0; i < state.Length; i++)
+ {
+ int x = i % MX, y = i / MX;
+ for (int r = 0; r < rules.Length; r++)
+ {
+ Rule rule = rules[r];
+ if (rule.Matches(x, y, state, MX, MY))
+ {
+ list.Add((rule, i));
+ for (int dy = 0; dy < rule.IMY; dy++) for (int dx = 0; dx < rule.IMX; dx++) amounts[x + dx + (y + dy) * MX]++;
+ }
+ }
+ }
+ (Rule, int)[] tiles = list.ToArray();
+ bool[] mask = AH.Array1D(tiles.Length, true);
+ List<(Rule, int)> solution = new();
+
+ List result = new();
+ Enumerate(result, solution, tiles, amounts, mask, state, MX);
+ return result;
+ }
+
+ static void Enumerate(List children, List<(Rule, int)> solution, (Rule, int)[] tiles, int[] amounts, bool[] mask, byte[] state, int MX)
+ {
+ int I = amounts.MaxPositiveIndex();
+ int X = I % MX, Y = I / MX;
+ if (I < 0)
+ {
+ children.Add(state.Apply(solution, MX));
+ return;
+ }
+
+ List<(Rule, int)> cover = new();
+ for (int l = 0; l < tiles.Length; l++)
+ {
+ var (rule, i) = tiles[l];
+ if (mask[l] && (X, Y).IsInside(rule, i % MX, i / MX)) cover.Add((rule, i));
+ }
+
+ foreach (var (rule, i) in cover)
+ {
+ solution.Add((rule, i));
+
+ List intersecting = new();
+ for (int l = 0; l < tiles.Length; l++) if (mask[l])
+ {
+ var (rule1, i1) = tiles[l];
+ if (Overlap(rule, i % MX, i / MX, rule1, i1 % MX, i1 / MX)) intersecting.Add(l);
+ }
+
+ foreach (int l in intersecting) Hide(l, false, tiles, amounts, mask, MX);
+ Enumerate(children, solution, tiles, amounts, mask, state, MX);
+ foreach (int l in intersecting) Hide(l, true, tiles, amounts, mask, MX);
+
+ solution.RemoveAt(solution.Count - 1);
+ }
+ }
+
+ static void Hide(int l, bool unhide, (Rule, int)[] tiles, int[] amounts, bool[] mask, int MX)
+ {
+ mask[l] = unhide;
+ var (rule, i) = tiles[l];
+ int x = i % MX, y = i / MX;
+ int incr = unhide ? 1 : -1;
+ for (int dy = 0; dy < rule.IMY; dy++) for (int dx = 0; dx < rule.IMX; dx++) amounts[x + dx + (y + dy) * MX] += incr;
+ }
+
+ static void Apply(this Rule rule, int x, int y, byte[] state, int MX)
+ {
+ for (int dy = 0; dy < rule.IMY; dy++) for (int dx = 0; dx < rule.IMX; dx++)
+ state[x + dx + (y + dy) * MX] = rule.output[dx + dy * rule.IMX];
+ }
+ static byte[] Apply(this byte[] state, List<(Rule, int)> solution, int MX)
+ {
+ byte[] result = new byte[state.Length];
+ Array.Copy(state, result, state.Length);
+ foreach (var (rule, i) in solution) Apply(rule, i % MX, i / MX, result, MX);
+ return result;
+ }
+}
+
+class Board
+{
+ public byte[] state;
+ public int parentIndex, depth, backwardEstimate, forwardEstimate;
+
+ public Board(byte[] state, int parentIndex, int depth, int backwardEstimate, int forwardEstimate)
+ {
+ this.state = state;
+ this.parentIndex = parentIndex;
+ this.depth = depth;
+ this.backwardEstimate = backwardEstimate;
+ this.forwardEstimate = forwardEstimate;
+ }
+
+ public double Rank(Random random, double depthCoefficient)
+ {
+ double result = depthCoefficient < 0.0 ? 1000 - depth : forwardEstimate + backwardEstimate + 2.0 * depthCoefficient * depth;
+ return result + 0.0001 * random.NextDouble();
+ }
+
+ public static List Trajectory(int index, List database)
+ {
+ List result = new();
+ for (Board board = database[index]; board.parentIndex >= 0; board = database[board.parentIndex]) result.Add(board);
+ return result;
+ }
+}
+
+class StateComparer : IEqualityComparer
+{
+ public bool Equals(byte[] a, byte[] b)
+ {
+ for (int i = 0; i < a.Length; i++) if (a[i] != b[i]) return false;
+ return true;
+ }
+
+ public int GetHashCode(byte[] a)
+ {
+ int result = 17;
+ for (int i = 0; i < a.Length; i++) unchecked { result = result * 29 + a[i]; }
+ return result;
+ }
+}
diff --git a/source/SymmetryHelper.cs b/source/SymmetryHelper.cs
new file mode 100644
index 0000000..be72bac
--- /dev/null
+++ b/source/SymmetryHelper.cs
@@ -0,0 +1,112 @@
+// Copyright (C) 2022 Maxim Gumin, The MIT License (MIT)
+
+using System;
+using System.Linq;
+using System.Collections.Generic;
+
+static class SymmetryHelper
+{
+ public static Dictionary squareSubgroups = new()
+ {
+ ["()"] = new bool[8] { true, false, false, false, false, false, false, false },
+ ["(x)"] = new bool[8] { true, true, false, false, false, false, false, false },
+ ["(y)"] = new bool[8] { true, false, false, false, false, true, false, false },
+ ["(x)(y)"] = new bool[8] { true, true, false, false, true, true, false, false },
+ ["(xy+)"] = new bool[8] { true, false, true, false, true, false, true, false },
+ ["(xy)"] = new bool[8] { true, true, true, true, true, true, true, true }
+ };
+
+ public static IEnumerable SquareSymmetries(T thing, Func rotation, Func reflection, Func same, bool[] subgroup = null)
+ {
+ T[] things = new T[8];
+
+ things[0] = thing; // e
+ things[1] = reflection(things[0]); // b
+ things[2] = rotation(things[0]); // a
+ things[3] = reflection(things[2]); // ba
+ things[4] = rotation(things[2]); // a2
+ things[5] = reflection(things[4]); // ba2
+ things[6] = rotation(things[4]); // a3
+ things[7] = reflection(things[6]); // ba3
+
+ List result = new();
+ for (int i = 0; i < 8; i++) if ((subgroup == null || subgroup[i]) && !result.Where(s => same(s, things[i])).Any()) result.Add(things[i]);
+ return result;
+ }
+
+ public static Dictionary cubeSubgroups = new()
+ {
+ ["()"] = AH.Array1D(48, l => l == 0),
+ ["(x)"] = AH.Array1D(48, l => l == 0 || l == 1),
+ ["(z)"] = AH.Array1D(48, l => l == 0 || l == 17),
+ ["(xy)"] = AH.Array1D(48, l => l < 8),
+ ["(xyz+)"] = AH.Array1D(48, l => l % 2 == 0),
+ ["(xyz)"] = AH.Array1D(48, true),
+ //["(xy)(z)"] = AH.Array1D(48, l => l < 8 || l == 17 || ...),
+ };
+
+ public static IEnumerable CubeSymmetries(T thing, Func a, Func b, Func r, Func same, bool[] subgroup = null)
+ {
+ T[] s = new T[48];
+
+ s[0] = thing; // e
+ s[1] = r(s[0]);
+ s[2] = a(s[0]); // a
+ s[3] = r(s[2]);
+ s[4] = a(s[2]); // a2
+ s[5] = r(s[4]);
+ s[6] = a(s[4]); // a3
+ s[7] = r(s[6]);
+ s[8] = b(s[0]); // b
+ s[9] = r(s[8]);
+ s[10] = b(s[2]); // b a
+ s[11] = r(s[10]);
+ s[12] = b(s[4]); // b a2
+ s[13] = r(s[12]);
+ s[14] = b(s[6]); // b a3
+ s[15] = r(s[14]);
+ s[16] = b(s[8]); // b2
+ s[17] = r(s[16]);
+ s[18] = b(s[10]); // b2 a
+ s[19] = r(s[18]);
+ s[20] = b(s[12]); // b2 a2
+ s[21] = r(s[20]);
+ s[22] = b(s[14]); // b2 a3
+ s[23] = r(s[22]);
+ s[24] = b(s[16]); // b3
+ s[25] = r(s[24]);
+ s[26] = b(s[18]); // b3 a
+ s[27] = r(s[26]);
+ s[28] = b(s[20]); // b3 a2
+ s[29] = r(s[28]);
+ s[30] = b(s[22]); // b3 a3
+ s[31] = r(s[30]);
+ s[32] = a(s[8]); // a b
+ s[33] = r(s[32]);
+ s[34] = a(s[10]); // a b a
+ s[35] = r(s[34]);
+ s[36] = a(s[12]); // a b a2
+ s[37] = r(s[36]);
+ s[38] = a(s[14]); // a b a3
+ s[39] = r(s[38]);
+ s[40] = a(s[24]); // a3 b a2 = a b3
+ s[41] = r(s[40]);
+ s[42] = a(s[26]); // a3 b a3 = a b3 a
+ s[43] = r(s[42]);
+ s[44] = a(s[28]); // a3 b = a b3 a2
+ s[45] = r(s[44]);
+ s[46] = a(s[30]); // a3 b a = a b3 a3
+ s[47] = r(s[46]);
+
+ List result = new();
+ for (int i = 0; i < 48; i++) if ((subgroup == null || subgroup[i]) && !result.Where(t => same(t, s[i])).Any()) result.Add(s[i]);
+ return result;
+ }
+
+ public static bool[] GetSymmetry(bool d2, string s, bool[] dflt)
+ {
+ if (s == null) return dflt;
+ bool success = d2 ? squareSubgroups.TryGetValue(s, out bool[] result) : cubeSubgroups.TryGetValue(s, out result);
+ return success ? result : null;
+ }
+}
diff --git a/source/TileModel.cs b/source/TileModel.cs
new file mode 100644
index 0000000..4f78c34
--- /dev/null
+++ b/source/TileModel.cs
@@ -0,0 +1,331 @@
+// Copyright (C) 2022 Maxim Gumin, The MIT License (MIT)
+
+using System;
+using System.Linq;
+using System.Xml.Linq;
+using System.Collections.Generic;
+
+class TileNode : WFCNode
+{
+ List tiledata;
+
+ int S, SZ;
+ int overlap, overlapz;
+
+ override protected bool Load(XElement xelem, bool[] parentSymmetry, Grid grid)
+ {
+ periodic = xelem.Get("periodic", false);
+ /*string*/ name = xelem.Get("tileset");
+ string tilesname = xelem.Get("tiles", name);
+ overlap = xelem.Get("overlap", 0);
+ overlapz = xelem.Get("overlapz", 0);
+
+ XDocument xdoc;
+ string filepath = $"resources/tilesets/{name}.xml";
+ try { xdoc = XDocument.Load(filepath, LoadOptions.SetLineInfo); }
+ catch (Exception)
+ {
+ Interpreter.WriteLine($"couldn't open tileset {filepath}");
+ return false;
+ }
+ XElement xroot = xdoc.Root;
+
+ bool fullSymmetry = xroot.Get("fullSymmetry", false);
+ XElement xfirsttile = xroot.Element("tiles").Element("tile");
+ string firstFileName = $"{tilesname}/{xfirsttile.Get("name")}.vox";
+ int[] firstData;
+ int SY;
+ (firstData, S, SY, SZ) = VoxHelper.LoadVox($"resources/tilesets/{firstFileName}");
+ if (firstData == null)
+ {
+ Interpreter.WriteLine($"couldn't read {firstFileName}");
+ return false;
+ }
+ if (S != SY)
+ {
+ Interpreter.WriteLine($"tiles should be square shaped: {S} != {SY}");
+ return false;
+ }
+ if (fullSymmetry && S != SZ)
+ {
+ Interpreter.WriteLine($"tiles should be cubes for the full symmetry option: {S} != {SZ}");
+ return false;
+ }
+
+ newgrid = Grid.Load(xelem, (S - overlap) * grid.MX + overlap, (S - overlap) * grid.MY + overlap, (SZ - overlapz) * grid.MZ + overlapz);
+ if (newgrid == null) return false;
+
+ tiledata = new List();
+ Dictionary positions = new();
+ byte[] newtile(Func f) => AH.FlatArray3D(S, S, SZ, f);
+
+ byte[] zRotate(byte[] p) => newtile((x, y, z) => p[y + (S - 1 - x) * S + z * S * S]);
+ byte[] yRotate(byte[] p) => newtile((x, y, z) => p[z + y * S + (S - 1 - x) * S * S]);
+ byte[] xRotate(byte[] p) => newtile((x, y, z) => p[x + z * S + (S - 1 - y) * S * S]);
+ byte[] xReflect(byte[] p) => newtile((x, y, z) => p[(S - 1 - x) + y * S + z * S * S]);
+ byte[] yReflect(byte[] p) => newtile((x, y, z) => p[x + (S - 1 - y) * S + z * S * S]);
+ byte[] zReflect(byte[] p) => newtile((x, y, z) => p[x + y * S + (S - 1 - z) * S * S]);
+
+ var namedTileData = new Dictionary>();
+ var tempStationary = new List();
+
+ var uniques = new List();
+ var xtiles = xroot.Element("tiles").Elements("tile");
+ int ind = 0;
+ foreach (XElement xtile in xtiles)
+ {
+ string tilename = xtile.Get("name");
+ double weight = xtile.Get("weight", 1.0);
+
+ string filename = $"resources/tilesets/{tilesname}/{tilename}.vox";
+ int[] vox = VoxHelper.LoadVox(filename).Item1;
+ if (vox == null)
+ {
+ Interpreter.WriteLine($"couldn't read tile {filename}");
+ return false;
+ }
+ (byte[] flatTile, int C) = vox.Ords(uniques);
+ if (C > newgrid.C)
+ {
+ Interpreter.WriteLine($"there were more than {newgrid.C} colors in vox files");
+ return false;
+ }
+
+ List localdata = fullSymmetry ? SymmetryHelper.CubeSymmetries(flatTile, zRotate, yRotate, xReflect, AH.Same).ToList()
+ : SymmetryHelper.SquareSymmetries(flatTile, zRotate, xReflect, AH.Same).ToList();
+
+ bool[] position = new bool[128];
+ namedTileData.Add(tilename, localdata);
+ foreach (byte[] p in localdata)
+ {
+ tiledata.Add(p);
+ tempStationary.Add(weight);
+ position[ind] = true;
+ ind++;
+ }
+ positions.Add(tilename, position);
+ }
+
+ P = tiledata.Count;
+ Console.WriteLine($"P = {P}");
+ weights = tempStationary.ToArray();
+
+ map = new Dictionary();
+ foreach (XElement xrule in xelem.Elements("rule"))
+ {
+ char input = xrule.Get("in");
+ string[] outputs = xrule.Get("out").Split('|');
+ bool[] position = new bool[P];
+ foreach (string s in outputs)
+ {
+ bool success = positions.TryGetValue(s, out bool[] array);
+ if (!success)
+ {
+ Interpreter.WriteLine($"unknown tilename {s} at line {xrule.LineNumber()}");
+ return false;
+ }
+ for (int p = 0; p < P; p++) if (array[p]) position[p] = true;
+ }
+ map.Add(grid.values[input], position);
+ }
+ if (!map.ContainsKey(0)) map.Add(0, AH.Array1D(P, true));
+
+ bool[][][] tempPropagator = AH.Array3D(6, P, P, false);
+
+ int index(byte[] p)
+ {
+ for (int i = 0; i < tiledata.Count; i++) if (AH.Same(p, tiledata[i])) return i;
+ return -1;
+ };
+
+ static string last(string attribute) => attribute?.Split(' ').Last();
+ byte[] tile(string attribute)
+ {
+ string[] code = attribute.Split(' ');
+ string action = code.Length == 2 ? code[0] : "";
+ byte[] starttile = namedTileData[last(attribute)][0];
+ for (int i = action.Length - 1; i >= 0; i--)
+ {
+ char sym = action[i];
+ if (sym == 'x') starttile = xRotate(starttile);
+ else if (sym == 'y') starttile = yRotate(starttile);
+ else if (sym == 'z') starttile = zRotate(starttile);
+ else
+ {
+ Interpreter.WriteLine($"unknown symmetry {sym}");
+ return null;
+ }
+ }
+ return starttile;
+ };
+
+ List tilenames = xtiles.Select(x => x.Get("name")).ToList();
+ tilenames.Add(null);
+
+ foreach (XElement xneighbor in xroot.Element("neighbors").Elements("neighbor"))
+ {
+ if (fullSymmetry)
+ {
+ string left = xneighbor.Get("left"), right = xneighbor.Get("right");
+ if (!tilenames.Contains(last(left)) || !tilenames.Contains(last(right)))
+ {
+ Interpreter.WriteLine($"unknown tile {last(left)} or {last(right)} at line {xneighbor.LineNumber()}");
+ return false;
+ }
+
+ byte[] ltile = tile(left), rtile = tile(right);
+ if (ltile == null || rtile == null) return false;
+
+ var lsym = SymmetryHelper.SquareSymmetries(ltile, xRotate, yReflect, (p1, p2) => false).ToArray();
+ var rsym = SymmetryHelper.SquareSymmetries(rtile, xRotate, yReflect, (p1, p2) => false).ToArray();
+
+ for (int i = 0; i < lsym.Length; i++)
+ {
+ tempPropagator[0][index(lsym[i])][index(rsym[i])] = true;
+ tempPropagator[0][index(xReflect(rsym[i]))][index(xReflect(lsym[i]))] = true;
+ }
+
+ byte[] dtile = zRotate(ltile);
+ byte[] utile = zRotate(rtile);
+
+ var dsym = SymmetryHelper.SquareSymmetries(dtile, yRotate, zReflect, (p1, p2) => false).ToArray();
+ var usym = SymmetryHelper.SquareSymmetries(utile, yRotate, zReflect, (p1, p2) => false).ToArray();
+
+ for (int i = 0; i < dsym.Length; i++)
+ {
+ tempPropagator[1][index(dsym[i])][index(usym[i])] = true;
+ tempPropagator[1][index(yReflect(usym[i]))][index(yReflect(dsym[i]))] = true;
+ }
+
+ byte[] btile = yRotate(ltile);
+ byte[] ttile = yRotate(rtile);
+
+ var bsym = SymmetryHelper.SquareSymmetries(btile, zRotate, xReflect, (p1, p2) => false).ToArray();
+ var tsym = SymmetryHelper.SquareSymmetries(ttile, zRotate, xReflect, (p1, p2) => false).ToArray();
+
+ for (int i = 0; i < bsym.Length; i++)
+ {
+ tempPropagator[4][index(bsym[i])][index(tsym[i])] = true;
+ tempPropagator[4][index(zReflect(tsym[i]))][index(zReflect(bsym[i]))] = true;
+ }
+ }
+ else if (xneighbor.Get("left", null) != null)
+ {
+ string left = xneighbor.Get("left"), right = xneighbor.Get("right");
+ if (!tilenames.Contains(last(left)) || !tilenames.Contains(last(right)))
+ {
+ Interpreter.WriteLine($"unknown tile {last(left)} or {last(right)} at line {xneighbor.LineNumber()}");
+ return false;
+ }
+
+ byte[] ltile = tile(left), rtile = tile(right);
+ if (ltile == null || rtile == null) return false;
+
+ tempPropagator[0][index(ltile)][index(rtile)] = true;
+ tempPropagator[0][index(yReflect(ltile))][index(yReflect(rtile))] = true;
+ tempPropagator[0][index(xReflect(rtile))][index(xReflect(ltile))] = true;
+ tempPropagator[0][index(yReflect(xReflect(rtile)))][index(yReflect(xReflect(ltile)))] = true;
+
+ byte[] dtile = zRotate(ltile);
+ byte[] utile = zRotate(rtile);
+
+ tempPropagator[1][index(dtile)][index(utile)] = true;
+ tempPropagator[1][index(xReflect(dtile))][index(xReflect(utile))] = true;
+ tempPropagator[1][index(yReflect(utile))][index(yReflect(dtile))] = true;
+ tempPropagator[1][index(xReflect(yReflect(utile)))][index(xReflect(yReflect(dtile)))] = true;
+ }
+ else
+ {
+ string top = xneighbor.Get("top", null), bottom = xneighbor.Get("bottom", null);
+ if (!tilenames.Contains(last(top)) || !tilenames.Contains(last(bottom)))
+ {
+ Interpreter.WriteLine($"unknown tile {last(top)} or {last(bottom)} at line {xneighbor.LineNumber()}");
+ return false;
+ }
+
+ byte[] ttile = tile(top), btile = tile(bottom);
+ if (ttile == null || btile == null) return false;
+
+ var tsym = SymmetryHelper.SquareSymmetries(ttile, zRotate, xReflect, (p1, p2) => false).ToArray();
+ var bsym = SymmetryHelper.SquareSymmetries(btile, zRotate, xReflect, (p1, p2) => false).ToArray();
+
+ for (int i = 0; i < tsym.Length; i++) tempPropagator[4][index(bsym[i])][index(tsym[i])] = true;
+ }
+ }
+
+ for (int p2 = 0; p2 < P; p2++) for (int p1 = 0; p1 < P; p1++)
+ {
+ tempPropagator[2][p2][p1] = tempPropagator[0][p1][p2];
+ tempPropagator[3][p2][p1] = tempPropagator[1][p1][p2];
+ tempPropagator[5][p2][p1] = tempPropagator[4][p1][p2];
+ }
+
+ List[][] sparsePropagator = new List[6][];
+ for (int d = 0; d < 6; d++)
+ {
+ sparsePropagator[d] = new List[P];
+ for (int t = 0; t < P; t++) sparsePropagator[d][t] = new List();
+ }
+
+ propagator = new int[6][][];
+ for (int d = 0; d < 6; d++)
+ {
+ propagator[d] = new int[P][];
+ for (int p1 = 0; p1 < P; p1++)
+ {
+ List sp = sparsePropagator[d][p1];
+ bool[] tp = tempPropagator[d][p1];
+
+ for (int p2 = 0; p2 < P; p2++) if (tp[p2]) sp.Add(p2);
+
+ int ST = sp.Count;
+ propagator[d][p1] = new int[ST];
+ for (int st = 0; st < ST; st++) propagator[d][p1][st] = sp[st];
+ }
+ }
+
+ return base.Load(xelem, parentSymmetry, grid);
+ }
+
+ protected override void UpdateState()
+ {
+ Random r = new();
+ for (int z = 0; z < grid.MZ; z++) for (int y = 0; y < grid.MY; y++) for (int x = 0; x < grid.MX; x++)
+ {
+ bool[] w = wave.data[x + y * grid.MX + z * grid.MX * grid.MY];
+ int[][] votes = AH.Array2D(S * S * SZ, newgrid.C, 0);
+
+ for (int t = 0; t < P; t++) if (w[t])
+ {
+ byte[] tile = tiledata[t];
+ for (int dz = 0; dz < SZ; dz++) for (int dy = 0; dy < S; dy++) for (int dx = 0; dx < S; dx++)
+ {
+ int di = dx + dy * S + dz * S * S;
+ votes[di][tile[di]]++;
+ }
+ }
+
+ for (int dz = 0; dz < SZ; dz++) for (int dy = 0; dy < S; dy++) for (int dx = 0; dx < S; dx++)
+ {
+ int[] v = votes[dx + dy * S + dz * S * S];
+ double max = -1.0;
+ //int max = -1;
+ byte argmax = 0xff;
+ for (byte c = 0; c < v.Length; c++)
+ {
+ //int vote = v[c];
+ double vote = v[c] + 0.1 * r.NextDouble();
+ if (vote > max)
+ {
+ argmax = c;
+ max = vote;
+ }
+ }
+ int sx = x * (S - overlap) + dx;
+ int sy = y * (S - overlap) + dy;
+ int sz = z * (SZ - overlapz) + dz;
+ newgrid.state[sx + sy * newgrid.MX + sz * newgrid.MX * newgrid.MY] = argmax;
+ }
+ }
+ }
+}
diff --git a/source/VoxHelper.cs b/source/VoxHelper.cs
new file mode 100644
index 0000000..43a1476
--- /dev/null
+++ b/source/VoxHelper.cs
@@ -0,0 +1,141 @@
+// Copyright (C) 2022 Maxim Gumin, The MIT License (MIT)
+
+using System;
+using System.IO;
+using System.Text;
+using System.Collections.Generic;
+
+static class VoxHelper
+{
+ public static (int[], int, int, int) LoadVox(string filename)
+ {
+ try
+ {
+ using FileStream file = File.Open(filename, FileMode.Open);
+ var stream = new BinaryReader(file);
+
+ int[] result = null;
+ int MX = -1, MY = -1, MZ = -1;
+
+ string magic = new(stream.ReadChars(4));
+ int version = stream.ReadInt32();
+
+ while (stream.BaseStream.Position < stream.BaseStream.Length)
+ {
+ byte[] bt = stream.ReadBytes(1);
+ char head = Encoding.ASCII.GetChars(bt)[0];
+
+ if (head == 'S')
+ {
+ string tail = Encoding.ASCII.GetString(stream.ReadBytes(3));
+ if (tail != "IZE") continue;
+
+ int chunkSize = stream.ReadInt32();
+ stream.ReadBytes(4);
+ //Console.WriteLine("found SIZE chunk");
+ MX = stream.ReadInt32();
+ MY = stream.ReadInt32();
+ MZ = stream.ReadInt32();
+ stream.ReadBytes(chunkSize - 4 * 3);
+ //Console.WriteLine($"size = ({MX}, {MY}, {MZ})");
+ }
+ else if (head == 'X')
+ {
+ string tail = Encoding.ASCII.GetString(stream.ReadBytes(3));
+ if (tail != "YZI") continue;
+
+ if (MX <= 0 || MY <= 0 || MZ <= 0) return (null, MX, MY, MZ);
+ result = new int[MX * MY * MZ];
+ for (int i = 0; i < result.Length; i++) result[i] = -1;
+
+ //Console.WriteLine("found XYZI chunk");
+ stream.ReadBytes(8);
+ int numVoxels = stream.ReadInt32();
+ //Console.WriteLine($"number of voxels = {numVoxels}");
+ for (int i = 0; i < numVoxels; i++)
+ {
+ byte x = stream.ReadByte();
+ byte y = stream.ReadByte();
+ byte z = stream.ReadByte();
+ byte color = stream.ReadByte();
+ result[x + y * MX + z * MX * MY] = color;
+ //Console.WriteLine($"adding voxel {x} {y} {z} of color {color}");
+ }
+ }
+ }
+ file.Close();
+ return (result, MX, MY, MZ);
+ }
+ catch (Exception) { return (null, -1, -1, -1); }
+ }
+
+ static void WriteString(this BinaryWriter stream, string s) { foreach (char c in s) stream.Write(c); }
+ public static void SaveVox(byte[] state, byte MX, byte MY, byte MZ, int[] palette, string filename)
+ {
+ List<(byte, byte, byte, byte)> voxels = new();
+ for (byte z = 0; z < MZ; z++) for (byte y = 0; y < MY; y++) for (byte x = 0; x < MX; x++)
+ {
+ int i = x + y * MX + z * MX * MY;
+ byte v = state[i];
+ if (v != 0) voxels.Add((x, y, z, (byte)(v + 1)));
+ }
+
+ FileStream file = File.Open(filename, FileMode.Create);
+ using BinaryWriter stream = new(file);
+
+ stream.WriteString("VOX ");
+ stream.Write(150);
+
+ stream.WriteString("MAIN");
+ stream.Write(0);
+ stream.Write(1092 + voxels.Count * 4);
+
+ stream.WriteString("PACK");
+ stream.Write(4);
+ stream.Write(0);
+ stream.Write(1);
+
+ stream.WriteString("SIZE");
+ stream.Write(12);
+ stream.Write(0);
+ stream.Write((int)MX);
+ stream.Write((int)MY);
+ stream.Write((int)MZ);
+
+ stream.WriteString("XYZI");
+ stream.Write(4 + voxels.Count * 4);
+ stream.Write(0);
+ stream.Write(voxels.Count);
+
+ foreach (var (x, y, z, color) in voxels)
+ {
+ stream.Write(x);
+ //stream.Write((byte)(size.y - v.y - 1));
+ stream.Write(y);
+ stream.Write(z);
+ stream.Write(color);
+ }
+
+ stream.WriteString("RGBA");
+ stream.Write(1024);
+ stream.Write(0);
+
+ foreach (int c in palette)
+ {
+ //(byte R, byte G, byte B) = c.ToTuple();
+ stream.Write((byte)((c & 0xff0000) >> 16));
+ stream.Write((byte)((c & 0xff00) >> 8));
+ stream.Write((byte)(c & 0xff));
+ stream.Write((byte)0);
+ }
+ for (int i = palette.Length; i < 255; i++)
+ {
+ stream.Write((byte)(0xff - i - 1));
+ stream.Write((byte)(0xff - i - 1));
+ stream.Write((byte)(0xff - i - 1));
+ stream.Write((byte)(0xff));
+ }
+ stream.Write(0);
+ file.Close();
+ }
+}
diff --git a/source/WaveFunctionCollapse.cs b/source/WaveFunctionCollapse.cs
new file mode 100644
index 0000000..d898eb2
--- /dev/null
+++ b/source/WaveFunctionCollapse.cs
@@ -0,0 +1,327 @@
+// Copyright (C) 2022 Maxim Gumin, The MIT License (MIT)
+
+using System;
+using System.Xml.Linq;
+using System.Collections.Generic;
+
+abstract class WFCNode : Branch
+{
+ protected Wave wave;
+ protected int[][][] propagator;
+ protected int P, N = 1;
+
+ (int, int)[] stack;
+ int stacksize;
+
+ protected double[] weights;
+ double[] weightLogWeights;
+ double sumOfWeights, sumOfWeightLogWeights, startingEntropy;
+
+ protected Grid newgrid;
+ Wave startwave;
+
+ protected Dictionary map;
+ protected bool periodic, shannon;
+
+ double[] distribution;
+ int tries;
+
+ public string name;
+
+ override protected bool Load(XElement xelem, bool[] parentSymmetry, Grid grid)
+ {
+ shannon = xelem.Get("shannon", false);
+ tries = xelem.Get("tries", 1000);
+
+ wave = new Wave(grid.state.Length, P, propagator.Length, shannon);
+ startwave = new Wave(grid.state.Length, P, propagator.Length, shannon);
+ stack = new (int, int)[wave.data.Length * P];
+
+ sumOfWeights = sumOfWeightLogWeights = startingEntropy = 0;
+
+ if (shannon)
+ {
+ weightLogWeights = new double[P];
+
+ for (int t = 0; t < P; t++)
+ {
+ weightLogWeights[t] = weights[t] * Math.Log(weights[t]);
+ sumOfWeights += weights[t];
+ sumOfWeightLogWeights += weightLogWeights[t];
+ }
+
+ startingEntropy = Math.Log(sumOfWeights) - sumOfWeightLogWeights / sumOfWeights;
+ }
+
+ distribution = new double[P];
+ return base.Load(xelem, parentSymmetry, newgrid);
+ }
+
+ override public void Reset()
+ {
+ base.Reset();
+ n = -1;
+ firstgo = true;
+ }
+
+ bool firstgo = true;
+ Random random;
+ public override bool Go()
+ {
+ if (n >= 0) return base.Go();
+
+ if (firstgo)
+ {
+ wave.Init(propagator, sumOfWeights, sumOfWeightLogWeights, startingEntropy, shannon);
+
+ for (int i = 0; i < wave.data.Length; i++)
+ {
+ byte value = grid.state[i];
+ if (map.ContainsKey(value))
+ {
+ bool[] startWave = map[value];
+ for (int t = 0; t < P; t++) if (!startWave[t]) Ban(i, t);
+ }
+ }
+
+ bool firstSuccess = Propagate();
+ if (!firstSuccess)
+ {
+ Console.WriteLine("initial conditions are contradictive");
+ return false;
+ }
+ startwave.CopyFrom(wave, propagator.Length, shannon);
+ int? goodseed = GoodSeed();
+ if (goodseed == null) return false;
+
+ random = new Random((int)goodseed);
+ stacksize = 0;
+ wave.CopyFrom(startwave, propagator.Length, shannon);
+ firstgo = false;
+
+ newgrid.Clear(-1);
+ ip.grid = newgrid;
+ return true;
+ }
+ else
+ {
+ int node = NextUnobservedNode(random);
+ if (node >= 0)
+ {
+ Observe(node, random);
+ Propagate();
+ }
+ else n++;
+
+ if (n >= 0 || ip.gif) UpdateState();
+ return true;
+ }
+ }
+
+ int? GoodSeed()
+ {
+ for (int k = 0; k < tries; k++)
+ {
+ int observationsSoFar = 0;
+ int seed = ip.random.Next();
+ random = new Random(seed);
+ stacksize = 0;
+ wave.CopyFrom(startwave, propagator.Length, shannon);
+
+ while (true)
+ {
+ int node = NextUnobservedNode(random);
+ if (node >= 0)
+ {
+ Observe(node, random);
+ observationsSoFar++;
+ bool success = Propagate();
+ if (!success)
+ {
+ Console.WriteLine($"CONTRADICTION on try {k} with {observationsSoFar} observations");
+ break;
+ }
+ }
+ else
+ {
+ Console.WriteLine($"wfc found a good seed {seed} on try {k} with {observationsSoFar} observations");
+ return seed;
+ }
+ }
+ }
+
+ Console.WriteLine($"wfc failed to find a good seed in {tries} tries");
+ return null;
+ }
+
+ int NextUnobservedNode(Random random)
+ {
+ int MX = grid.MX, MY = grid.MY, MZ = grid.MZ;
+ double min = 1E+4;
+ int argmin = -1;
+ for (int z = 0; z < MZ; z++) for (int y = 0; y < MY; y++) for (int x = 0; x < MX; x++)
+ {
+ if (!periodic && (x + N > MX || y + N > MY || z + 1 > MZ)) continue;
+ int i = x + y * MX + z * MX * MY;
+ int remainingValues = wave.sumsOfOnes[i];
+ double entropy = shannon ? wave.entropies[i] : remainingValues;
+ 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)
+ {
+ bool[] w = wave.data[node];
+ for (int t = 0; t < P; t++) distribution[t] = w[t] ? weights[t] : 0.0;
+ int r = distribution.Random(random.NextDouble());
+ for (int t = 0; t < P; t++) if (w[t] != (t == r)) Ban(node, t);
+ }
+
+ bool Propagate()
+ {
+ int MX = grid.MX, MY = grid.MY, MZ = grid.MZ;
+
+ while (stacksize > 0)
+ {
+ (int i1, int p1) = stack[stacksize - 1];
+ stacksize--;
+
+ int x1 = i1 % MX, y1 = (i1 % (MX * MY)) / MX, z1 = i1 / (MX * MY);
+
+ for (int d = 0; d < propagator.Length; d++)
+ {
+ int dx = DX[d], dy = DY[d], dz = DZ[d];
+ int x2 = x1 + dx, y2 = y1 + dy, z2 = z1 + dz;
+ if (!periodic && (x2 < 0 || y2 < 0 || z2 < 0 || x2 + N > MX || y2 + N > MY || z2 + 1 > MZ)) continue;
+
+ if (x2 < 0) x2 += MX;
+ else if (x2 >= MX) x2 -= MX;
+ if (y2 < 0) y2 += MY;
+ else if (y2 >= MY) y2 -= MY;
+ if (z2 < 0) z2 += MZ;
+ else if (z2 >= MZ) z2 -= MZ;
+
+ int i2 = x2 + y2 * MX + z2 * MX * MY;
+ int[] p = propagator[d][p1];
+ int[][] compat = wave.compatible[i2];
+
+ for (int l = 0; l < p.Length; l++)
+ {
+ int t2 = p[l];
+ int[] comp = compat[t2];
+
+ comp[d]--;
+ if (comp[d] == 0) Ban(i2, t2);
+ }
+ }
+ }
+
+ return wave.sumsOfOnes[0] > 0;
+ }
+
+ void Ban(int i, int t)
+ {
+ wave.data[i][t] = false;
+
+ int[] comp = wave.compatible[i][t];
+ for (int d = 0; d < propagator.Length; d++) comp[d] = 0;
+ stack[stacksize] = (i, t);
+ stacksize++;
+
+ wave.sumsOfOnes[i] -= 1;
+ if (shannon)
+ {
+ double sum = wave.sumsOfWeights[i];
+ wave.entropies[i] += wave.sumsOfWeightLogWeights[i] / sum - Math.Log(sum);
+
+ wave.sumsOfWeights[i] -= weights[t];
+ wave.sumsOfWeightLogWeights[i] -= weightLogWeights[t];
+
+ sum = wave.sumsOfWeights[i];
+ wave.entropies[i] -= wave.sumsOfWeightLogWeights[i] / sum - Math.Log(sum);
+ }
+ }
+
+ protected abstract void UpdateState();
+
+ protected static int[] DX = { 1, 0, -1, 0, 0, 0 };
+ protected static int[] DY = { 0, 1, 0, -1, 0, 0 };
+ protected static int[] DZ = { 0, 0, 0, 0, 1, -1 };
+}
+
+class Wave
+{
+ public bool[][] data;
+ public int[][][] compatible;
+
+ public int[] sumsOfOnes;
+ public double[] sumsOfWeights, sumsOfWeightLogWeights, entropies;
+
+ public Wave(int length, int P, int D, bool shannon)
+ {
+ data = AH.Array2D(length, P, true);
+ compatible = AH.Array3D(length, P, D, -1);
+ sumsOfOnes = new int[length];
+
+ if (shannon)
+ {
+ sumsOfWeights = new double[length];
+ sumsOfWeightLogWeights = new double[length];
+ entropies = new double[length];
+ }
+ }
+
+ public void Init(int[][][] propagator, double sumOfWeights, double sumOfWeightLogWeights, double startingEntropy, bool shannon)
+ {
+ int P = data[0].Length;
+ for (int i = 0; i < data.Length; i++)
+ {
+ for (int p = 0; p < P; p++)
+ {
+ data[i][p] = true;
+ for (int d = 0; d < propagator.Length; d++) compatible[i][p][d] = propagator[opposite[d]][p].Length;
+ }
+
+ sumsOfOnes[i] = P;
+ if (shannon)
+ {
+ sumsOfWeights[i] = sumOfWeights;
+ sumsOfWeightLogWeights[i] = sumOfWeightLogWeights;
+ entropies[i] = startingEntropy;
+ }
+ }
+ }
+
+ public void CopyFrom(Wave wave, int D, bool shannon)
+ {
+ for (int i = 0; i < data.Length; i++)
+ {
+ bool[] datai = data[i], wavedatai = wave.data[i];
+ for (int t = 0; t < datai.Length; t++)
+ {
+ datai[t] = wavedatai[t];
+ for (int d = 0; d < D; d++) compatible[i][t][d] = wave.compatible[i][t][d];
+ }
+
+ sumsOfOnes[i] = wave.sumsOfOnes[i];
+
+ if (shannon)
+ {
+ sumsOfWeights[i] = wave.sumsOfWeights[i];
+ sumsOfWeightLogWeights[i] = wave.sumsOfWeightLogWeights[i];
+ entropies[i] = wave.entropies[i];
+ }
+ }
+ }
+
+ static readonly int[] opposite = { 2, 3, 0, 1, 5, 4 };
+}
diff --git a/source/XMLHelper.cs b/source/XMLHelper.cs
new file mode 100644
index 0000000..4fcf7ee
--- /dev/null
+++ b/source/XMLHelper.cs
@@ -0,0 +1,39 @@
+// Copyright (C) 2022 Maxim Gumin, The MIT License (MIT)
+
+using System;
+using System.Linq;
+using System.Xml.Linq;
+using System.ComponentModel;
+using System.Collections.Generic;
+
+static class XMLHelper
+{
+ public static T Get(this XElement xelem, string attribute)
+ {
+ XAttribute a = xelem.Attribute(attribute);
+ if (a == null) throw new Exception($"xelement {xelem.Name} didn't have attribute {attribute}");
+ return (T)TypeDescriptor.GetConverter(typeof(T)).ConvertFromInvariantString(a.Value);
+ }
+
+ public static T Get(this XElement xelem, string attribute, T dflt)
+ {
+ XAttribute a = xelem.Attribute(attribute);
+ return a == null ? dflt : (T)TypeDescriptor.GetConverter(typeof(T)).ConvertFromInvariantString(a.Value);
+ }
+
+ public static int LineNumber(this XElement xelem) => ((System.Xml.IXmlLineInfo)xelem).LineNumber;
+
+ public static IEnumerable Elements(this XElement xelement, params string[] names) => xelement.Elements().Where(e => names.Any(n => n == e.Name));
+ public static IEnumerable MyDescendants(this XElement xelem, params string[] tags)
+ {
+ Queue q = new();
+ q.Enqueue(xelem);
+
+ while (q.Any())
+ {
+ XElement e = q.Dequeue();
+ if (e != xelem) yield return e;
+ foreach (XElement x in e.Elements(tags)) q.Enqueue(x);
+ }
+ }
+}