<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Dutton Software &#187; Python</title>
	<atom:link href="http://www.duttonsoftware.com/category/python/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.duttonsoftware.com</link>
	<description>Code, plug-ins &#38; more</description>
	<lastBuildDate>Fri, 05 Mar 2010 17:19:04 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0.1</generator>
		<item>
		<title>Solving Boggle-type word games in Python</title>
		<link>http://www.duttonsoftware.com/2010/01/07/solving-boggle-type-word-games-in-python/</link>
		<comments>http://www.duttonsoftware.com/2010/01/07/solving-boggle-type-word-games-in-python/#comments</comments>
		<pubDate>Fri, 08 Jan 2010 00:14:46 +0000</pubDate>
		<dc:creator>Aaron</dc:creator>
				<category><![CDATA[Python]]></category>
		<category><![CDATA[python boggle scramble2]]></category>

		<guid isPermaLink="false">http://www.duttonsoftware.com/?p=190</guid>
		<description><![CDATA[Recently I&#8217;ve been spending a lot of my spare time learning the Python computer programming language. This all began because of an interest in the Google App Engine, which uses Python as the primary language. I decided that it&#8217;d been a while since I picked up a new programming language (the last being C#) and [...]]]></description>
			<content:encoded><![CDATA[<p>Recently I&#8217;ve been spending a lot of my spare time learning the <a href="http://www.python.org/" target="_blank">Python computer programming language</a>.  This all began because of an interest in the Google App Engine, which uses Python as the primary language.  I decided that it&#8217;d been a while since I picked up a new programming language (the last being C#) and I knew I&#8217;d have some spare time over the holidays.</p>
<p>Anyhow, there are a number of excellent learning resources for Python, but I seem to learn best by doing.  So, after tackling a number of levels in the <a href="http://www.pythonchallenge.com/" target="_blank">Python challenge</a> I decided to make my own challenge; Rack up a high score in Scramble and Scramble2, which is a &#8220;Boggle&#8221;-type game for Facebook and the iTouch/iPhone respectively.<br />
<a style="text-align: right;" title="Boggle" href="http://www.flickr.com/photos/27675896@N07/4039402557/" target="_blank"><img src="http://farm3.static.flickr.com/2523/4039402557_b779a76054_m.jpg" border="0" alt="Boggle" /></a><br />
<small><a title="Attribution License" href="http://creativecommons.org/licenses/by/2.0/" target="_blank"><img src="http://www.duttonsoftware.com/wp-content/plugins/photo-dropper/images/cc.png" border="0" alt="Creative Commons License" width="16" height="16" align="absmiddle" /></a> <a href="http://www.photodropper.com/photos/" target="_blank">photo</a> credit: <a title="therichbrooks" href="http://www.flickr.com/photos/27675896@N07/4039402557/" target="_blank">therichbrooks</a></small></p>
<p><span id="more-190"></span>The game is played on a 4&#215;4 or 5&#215;5 grid of letters.  The object is to attain a high score.  You score by connecting adjacent letters into a word.  Longer words result in disproportionally higher scores.  The minimum word length is 3.  You cannot use a letter-space more than once in the same word.</p>
<p>I created a Python program which compared all possible paths to a dictionary of known words and then printed them out in order from longest to shortest.  The program is admittedly lacking in a user-interface.  You have to re-run it to change the board size and it&#8217;s not super-optimized for either speed or memory.  However, it does work quite well and is fast enough to use.  It takes about 20-30 seconds to do the initial load of all the paths and then takes between 1-5 seconds to compute possible words once you type in the values from the game.</p>
<p>Below are the results from my Python program (left) and showing my &#8220;high score&#8221; of 369 (right).</p>
<p><a href="http://www.duttonsoftware.com/wp-content/uploads/2010/01/scramble-solver-running-in-python-interpreter.png"><img class="alignnone size-medium wp-image-200" title="Scramble solver running in python interpreter" src="http://www.duttonsoftware.com/wp-content/uploads/2010/01/scramble-solver-running-in-python-interpreter-255x300.png" alt="" width="255" height="300" /></a> <a href="http://www.duttonsoftware.com/wp-content/uploads/2010/01/scramble-screenshot-with-high-score.png"><img class="alignnone size-medium wp-image-192" title="Scramble screenshot with high score" src="http://www.duttonsoftware.com/wp-content/uploads/2010/01/scramble-screenshot-with-high-score-300x211.png" alt="" width="300" height="211" /></a></p>
<p>With a better dictionary you could improve the score.  In the example above, my dictionary has 3 of the 5 seven-letter words but misses the eight-letter word in the puzzle.</p>
<p>You can download the <a href="http://www.duttonsoftware.com/wp-content/uploads/2010/01/Scramble-Solver.zip">Scramble Solver.py &#8211; Python source code</a>.  I used a list of words separated by carriage returns from a spell-checking &#8220;dictionary&#8221;.  You are on your own for finding a word list.  I&#8217;m providing the source code so that you can see it, not so you can beat the game :)</p>
<p>Below is a complete listing of the source code:</p>
<pre class="brush: python;">
class wordlist:
    &quot;&quot;&quot;
    Create a clean list of words from the base dictionary
    &quot;&quot;&quot;
    def __init__(self, max_length):
        # Constants
        self._rawpath = 'en_us.dic'
        self._maxlength = max_length

    def load(self):
        &quot;&quot;&quot;
        Returns a clean list of words
        &quot;&quot;&quot;
        print 'Creating list of words...'
        raw_dictionary = open(self._rawpath, 'r')
        goodwords = set()

        for line in raw_dictionary:
            # Make sure we have a word of the correct size and not starting with a number and with no apostrophes
            if (2 &lt; len(line) &lt; self._maxlength + 1
                and line[0] not in ['0','1','2','3','4','5','6','7','8','9']
                and &quot;'&quot; not in line):
                # Strip off trailing \n and convert to lowercase
                goodwords.add(line[:-1].lower())
        raw_dictionary.close()

        print 'Created list of words.'

        return goodwords

class pathlist:
    &quot;&quot;&quot;
    Creates a path list which contains all possible paths through the grid.
    &quot;&quot;&quot;

    def __init__(self, board_size, max_length):
        # Constants
        self._boardsize = board_size
        self._maxlength = max_length

        # Properties
        self.paths = []

    def _traversepath(self, startindex, previouspath):
        &quot;&quot;&quot;
        Finds all the paths for a given starting point,
        used recursively to explore adjacent segments
        &quot;&quot;&quot;
        # Immediately exit if we are trying to create a circular path
        # or if the path length gets too long
        if startindex in previouspath or len(previouspath) &gt;= self._maxlength:
            return

        # Copy the path and then add the next segment
        nextpath = previouspath[:]
        nextpath.append(startindex)

        # Only store paths of at least three letters
        if len(nextpath) &gt; 2:
            self.paths.append(nextpath)

        # Grab the row and column for the given startindex
        row = startindex // self._boardsize
        col = startindex % self._boardsize

        # Traverse adjacent paths
        # Up to 8 adjacent paths possible

        # Row above: row - 1
        if row - 1 &gt;= 0 and col - 1 &gt;= 0: self._traversepath(startindex - self._boardsize - 1, nextpath)
        if row - 1 &gt;= 0: self._traversepath(startindex - self._boardsize, nextpath)
        if row - 1 &gt;= 0 and col + 1 &lt;= 3: self._traversepath(startindex - self._boardsize + 1, nextpath)

        # Same row: row - 0
        if col - 1 &gt;= 0: self._traversepath(startindex - 1, nextpath)
        if col + 1 &lt;= 3: self._traversepath(startindex + 1, nextpath)

        # Row below: row + 1
        if row + 1 &lt;= 3 and col - 1 &gt;= 0: self._traversepath(startindex + self._boardsize - 1, nextpath)
        if row + 1 &lt;= 3: self._traversepath(startindex + self._boardsize, nextpath)
        if row + 1 &lt;= 3 and col + 1 &lt;= 3: self._traversepath(startindex + self._boardsize + 1, nextpath)

    def load(self):
        &quot;&quot;&quot;
        Returns a list of possible paths
        &quot;&quot;&quot;
        self.paths = []
        print 'Creating the path list...'
        # Generate self.paths by going through all positions
        for i in range(0, self._boardsize ** 2):
            self._traversepath(i, [])

        print 'Created the path list.'

        return self.paths

class solver:
    &quot;&quot;&quot;
    Class for solving 4x4 and 5x5 grid word puzzles using a dictionary
    &quot;&quot;&quot;
    def __init__(self, paths = None, dict = None):
        &quot;&quot;&quot;
        Loads up the dictionary and paths from file
        &quot;&quot;&quot;
        print 'Loading words...'
        self.dictionary = dict
        print 'Loaded %d words.' % len(self.dictionary)

        print 'Loading paths...'
        self.paths = paths
        print 'Loaded %d paths.' % len(self.paths)

        # initialize grid
        self.grid = []

    def _translate_path_to_letters(self, path, letters):
        &quot;&quot;&quot;
        Translates a numerical path to letters given a grid.

        Returns a string
        &quot;&quot;&quot;
        output = ''
        for p in path:
            output += letters[p]
        return output

    def _get_intersecting_words(self):
        &quot;&quot;&quot;
        Finds the intersection between the dictionary words
        and the (path-based) possible words

        Returns a subset of the possible words
        &quot;&quot;&quot;
        realwords = {}
        for word in self.dictionary:
            if self.possiblewords.has_key(word):
                realwords[word] = self.possiblewords[word]
        return realwords

    def get_words(self, letters):
        &quot;&quot;&quot;
        Returns a dictionary with keys of valid words.
        The values of the dictionary are the paths taken to get those valid words.
        For example, in the sample data set (4x4 board):
            Key: genius
            Value: [4, 8, 5, 6, 9, 10]
        &quot;&quot;&quot;
        output = ''
        self.grid = letters
        self.possiblewords = dict()

        # Translate all paths into letters
        print 'Translating all possible paths into possible words...'
        for path in self.paths:
            self.possiblewords[self._translate_path_to_letters(path, letters)] = path
        print 'Translated paths into %d unique words.' % len(self.possiblewords)

        print 'Finding possible real words...'
        realwords = self._get_intersecting_words()
        print 'Found %d possible real words.' % len(realwords)

        return realwords

MAXLENGTH = 10
BOARDSIZE = 4

pl = pathlist(BOARDSIZE, MAXLENGTH)
wl = wordlist(MAXLENGTH)
s = solver(pl.load(), wl.load())

while True:
    print 'Enter all %d lines in a row, with no spaces between them.  Or, press ENTER to use the default values.  Type &quot;q&quot; by itself to quit.' % BOARDSIZE
    line = raw_input(&quot;line:&quot;).lower()

    if line == 'q':
        break

    # Check to see if we should use the default values
    if len(line) == 0:
        print 'Using default values.'
        # Assumes BOARDSIZE of 4 or 5 only
        if BOARDSIZE == 4:
            line = 'oiei' + 'gnic' + 'eusn' + 'oliy'
        else:
            line = ''

    # Find all the possible words and their paths
    wordset = [word for word in s.get_words(line).keys()]

    # Print out the words, sorted from longest to shortest, ignoring the paths
    for word in sorted(wordset, None, len, True):
        print word
</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.duttonsoftware.com/2010/01/07/solving-boggle-type-word-games-in-python/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
