/* This program demonstrates how to use dialog boxes, selected observations, and the PolygonPlot to create a simulation of cellular automata using the rules known as Conway's Game of Life. For complete rules and a behavior of the program, see the end of this file. */ delayPerIteration = 50; /* slow down the display. Use 0 for max speed. */ declare String sMsg = "Enter grid size > 3 or Cancel to end the program"; size = 20; size = GetDoubleOrQuit( size, 4, 100, "Grid Size", sMsg ); grid = j(size,size,0); /* create square grid of cells; each cell is actually four observations at corners of square */ polys = makePolyGrid( nrow(grid), ncol(grid) ); declare DataObject dobj = DataObject.Create( "Game of Life", {"X" "Y" "ID"}, polys ); declare PolygonPlot plot = PolygonPlot.Create( dobj, "X", "Y", "ID", false ); run SetupGrid( dobj, plot ); pause "Shift-click cells to create initial configuration of 'live' cells,\nthen choose the 'Resume' button"J; /* If no selection, generate random selection */ dobj.GetSelectedObsNumbers( mSelectedObs ); if nrow( mSelectedObs )=0 then do; grid = round( 0.6*uniform( grid ) ); run Grid2Selection( dobj, grid ); end; sMsg = "Enter number of Generations > 0 or Cancel to end the program"; nGenerations = 100; nGenerations = GetDoubleOrQuit( nGenerations, 0, 1000, "Number of Generations", sMsg ); do while ( nGenerations > 0 ); run GameOfLife( dobj, nGenerations ); nGenerations = GetDoubleOrQuit( nGenerations, 0, 1000, "Continue?", sMsg ); end; /*************************************************/ /************* Module Definitions ****************/ /*************************************************/ /* Play Conway's Game of Life. Pass in a data object that has observations selected. */ start GameOfLife( DataObject dobj, nGenerations ) global (delayPerIteration); grid = Selection2Grid( dobj ); do i = 1 to nGenerations; if grid=0 then return; /* extinction */ run update_generation( grid ); run Grid2Selection( dobj, grid ); run Delay( delayPerIteration ); end; finish; /* count number of neighbors and apply Rules of Life */ start update_generation( grid ); nbrs = neighbors( grid ); grid = (nbrs=3) | (nbrs=2 & grid=1); finish; /* Count number of neighbors for each cell */ start neighbors( grid ); /* augment grid by wrapping around rows/cols to create torus */ m = grid[,ncol(grid)] || grid || grid[,1]; m = m[nrow(m),] // m // m[1,]; /* Matrix operation to count neighbors. */ nRow = nrow(m); nCol = ncol(m); rPre = 1:nRow-2; rAft = 3:nRow; r = 2:nRow-1; cPre = 1:nCol-2; cAft = 3:nCol; c = 2:nCol-1; mNbrs = m[rPre,cPre] + m[rPre,c ] + m[rPre,cAft]; *NW + N + NE; mNbrs = mNbrs + m[r,cAft] + m[r,cPre]; *W + E; mNbrs = mNbrs + m[rAft,cAft] + m[rAft,c] + m[rAft,cPre]; *SW + S + SE; return ( mNbrs ); finish; /* Take grid of 0/1's and select observations corresponding to live cells. Each square in the graphic is the result of four observations */ start Grid2Selection( DataObject dobj, grid ); dobj.DeselectAllObs(); /* use fact that we can access grid[i,j] as grid[ (ncol(grid)-1)*i + j ] */ select = loc(grid=1); if ncol(select)>0 then do; select = 4*(select-1) + 1; dobj.SelectObs( select ); end; finish; /* Take selected observations and produce corresponding grid in which each selected observations corresponds to a live cell. */ start Selection2Grid( DataObject dobj ); gridSize = sqrt( dobj.GetNumObs()/4 ); m = j(gridSize, gridSize, 0); dobj.GetSelectedObsNumbers( mSel ); /* If no selected cells, then extinction, so return grid of 0s */ if nrow( mSel) > 0 then do; mSel = union( floor( (mSel-1) / 4) + 1); m[ mSel ] = 1; end; return ( m ); finish; /* Create polygon plot with nxn squares arranged in a grid */ start makePolyGrid( nRows, nCols ); m = j(4*nRows*nCols, 2, 0 ); dx = 1.0/nRows; dy = 1.0/nCols; do i = 0 to nRows-1; do j = 0 to nCols-1; s = makeRect( j*dx, 1-i*dy, (j+1)*dx, 1-(i+1)*dy ); ind = 4*(i*nCols+j)+1; m[ind:ind+3,] = s; end; end; id = T( 1:(nRows*nCols) ); id = shape( id||id||id||id, 4*nRows*nCols, 1 ); m = m || id; return ( m ); finish; /* Create four observations that correspond to a rectangle when interpreted by the polygon plot. */ start makeRect( x1, y1, x2, y2 ); m = j(4,2,0); m[,1] = x1 // x1 // x2 // x2; m[,2] = y1 // y2 // y2 // y1; return ( m ); finish; start SetupGrid( DataObject dobj, PolygonPlot plot ); dobj.SetMarkerFillColor( OBS_ALL, 196i, 255i, 255i ); dobj.SetMarkerOutlineColor( OBS_ALL, 0i, 0i, 255i ); plot.ShowAxes( false ); plot.SetMarkerSize( 1 ); plot.ShowObs( false ); plot.SetGraphAreaMargins( 0.24, 0.18, 0.02, 0.02 ); plot.ShowWindow(); finish; start GetDoubleOrQuit( defaultValue, int minValue, int maxValue, String sTitle, String sMsg ); x = defaultValue; needValue = 1; do while ( needValue ); needValue = (DoDialogModifyDouble( x, sTitle, sMsg )=0); if needValue then quit; /* Dialog canceled */ if ( x < minValue | x > maxValue ) then do; declare String s = "Value must be between " + (String)minValue + " and " + (String)maxValue; run DoMessageBoxOK( "Invalid value", s ); x = defaultValue; /* ask again if invalid entry */ needValue = 1; end; end; return ( x ); finish; /* * This program asks the user to set * 1) the size of grid * 2) the initial configuration of "live cells" * shift-click on the cells in grid to select "live" cells * 3) the number of generations desired * * John H. Conway (Scientific American, October 1970, p. 120) invented a game called * Life to model the process of birth, survival, and death. The idea is that * organisms require others in order to survive and procreate but that overcrowding * results in death. This program simulates the game Life. The games follows * these rules: * * A) Birth Rule: An organism is born into any empty cell that has exactly three * living neighbors. * B) Survival Rule: An organism with either two or three neighbors survives from * one generation to the next. * C) Death Rule: An organism with four or more neighbors dies from overcrowding. * An organism with fewer than two neightbors dies from loneliness. * * Some interesting initial patterns to choose: * Glider: 0 0 1 * 1 0 1 * 0 1 1 * * Exploder: 0 1 0 * 1 1 1 * 1 0 1 * 0 1 0 * */