<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>http://sokobano.de/wiki/index.php?action=history&amp;feed=atom&amp;title=Heiner%27s_solver</id>
	<title>Heiner&#039;s solver - Revision history</title>
	<link rel="self" type="application/atom+xml" href="http://sokobano.de/wiki/index.php?action=history&amp;feed=atom&amp;title=Heiner%27s_solver"/>
	<link rel="alternate" type="text/html" href="http://sokobano.de/wiki/index.php?title=Heiner%27s_solver&amp;action=history"/>
	<updated>2026-04-17T20:10:23Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.43.6</generator>
	<entry>
		<id>http://sokobano.de/wiki/index.php?title=Heiner%27s_solver&amp;diff=5322&amp;oldid=prev</id>
		<title>Matthias Meger: /* Generalizing to more packets */</title>
		<link rel="alternate" type="text/html" href="http://sokobano.de/wiki/index.php?title=Heiner%27s_solver&amp;diff=5322&amp;oldid=prev"/>
		<updated>2015-10-21T20:49:51Z</updated>

		<summary type="html">&lt;p&gt;&lt;span class=&quot;autocomment&quot;&gt;Generalizing to more packets&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;I&amp;#039;m [[User:Heiner|Heiner]], and have written a &amp;quot;brute force&amp;quot; sokoban solver.&lt;br /&gt;
