mirror of
git://git.tartarus.org/simon/puzzles.git
synced 2025-04-20 07:31:30 -07:00
Files

The big mathematical news this month is that a polygon has been discovered that will tile the plane but only aperiodically. Penrose tiles achieve this with two tile types; it's been an open question for decades whether you could do it with only one tile. Now someone has announced the discovery of such a thing, so _obviously_ this mathematically exciting tiling ought to be one of the Loopy grid options! The polygon, named a 'hat' by its discoverers, consists of the union of eight cells of the 'Kites' periodic tiling that Loopy already implements. So all the vertex coordinates of the whole tiling are vertices of the Kites grid, which makes handling the coordinates in an exact manner a lot easier than Penrose tilings. What's _harder_ than Penrose tilings is that, although this tiling can be generated by a vaguely similar system of recursive expansion, the expansion is geometrically distorting, which means you can't easily figure out which tiles can be discarded early to save CPU. Instead I've come up with a completely different system for generating a patch of tiling, by using a hierarchical coordinate system to track a location within many levels of the expansion process without ever simulating the process as a whole. I'm really quite pleased with that technique, and am tempted to try switching the Penrose generator over to it too - except that we'd have to keep the old generator around to stop old game ids being invalidated, and also, I think it would be slightly trickier without an underlying fixed grid and without overlaps in the tile expansion system. However, before coming up with that, I got most of the way through implementing the more obvious system of actually doing the expansions. The result worked, but was very slow (because I changed approach rather than try to implement tree-pruning under distortion). But the code was reusable for two other useful purposes: it generated the lookup tables needed for the production code, and it also generated a lot of useful diagrams. So I've committed it anyway as a supporting program, in a new 'aux' source subdirectory, and in aux/doc is a writeup of the coordinate system's concepts, with all those diagrams. (That's the kind of thing I'd normally put in a huge comment at the top of the file, but doing all those diagrams in ASCII art would be beyond miserable.) From a gameplay perspective: the hat polygon has 13 edges, but one of them has a vertex of the Kites tiling in the middle, and sometimes two other tile boundaries meet at that vertex. I've chosen to represent every hat as having degree 14 for Loopy purposes, because if you only included that extra vertex when it was needed, then people would be forever having to check whether this was a 13-hat or a 14-hat and it would be nightmarish to play. Even so, there's a lot of clicking involved to turn all those fiddly individual edges on or off. This grid is noticeably nicer to play in 'autofollow' mode, by setting LOOPY_AUTOFOLLOW in the environment to either 'fixed' or 'adaptive'. I'm tempted to make 'fixed' the default, except that I think it would confuse players of ordinary square Loopy!
259 lines
12 KiB
HTML
259 lines
12 KiB
HTML
<html>
|
||
<head>
|
||
<meta http-equiv="Content-type" content="text/html; charset=UTF-8" />
|
||
<title>Generating hat tilings for Loopy</title>
|
||
<style type="text/css">
|
||
table, tbody, tr, td, th {
|
||
border: 1px solid rgba(0, 0, 0, 0.3);
|
||
border-collapse: collapse;
|
||
}
|
||
table.noborder, table.noborder tbody, table.noborder td,
|
||
table.noborder th, table.noborder tr { border: none; }
|
||
table {
|
||
margin: 1em;
|
||
}
|
||
th, td {
|
||
padding: 0.5em;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<h1>Generating hat tilings for Loopy</h1>
|
||
<p>The <a href="https://arxiv.org/abs/2303.10798">original paper</a>
|
||
describes a method for generating hat tilings from a system of four
|
||
'metatiles'. You can start with any one of the four tiles, and then
|
||
recursively apply a set of expansion rules that turn each tile into
|
||
a collection of smaller tiles from the same set.</p>
|
||
<p>This table shows the four tiles, with their one-letter names as
|
||
given in the paper, and how each one is expanded.</p>
|
||
<p>All the tiles have a significant orientation. The H, T and P
|
||
tiles are marked with arrows to indicate the orientation. The F tile
|
||
is asymmetric, so no arrow is needed.</p>
|
||
<p>I've assigned each tile in each expansion a number, which Loopy
|
||
will use for its coordinate system.</p>
|
||
<table>
|
||
<tr>
|
||
<th>Tile name</th>
|
||
<th>Single tile</th>
|
||
<th>Expansion</th>
|
||
<tr>
|
||
<td>H</td>
|
||
<td><img src="single-H.svg"></td>
|
||
<td><img src="expanded-H.svg"></td>
|
||
</tr>
|
||
<tr>
|
||
<td>T</td>
|
||
<td><img src="single-T.svg"></td>
|
||
<td><img src="expanded-T.svg"></td>
|
||
</tr>
|
||
<tr>
|
||
<td>P</td>
|
||
<td><img src="single-P.svg"></td>
|
||
<td><img src="expanded-P.svg"></td>
|
||
</tr>
|
||
<tr>
|
||
<td>F</td>
|
||
<td><img src="single-F.svg"></td>
|
||
<td><img src="expanded-F.svg"></td>
|
||
</tr>
|
||
</table>
|
||
<p><strong>Note that these expansions overlap</strong>. When two
|
||
adjacent metatiles are expanded, the outer layer of P and F tiles in
|
||
their expansions must be placed so that they overlap each other. The
|
||
original paper suggests a set of tiles to remove from these
|
||
expansions so that each metatile expands to a <em>disjoint</em> set
|
||
of smaller tiles. In our implementation, however, we prefer to keep
|
||
the overlap, because our coordinate system will use it.</p>
|
||
<p>Once you've generated a large enough patch of metatiles for your
|
||
needs, the final step is to convert it into the actual hat tiles.
|
||
The expansion of each metatile into hats is shown here. Again, I've
|
||
assigned numbers to each hat for coordinate-system purposes:</p>
|
||
<table>
|
||
<tr>
|
||
<th>Tile name</th>
|
||
<th>Conversion into hats</th>
|
||
<tr>
|
||
<td>H</td>
|
||
<td><img src="single-H.svg"></td>
|
||
<td><img src="hats-single-H.svg"></td>
|
||
</tr>
|
||
<tr>
|
||
<td>T</td>
|
||
<td><img src="single-T.svg"></td>
|
||
<td><img src="hats-single-T.svg"></td>
|
||
</tr>
|
||
<tr>
|
||
<td>P</td>
|
||
<td><img src="single-P.svg"></td>
|
||
<td><img src="hats-single-P.svg"></td>
|
||
</tr>
|
||
<tr>
|
||
<td>F</td>
|
||
<td><img src="single-F.svg"></td>
|
||
<td><img src="hats-single-F.svg"></td>
|
||
</tr>
|
||
</table>
|
||
<p>(The hat in the middle of the H is shaded to indicate that it's
|
||
one of the rare reflected ones. All the other hats are rotations of
|
||
each other.)</p>
|
||
<p>Given all of this, an obvious approach to generating a random
|
||
patch of hat tiling would be to start with a single metatile,
|
||
iterate the expansion process a few times until you have a tiled
|
||
area much larger than you need, and then pick a subrectangle of
|
||
it at random.</p>
|
||
<p>Loopy's algorithm for generating Penrose tilings (which admit a
|
||
similar, though less complicated, expansion system) works in exactly
|
||
this way.</p>
|
||
<p>One problem with that algorithm is that it spends a lot of effort
|
||
on generating huge areas of tiles that aren't actually needed. So
|
||
you'd prefer to adjust the algorithm so that at every stage of
|
||
expansion it spots tiles completely outside the target rectangle,
|
||
and throws them away <em>before</em> spending 5 iterations on
|
||
exponentially expanding them into huge amounts of detail that will
|
||
only be thrown away anyway later.</p>
|
||
<p>That works well for Penrose tilings, because there, the expansion
|
||
procedure is geometrically precise: coordinates in the expanded
|
||
tiling are scaled up by an exact factor from coordinates in the
|
||
original tiling. So at every stage it's easy to know exactly where
|
||
your target rectangle <em>is</em>, and discard things that don't
|
||
overlap it.</p>
|
||
<p>But the metatiles shown here don't have that property. The tiles
|
||
distort slightly as they expand. The <em>topological</em> properties
|
||
of the expanded tiling match the original (which expanded tiles
|
||
connect to each other), but the geometry (precise distances) is
|
||
different. So it would be harder to implement the pruning for this
|
||
tiling. The target rectangle might not even be rectangular in every
|
||
iteration!</p>
|
||
<p>Instead, I came up with a completely different mechanism, by
|
||
devising a coordinate system to track our position within multiple
|
||
layers of tile expansion. This allows us to generate precisely the
|
||
area of tiling we need, and waste no effort at all on anything
|
||
outside the target region.</p>
|
||
<p>We begin by assigning an integer index to each kite making up an
|
||
individual hat:</p>
|
||
<img src="hat-kites.svg">
|
||
<p>(For a reflected hat, these indices work in mirror image, so that
|
||
for example 5 is still the kite in the middle.)</p>
|
||
<p>Together with the indices we've assigned to hats within each
|
||
metatile, and to metatiles in the expansion of another metatile,
|
||
this gives us a coordinate system that can identify an individual
|
||
kite in an n-times-expanded metatile. For each large metatile
|
||
expansion, you can give the index of the smaller metatile selected
|
||
from its expansion; when we reach the last layer of metatiles and
|
||
expand them into hats, we can give the index of the hat in that
|
||
metatile; finally we can index the kite in that hat.</p>
|
||
<p><strong>But note that a kite can have multiple
|
||
coordinates</strong>, because of the overlap between the expansions
|
||
of adjacent metatiles. This will be useful!</p>
|
||
<p>Our next step is to unambiguously name the four directions in
|
||
which you can move from one kite to an adjacent kite. The directions
|
||
should be independent of the orientation of the kite. I've chosen to
|
||
name them from the viewpoint of someone standing at the pointy end
|
||
of the kite and looking towards the blunt end:</p>
|
||
<dl>
|
||
<dt><strong>Left</strong></dt>
|
||
<dd>Rotate 60° anticlockwise about the pointy end of the kite. For
|
||
example, in the above hat, going 'left' from kite 5 takes you to
|
||
kite 4.</dd>
|
||
<dt><strong>Right</strong></dt>
|
||
<dd>Rotate 60° clockwise about the pointy end. From kite 5, this
|
||
would take you to kite 6.</dd>
|
||
<dt><strong>Forward left</strong></dt>
|
||
<dd>Head forwards and slightly left, to the kite sharing the
|
||
left-hand one of this kite's short edges (as seen from the
|
||
centre). Equivalently, rotate 120° <em>clockwise</em> about the
|
||
blunt end. From kite 5, this takes you to kite 2.</dd>
|
||
<dt><strong>Forward right</strong></dt>
|
||
<dd>Head forwards and slightly right. Or rotate 120° anticlockwise
|
||
about the blunt end, if you prefer to think of it that way. From
|
||
kite 5, this takes you to kite 1.</dd>
|
||
</dl>
|
||
<p>The idea is that if we know how to transform the coordinates of a
|
||
single kite into the coordinates of each of those four adjacent
|
||
kites, then we can iterate that over a whole area and determine the
|
||
coordinates of every kite in the whole tiling.</p>
|
||
<p>Having done that, it's easy to identify each individual kite, by
|
||
several different methods. For example, you could iterate over edges
|
||
of the tiling to see whether the kites on each side have coordinates
|
||
differing only in the kite index; if so, they're part of the same
|
||
hat, and if not, not. Or a completely different approach (in fact
|
||
the one Loopy actually uses) would be to trace round the boundary of
|
||
each hat by starting from its kite #0 and just knowing what shape a
|
||
hat is.</p>
|
||
<p>So now we have to come up with an algorithm that lets us
|
||
transform a kite coordinate by making one of the four permitted
|
||
moves. To do this, we use two multilevel types of map.</p>
|
||
<p>The <strong>kitemap</strong> for a given metatile type is made by
|
||
expanding the metatile once into more metatiles, and then into hats.
|
||
For example, the T tile:</p>
|
||
<table class="noborder">
|
||
<tr>
|
||
<td><img src="single-T.svg"></td>
|
||
<td><img src="arrow.svg"></td>
|
||
<td><img src="expanded-T.svg" height="200px"></td>
|
||
<td><img src="arrow.svg"></td>
|
||
<td><img src="kitemap-T.svg" height="500px"></td>
|
||
</tr>
|
||
</table>
|
||
<p>In each kite, we show a three-part coordinate, in little-endian
|
||
fashion (because that matches the order the coordinates are stored
|
||
in an array in the code that actually generates the tilings). For
|
||
example, 7.3.0 means kite 7 in hat 3 of metatile 0 of the
|
||
expansion.</p>
|
||
<p>This map can be converted into a lookup table, indexed by those
|
||
three-part coordinates and also the four move directions, which
|
||
allows you to look up that (for example) going Left from kite 7.3.0
|
||
goes to 0.0.0, or going Forward Left from 7.3.0 goes to 3.1.3.</p>
|
||
<p>But if you're at the very edge of the kitemap, this isn't enough.
|
||
For example, kite 0.0.4 right at the top can go Left to 1.0.4, but
|
||
if it wants to go in any of the other three directions, this map
|
||
doesn't help at all.</p>
|
||
<p>This is where the overlap between the metatile expansions comes
|
||
in. If you're in kite 0.0.4, then in particular, you're in the F
|
||
tile numbered 4 in the expansion of a larger T metatile. And that F
|
||
tile is <em>also</em> part of the expansion of at least one other
|
||
second-order metatile – maybe two of them – which means that there
|
||
are other equivalent coordinates describing the same kite, which
|
||
will place it in a different kitemap where it <em>isn't</em> right
|
||
on the edge,</p>
|
||
<p>In order to find those equivalent coordinates, we create a second
|
||
map for each metatile type, called the <strong>metamap</strong>.
|
||
This one arises from expanding the metatile twice into other
|
||
metatiles, instead of into hats:</p>
|
||
<table class="noborder">
|
||
<tr>
|
||
<td><img src="single-T.svg"></td>
|
||
<td><img src="arrow.svg"></td>
|
||
<td><img src="expanded-T.svg" height="200px"></td>
|
||
<td><img src="arrow.svg"></td>
|
||
<td><img src="metamap-T.svg" height="500px"></td>
|
||
</tr>
|
||
</table>
|
||
<p>Again, the coordinates are little-end first, so that 7.4 means
|
||
the 7th smallest-size tile expanded from the 4th medium-sized tile
|
||
expanded from the original single large tile.</p>
|
||
<p>Unlike the kitemap, the metamap is not used for <em>moving
|
||
around</em> the tiling to a different kite. It's used for rewriting
|
||
the coordinates of the current kite into equivalent forms. So each
|
||
the small tile in the metamap that's part of the expansion
|
||
of <em>more than one</em> medium-sized tile has more than one
|
||
coordinate pair. For example, tile 5.2 is also tile 5.4, and tile
|
||
7.0 is also 8.3 <em>and</em> also 4.5 (because it's where three
|
||
medium-tile expansions meet).</p>
|
||
<p>Using both of these maps (converted into appropriate lookup
|
||
tables in the code), you can always eventually find a valid
|
||
coordinate representation of whichever kite you like adjacent to
|
||
your current one. If the kitemap corresponding to the current
|
||
coordinates doesn't tell you the coordinates of the next kite, then
|
||
you can try rewriting the two least-significant metatile indices
|
||
(using the metamap corresponding to the type of the next-larger
|
||
metatile still) and then see if that gives you a new kitemap that
|
||
works. If even that doesn't work, you can move another level up, and
|
||
try a metamap rewrite on the 2nd and 3rd smallest metatile levels,
|
||
or the 3rd and 4th, etc. And eventually, you find something you can
|
||
do.</p>
|
||
<p>The full set of kitemaps and metamaps for all the tile
|
||
types is in <a href="hatmaps.html">hatmaps.html</a>.</p>
|
||
</body>
|
||
</html>
|