{"id":1805,"date":"2014-07-29T14:12:35","date_gmt":"2014-07-29T04:12:35","guid":{"rendered":"http:\/\/www.somethinkodd.com\/oddthinking\/?p=1805"},"modified":"2014-07-29T14:12:35","modified_gmt":"2014-07-29T04:12:35","slug":"light-up-puzzle","status":"publish","type":"post","link":"https:\/\/www.somethinkodd.com\/oddthinking\/2014\/07\/29\/light-up-puzzle\/","title":{"rendered":"Light Up Puzzle"},"content":{"rendered":"<p>Long-term readers (given the radio silence, do I have any other type?) will remember that I wrote a <a href=\"http:\/\/www.somethinkodd.com\/oddthinking\/2008\/01\/04\/message-passing-in-my-puzzle-solving-architecture\/\">Puzzle Solving Framework<\/a> to help solve &#8220;newspaper&#8221; pencil-and-paper puzzles. I&#8217;ve been thinking of preparing a presentation about it for a local Python group, so I decided to implement another one.<\/p>\n<h2>Introducing Light Up<\/h2>\n<p><a href=\"http:\/\/en.wikipedia.org\/wiki\/Light_Up_(puzzle)\" title=\"Wikipedia definition of Light_Up_(puzzle)\" class=\"wikipedia\">Light Up<\/a> is an example of such a puzzle. Simon Tatham&#8217;s Portable Puzzle Collection includes an <a href=\"http:\/\/www.chiark.greenend.org.uk\/~sgtatham\/puzzles\/js\/lightup.html\">online version<\/a> (amongst others).<\/p>\n<h2>Quick Rule Summary<\/h2>\n<p>The puzzle is presented as a grid of white and black cells. The black cells are pre-set &#8211; like in a cross-word. Some black cells are marked with a number from 0 to 4.<\/p>\n<p>The task is to mark all the white cells as either empty (indicated with a small rectangular dot) or containing a light globe (indicated with a large circle).<\/p>\n<p>Lights illuminate all white cells squares in their line-of-sight in four directions, until blocked by a black square. <\/p>\n<p>The constraints are:<\/p>\n<ol>\n<li>Lights may not be in direct line-of-sight of other lights.<\/li>\n<li>All white cells must be illuminated.<\/li>\n<li>The numbered blocks must be directly adjacent (i.e. four directions) with that many lights.<\/li>\n<\/ol>\n<p>Go try some now, before the spoilers.<\/p>\n<h2>Tactics<\/h2>\n<p>Here&#8217;s an example screen-shot (from my own program) of an unsolved puzzle.<\/p>\n<p><a href=\"http:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2014\/07\/lightup_2014-07-29T13-37-49.874000.jpg\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2014\/07\/lightup_2014-07-29T13-37-49.874000.jpg\" alt=\"Puzzle 0 - Initial\" width=\"400\" height=\"400\" class=\"alignleft size-full wp-image-1811\" srcset=\"https:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2014\/07\/lightup_2014-07-29T13-37-49.874000.jpg 400w, https:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2014\/07\/lightup_2014-07-29T13-37-49.874000-150x150.jpg 150w, https:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2014\/07\/lightup_2014-07-29T13-37-49.874000-300x300.jpg 300w\" sizes=\"auto, (max-width: 400px) 100vw, 400px\" \/><\/a><\/p>\n<p>Where can we start to solve this puzzle?<\/p>\n<h3>Tactic A<\/h3>\n<p>We can mark the cells around the 0 cell as empty. There can&#8217;t be any lights around it.<\/p>\n<p>More generally, if the number on a cell equals the number of adjacent lights already known, any remaining adjacent cells must be empty.<\/p>\n<p>If I teach the solver this rule, it can solve this much of this puzzle already.<\/p>\n<p><a href=\"http:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2014\/07\/lightup_2014-07-29T13-37-58.480000.jpg\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2014\/07\/lightup_2014-07-29T13-37-58.480000.jpg\" alt=\"Puzzle 0 - Tactic A\" width=\"400\" height=\"400\" class=\"alignleft size-full wp-image-1812\" srcset=\"https:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2014\/07\/lightup_2014-07-29T13-37-58.480000.jpg 400w, https:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2014\/07\/lightup_2014-07-29T13-37-58.480000-150x150.jpg 150w, https:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2014\/07\/lightup_2014-07-29T13-37-58.480000-300x300.jpg 300w\" sizes=\"auto, (max-width: 400px) 100vw, 400px\" \/><\/a><\/p>\n<h3>Tactic B<\/h3>\n<p>Now consider the block numbered with a 3. There are only three remaining places a light could go, so they all must be lights.<\/p>\n<p>More generally, if the number on a cell subtract the number of adjacent lights already known equals the number of adjacent unknown cells, they must be lights.<\/p>\n<p>These rules apply recursively. If I teach the solver this rule, it can solve this much of the puzzle already: <\/p>\n<p><em><strong>Legend:<\/strong> The yellow areas indicate a cell is illuminated.<\/em> <\/p>\n<p><a href=\"http:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2014\/07\/lightup_2014-07-29T13-38-03.974000.jpg\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2014\/07\/lightup_2014-07-29T13-38-03.974000.jpg\" alt=\"Puzzle 0 - Tactic A and B\" width=\"400\" height=\"400\" class=\"alignleft size-full wp-image-1813\" srcset=\"https:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2014\/07\/lightup_2014-07-29T13-38-03.974000.jpg 400w, https:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2014\/07\/lightup_2014-07-29T13-38-03.974000-150x150.jpg 150w, https:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2014\/07\/lightup_2014-07-29T13-38-03.974000-300x300.jpg 300w\" sizes=\"auto, (max-width: 400px) 100vw, 400px\" \/><\/a><\/p>\n<h3>Tactic C<\/h3>\n<p>Now consider the unlit, empty cell halfway up the left side.<\/p>\n<p>There is only one possible place a light could go to light this cell &#8211; the bottom left.<\/p>\n<p>More generally, if an unlit cell has only one unknown cell in its line-of-site. Than unknown cell must be a light.<\/p>\n<p>Once we implement that rule, the final three cells are all marked as lights.<\/p>\n<p><a href=\"http:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2014\/07\/lightup_2014-07-29T13-38-07.929000.jpg\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2014\/07\/lightup_2014-07-29T13-38-07.929000.jpg\" alt=\"Puzzle 1 - Tactic A, B and C\" width=\"400\" height=\"400\" class=\"alignleft size-full wp-image-1814\" srcset=\"https:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2014\/07\/lightup_2014-07-29T13-38-07.929000.jpg 400w, https:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2014\/07\/lightup_2014-07-29T13-38-07.929000-150x150.jpg 150w, https:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2014\/07\/lightup_2014-07-29T13-38-07.929000-300x300.jpg 300w\" sizes=\"auto, (max-width: 400px) 100vw, 400px\" \/><\/a><\/p>\n<p>Ta daaa! A solver! Exciting!<\/p>\n<p>Wait&#8230; Does it solve every puzzle?<\/p>\n<p>Lets try this one:<\/p>\n<p><a href=\"http:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2014\/07\/lightup_2014-07-29T13-51-54.130000.jpg\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2014\/07\/lightup_2014-07-29T13-51-54.130000.jpg\" alt=\"Puzzle 1 - Initial\" width=\"400\" height=\"400\" class=\"alignleft size-full wp-image-1819\" srcset=\"https:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2014\/07\/lightup_2014-07-29T13-51-54.130000.jpg 400w, https:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2014\/07\/lightup_2014-07-29T13-51-54.130000-150x150.jpg 150w, https:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2014\/07\/lightup_2014-07-29T13-51-54.130000-300x300.jpg 300w\" sizes=\"auto, (max-width: 400px) 100vw, 400px\" \/><\/a><\/p>\n<p>How far do we get with Tactics A, B and C?<\/p>\n<p><a href=\"http:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2014\/07\/lightup_2014-07-29T13-52-20.193000.jpg\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2014\/07\/lightup_2014-07-29T13-52-20.193000.jpg\" alt=\"Puzzle 1 - Tactics A, B and C\" width=\"400\" height=\"400\" class=\"alignleft size-full wp-image-1817\" srcset=\"https:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2014\/07\/lightup_2014-07-29T13-52-20.193000.jpg 400w, https:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2014\/07\/lightup_2014-07-29T13-52-20.193000-150x150.jpg 150w, https:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2014\/07\/lightup_2014-07-29T13-52-20.193000-300x300.jpg 300w\" sizes=\"auto, (max-width: 400px) 100vw, 400px\" \/><\/a><\/p>\n<p>Oh dear! Not very far at all. Time to add a new tactic.<\/p>\n<h3>Tactic D<\/h3>\n<p>This one is a little more complicated. Consider the cell in the bottom right corner. If it was a light, the light would illuminate two of the direct neighbours of the 3 cell nearby, making it impossible for the 3 cell to be satisfied.<\/p>\n<p>More generally, if all but one of the remaining direct neighbours must be lights, then a &#8220;diagonal&#8221; cell sitting between two unknown cells must be empty.<\/p>\n<p>This gives us this extra information: <\/p>\n<p><a href=\"http:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2014\/07\/lightup_2014-07-29T13-52-40.841000.jpg\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2014\/07\/lightup_2014-07-29T13-52-40.841000.jpg\" alt=\"Puzzle 1 - a few steps of Tactic D\" width=\"400\" height=\"400\" class=\"alignleft size-full wp-image-1818\" srcset=\"https:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2014\/07\/lightup_2014-07-29T13-52-40.841000.jpg 400w, https:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2014\/07\/lightup_2014-07-29T13-52-40.841000-150x150.jpg 150w, https:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2014\/07\/lightup_2014-07-29T13-52-40.841000-300x300.jpg 300w\" sizes=\"auto, (max-width: 400px) 100vw, 400px\" \/><\/a><\/p>\n<p>If we then recursively apply all these tactics&#8230;<\/p>\n<p><a href=\"http:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2014\/07\/lightup_2014-07-29T13-38-22.991000.jpg\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2014\/07\/lightup_2014-07-29T13-38-22.991000.jpg\" alt=\"Puzzle 1 - Solved\" width=\"400\" height=\"400\" class=\"alignleft size-full wp-image-1808\" srcset=\"https:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2014\/07\/lightup_2014-07-29T13-38-22.991000.jpg 400w, https:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2014\/07\/lightup_2014-07-29T13-38-22.991000-150x150.jpg 150w, https:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2014\/07\/lightup_2014-07-29T13-38-22.991000-300x300.jpg 300w\" sizes=\"auto, (max-width: 400px) 100vw, 400px\" \/><\/a><\/p>\n<p>Ta daa! We have a solver! Wait, we&#8217;ve been here before. Does it really solve every puzzle? What about this one?<\/p>\n<p><a href=\"http:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2014\/07\/lightup_2014-07-29T13-38-39.878000.jpg\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2014\/07\/lightup_2014-07-29T13-38-39.878000.jpg\" alt=\"Puzzle 2 - Initial\" width=\"400\" height=\"400\" class=\"alignleft size-full wp-image-1809\" srcset=\"https:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2014\/07\/lightup_2014-07-29T13-38-39.878000.jpg 400w, https:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2014\/07\/lightup_2014-07-29T13-38-39.878000-150x150.jpg 150w, https:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2014\/07\/lightup_2014-07-29T13-38-39.878000-300x300.jpg 300w\" sizes=\"auto, (max-width: 400px) 100vw, 400px\" \/><\/a><\/p>\n<p>All the tactics lead to&#8230;<\/p>\n<p><a href=\"http:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2014\/07\/lightup_2014-07-29T13-38-58.835000.jpg\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2014\/07\/lightup_2014-07-29T13-38-58.835000.jpg\" alt=\"Puzzle 2 - Not quite solved\" width=\"400\" height=\"400\" class=\"alignleft size-full wp-image-1810\" srcset=\"https:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2014\/07\/lightup_2014-07-29T13-38-58.835000.jpg 400w, https:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2014\/07\/lightup_2014-07-29T13-38-58.835000-150x150.jpg 150w, https:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2014\/07\/lightup_2014-07-29T13-38-58.835000-300x300.jpg 300w\" sizes=\"auto, (max-width: 400px) 100vw, 400px\" \/><\/a><\/p>\n<p>Nope. Not solved.<\/p>\n<h2>Drawing the Line<\/h2>\n<p>This is the point where I stop. I don&#8217;t have any more simple crank-turning rules to hand. I could brute-force it, but that is against everything I stand for. I could look for more simple rules, but eventually I am going to fail, because <a href=\"http:\/\/people.cs.umass.edu\/~mcphailb\/papers\/2005lightup.pdf\">Light Up turns out to be NP-complete<\/a>.<\/p>\n<p>So, that&#8217;s the point where I declare this code isn&#8217;t a Light Up  <em>solver<\/em>, it is a Light Up <em>helper<\/em>, taking away the mundane part of the puzzle and leaving the user to solve only the hard parts.<\/p>\n<p>(Just so we are clear: I am declaring this a rousing success &#8211; especially as it only took an evening to knock out, with the framework.)<\/p>\n<h2>Implementation Details<\/h2>\n<p>There was very little novel here compared to other puzzles I have discussed before.<\/p>\n<p>It came in at about 400 lines, give or take, for the solver, and another 270 for the GUI.<\/p>\n<p>Each cell was either a Blank Cell (to be solved), a Blank Block or a Numbered Block.<\/p>\n<p>Numbered Blocks were configured with all 8 of their immediate neighbours. Each numbered block was responsible for implementing tactics A, B and D.<\/p>\n<p>Blank Cells were configured with an (unordered) set of all of the other blank cells in their line-of-site. Each Blank Cell was responsible for implementing Tactic C. If it was marked as a light, it was also responsible for informing all of its line-of-site neighbours that they were empty.<\/p>\n<p>Part of the elegance of the implementations of other puzzles is that each cell was responsible for figuring out its own constraints, by inspecting its neighbours. Unfortunately, this implementation wasn&#8217;t as elegant. Each of the tactics resulted in a conclusion being formed, not about the cell implementing it, but about one of its neighbours. e.g. when a Numbered Block applied Tactic A, it did not change its own state, but changed the state (via a method call) on the blank cells near it.<\/p>\n<p>One minor novelty is that I added toggles to turn one or off particular tactics, so I could illustrate progress more clearly, and a screen-shot function.<\/p>\n<h2>Conclusion<\/h2>\n<p>Another success for the Puzzle Solving Framework, but not a terribly exciting one.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Long-term readers (given the radio silence, do I have any other type?) will remember that I wrote a Puzzle Solving Framework to help solve &#8220;newspaper&#8221; pencil-and-paper puzzles. I&#8217;ve been thinking of preparing a presentation about it for a local Python group, so I decided to implement another one. Introducing Light Up Light Up is an [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_s2mail":"yes","footnotes":""},"categories":[33,34],"tags":[91],"class_list":["post-1805","post","type-post","status-publish","format-standard","hentry","category-puzzle-solving","category-software-development","tag-puzzles"],"_links":{"self":[{"href":"https:\/\/www.somethinkodd.com\/oddthinking\/wp-json\/wp\/v2\/posts\/1805","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.somethinkodd.com\/oddthinking\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.somethinkodd.com\/oddthinking\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.somethinkodd.com\/oddthinking\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.somethinkodd.com\/oddthinking\/wp-json\/wp\/v2\/comments?post=1805"}],"version-history":[{"count":6,"href":"https:\/\/www.somethinkodd.com\/oddthinking\/wp-json\/wp\/v2\/posts\/1805\/revisions"}],"predecessor-version":[{"id":1822,"href":"https:\/\/www.somethinkodd.com\/oddthinking\/wp-json\/wp\/v2\/posts\/1805\/revisions\/1822"}],"wp:attachment":[{"href":"https:\/\/www.somethinkodd.com\/oddthinking\/wp-json\/wp\/v2\/media?parent=1805"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.somethinkodd.com\/oddthinking\/wp-json\/wp\/v2\/categories?post=1805"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.somethinkodd.com\/oddthinking\/wp-json\/wp\/v2\/tags?post=1805"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}