Game of Life

by James Hall: L6 Age ~17

Introduction

John Conway’s "Game of Life" simulates how small structures with simple rules of interaction can lead to complex behaviour. The user places cells in a grid, and these cells obey the following rules (copied from an on-line version of the game).

For a space that is 'populated':

  • each cell with one or no neighbours dies, as if by loneliness;
  • each cell with four or more neighbours dies, as if by overpopulation;
  • each cell with two or three neighbours survives.

For a space that is 'empty' or 'unpopulated':

  • each cell with three neighbours becomes populated.

The following screenshot shows the program in action.

Program in action

Program in action

Click repeatedly in the grid to create a pattern of cells. The buttons described here from the top down allow you to (1) start afresh, (2) run the algorithm a step at a time, (3) run rapidly through each stage, (4) pause, (5) save the complete grid to a file, (6) open an existing file of a complete grid and (7) save a selected rectangular area of the grid (a "pattern"). When you press the last button you see new buttons to save a pattern or cancel. Use the mouse in the grid to drag a green rectangle to mark the area to save. We show a pattern being saved in the following screenshot.

Saving a pattern

Saving a pattern

All the patterns are saved to a single text file. The first line contains the number of the pattern saved and then the dimensions of each pattern are followed by a bitmap of the pattern. An example is:

Pattern file

Pattern file

You can select a pattern by clicking on its name in the white box at the bottom right of the screen then display it by clicking on the grid with the right mouse button.

Download here a zip file containing the source code (gameoflife.txt, saveall.txt, loadall.txt and savepatt.txt) a folder savedata of text files and a folder named Icons. You should unzip the files so that Icons and savedata are in the program folder. You need to compile the source files to produce the executables saveall.exe, loadall.exe and savepatt.exe that program GameOfLife uses. (You may already have saveall.exe and loadall.exe if you have used program Crazy Paint).

