<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Untitled Publication]]></title><description><![CDATA[Untitled Publication]]></description><link>https://dev.element77.com</link><generator>RSS for Node</generator><lastBuildDate>Wed, 15 Apr 2026 03:05:56 GMT</lastBuildDate><atom:link href="https://dev.element77.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Solving Letter Boxed In Python]]></title><description><![CDATA[Letter Boxed Introduction
Letter Boxed is a word puzzle game developed by The New York Times as part of their daily digital games collection. In Letter Boxed, players are presented with a square grid where the border is filled with letters. The objec...]]></description><link>https://dev.element77.com/solving-letter-boxed-in-python</link><guid isPermaLink="true">https://dev.element77.com/solving-letter-boxed-in-python</guid><category><![CDATA[letter boxed]]></category><category><![CDATA[nytimes]]></category><category><![CDATA[Python]]></category><category><![CDATA[nltk]]></category><dc:creator><![CDATA[Arun Viswanathan]]></dc:creator><pubDate>Tue, 25 Jun 2024 23:35:40 GMT</pubDate><content:encoded><![CDATA[<h2 id="heading-letter-boxed-introduction">Letter Boxed Introduction</h2>
<p><a target="_blank" href="https://www.nytimes.com/puzzles/letter-boxed">Letter Boxed</a> is a word puzzle game developed by The New York Times as part of their daily digital games collection. In Letter Boxed, players are presented with a square grid where the border is filled with letters. The objective is to connect these letters to form words, in such a way that each word starts with the ending letter of the previous word, creating a continuous chain.</p>
<p>The rules are fairly simple:</p>
<ol>
<li><p>Each word must be at least three letters long.</p>
</li>
<li><p>Words must be formed by connecting letters from different edges i.e., a word cannot use consecutive letters from the same edge.</p>
</li>
<li><p>Subsequent words must begin with the ending letter of the previous word.</p>
</li>
<li><p>The game typically requires finding a series of words that use up all the given letters on the edges.</p>
</li>
</ol>
<p>The challenge lies in strategically determining a sequence of words that utilizes every letter at least once, and achieving this with the fewest possible words.</p>
<h2 id="heading-solution">Solution</h2>
<h3 id="heading-word-list">Word List</h3>
<p>For word games, a word list is quite essential for the search. With Python, there is easy access to the <a target="_blank" href="https://www.nltk.org/nltk_data/">Natural Language ToolKit (NLTK) corpora</a> via the <code>nltk</code> package. The <code>words</code> corpus contains over 235,000 English words.</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> nltk

nltk.download(<span class="hljs-string">'words'</span>)
nltk_words = nltk.corpus.words.words()
</code></pre>
<h3 id="heading-search">Search</h3>
<p>The search for words that cover the entire word list is quite a large search space. There are a few ways to trim the word list quite rapidly.</p>
<ol>
<li><p>Make sure that the words meet the minimum length requirement of Letter Boxed</p>
</li>
<li><p>Make sure that the words only contain the letters provided in the Letter Boxed square and obey the rule that consecutive letters come from different edges of the square.</p>
</li>
</ol>
<pre><code class="lang-python"><span class="hljs-comment"># Check if the word is valid for the Letter Boxed game</span>

<span class="hljs-comment"># word</span>
<span class="hljs-comment">#     word from the dictionary</span>
<span class="hljs-comment"># args.top, args.left, args.bottom, args.right</span>
<span class="hljs-comment">#     letters from each side of the LetterBoxed game</span>

<span class="hljs-comment"># Check if the word meets the minimum length</span>
<span class="hljs-keyword">if</span> len(word) &lt; args.min <span class="hljs-keyword">or</span> len(word) &gt; args.max:
    <span class="hljs-keyword">return</span> <span class="hljs-literal">False</span>

<span class="hljs-comment"># Check if word contains only letters in the box without consecutive letters from same side</span>
sides = {<span class="hljs-string">'t'</span>: args.top, <span class="hljs-string">'l'</span>: args.left, <span class="hljs-string">'b'</span>: args.bottom, <span class="hljs-string">'r'</span>: args.right}
prev_set_name = <span class="hljs-literal">None</span>
<span class="hljs-keyword">for</span> letter <span class="hljs-keyword">in</span> word:
    set_name = next((name <span class="hljs-keyword">for</span> name, side <span class="hljs-keyword">in</span> sides.items() <span class="hljs-keyword">if</span> letter <span class="hljs-keyword">in</span> side), <span class="hljs-literal">None</span>)
    <span class="hljs-keyword">if</span> set_name <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span> <span class="hljs-keyword">or</span> set_name == prev_set_name:
        <span class="hljs-keyword">return</span> <span class="hljs-literal">False</span>
    prev_set_name = set_name
</code></pre>
<p>Once the word list has been pruned, typically the search space reduces by ~95-99%. Then the problem becomes a recursive search with backtracking starting with a random word from the word list and checking to see if all the letters of the Letter Boxed game are covered.</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">recursive_solve</span>(<span class="hljs-params">args, all_solutions, solution=None, depth=<span class="hljs-number">0</span></span>):</span>
    <span class="hljs-string">"""
    A recursive function to solve a given problem using backtracking.

    Args:
        args: The arguments for the recursive solve function.
        all_solutions: A list to store all the found solutions.
        solution: The current solution being built (default is None).
        depth: The current depth of recursion (default is 0).

    Returns:
        list: A list of all the found solutions.
    """</span>
    solution = solution <span class="hljs-keyword">or</span> []
    <span class="hljs-keyword">if</span> depth != args.depth:
        <span class="hljs-keyword">for</span> word <span class="hljs-keyword">in</span> args.search_words:
            potential_solution = solution + [word]
            used_letters = set(list(<span class="hljs-string">''</span>.join(potential_solution)))
            <span class="hljs-keyword">if</span> args.all_letters == used_letters:
                logging.info(<span class="hljs-string">"Found solution: %s"</span>, potential_solution)
                all_solutions.append(potential_solution)
            <span class="hljs-keyword">else</span>:
                last_letter = word[<span class="hljs-number">-1</span>]
                args.search_words = [x <span class="hljs-keyword">for</span> x <span class="hljs-keyword">in</span> args.all_search_words
                                     <span class="hljs-keyword">if</span> x[<span class="hljs-number">0</span>] == last_letter <span class="hljs-keyword">and</span> x != word]
                all_solutions = recursive_solve(args, all_solutions, potential_solution, depth + <span class="hljs-number">1</span>)
    <span class="hljs-keyword">return</span> all_solutions
</code></pre>
<p>Full code for this can be found at <a target="_blank" href="https://github.com/arunkv/letter-boxed/blob/main/lb.py">https://github.com/arunkv/letter-boxed/blob/main/lb.py</a> A typical two-word solve for Letter Boxed finds <em>all</em> possible solutions in under a second.</p>
]]></content:encoded></item></channel></rss>