{"id":232,"date":"2018-09-11T22:29:31","date_gmt":"2018-09-11T21:29:31","guid":{"rendered":"http:\/\/www.science-now.org\/giampaolo\/?p=232"},"modified":"2018-09-11T22:29:31","modified_gmt":"2018-09-11T21:29:31","slug":"game-of-life","status":"publish","type":"post","link":"https:\/\/www.science-now.org\/giampaolo\/2018\/09\/11\/game-of-life\/","title":{"rendered":"Game of Life"},"content":{"rendered":"<p>Recently, I&#8217;ve been reading a very interesting book (<a href=\"https:\/\/www.goodreads.com\/book\/show\/301563.The_Recursive_Universe\" target=\"_blank\" rel=\"noopener\">The Recursive Universe<\/a>) about a game called Game of Life.<\/p>\n<p><!--more--><\/p>\n<p>The game consists of three elements:<\/p>\n<ul>\n<li>An (unlimited) grid where each cell can be defined alive or dead;<\/li>\n<li>A number of alive cells placed somewhere in the grid;<\/li>\n<li>Three simple rules which set the conditions for cells to duplicate or die<\/li>\n<\/ul>\n<p>Once the grid and the set are defined, the next generation of cells is obtained by applying three rules recursively:<\/p>\n<ul>\n<li>Any live cell with fewer than 2 neighbours and more than 3 dies by isolation or overpopulation, respectively;<\/li>\n<li>Any live cell with exactly 2 or 3 live cells stays alive;<\/li>\n<li>Any dead cell with exacly 3 live neighbours becomes alive.<\/li>\n<\/ul>\n<p>I thought about implementing the Game of Life in a simple Python script. The World in the Game of Life universe becomes a Python class. The whole source code is available at the end of the post (and on Bitbucket at <a href=\"https:\/\/bitbucket.org\/co2h5\/gameoflife\" rel=\"noopener\" target=\"_blank\">bitbucket.org\/co2h5\/gameoflife<\/a> ). Here, a quick description of the class is given.<\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\nclass World:\r\n    def __init__(self, sizex, sizey):\r\n        # This method initialises the class by defining the maximum size of the World. \r\n        ...\r\n\r\n    def set(self, x, y, value):\r\n        # Set the state of the cell at location (x,y). The state can be on (alive) or off (dead). If the location is outside the World, no assignment is performed.\r\n        ...\r\n\r\n    def get(self, x,y):\r\n        # Get the state of the cell at location (x,y). If the location is outside the World, a value corresponding to a dead cell is returned.\r\n        ...\r\n\r\n    def empty_grid(self):\r\n        # Initialise an empty World by allocating memory and setting all cells to off. \r\n        ...\r\n\r\n    def count_neighbours(self, x, y):\r\n        # Count the number of neighbouring cells which are alive.\r\n        ...\r\n\r\n    def next_step(self):\r\n        # Take the next step of the evolution by applying the three rules recursively.\r\n        ...\r\n\r\n    def generate_picture(self, filename, blksize=2, xrange=None, yrange=None, colorscheme=1):\r\n        # Generate a picture of the World and save it to file. This method uses the Pillow library to generate a PNG file.\r\n        ...\r\n\r\n    def save_to_file(self, filename):\r\n        # Save the state of the simulation to file so that it is possible to restart from this point later now. This method uses Scipy IO library.\r\n        ...\r\n\r\n    def load_from_file(self, filename):\r\n        # Load the state of the simulation from file so that it is possible to restart from that point. This method uses Scipy IO library.\r\n        ...\r\n<\/pre>\n<p>And now&#8230; some interesting animations from the Game of Life! <\/p>\n<h1>R-pentomino<\/h1>\n<p>Evolution in the Game of Life is something very interesting. Once the initial set of alive cells is given, the user does not intervene anymore. A simple initial set of cells can lead to an interesting final pattern. For example, the pattern described below is called <a href=\"http:\/\/www.conwaylife.com\/wiki\/R-pentomino\" rel=\"noopener\" target=\"_blank\">r-pentomino<\/a> and it takes around 1100 generations to get to its final step. This is the animation which describes the whole evolution:<\/p>\n<p><a href=\"http:\/\/www.science-now.org\/giampaolo\/wp-content\/uploads\/2018\/08\/r_pentomino.gif\"><img decoding=\"async\" src=\"http:\/\/www.science-now.org\/giampaolo\/wp-content\/uploads\/2018\/08\/r_pentomino.gif\" alt=\"\" width=\"80%\" class=\"aligncenter size-full wp-image-234\" \/><\/a><\/p>\n<h1>Random set<\/h1>\n<p>The fun is also to try playing the Game of Life with a random set of cells. For example, what will happen to these few cells?<\/p>\n<p><a href=\"http:\/\/www.science-now.org\/giampaolo\/wp-content\/uploads\/2018\/08\/random.gif\"><img decoding=\"async\" src=\"http:\/\/www.science-now.org\/giampaolo\/wp-content\/uploads\/2018\/08\/random.gif\" alt=\"\" width=\"80%\" class=\"aligncenter size-full wp-image-234\" \/><\/a><\/p>\n<p>More information about the Game of Life can be found on <a href=\"http:\/\/www.conwaylife.com\/wiki\/Main_Page\" rel=\"noopener\" target=\"_blank\">this Wiki<\/a>. <\/p>\n<h1>Source code<\/h1>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\nimport subprocess\r\nimport random\r\nimport numpy as np\r\nimport scipy.io as sio\r\n\r\nclass World:\r\n    def __init__(self, sizex, sizey):\r\n        self.grid = (sizex, sizey)\r\n        self.data = self.empty_grid()\r\n        self.age = 0\r\n\r\n    def set(self, x, y, value):\r\n        if x &lt; 0 or x &gt; self.grid[0]:\r\n            return\r\n\r\n        if y &lt; 0 or y &gt; self.grid[1]:\r\n            return\r\n\r\n        self.data[x,y] = value\r\n\r\n    def get(self, x,y):\r\n        if x &lt; 0 or x &gt;= self.grid[0]:\r\n            return False\r\n\r\n        if y &lt; 0 or y &gt;= self.grid[1]:\r\n            return False\r\n\r\n        return self.data[x,y]\r\n\r\n    def empty_grid(self):\r\n        return np.zeros(self.grid, dtype=bool)\r\n\r\n    def count_neighbours(self, x, y):\r\n        count = 0\r\n\r\n        for c in range(x-1, x+2):\r\n            for r in range(y-1, y+2):\r\n                if x == c and r == y:\r\n                    continue\r\n                if self.get(c,r):\r\n                    count += 1\r\n\r\n        return count\r\n\r\n    def place_cells(self, cells, offset=(0,0)):\r\n        for x, y in cells:\r\n            self.set(x+offset[0], y+offset[1], True)\r\n\r\n    def place_cells_random(self, num, xrange=None, yrange=None):\r\n        if xrange is not None:\r\n            xmin, xmax = xrange\r\n        else:\r\n            xmin, xmax = 0, self.grid[0]\r\n\r\n        if yrange is not None:\r\n            ymin, ymax = yrange\r\n        else:\r\n            ymin, ymax = 0, self.grid[1]\r\n\r\n        for i in range(num):\r\n            x = random.randint(xmin, xmax-1)\r\n            y = random.randint(ymin, ymax-1)\r\n            self.set(x, y, True)\r\n\r\n    def next_step(self):\r\n        new_data = self.empty_grid()\r\n\r\n        for x in range(self.grid[0]):\r\n            for y in range(self.grid[1]):\r\n                n = self.count_neighbours(x,y)\r\n\r\n                if n == 2:\r\n                    new_data[x,y] = self.get(x,y)\r\n                elif n == 3:\r\n                    new_data[x,y] = True\r\n\r\n        self.data = new_data\r\n        self.age += 1\r\n\r\n    def generate_picture(self, filename, blksize=2, xrange=None, yrange=None, colorscheme=1):\r\n        from PIL import Image, ImageDraw\r\n\r\n        if xrange is not None:\r\n            xmin, xmax = xrange\r\n        else:\r\n            xmin, xmax = 0, self.grid[0]\r\n\r\n        if yrange is not None:\r\n            ymin, ymax = yrange\r\n        else:\r\n            ymin, ymax = 0, self.grid[1]\r\n\r\n        if colorscheme == 2:\r\n            bkc = 'white'\r\n            fgc = 'black'\r\n        else:\r\n            bkc = 'black'\r\n            fgc = 'white'\r\n\r\n        img = Image.new('RGB', ((xmax - xmin)*blksize, (ymax - ymin)*blksize))\r\n        draw = ImageDraw.Draw(img)\r\n\r\n        draw.rectangle((0, 0, (xmax - xmin)*blksize, (ymax - ymin)*blksize), fill=bkc)\r\n\r\n        for x in range(xmin, xmax):\r\n            for y in range(ymin, ymax):\r\n                if self.get(x,y):\r\n                    draw.rectangle(((x-xmin)*blksize, (y-ymin)*blksize,\r\n                                    (x-xmin+1)*blksize, (y-ymin+1)*blksize),\r\n                                   fill=fgc)\r\n\r\n        img.save(filename)\r\n\r\n    def save_to_file(self, filename):\r\n        sio.savemat(filename, {'data': self.data, 'age': self.age}, appendmat=True)\r\n        pass\r\n\r\n    def load_from_file(self, filename):\r\n        data_dic = sio.loadmat(filename, appendmat=True)\r\n\r\n        self.grid = (self.data.shape[0], self.data.shape[1])\r\n        self.data = np.array(data_dic['data'], dtype=bool)\r\n        self.age = data_dic['age'][0,0]\r\n\r\nif __name__ == &quot;__main__&quot;:\r\n    cells = [\r\n        ( 0,  1),\r\n        ( 0,  0),\r\n        ( 0, -1),\r\n        (-1,  0),\r\n        ( 1,  1),\r\n    ]\r\n\r\n    wld = World(256,256)\r\n    try:\r\n        wld.load_from_file('last_step')\r\n    except FileNotFoundError:\r\n        wld.place_cells(cells, offset=(128,128))\r\n    #    #wld.place_cells_random(128, xrange=(448,576), yrange=(448,576))\r\n\r\n    for i in range(wld.age, wld.age + 1200):\r\n        print(&quot;Step {}&quot;.format(i))\r\n        wld.generate_picture('step{:05d}.png'.format(i),\r\n                             xrange=(43,213),\r\n                             yrange=(64,192),\r\n                             blksize=8, colorscheme=2)\r\n        wld.save_to_file('last_step')\r\n        wld.next_step()\r\n\r\n    output_gif = 'r_pentomino.gif'\r\n    subprocess.call(['ffmpeg', '-framerate', str(20), '-i', 'step%05d.png', '-pix_fmt', 'gray', output_gif])\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Recently, I&#8217;ve been reading a very interesting book (The Recursive Universe) about a game called Game of Life.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[9],"tags":[33,32,10],"class_list":["post-232","post","type-post","status-publish","format-standard","hentry","category-programming","tag-evolution","tag-game-of-life","tag-python"],"_links":{"self":[{"href":"https:\/\/www.science-now.org\/giampaolo\/wp-json\/wp\/v2\/posts\/232","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.science-now.org\/giampaolo\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.science-now.org\/giampaolo\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.science-now.org\/giampaolo\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.science-now.org\/giampaolo\/wp-json\/wp\/v2\/comments?post=232"}],"version-history":[{"count":9,"href":"https:\/\/www.science-now.org\/giampaolo\/wp-json\/wp\/v2\/posts\/232\/revisions"}],"predecessor-version":[{"id":244,"href":"https:\/\/www.science-now.org\/giampaolo\/wp-json\/wp\/v2\/posts\/232\/revisions\/244"}],"wp:attachment":[{"href":"https:\/\/www.science-now.org\/giampaolo\/wp-json\/wp\/v2\/media?parent=232"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.science-now.org\/giampaolo\/wp-json\/wp\/v2\/categories?post=232"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.science-now.org\/giampaolo\/wp-json\/wp\/v2\/tags?post=232"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}