Here I want to tell about it.&lt;br /&gt;
&lt;br /&gt;
== How it started ==&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;initial ignition&amp;quot; was an idea about &amp;quot;deadlocks&amp;quot;, i.e. their detection.&lt;br /&gt;
Somewhere I had seen a hand written list of small patterns,&lt;br /&gt;
used for deadlock detection.&lt;br /&gt;
That appeared to me to be the wrong way.&lt;br /&gt;
And I had an idea how to compute a certain class of deadlocks&lt;br /&gt;
from basic principles.&lt;br /&gt;
So I started to write a simple sokoban solver in Tcl.&lt;br /&gt;
That was in 2007.&lt;br /&gt;
&lt;br /&gt;
Later (still in 2007) I changed to ANSI-C, since Tcl wasn&amp;#039;t fast enough, any more,&lt;br /&gt;
and also used too much memory... my method needs large amounts of memory.&lt;br /&gt;
&lt;br /&gt;
But the Tcl prototype was a good exercise to prepare the C program.&lt;br /&gt;
&lt;br /&gt;
== Legend ==&lt;br /&gt;
&lt;br /&gt;
The terms I currently use, may be a bit non-standard.&lt;br /&gt;
I found this Wiki in late 2010, and start to recognize that&lt;br /&gt;
others use different words.  While I have not switched,&lt;br /&gt;
this is what I used up to now:&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;actor&amp;#039;&amp;#039;&amp;#039;: the moving &amp;quot;man&amp;quot;, the warehouse keeper, the &amp;quot;&amp;lt;code&amp;gt;@&amp;lt;/code&amp;gt;&amp;quot;&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;packet&amp;#039;&amp;#039;&amp;#039;: the moved objects (box), the &amp;quot;&amp;lt;code&amp;gt;$&amp;lt;/code&amp;gt;&amp;quot;&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;harbor&amp;#039;&amp;#039;&amp;#039;: the packet destinations (goal), the &amp;quot;&amp;lt;code&amp;gt;.&amp;lt;/code&amp;gt;&amp;quot;&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;configuration&amp;#039;&amp;#039;&amp;#039;: a complete description of a situation: location of actor and of all the packets.  What cannot be changed by &amp;quot;playing the game&amp;quot;, is not considered part of a configuration, but is stored elsewhere, e.g. walls and harbor locations.&lt;br /&gt;
&lt;br /&gt;
== The basic solver method ==&lt;br /&gt;
&lt;br /&gt;
I use the most simple method, not specific to sokoban.&lt;br /&gt;
We have a starting configuration, a terminal configuration,&lt;br /&gt;
and a definition of legal moves, which allows writing a function&lt;br /&gt;
which maps any configuration to the set of legal successors.&lt;br /&gt;
&lt;br /&gt;
This setup is enough for writing a simple puzzle solver,&lt;br /&gt;
that searches for the shortest solution path.&lt;br /&gt;
&lt;br /&gt;
We have a collection of pairs (configuration, distance),&lt;br /&gt;
e.g. implemented as a hash table.&lt;br /&gt;
We enter the pair (start-config, 0).&lt;br /&gt;
Then, for increasing distance &amp;#039;&amp;#039;d&amp;#039;&amp;#039;, from all entries with distance &amp;#039;&amp;#039;d&amp;#039;&amp;#039;&lt;br /&gt;
we generate all immediate successors,&lt;br /&gt;
and those, which are not yet known, are entered with distance &amp;#039;&amp;#039;d+1&amp;#039;&amp;#039;.&lt;br /&gt;
Each new entry is checked, whether it is the seeked for terminal configuration.&lt;br /&gt;
&lt;br /&gt;
Once the terminal configuration is entered, we can stop the above,&lt;br /&gt;
and extract a shortest solution path,&lt;br /&gt;
starting with the terminal configuration,&lt;br /&gt;
and searching for an immediate predecessor with a distance 1 smaller,&lt;br /&gt;
and so on.&lt;br /&gt;
&lt;br /&gt;
=== What is a &amp;quot;move&amp;quot; ===&lt;br /&gt;
&lt;br /&gt;
I choose a &amp;quot;move&amp;quot; to be an arbitrary actor walk followed by exactly 1 &amp;#039;&amp;#039;push&amp;#039;&amp;#039; action.&lt;br /&gt;
That way I generate push-optimal solutions.&lt;br /&gt;
I could have chosen to make every small step of the actor a &amp;quot;move&amp;quot;,&lt;br /&gt;
and would get move-optimal solutions.&lt;br /&gt;
The drawback would be the need of much higher distances,&lt;br /&gt;
and to generate many more configurations.&lt;br /&gt;
&lt;br /&gt;
Counting pushes instead of moves appeared to me to be the most natural measure.&lt;br /&gt;
Your mileage may vary.&lt;br /&gt;
&lt;br /&gt;
=== Solving forward ===&lt;br /&gt;
&lt;br /&gt;
The above description is the &amp;#039;&amp;#039;forward&amp;#039;&amp;#039; method.&lt;br /&gt;
It is the most natural thing to do,&lt;br /&gt;
but it is not the only way, not even the best one...&lt;br /&gt;
&lt;br /&gt;
=== Solving backwards ===&lt;br /&gt;
&lt;br /&gt;
The attentive reader already noted, that the solution path extraction&lt;br /&gt;
already needs an inverse move generator, generating immediate predecessor&lt;br /&gt;
configurations.&lt;br /&gt;
For sokoban this is not really hard to write.&lt;br /&gt;
&lt;br /&gt;
Why shouldn&amp;#039;t we start with the terminal configuration,&lt;br /&gt;
and generate their predecessors, until we find the initial configuration?&lt;br /&gt;
Nothing can stop us, it is a perfect mirror image of the above method,&lt;br /&gt;
except... we do &amp;#039;&amp;#039;not&amp;#039;&amp;#039; generate the &amp;#039;&amp;#039;exact same set&amp;#039;&amp;#039; of configurations.&lt;br /&gt;
&lt;br /&gt;
I did some experiments, and learned, that often the backward method generated&lt;br /&gt;
less configurations than the forward method,&lt;br /&gt;
but not always.&lt;br /&gt;
&lt;br /&gt;
While contemplating how to decide for the best of both,&lt;br /&gt;
I found a third method...&lt;br /&gt;
&lt;br /&gt;
=== Solving mixed ===&lt;br /&gt;
&lt;br /&gt;
In a sense this is very simple.&lt;br /&gt;
We start solving forward &amp;#039;&amp;#039;and&amp;#039;&amp;#039; backwards,&lt;br /&gt;
in separate collections (hash tables),&lt;br /&gt;
one distance step at a time.&lt;br /&gt;
And we check the new entries in one collection&lt;br /&gt;
also against the other collection,&lt;br /&gt;
and when we find a match,&lt;br /&gt;
we have a solution, of minimal total distance.&lt;br /&gt;
&lt;br /&gt;
Since these search trees tend to have much more entries for the larger distances,&lt;br /&gt;
we now have used considerably less memory.&lt;br /&gt;
That is quite important, since our method is clearly memory bound.&lt;br /&gt;
&lt;br /&gt;
We still can improve this: instead of using a fixed alternation&lt;br /&gt;
between forward and backward steps,&lt;br /&gt;
we can do the next distance increasing step on that collection,&lt;br /&gt;
where the &amp;#039;&amp;#039;front&amp;#039;&amp;#039; (the entries with the maximal distance)&lt;br /&gt;
is smaller.&lt;br /&gt;
The size of the front is a good (relative) estimate of the next front.&lt;br /&gt;
&lt;br /&gt;
The extraction of a shortest solution path is not exactly trivial anymore,&lt;br /&gt;
but it is not really hard, either.&lt;br /&gt;
Details are left as an exercise &amp;lt;code&amp;gt;;-)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That is what I currently use (Dec-2010).&lt;br /&gt;
Maybe everybody knew this already,&lt;br /&gt;
but for me it was a genuine invention.&lt;br /&gt;
&lt;br /&gt;
== Computing deadlocks ==&lt;br /&gt;
&lt;br /&gt;
Up to now, we have used very little knowledge about sokoban.&lt;br /&gt;
We have used the rules by writing 2 functions, computing&lt;br /&gt;
immediate successor and predecessor configurations.&lt;br /&gt;
&lt;br /&gt;
Now we use another fact / observation:&lt;br /&gt;
when we remove a packet from a configuration,&lt;br /&gt;
and generate successor or predecessor configurations,&lt;br /&gt;
we &lt;br /&gt;
# loose all those configurations, which moved the removed packet&lt;br /&gt;
# but we do still have all other configurations (now with one packet less)&lt;br /&gt;
# and we may get some more configurations, which were not legal before removing that packet.&lt;br /&gt;
The important point is 2.&lt;br /&gt;
In the game Atomix e.g. it would not be valid, but in Sokoban&lt;br /&gt;
the other packets only can hurt the movement of a packet,&lt;br /&gt;
they cannot help to find more legal movements.&lt;br /&gt;
&lt;br /&gt;
Computing with a reduced set of packets we get a kind of upper bound&lt;br /&gt;
for what is possible.&lt;br /&gt;
&lt;br /&gt;
Let us make an example:&lt;br /&gt;
say, we start with any packet from the starting configuration,&lt;br /&gt;
but only with that single packet, and generate all successors,&lt;br /&gt;
and their successors and so on, until we get no more configurations,&lt;br /&gt;
then we have a collection of all possible packet locations.&lt;br /&gt;
The full game will never be able to push a packet to any location&lt;br /&gt;
not found in this collection.&lt;br /&gt;
&lt;br /&gt;
So what?  Read on...&lt;br /&gt;
&lt;br /&gt;
Let us do the same in the opposite direction, backwards,&lt;br /&gt;
starting with single packets on any of the harbors.&lt;br /&gt;
&lt;br /&gt;
Now we have a collection of reachable,&lt;br /&gt;
and a collection of solvable configurations.&lt;br /&gt;
Only those, which are in &amp;#039;&amp;#039;both&amp;#039;&amp;#039; these collections&lt;br /&gt;
have a chance to be part of a solution.&lt;br /&gt;
All others are &amp;#039;&amp;#039;bad&amp;#039;&amp;#039; and should be avoided.&lt;br /&gt;
&lt;br /&gt;
If we remember the results of this computation,&lt;br /&gt;
we can check every generated new configuration,&lt;br /&gt;
whether it contains any packet on a location, which is &amp;quot;bad&amp;quot;,&lt;br /&gt;
and immediately forget about it.&lt;br /&gt;
&lt;br /&gt;
=== Dead locations ===&lt;br /&gt;
&lt;br /&gt;
What I have described above, is a way to recognize all &amp;quot;dead locations&amp;quot;,&lt;br /&gt;
i.e. locations which are immediate traps for a packet...&lt;br /&gt;
and some not so obvious ones.&lt;br /&gt;
&lt;br /&gt;
Instead of hard coding some special rules to find &amp;quot;dead locations&amp;quot;&lt;br /&gt;
we have computed them from basic principles,&lt;br /&gt;
what esthetically pleases me.&lt;br /&gt;
&lt;br /&gt;
=== Generalizing to more packets ===&lt;br /&gt;
&lt;br /&gt;
The above method can be generalized to using &amp;#039;&amp;#039;k&amp;#039;&amp;#039; of the packets,&lt;br /&gt;
instead of just 1.&lt;br /&gt;
I named this the &amp;#039;&amp;#039;k-packet-game&amp;#039;&amp;#039; or &amp;#039;&amp;#039;k&amp;#039;&amp;#039;-PG for short.&lt;br /&gt;
* Generate all subsets of cardinality &amp;#039;&amp;#039;k&amp;#039;&amp;#039; of the initial packets (actor as initial)&lt;br /&gt;
* Completely &amp;quot;solve&amp;quot; this forwards, do not stop until no more legal configurations can be generated&lt;br /&gt;
* Generate all subsets of cardinality &amp;#039;&amp;#039;k&amp;#039;&amp;#039; of the harbors as packets (all possible actor locations)&lt;br /&gt;
* Completely &amp;quot;solve&amp;quot; this backwards, do not stop until no more legal configurations can be generated&lt;br /&gt;
* Compute a compact representation of all configurations, which are in one of theses collections, but not in the other&lt;br /&gt;
Then, use this compact data to check every generated configuration with &amp;#039;&amp;#039;k&amp;#039;&amp;#039; or more packets,&lt;br /&gt;
whether it contains such a subset of &amp;#039;&amp;#039;k&amp;#039;&amp;#039; packets, and ignore it, as it cannot possibly&lt;br /&gt;
be part of any solution.&lt;br /&gt;
&lt;br /&gt;
When playing the &amp;#039;&amp;#039;k+1&amp;#039;&amp;#039;-packet-game, we already use the results of the &amp;#039;&amp;#039;k&amp;#039;&amp;#039;-packet-game.&lt;br /&gt;
That can help to reduce the total amount of memory, sometimes significantly.&lt;br /&gt;
&lt;br /&gt;
Checking for subsets against a large amount of such candidates is a bit tricky&lt;br /&gt;
to do efficiently.&lt;br /&gt;
&lt;br /&gt;
With increasing &amp;#039;&amp;#039;k&amp;#039;&amp;#039; this becomes increasingly memory demanding.&lt;br /&gt;
And for some levels the results are not so good,&lt;br /&gt;
but for some levels this makes a huge difference.&lt;br /&gt;
&lt;br /&gt;
== What else? ==&lt;br /&gt;
&lt;br /&gt;
Well, now you know most secrets about my sokoban solver.&lt;br /&gt;
Of course, I have lots of ideas, what I still want to do.&lt;br /&gt;
Some examples are:&lt;br /&gt;
* using bipartite matching over the reachability results from the 1-PG&lt;br /&gt;
* implementing less memory demanding encodings of the &amp;quot;collections&amp;quot; of (configuration,distance) pairs&lt;br /&gt;
* tunnel macros&lt;br /&gt;
* symmetry&lt;br /&gt;
* compute move-optimal solution inside push-optimal options&lt;/div&gt;</summary>
		<author><name>Matthias Meger</name></author>
	</entry>
</feed>