In order to run program GameOfLife, you will need to have downloaded Stefan Berinde`s wingraph.zip file as described in our Graphics tutorial. You should copy the unzipped wincrt.pas, winmouse,pas and wingraph.pas (from the src folder) into your program folder. (The compiled units are included in the zip file but you might as well have the source code available for reference). You should find these files useful for your own graphics programs.

Follow the link at the bottom of this page to see the source code of savepatt, which is very similar to that of saveall (included in our section on program CrazyPaint).

The Program

program GameOfLife;
{
    Copyright (c) 2011 James Hall

    Licensed under the Apache License, Version 2.0 (the "License"); you may not
    use this file except in compliance with the License, as described at
    http://www.apache.org/licenses/ and http://www.pp4s.co.uk/licenses/
}
{$mode objfpc}{$H+}
{$APPTYPE GUI}
uses
  Classes, SysUtils, wingraph, wincrt, winmouse, DOS, math;
var
  gd, gm : smallint;
  grid : array[0 .. 101, 0 .. 101] of boolean;
  neighbours : array[0 .. 101, 0 .. 101] of integer;
  screen : array[1 .. 900, 1 .. 900] of integer;
  pattadd : array[1 .. 100, 1 .. 2] of integer; //New pattern to add to patt
  patt : array[1 .. 100, 1 .. 100, 1 .. 100] of integer; //All the patterns
  pattname : array[1 .. 100] of string[20];
  i, j, m, n, max, temp, numpatt, currpat, boxline, startx, starty : integer;
  big : int64;
  click, clickr, gogogo : boolean;
  currname : string; //Name of current pattern
  bitmap : pointer;
  mybitmap : file;
  datafile : textfile;

procedure neicheck;
//Calculates and stores number of neighbours for each cell
begin
  for i := 1 to max do
    for j := 1 to max do
      begin
        neighbours[i, j] := 0;
        for m := 1 to 3 do
          for n := 1 to 3 do
            if not((m = 2) and (n = 2)) then
              if grid[i + m - 2, j + n - 2] = true then
                inc(neighbours[i, j]);
      end;
end;

procedure setgrid;
//Determine which cells survive based on the number of neighbours
begin
  for i := 1 to max do
    for j := 1 to max do
      begin
        if (neighbours[i, j] <  2) or (neighbours[i, j] > 3) then
          grid[i, j] := false;
        if (neighbours[i, j] = 3) then
          grid[i, j] := true;
      end;
end;

procedure drawgrid;
begin
  for i := 1 to max do
    for j := 1 to max do
      begin
        setfillstyle(solidfill, black);
        if grid[i, j] = true then
        //Draw white square to represent cell
          setfillstyle(solidfill, white);
        bar(95 + i * 5, 95 + j * 5, 100 + i * 5, 100 + j * 5);
       end;
  setcolor(gray);
  //Draw vertical and horizontal lines
  for i := 1 to max + 1 do
    line(95 + i * 5, 100, 95 + i * 5, 100 + max * 5);
  for i := 1 to max + 1 do
    line(100, 95 + i * 5, 100 + max * 5, 95 + i * 5);
end;

procedure newgrid;
//Clear all cells
begin
  gogogo := false;
  for i := 0 to max + 1 do
    for j := 0 to max + 1 do
      begin
        grid[i, j] := false;
        neighbours[i, j] := 0;
      end;
end;

procedure putbut(x, y, num : integer; name : string);
//Puts the button with icon "name" on to the screen
var
  width, height, memorysize : integer;
begin
  assignfile(mybitmap, name);
  reset(mybitmap, 1);
  blockread(mybitmap, temp, 2);
  for i := 1 to 4 do
    blockRead(mybitmap, temp, 4);
  blockread(mybitmap, width, 4);
  blockread(mybitmap, height, 4);
  closefile(mybitmap);

  for i := 1 to width do
    for j := 1 to height do
      screen[x - 1 + i, y - 1 + j] := num;
  memorysize := imageSize(1, 1, width, height);
  getMem(bitmap, memorysize);
  assignfile(mybitmap, name);
  reset(mybitmap, 1);
  blockread(mybitmap, bitmap^, memorysize);
  closefile(mybitmap);
  putimage(x, y, bitmap^, copyput);
  freeMem(bitmap, memorysize);
end;

procedure setscreen;
{Puts all icons on the screen using the putbut procedure
and writes button codes 2-8 to the screen array}
begin
 for i := 1 to 900 do
   for j := 1 to 700 do
     screen[i, j] := 0;
 for i := 1 to 5 * max do
   for j := 1 to 5 * max do
     screen[99 + i, 99 + j] := 1;
 putbut(650, 100, 2, 'Icons/new.bmp');
 putbut(650, 160, 3, 'Icons/step.bmp');
 putbut(650, 220, 4, 'Icons/play.bmp');
 putbut(650, 280, 5, 'Icons/stop.bmp');
 putbut(650, 340, 6, 'Icons/save.bmp');
 putbut(650, 400, 7, 'Icons/load.bmp');
 putbut(650, 440, 8, 'Icons/save.bmp');
 for i := 700 to 850 do
   for j := 600 to 690 do
     screen[i, j] := 9;
end;

procedure checkname;
//Reads the currently selected name, which is the second line in allnames.txt
begin
  assignfile(datafile, 'savedata/allnames.txt');
  reset(datafile);
  readln(datafile, currname);
  readln(datafile, currname);
  closefile(datafile);
end;

procedure checkalt;
//Check procedure to make sure that savedata folder is in place
begin
  assignfile(datafile, 'savedata/saveint.txt');
  reset(datafile);
  readln(datafile, temp);
  rewrite(datafile);
  writeln(datafile, '0');
  closefile(datafile);
end;

procedure saveall;
//Writes the current grid as an int64 per line of the text file selected by saveall.exe
//Uses 50 bits of two int64 integers to represent each horizontal line of the grid
begin
  exec('saveall.exe', '');
  checkalt;
  if temp = 1 then  //savedata folder is in place
    begin
      checkname;
      assignfile(datafile, 'savedata/' + currname + '.txt');
      rewrite(datafile);
      m := 0;
      n := 1;
      for j := 1 to 200 do
        begin
          big := 0;
          for i := 1 to 50 do
            begin
              inc(m);
              if m > 100 then
                begin
                  m := 1;
                  inc(n);
                end;
              if grid[m, n] = true then
                big := big + round(power(2 , i - 1));
            end;
          writeln(datafile, big);
        end;
      closefile(datafile);
    end;
end;

procedure loadall;
//Loads a grid from the file selected by loadall.exe
begin
  exec('loadall.exe', '');
  checkalt;
  if temp = 1 then
    begin
      for i := 1 to 100 do
        for j := 1 to 100 do
          grid[i, j] := false;
      checkname;
      assignfile(datafile, 'savedata/' + currname + '.txt');
      reset(datafile);
      m := 0;
      n := 1;
      readln(datafile, big);
      for i := 1 to 100 do
        for j := 1 to 100 do
          begin
            inc(m);
            if m > 50 then
              begin
                m := 1;
                inc(n);
                readln(datafile, big);
              end;
            //Examine the bits in big
            if big mod 2 = 1 then
              grid[j, i] := true;
            big := big div 2;
          end;
      closefile(datafile);
    end;
end;

procedure loadpatterns;
{Loads all the patterns in patterns.txt to the array patt
then loads all the pattern names in patternnames.txt to the array pattname}
var
  tempstr : string;
begin
  assignfile(datafile, 'savedata\patterns.txt');
  reset(datafile);
  readln(datafile, numpatt);
  for i := 1 to numpatt do
    begin
      readln(datafile, pattadd[i, 1]);
      readln(datafile, pattadd[i, 2]);
      for j := 1 to pattadd[i, 2] do
        begin
          readln(datafile, tempstr);
          for n := 1 to pattadd[i, 1] do
            patt[i, n, j] := strtoint(tempstr[n]);
        end;
    end;
  closefile(datafile);
  assignfile(datafile, 'savedata\patternnames.txt');
  reset(datafile);
  readln(datafile, numpatt);
  readln(datafile, currpat);
  for i := 1 to numpatt do
    readln(datafile, pattname[i]);
  closefile(datafile); 
end;

procedure savepatterns;
{Saves all the patterns in the array patt to patterns.txt
then saves all the pattern names in the array pattname to patternnames.txt}
var
  tempstr : string;
begin
  m := 0;
  assignfile(datafile, 'savedata\patterns.txt');
  rewrite(datafile);
  writeln(datafile, numpatt);
  for i := 1 to numpatt do
    begin
      writeln(datafile, pattadd[i, 1]);
      writeln(datafile, pattadd[i, 2]);
      for j := 1 to pattadd[i, 2] do
        begin
          tempstr := '';
          for n := 1 to pattadd[i, 1] do
            tempstr := tempstr + inttostr(patt[i, n, j]);
          writeln(datafile, tempstr);
        end;
    end;
  closefile(datafile);
  assignfile(datafile, 'savedata\patternnames.txt');
  rewrite(datafile);
  writeln(datafile, numpatt);
  writeln(datafile, currpat);
  for i := 1 to numpatt do
    writeln(datafile, pattname[i]);
  closefile(datafile);
end;

procedure checkbox;
//User selects the pattern from those displayed in the white box.
//This pattern is added to the screen using a right click.
begin
  //600 680
  if (getmousey - 595) div 10 + boxline - 1 <= numpatt then
    begin
      currpat := (getmousey - 595) div 10 + boxline - 1;
      setfillstyle(solidfill, black);
      setcolor(white);
      bar(700, 580, 850, 595);
      outtextxy(700, 580, pattname[currpat]);
    end;
end;

procedure updatebox(way : integer);
//Write the names of the patterns to the white box
begin
  if way = 1 then
    if boxline > 1 then
       dec(boxline);
  if way = 2 then
     if boxline < numpatt - 6 then
       inc(boxline);
  setfillstyle(solidfill, white);
  bar(700, 600, 800, 680);
  setcolor(black);
  for i:= 1 to 7 do
    if i <= numpatt then
      outtextxy(700, 590 + 10 * i, pattname[boxline + i - 1]);
end;

procedure snap;
{Creates bitmap of area of grid selected by user
and creates buttons to save and cancel}
var
  down, endit : boolean;
  topx, topy, botx, boty, len, wid : integer;
begin
  for i := 1 to 800 do
    for j := 1 to 800 do
      screen[i, j] := 0;
  //Set code for grid to 1
  for i := 1 to 5 * max do
    for j := 1 to 5 * max do
      screen[99 + i, 99 + j] := 1;
  //Code for Cancel
  for i := 550 to 620 do
    for j := 650 to 665 do
      screen[i, j] := 3;
  //Code for Save (pattern)
  for i := 480 to 530 do
    for j := 650 to 665 do
      screen[i, j] := 4;
  //Create Cancel and Save buttons
  setfillstyle(solidfill, gray);
  setcolor(black);
  bar(550, 650, 620, 665);
  outtextxy(560, 650, 'Cancel');
  bar(480, 650, 530, 665);
  outtextxy(490, 650, 'Save');
  down := false;
  endit := false;
  topx := 0;
  topy := 0;
  botx := 0;
  boty := 0;
  repeat
    case getmousebuttons of
      0 : down := false;
      mouseleftbutton : begin
                          case screen[getmousex, getmousey] of
                            1 : begin   //Click on grid
                                  if down = true then
                                    begin
                                      setcolor(gray);
                                      rectangle(topx * 5 + 95, topy * 5 + 95, botx * 5 + 100, boty * 5 + 100);
                                      botx := (getmousex - 95) div 5;
                                      boty := (getmousey - 95) div 5;
                                      if botx > 100 then
                                        botx := 100;
                                      if boty > 100 then
                                        boty := 100;
                                      //Draw green rectangel to mark area for saving
                                      setcolor(green);
                                      rectangle(topx * 5 + 95, topy * 5 + 95, botx * 5 + 100, boty * 5 + 100);
                                    end;
                                  if down = false then
                                    begin
                                      setcolor(gray);
                                      rectangle(topx * 5 + 95, topy * 5 + 95, botx * 5 + 100, boty * 5 + 100);
                                      topx := (getmousex - 95) div 5;
                                      topy := (getmousey - 95) div 5;
                                      botx := topx;
                                      boty := boty;
                                      down := true;
                                    end;
                                end;
                            3 : begin  //Cancel
                                  down := false;
                                  endit := true;
                                end;
                            4 : begin  //save pattern to file selected using savepatt.exe
                                  len := botx - topx + 1;
                                  wid := boty - topy + 1;
                                  if (len > 0) and (wid > 0) then
                                    begin
                                      exec('savepatt.exe', '');
                                      checkalt;
                                      if temp = 1 then
                                        begin
                                          assignfile(datafile, 'savedata\patternnames.txt');
                                          reset(datafile);
                                          readln(datafile, numpatt);
                                          readln(datafile, currpat);
                                          //Read existing pattern names
                                          for i := 1 to numpatt do
                                             readln(datafile, pattname[i]);
                                          closefile(datafile);
                                          pattadd[currpat, 1] := len;
                                          pattadd[currpat, 2] := wid;
                                          for i := 1 to wid do
                                            for j := 1 to len do
                                              patt[currpat, j, i] := 0;
                                          //Add the bitmap to the array
                                          for i := 1 to wid do
                                            for j := 1 to len do
                                              if grid[topx + j - 1, topy + i - 1] = true then
                                                patt[currpat, j, i] := 1;
                                          //Write the bitmap one line per bit to the console window
                                          for i := 1 to wid do
                                            for j := 1 to len do
                                              writeln(patt[currpat, j, i]);
                                          endit := true;
                                        end;
                                    end;
                                end;
                          end;
                        end;
    end;
    updategraph(updatenow);
  until endit = true;
  setfillstyle(solidfill, black);
  bar(480, 650, 620, 665);
  drawgrid;
  setscreen;
end;

begin
  currpat := 0;
  boxline := 1;
  currname := 'Untitled';
  click := false;
  clickr := false;
  setwindowsize(900, 700);
  gd := 9;
  gm := 13;
  loadpatterns;
  initgraph(gd, gm, 'Game of Life');
  updategraph(updateoff);
  newgrid;
  max := 100;
  drawgrid;
  setscreen;
  updatebox(0);
  repeat
    case getmousebuttons of
      mouseleftbutton  : click := true;
      mouserightbutton : clickr := true;
      0 : begin
            if click = true then
              begin
                case screen[getmousex, getmousey] of
                  1:  begin   //Click in grid
                        if (getmousex < 600) and (getmousex >= 100) and (getmousey < 600) and (getmousey >= 100) then
                          begin
                            if grid[(getmousex - 95) div 5, (getmousey - 95) div 5] = true then
                              grid[(getmousex - 95) div 5, (getmousey - 95) div 5] := false
                            else
                              grid[(getmousex - 95) div 5,(getmousey - 95) div 5] := true;
                          end;
                      end;
                  2:  begin    //Reset
                        currname := 'Untitled';
                        newgrid;
                        drawgrid;
                      end;
                  3:  begin    //Step through
                        neicheck;
                        setgrid;
                        delay(1);
                        drawgrid;
                      end;
                  4:  gogogo := true;  //Start rapid stepping
                  5:  gogogo := false; //Pause rapid stepping
                  6:  saveall;  //Save complete grid to file
                  7 : loadall;  //Load complete grid from file
                  8 : snap;     //Save area of grid
                  9 : checkbox; //Select a pattern to be added to the screen with
                                //a click of the right mouse button
                end;
                delay(5);
                drawgrid;
                click := false;
              end;

            if clickr = true then //Right button clicked to add pattern to grid
              begin
                case screen[getmousex, getmousey] of
                  1 : begin
                        if currpat <> 0 then
                          begin
                            if (getmousex < 600) and (getmousex >= 100) and (getmousey < 600) and (getmousey >= 100) then
                              begin
                                startx := (getmousex - 95) div 5;
                                starty := (getmousey - 95) div 5;
                                for i := 1 to pattadd[currpat, 1] do
                                  for j := 1 to pattadd[currpat, 2] do
                                    begin
                                      if (i + startx < 102) and (j + starty < 102) then
                                        begin
                                          if patt[currpat, i, j] = 1 then
                                             grid[startx + i - 1, starty + j - 1] := true;
                                          if patt[currpat, i, j] = 0 then
                                             grid[startx + i - 1, starty + j - 1] := false;
                                        end;
                                    end;
                              end;
                          end;
                      end;
                end;
                delay(5);
                drawgrid;
                clickr := false;
              end;
          end;
    end;
    case getmousewheel of
      120 : updatebox(1);
      -120 : updatebox(2);
    end;
    if gogogo = true then
      begin
        neicheck;
        setgrid;
        delay(1);
        drawgrid;
      end;
    updategraph(updatenow);
  until closegraphrequest = true;
  savepatterns;
  closegraph;
end.

Remarks

Could you write your own game of life program?

Programming - a skill for life!

Seven programs including GameOfLife, PixelSort and SuperHappyFunLand by James Hall