{"id":453,"date":"2007-12-24T09:08:00","date_gmt":"2007-12-23T23:08:00","guid":{"rendered":"http:\/\/www.somethinkodd.com\/oddthinking\/2007\/12\/24\/virus-2-a-puzzle\/"},"modified":"2007-12-24T09:17:42","modified_gmt":"2007-12-23T23:17:42","slug":"virus-2-a-puzzle","status":"publish","type":"post","link":"https:\/\/www.somethinkodd.com\/oddthinking\/2007\/12\/24\/virus-2-a-puzzle\/","title":{"rendered":"Virus 2: A Puzzle"},"content":{"rendered":"<h3>Introducing the Puzzle<\/h3>\n<p><a href=\"http:\/\/rfshq.com\/forum\/games\/virus2.swf\">Virus 2<\/a> is a flash game with a disturbing name, but quite simple game-play.<\/p>\n<p>On a grid of randomly-coloured hexagons, you control a contiguous area of like-coloured cells. You can change the colours of every cell under your control, and where you match the colours of neighbouring cells, you absorbed them. <\/p>\n<div class=\"aside\">I think it is supposed to be reminiscent of how a virus grows, but I suspect the authors are confusing virus with amoebae.<\/div>\n<p>The aim is to absorb all the cells on the 20&times;16 playing board in as few moves as possible. (As the game cycles, the target count drops until you fail to meet it.)<\/p>\n<p><a href=\"http:\/\/rfshq.com\/forum\/games\/virus2.swf\">Go ahead and play it for a while.<\/a> When you get back, it will be time to try to <a href=\"http:\/\/www.somethinkodd.com\/oddthinking\/2005\/05\/21\/puzzling-over-puzzles\/\">truly solve it<\/a>.<\/p>\n<h3>Rules of Engagement<\/h3>\n<p>So how do you go about solving it? The obvious solution is a brute-force search. But I am <a href=\"http:\/\/www.somethinkodd.com\/oddthinking\/2007\/11\/02\/wine-gumpodcast-selection-algorithm-solution\/\">a bit over that<\/a> right now &#8211; there&#8217;s no finesse.<\/p>\n<p>So, I decided to aspire to some rather arbitrary rules of engagement. I wanted to maintain the algorithmic interest.<\/p>\n<div class=\"aside\">Let me break the tension now by warning you up front. This project was a bit of a fizzer. It was strictly a success. It has pretty pictures. However, the solution isn&#8217;t as interesting as I hoped.<\/div>\n<h4>The Rules<\/h4>\n<ul>\n<li>The solver algorithm should be based on examination of the current board state. It should not do any exhaustive searching. (I didn&#8217;t want to be too limiting, so I permitted myself one or two moves worth of look-ahead if I needed it.)<\/li>\n<li>Each move on a given grid should be assigned a metric value, and the highest value should be chosen as the next move.<\/li>\n<li>The metric algorithm should consist of the weighted averages of a number of sub-metrics. This was an design choice based on elegance rather than appropriateness, and I reserved the right to revisit it, as the solution became more clear.<\/li>\n<li>Sub-metrics that I came up with included: number of neighbour cells of that colour, the least recently used colour, some function of the percentage of cells of that colour that are neighbours, the number of neighbours to the cells of that colour that are neighbours to the virus (effectively, looking ahead 1 move.) As I discovered which sub-metrics worked, I expected to come up with more.<\/li>\n<li>While I wasn&#8217;t allowed to search during the puzzle-solving phase, I was allowed to search during the software-development phase. My interesting idea was to search through the space of weightings of the sub-metrics, finding the best overall metric that combined the advice of the sub-metrics. I wanted Simulated Annealing and everything! Once the best weightings were found (which could take a lot of processing time,) I would have the parameters to plug into the algorithm (which would then execute quickly). That algorithm might not find the optimal solution to a particular puzzle, but it would, on average, quickly find good (perhaps great?) solutions.<\/li>\n<\/ul>\n<p>Again, these are rather arbitrary restrictions designed to make the software solution fun to produce, rather than strictly finding the optimal solution to a given problem.<\/p>\n<p>Chatting to Andrew D., who introduced me to the puzzle, I learnt that he solves the puzzle (by hand) in a number of phases. For example, in the early phases, he aims to grow the virus towards the center. In the middle phases, he concentrates on growing the virus as quickly as possible. My arbitrary restrictions largely preclude me from such a solution.<\/p>\n<h3>Setting a Goal<\/h3>\n<p>What&#8217;s my goal? <\/p>\n<p>Well, my goal isn&#8217;t to find the optimal solution for any puzzle. If I wanted to do that, I would do a search.<\/p>\n<p>My goal isn&#8217;t to find the best ever solution to any puzzle &#8211; e.g. &#8220;Look, I solved this is 20 moves!&#8221; There&#8217;s too much dependency on the random configuration of the puzzle for this to have meaning. A puzzle could be solved in zero moves if you are lucky enough to have every cell the same colour.<\/p>\n<p>So, I guess my goal is to play better than a human player. It seems an expert human player solves the puzzle generally in mid-to-high-twenties. If I can average, say, 25 moves, I shall declare it a success.<\/p>\n<h3>Implementation of the Basic Game Rules<\/h3>\n<p>I created some familiar data-structures. Grid objects represents the grid; it is a container of HexCells. A HexCell object represents on cell in the grid. It knows its colour, co-ordinates and neighbours. Given a GameWindow object, a HexCell can render itself graphically.<\/p>\n<p>This was largely based on my experience with <a href=\"http:\/\/www.somethinkodd.com\/oddthinking\/2007\/11\/02\/wine-gumpodcast-selection-algorithm-solution\/\">Kakuro<\/a>, <a href=\"http:\/\/www.somethinkodd.com\/oddthinking\/2005\/06\/12\/the-no-nos-and-yes-yeses-of-the-nonogram-solution\/\">Nonograms<\/a>, etc.<\/p>\n<p>After a lot of high-school geometry, here&#8217;s the result &#8211; it is an 8&#215;8 test sample.<\/p>\n<p><a href='http:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2007\/12\/hexgame_basiclayout_small.PNG' title='Virus 2 - Basic Layout of Small Example Puzzle'><img src='http:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2007\/12\/hexgame_basiclayout_small.PNG' alt='Virus 2 - Basic Layout of Small Example Puzzle' \/><\/a><\/p>\n<p>Pretty, isn&#8217;t it!<\/p>\n<p>You will notice the &#8220;virus&#8221; in the bottom left. Unlike the original, I always colour the virus black. It&#8217;s real colour is irrelevant to the puzzle, and I thought making it distinct would help.<\/p>\n<h3>From Hex Cells to Clusters<\/h3>\n<p>The next step was to abstract away from individual hex cells. A cluster is any group of adjacent cells of the same colour. They work together. So rather than dealing with cells, the final algorithm manipulates the clusters.<\/p>\n<p>One key advantage here is that the number of nodes that the algorithm must deal with is reduced from (16&times;20=) 320 to an average of 200. Another is that it is easier to work out the true value of matching a particular colour.<\/p>\n<p>Initially, each cell creates its own cluster with a unique id and a colour. The cluster is now delegated the responsibility for storing the colour on behalf of the cell.<\/p>\n<p>Then the cell informs the clusters of its neighbours, and the clusters start to merge. If a cluster finds its neighbour is the same colour as it, they merge together, giving control to the lower numbered cluster.<\/p>\n<p>Each cluster still exists after a merge, but when asked for its data (e.g. by its corresponding cell), it refers to its associated cluster recursively. (A cluster keeps a cache of its best associated cluster so it doesn&#8217;t have to recurse through the entire merge history each time it is accessed.)<\/p>\n<p><a href='http:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2007\/12\/hexgame_debuglayout_small.PNG' title='Virus 2 - Debug Layout of Small Example Puzzle'><img src='http:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2007\/12\/hexgame_debuglayout_small.PNG' alt='Virus 2 - Debug Layout of Small Example Puzzle' \/><\/a><\/p>\n<p>The picture isn&#8217;t quite so pretty now. The numbers represent the co-ordinates of the lowest numbered cluster associated with that cell. You can see neighbouring cells with the same colour all share the same cluster.<\/p>\n<p>The virus cluster is represented by &#8220;****&#8221;.<\/p>\n<p>Also on this diagram, there are thin white circles around the cells in &#8220;susceptible&#8221; clusters. Susceptible clusters are the clusters that are direct neighbours of the virus cluster. They are the ones that may be absorbed in the next move.<\/p>\n<h3>First Sub-Metric: Random<\/h3>\n<p>Before I could write the bit metric that combined all the sub-metrics, I needed some sub-metrics to base it on.<\/p>\n<p>The first one was a &#8220;control&#8221; sub-metric &#8211; random choice. I wrote a function that, given a grid, randomly chose one of the colours of a susceptible cluster.<\/p>\n<p>Not only was this a quick test sub-metric, but I hoped to find, during the parameter searching phase, that the random metric was weighted very low by my searching algorithm. That would provide some evidence that the searching algorithm was working.<\/p>\n<p>Using the Random sub-metric alone, I ran a sample of 500 runs. It showed a mean solution time of 41.0 moves (ranging from 29 to 59 moves).<\/p>\n<h3>End Of The Line for the Graphics<\/h3>\n<p>About this point, all the work I did on the graphics was discarded. It was useful, because it helped me debug the clustering, but now it no longer had much point, and just slowed down the execution.<\/p>\n<p>Before I go on the ignore it, here is one more image &#8211; a full-sized puzzle mid-solution.<\/p>\n<p><a href='http:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2007\/12\/hexgame_debuglayout_large.PNG' title='Virus 2 - Basic Layout of Full-Sized Example Puzzle'><img src='http:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2007\/12\/hexgame_debuglayout_large.thumbnail.PNG' alt='Virus 2 - Basic Layout of Full-Sized Example Puzzle' \/><\/a><\/p>\n<h3>Second Sub-Metric: Least Recent<\/h3>\n<p>Next, I implemented an algorithm that I thought would be useful. From the choice of susceptible cluster, it selected the least recently chosen colour. It makes intuitive sense that the least recently chosen colour is likely to have accumulated the most nearby neighbours. It was also rather simple to implement.<\/p>\n<p>Using the Least Recent sub-metric alone, I ran a sample of 500 runs. It showed a mean solution time of 37.6 moves (ranging from 25 to 49 moves)<\/p>\n<p>That&#8217;s a good improvement already over the Random sub-metric baseline.<\/p>\n<h3>Third Sub-Metric: Most Neighbours<\/h3>\n<p>The next algorithm was the most obvious one: chose the colour that has the most matching clusters nearby.<\/p>\n<p>I hoped it would supersede the Least Recent used algorithm by using facts rather than heuristics to find the colour with the biggest immediate growth.<\/p>\n<p>Using the Most Neighbours sub-metric alone, I ran a sample of 500  runs. It showed a mean solution time of 33.2 moves (ranging from 25 to 41 moves). <\/p>\n<p>That&#8217;s another good improvement, and it is reflected not only in a smaller average, but a smaller range. It is a more consistent result, which is reassuring.<\/p>\n<h3>Fourth Sub-Metric: Most Neighbours&#8217; Neighbours<\/h3>\n<p>I had given myself permission to use some limited look-ahead. It was time to exercise that permission to implement a single move look-ahead. <\/p>\n<p>The algorithm chose the colour of the neighbouring clusters that themselves had the most neighbouring clusters. That is, what is the best move this turn to maximise the best move next turn?<\/p>\n<p>This sub-metric was still a bit naive. It didn&#8217;t work out &#8220;If I go Blue this time, I will have 5 Green neighbours, so that&#8217;s the best move.&#8221; It worked out &#8220;If I go Blue this time, I will have 25 neighbours, so there is probably at least one move that is good from there.&#8221; The look-ahead heuristic was simplistic.<\/p>\n<p>Using the Most Neighbours&#8217; Neighbours sub-metric alone, I ran a sample of 500 runs. It showed a mean solution time of 27.8 moves (ranging from 21 to 34 moves). <\/p>\n<p>That was a good improvement again. A very good improvement. I wasn&#8217;t still expecting big jumps like that at this point &#8211; basically, it is already a better player than me (I&#8217;ve only player about 10 or 15 games, manually.)<\/p>\n<p>In a way, this was a disappointingly high improvement. Boring, old look-ahead was proving to be very successful. I guess that tells me what the next algorithm should be.<\/p>\n<h3>Fifth Sub-Metric: Most Neighbours&#8217; Neighbours&#8217; Neighbours<\/h3>\n<p>I went ahead and implemented two levels of lookahead.  What colour&#8217;s neighbours have the most neighbour&#8217;s neighbours?<\/p>\n<p>Again, this sub-metric was naive. It wasn&#8217;t working out &#8220;If I go Blue then Green I will have 5 neighbours.&#8221; It was working out &#8220;If I go blue, and then add up all the neighbours of those neighbours, I get a total of 15 neighbours.&#8221;<\/p>\n<p>Nonetheless, this sub-metric had me feeling a little bit dirty. Look-ahead is like searching, but wearing a leash so you don&#8217;t go too far. Two levels of look-ahead (in a puzzle that only requires 20-30 moves) is just starting to smell a little bit exhaustive.<\/p>\n<p>Using the Most Neighbours&#8217; Neighbours&#8217; Neighbours sub-metric alone, I ran a sample of 500 runs. It showed a mean solution time of 24.9 moves (ranging from 20 to 31 moves). <\/p>\n<p>Note: 24.9 &lt; 25 moves, which was my original target.<\/p>\n<h3>Conclusion<\/h3>\n<p>What a let-down! I already reached my goal and I didn&#8217;t need to implement any parameter searching, simulated-annealing, parameterised sub-metrics or nothing.<\/p>\n<p>I just had to stray towards the dullness of lookahead (which is just a cut-down version of an exhaustive search).  If I wanted to improve it any further, I knew what I needed to do. Make the look-ahead less naive and let out the leash to another level of look-ahead.<\/p>\n<p>Boring.<\/p>\n<p>I stopped instead.<\/p>\n<p>I declare this a fizzer. I reached my stated goal, but without enough fun.<\/p>\n<p>One more pretty picture before I go &#8211; a histogram showing the results in more detail.<\/p>\n<p><a href='http:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2007\/12\/hexgameresults.png' title='Histogram of Results of 500 Game Sample Runs, by Algorithm'><img src='http:\/\/www.somethinkodd.com\/oddthinking\/wp-content\/uploads\/2007\/12\/hexgameresults.thumbnail.png' alt='Histogram of Results of 500 Game Sample Runs, by Algorithm' \/><\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p><a href=\"http:\/\/rfshq.com\/forum\/games\/virus2.swf\">Virus 2<\/a> is a flash game with a disturbing name, but quite simple game-play.<\/p>\n<p>I set out to solve it under some arbitrary constraints.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_s2mail":"","footnotes":""},"categories":[33,34],"tags":[55,69,54],"class_list":["post-453","post","type-post","status-publish","format-standard","hentry","category-puzzle-solving","category-software-development","tag-puzzle","tag-software","tag-solution"],"_links":{"self":[{"href":"https:\/\/www.somethinkodd.com\/oddthinking\/wp-json\/wp\/v2\/posts\/453","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=453"}],"version-history":[{"count":0,"href":"https:\/\/www.somethinkodd.com\/oddthinking\/wp-json\/wp\/v2\/posts\/453\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.somethinkodd.com\/oddthinking\/wp-json\/wp\/v2\/media?parent=453"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.somethinkodd.com\/oddthinking\/wp-json\/wp\/v2\/categories?post=453"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.somethinkodd.com\/oddthinking\/wp-json\/wp\/v2\/tags?post=453"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}