Problem trying to implement 'Game of Life' in python as a beginner - python

So I am an absolute beginner in python and tried my hand at implementing Conway's game of life.
I am not using any libraries at all, my grid is just a 50x50 matrix of 1s and 0s.
The next_gen output I get is not matching the expected output but I couldn't figure out why, any help would be appreciated.
Here's my code:
def alive_neighbours(grid, r, c):
count = 0
if grid[r-1][c-1] == 1:
count += 1
if grid[r-1][c] == 1:
count += 1
if grid[r-1][c+1] == 1:
count += 1
if grid[r][c-1] == 1:
count += 1
if grid[r][c+1] == 1:
count += 1
if grid[r+1][c-1] == 1:
count += 1
if grid[r+1][c] == 1:
count += 1
if grid[r+1][c+1] == 1:
count += 1
return count
grid = [[0 for i in range(50)] for j in range(50)]
grid[25][25] = 1
grid[26][26] = 1
grid[27][24] = 1
grid[27][25] = 1
grid[27][26] = 1
grid[49][49] = 1
def next_gen(grid):
new_grid = grid[:]
for r in range(1, 49):
for c in range(1, 49):
neighbour = alive_neighbours(grid, r, c)
if (r == 0 or c == 0) or (r == 49 or c == 49):
pass # I am yet to define edge case
else:
if grid[r][c] == 1 and (neighbour > 3 or neighbour < 2):
new_grid[r][c] = 0
continue
elif grid[r][c] == 1 and (neighbour == 2 or 3):
continue
elif grid[r][c] == 0 and neighbour == 3:
new_grid[r][c] = 1
continue
else:
continue
grid = new_grid[:]
def printf(grid):
for r in range(50):
for c in range(50):
if grid[r][c] == 1:
print("*", end=" ")
else:
print(" ", end=" ")
print("")
x = 0
while x != '-1':
x = (input("x: "))
printf(grid)
next_gen(grid)
I also tried rewriting my next_gen function, but using that there is absolutely no change in the matrix
next_gen:
def next_gen(grid):
new_grid = grid[:]
for r in range(1, 49):
for c in range(1, 49):
neighbour = alive_neighbours(grid, r, c)
if (r == 0 or c == 0) or (r == 49 or c == 49):
pass
else:
if grid[r][c] == 1 and neighbour == 2 or 3:
continue
if grid[r][c] == 0 and neighbour == 3:
new_grid[r][c] = 1
continue
if grid[r][c] == 1:
new_grid[r][c] = 0
continue
grid = new_grid[:]

As bruno said in his answer there are a few issues in your code, He already told about your issue with grid and how allocating to it in the function actually points the local scope version to the new grid and not the global scope one. He also covers how to resolve this.
The other issue you will have is that you have undertood that just doing new_grid = grid will mean that new_grid and grid point at the same list. So to prevent this you have correctly done new_grid = grid[:] as this will create a new list in memory and copy the data from the grid list. However thats a shallow copy, so you will create a new list object but copy all the list references inside your list. we can demonstrate this by doing a shallow copy of a list and then changing a value in the new list.
grid_size = 2
grid = [[0 for i in range(grid_size)] for j in range(grid_size)]
new_grid = grid[:]
new_grid[1][1] = "R"
print("grid:", grid)
print("newg:", new_grid)
#output#
grid: [[0, 0], [0, 'R']]
newg: [[0, 0], [0, 'R']]
So you can see that changing the inner list in one will change the inner list in the other. so you need to do a deep copy of the list so that your not changing the original grid as you go. Since conways states are based on the original grid state and squares changing shouldnt impact the others. I think your already aware of this concept.
I also made a change to the alive neighbours to simplyfy it. Below is a quick draft adaptation. when you run it you should see your glider heading off to the bottom right corner
from copy import deepcopy
def alive_neighbours(grid, r, c):
differences = (0, -1, +1)
cells_in_square = [(r + a, c + b) for a in differences for b in differences]
total = 0
for x,y in cells_in_square[1:]:
try:
if x >=0 and y>=0:
total += grid[x][y]
except IndexError as ie:
pass #ignore index errors as at the edge of the grid
return total
def next_gen(grid):
new_grid = deepcopy(grid)
for r in range(len(grid)):
for c in range(len(grid)):
neighbour = alive_neighbours(grid, r, c)
if grid[r][c] == 1 and (neighbour > 3 or neighbour < 2):
new_grid[r][c] = 0
elif grid[r][c] == 0 and neighbour == 3:
new_grid[r][c] = 1
return new_grid
def printf(grid):
for r in grid:
for c in r:
if c == 1:
print("*", end=" ")
else:
print(" ", end=" ")
print("")
grid_size = 50
grid = [[0 for i in range(grid_size)] for j in range(grid_size)]
grid[25][25] = 1
grid[26][26] = 1
grid[27][24] = 1
grid[27][25] = 1
grid[27][26] = 1
grid[49][49] = 1
while True:
x = (input("press enter to see next grid: "))
if x:
break
printf(grid)
grid = next_gen(grid)
UPDATE
other then the glider you started with the below is a nice start for a cool exploder
grid_size = 50
grid = [[0 for i in range(grid_size)] for j in range(grid_size)]
grid[25][25] = 1
grid[26][24] = 1
grid[26][25] = 1
grid[26][26] = 1
grid[27][24] = 1
grid[27][26] = 1
grid[28][25] = 1

There are actually quite a few issues with your code, but the first and main problem is with your updated grid never being returned to the caller.
Here:
def next_gen(grid):
new_grid = grid[:]
# ...
# code modifying new_grid
# ...
grid = new_grid[:]
within the function, grid is a local name. Rebinding this name at the end of the function only affects the local name, it doesn't do anything to the global one. you should read this reference article for more in-depth explanations.
What you want is to return the grid to the caller instead:
def next_gen(grid):
new_grid = grid[:]
# ...
# code modifying new_grid
# ...
# return the new grid to the caller
return new_grid
x = 0
while x != '-1':
x = (input("x: "))
printf(grid)
# replace previous grid with the new one
grid = next_gen(grid)
For some other issues, this:
if grid[r][c] == 1 and neighbour == 2 or 3:
doesn't do what you think it does.
The neighbour == 2 or 3 part is actually executed as (neighbour == 2) or 3. Now the or operator returns either the first of it's operands that is not false, or the last of it's operand. Note that "that is not false" means "that does not have a false value in a boolean context" (all Python objects have a "truth" value, and for numbers, all numbers are true expect for zeros). So in the end, if the neighbours is different from 2, the value of neighbour == 2 or 3is3, whatever the value ofneighbour`:
>>> foo
1
>>> foo == 1 or 3
True
>>> foo == 2 or 3
3
>>>
And since 3 is true, the expression will have a true value even if neighbours is actually 1 or 4 or 5 or etc...
TL;DR: you want either:
`neighbour == 2 or neighbour == 3`
or more simply:
`neighbour in (2, 3)`

Related

How to fix cellular automata/spatial prisoners dilemma that is not replicating properly

I am trying to replicate the results of a paper (if you are interested, its Nowak & May, 1992: Evolutionary Games and Spatial Chaos) that create a set of fractals by running a prisoners dilemma on an n x n grid (for example,
https://www.researchgate.net/figure/Spatial-version-of-the-Prisoners-Dilemma-for-symmetrical-initial-conditions-Nowak_fig3_277476479), but my results are not what they should be.
The idea is that the grid is populated entirely by Cooperators, except for a single Defector object that is placed in the center of the grid. Different interactions yield different payoffs: mutual defectors yield a payoff of 0, mutual cooperators a payoff of 1 each, and a defector against a cooperator yields a payoff of b for the defector and 0 for the cooperator, where b > 1. All objects in the grid play against each other and receive a score according to the above payoff structure. After each generation, each object on a node is replaced by the neighbor with the highest score. Since the defector strategy is the superior strategy, it should invade the Cooperator population and produce said fractal images, as a cellular automata would.
The main way I have tried doing this (also the main area I have had trouble with) is through the replace_pop function shown below. After each round, the program loops through the grid and replaces any object on a node with a neighbour object that has a higher score. I thought that this would have been sufficient but as one can see after even a few generations, there is some form of replication but just not in the way it should happen, making it difficult to pinpoint what exactly is going wrong. At N = 1 (N is the number of generations) the result seems correct, as the neighbouring (neighbours are left, right, above and below) Cooperators become Defectors, but as N grows larger the image just goes astray.
I also reinitialized each objects score to 0 after each generation to ensure that proper replication can take place. When this is not done however, the population evolves in the same fashion as the N = 1 case above but for all subsequent generations, which is peculiar because there should be defectors that have higher scores than surrounding Cooperators. I am not sure where I am going wrong? My code is below (sorry for including all of it but I do not know where exactly is the problem). I am pretty new to Python and Stack so any help would be appreciated.
import random
import matplotlib.pyplot as plt
row = 99
col = 99
class Cooperator:
def __init__(self):
self.score = 0
self.id = 'C'
class Defector:
def __init__(self):
self.score = 0
self.id = 'D'
class Grid:
def __init__(self, rowsize, colsize):
self.rowsize = rowsize
self.colsize = colsize
def make_grid(self):
n = self.rowsize
m = self.colsize
arr = [[0 for j in range(m)] for i in range(n)]
return arr
def populate_grid(self):
empty_grid = self.make_grid()
for i in range(self.rowsize):
for j in range(self.colsize):
empty_grid[i][j] = Cooperator()
empty_grid[i//2][j//2] = Defector()
return empty_grid
def shuffle_population(self):
populated_grid = self.populate_grid()
for i in range(self.rowsize):
random.shuffle(populated_grid[i])
return populated_grid
def von_neumann_neighbourhood(array, row, col, wrapped=True):
"""gets von neumann neighbours for a specfic point on grid with or without wrapping"""
neighbours = []
#conditions for in bound points
if row + 1 <= len(array) - 1:
neighbours.append(array[row + 1][col])
if row - 1 >= 0:
neighbours.append(array[row - 1][col])
if col + 1 <= len(array[0]) - 1:
neighbours.append(array[row][col + 1])
if col - 1 >= 0:
neighbours.append(array[row][col - 1])
#if wrapped is on, conditions for out of bound points
if row - 1 < 0 and wrapped == True:
neighbours.append(array[-1][col])
if col - 1 < 0 and wrapped == True:
neighbours.append(array[row][-1])
if row + 1 > len(array) - 1 and wrapped == True:
neighbours.append(array[0][col])
if col + 1 > len(array[0]) - 1 and wrapped == True:
neighbours.append(array[row][0])
return neighbours
def play_round(array, row, col):
b = 1.70
player = array[row][col]
neighbours = von_neumann_neighbourhood(array, row, col)
for neighbour in neighbours:
if player.id == 'C' and neighbour.id == 'C':
player.score += 1
neighbour.score += 1
if player.id == 'D' and neighbour.id == 'D':
player.score += 0
neighbour.score += 0
if player.id == 'D' and neighbour.id == 'C':
player.score += b
neighbour.score += 0
if player.id == 'C' and neighbour.id == 'D':
player.score += 0
neighbour.score += b
def replace_pop(array, row, col):
neighbour_score = 0
type_neighbour = ""
neighbours = von_neumann_neighbourhood(array, row, col)
player_score = array[row][col].score
for neighbour in neighbours:
if neighbour.score > neighbour_score:
neighbour_score = neighbour.score
type_neighbour = neighbour.id
if player_score < neighbour_score:
if type_neighbour == "C":
array[row][col] = Cooperator()
if type_neighbour == "D":
array[row][col] = Defector()
N = 1
last_gen = []
def generations(N, row, col, array):
for gen in range(N):
for z in range(row):
for x in range(col):
play_round(array, z, x)
for r in range(row):
last_gen.append([])
for c in range(col):
last_gen[r].append(lattice[r][c].id)
replace_pop(array, r, c)
for obj in lattice:
for ob in obj:
ob.score = 0
lattice = Grid(row, col).populate_grid()
generations(N, row, col, lattice)
heatmap_stuff = []
for z in range(row):
heatmap_stuff.append([])
for v in range(col):
if lattice[z][v].id == 'C' and last_gen[z][v] == 'C':
heatmap_stuff[z].append(1)
if lattice[z][v].id == 'D' and last_gen[z][v] == 'D':
heatmap_stuff[z].append(0)
if lattice[z][v].id == 'C' and last_gen[z][v] == 'D':
heatmap_stuff[z].append(3)
if lattice[z][v].id == 'D' and last_gen[z][v] == 'C':
heatmap_stuff[z].append(4)
plt.imshow(heatmap_stuff, interpolation='nearest')
plt.colorbar()
plt.show()
Edit: I have updated the code in line with Ilmari's suggestions. Although the results look better, as well as returning an actual fractal in real-time, the results are still not optimal, leading me to think there might be a bug elsewhere since the cells seem to be updating correctly. Below is the updated code I have added/replaced to the previous code.
def get_moore_neighbours(grid, row, col):
neighbours = []
for x, y in (
(row - 1, col), (row + 1, col), (row, col - 1),
(row, col + 1), (row - 1, col - 1), (row - 1, col + 1),
(row + 1, col - 1), (row + 1, col + 1)):
if not (0 <= x < len(grid) and 0 <= y < len(grid[x])):
# out of bounds
continue
else:
neighbours.append(grid[x][y])
return neighbours
def calculate_score(grid, row, col):
b = 1.85
player = grid[row][col]
neighbours = get_moore_neighbours(grid, row, col)
for neighbour in neighbours:
if player.id == 'C' and neighbour.id == 'C':
player.score += 1
neighbour.score += 1
if player.id == 'D' and neighbour.id == 'D':
player.score += 0
neighbour.score += 0
if player.id == 'D' and neighbour.id == 'C':
player.score += b
neighbour.score += 0
if player.id == 'C' and neighbour.id == 'D':
player.score += 0
neighbour.score += b
return player.score
def best_neighbor_type(grid, row, col):
neighbour_score = 0
type_neighbour = ""
neighbours = get_moore_neighbours(grid, row, col)
player_score = grid[row][col].score
for neighbour in neighbours:
if neighbour.score > neighbour_score:
neighbour_score = neighbour.score
type_neighbour = neighbour.id
if player_score < neighbour_score:
if type_neighbour == "C":
return 'C'
if type_neighbour == "D":
return 'D'
if player_score >= neighbour_score:
return grid[row][col].id
N = 15
heatmap_data = Grid(row, col).make_grid()
lattice = Grid(row, col).populate_grid()
dbl_buf = Grid(row, col).populate_grid()
for gen in range(N):
for r in range(row):
for c in range(col):
lattice[r][c].score = calculate_score(lattice, r, c)
for r in range(row):
for c in range(col):
dbl_buf[r][c].id = best_neighbor_type(lattice, r, c)
for r in range(row):
for c in range(col):
if lattice[r][c].id == 'C' and dbl_buf[r][c].id == 'C':
heatmap_data[r][c] = 1
if lattice[r][c].id == 'D' and dbl_buf[r][c].id == 'D':
heatmap_data[r][c] = 2
if lattice[r][c].id == 'C' and dbl_buf[r][c].id == 'D':
heatmap_data[r][c] = 3
if lattice[r][c].id == 'D' and dbl_buf[r][c].id == 'C':
heatmap_data[r][c] = 4
plt.imshow(heatmap_data, interpolation='nearest')
plt.pause(0.01)
(lattice, dbl_buf) = (dbl_buf, lattice)
plt.show()
Looking at your code, a few issues jump out:
You never reset the last_gen array between generations, so you're constantly appending new (empty) rows to it and making the first row rows longer and longer. This is almost certainly a bug.
You also never use the last_gen array for anything except generating the heat map. In particular, your replace_pop() function is modifying the same array (creatively named array) that it reads the neighbor states from.
The second issue means that the behavior of your code will depend on the order in which you loop over the cells to call replace_pop() in each generation, since replacing one cell with a different neighbor will affect the neighborhood of all of its neighbors that haven't yet been updated in this generation.
In a cellular automaton like described in the paper you cite, all the cells are supposed to update their state effectively simultaneously, such that changes to each cell's state won't become visible to its neighbors until the next generation.
In practice, the simplest way to implement this kind of "simultaneous" updating is to use double buffering, where you first copy the state of all the cells into a second array, and then update the first array based on the copy you just made. Or, more efficiently, just swap the (references to) the arrays instead of copying one into the other. The code would look something like this:
lattice = Grid(row, col).populate_grid()
dbl_buf = Grid(row, col)
for gen in range(N):
for r in range(row):
for c in range(col):
lattice[r][c].score = calculate_score(lattice, r, c)
# This is probably the best spot for generating output, since both
# buffers contain consistent and up-to-date IDs and scores here.
for r in range(row):
for c in range(col):
dbl_buf[r][c].id = best_neighbor_type(lattice, r, c)
(lattice, dbl_buf) = (dbl_buf, lattice)
where the calculate_score() function returns the score of the given cell on the lattice based on the types of its neighbors, and the best_neighbor_id() function returns the type ID of the highest-scoring neighbor of the cell on the lattice.
Addendum: Your implementation of calculate_score() in your updated code has some bugs:
you start the calculations from the previous score value (which is actually from two generations back due to the double buffering),
you're redundantly writing the score directly to the grid inside the function, rather than just returning the score to the caller, and
you're also redundantly updating the scores of the cell's neighbors, leading to some interactions being effective counted twice.
However, the real reason why you're getting different results than in the Nowak & May paper is because of a conceptual difference: the paper assumes that cells also play the game with themselves, effectively giving cooperators a one point score boost. Your implementation doesn't include that, leading to different dynamics for the same parameter values.
Anyway, here's how I'd rewrite the function:
def calculate_score(grid, row, col):
neighbours = get_moore_neighbours(grid, row, col)
player = grid[row][col]
score = 0
if player.id == 'C': score += 1 # self-interaction!
for neighbour in neighbours:
if player.id == 'C' and neighbour.id == 'C':
score += 1
if player.id == 'D' and neighbour.id == 'C':
score += b
return score
With that change, your code produces very similar patterns as in the Nowak & May paper:
BTW, I'm not sure how Nowak & May handle the edges of the lattice, which might cause the patterns to diverge once they hit the edge. Your implementation effectively excludes any neighbors outside the edges from the score calculation, as if the lattice was surrounded by non-spreading defectors.

Finding the number of paths in a maze with obstacles

I've been working on this leetcode problem, which is essentially finding the number of valid paths in a maze given some obstacleGrid matrix. If obstacleGrid[i][j] == 1, then we have an obstacle at (i,j) and we have zero otherwise, which a valid spot. We can only move down and right with the goal of starting from the upper left to the bottom right.
I have written the code below:
def uniquePathsWithObstacles(self, obstacleGrid):
# obstruction at the start
if (obstacleGrid[0][0] == 1): return 0
# obstruction at the end
if (obstacleGrid[-1][-1] == 1): return 0
m, n = len(obstacleGrid), len(obstacleGrid[0])
memo = [[0] * n] * m
# starting move
memo[0][0] = 1
# now check the first row
for j in range(1, n):
memo[0][j] = 1 if (obstacleGrid[0][j] == 0 and memo[0][j-1] != 0) else 0
# now check the first column
for i in range(1, m):
memo[i][0] = 1 if (obstacleGrid[i][0] == 0 and memo[i-1][0] != 0) else 0
# now check everything else
for i in range(1, m):
for j in range(1, n):
if (obstacleGrid[i][j] == 1): memo[i][j] = 0
else: memo[i][j] = memo[i-1][j] + memo[i][j-1]
return memo[-1][-1]
I took the obvious DP approach and I know the idea works but something is wrong with the code; for some reason I don't think my memo matrix is being updated properly? I feel like the problem is staring at me in the face but for some reason I can't see it. Any help appreciated!
Edit: For obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]] and if I had a print(memo) right before the return statement, I get [[1, 1, 2], [1, 1, 2], [1, 1, 2]]. This happens to give me the right answer, but the memo matrix is wrong!
One problem lies in the line memo = [[0] * n] * m.
This does not really create mcopies of the same list, but instead, it only creates the [0] * n list once and then creates memo as a list of m references to this list. Any change to any of these lists therefore modifies all other lists!
You can try this yourself:
memo = [[0] * 3] * 4
memo[0][1] = 1
print(memo)
This gives [[0, 1, 0], [0, 1, 0], [0, 1, 0], [0, 1, 0]].
Instead, you have to initialize each list on their own, e.g.,
memo = []
for i in range(m):
memo.append([0] * n)
I just tried to do this with recursion as an comparison rather than an answer.
import numpy as np
def number_of_paths(obstacles):
"""
Calculate the number of paths available in a maze with obstacles, with only right and down moves, from top left
to bottom right.
Args:
obstacles (ndarray): binary matrix with 1 representing obstacle
Returns:
int: the number of paths
"""
if obstacles[0,0] == 1:
raise ValueError # cannot start on an obstacle
count = 0
if obstacles.shape == (2,1):
return 1
if obstacles.shape == (1,2):
return 1
if obstacles.shape[1] > 1 and obstacles[0,1] == 0:
count += number_of_paths(obstacles[:,1:])
if obstacles.shape[0] > 1 and obstacles[1,0] == 0:
count += number_of_paths(obstacles[1:,:])
return count
your code is correct and 1 line must be updated per the below:
def uniquePathsWithObstacles(self, obstacleGrid):
# obstruction at the start
if (obstacleGrid[0][0] == 1): return 0
# obstruction at the end
if (obstacleGrid[-1][-1] == 1): return 0
m, n = len(obstacleGrid), len(obstacleGrid[0])
memo = [[0] * n for i in range(m)]
# starting move
memo[0][0] = 1
# now check the first row
for j in range(1, n):
#memo[0][j] = 1 if (obstacleGrid[0][j] == 0 and memo[0][j-1] != 0) else 0
memo[0][j] = 1 if (obstacleGrid[0][j] == 0 and memo[0][j-1] != 0) else 0
# now check the first column
for i in range(1, m):
memo[i][0] = 1 if (obstacleGrid[i][0] == 0 and memo[i-1][0] != 0) else 0
# now check everything else
for i in range(1, m):
for j in range(1, n):
if (obstacleGrid[i][j] == 1): memo[i][j] = 0
else: memo[i][j] = memo[i-1][j] + memo[i][j-1]
return memo[-1][-1]

Conway's Game of Life not counting neighbors correctly

I am doing the standard Conway's Game of Life program using Python. I am having an issue when trying to count neighbors as I iterate through the array. I created print statements that print the succession of the if statement, as well as the value of count for each statement.
Here is my code: ( I have the questions inside the # throughout the code)
import random
numrows = 10
numcols = 10
def rnd():
rn = random.randint(0,1)
return rn
def initial():
grid = []
count = 0
for x in range(numrows):
grid.append([])
for y in range(numcols):
rand=random.randrange(1,3)
if(rand == 1):
grid[x].append('O')
else:
grid[x].append('-')
for x in grid:
print(*x, sep=' ',end="\n") #this prints the random 2d array
print("")
print("")
answer = 'y'
newgrid = []
count = 0
while(answer == 'y'): # I believe I am going through, checking neighbors
# and moving onto the next index inside these for
#loops below
for r in range(0,numrows):
grid.append([])
for c in range(0,numcols):
if(r-1 > -1 and c-1 > -1): #I use this to check out of bound
if(newgrid[r-1][c-1] == 'O'):#if top left location is O
count = count + 1 #should get count += 1
else:
count = count
print("top left check complete")
print(count)
if(r-1 > -1):
if(newgrid[r-1][c] == 'O'):
count = count + 1
else:
count = count
print("top mid check complete")
print(count)
if(r-1 > -1 and c+1 < numcols):
if(newgrid[r-1][c+1] == 'O'):
count = count + 1
else:
count = count
print("top right check complete")
print(count)
if(c-1 > -1 and r-1 > -1):
if(newgrid[r][c-1] == 'O'):
count = count + 1
else:
count = count
print("mid left check complete")
print(count)
if(r-1 > -1 and c+1 < numcols):
if(newgrid[r][c+1] == 'O'):
count = count + 1
else:
count = count
print("mid right check complete")
print(count)
if(r+1 < numrows and c-1 > -1):
if(newgrid[r+1][c-1] == 'O'):
count = count + 1
else:
count = count
print("bot left check complete")
print(count)
if(r+1 < numrows and c-1 > -1):
if(newgrid[r+1][c] == 'O'):
count = count + 1
else:
count = count
print("bot mid check complete")
print(count)
if(r+1 < numrows and c+1 < numcols):
if(newgrid[r+1][c+1] == 'O'):
count = count + 1
else:
count = count
print("bot right check complete")
print(count)
# I am not sure about the formatting of the code below, how do I know that
# the newgrid[r][c] location is changing? should it be according to the for-
# loop above? Or should it get it's own? If so, how could I construct it as
# to not interfere with the other loops and items of them?
if(newgrid[r][c] == '-' and count == 3):
newgrid[r][c] ='O'
elif(newgrid[r][c] == 'O' and count < 2):
newgrid[r][c] = '-'
elif(newgrid[r][c] == 'O' and (count == 2 or count == 3)):
newgrid[r][c] = 'O'
elif(newgrid[r][c] == 'O' and count > 3):
newgrid[r][c] = '-'
# I'm also confused how to go about printing out the 'new' grid after each
# element has been evaluated and changed. I do however know that after the
# new grid prints, that I need to assign it to the old grid, so that it can
# be the 'new' default grid. How do I do this?
for z in newgrid:
print(*z, sep=' ',end="\n")
answer = input("Continue? y or n( lower case only): ")
newgrid = grid
if(answer != 'y'):
print(" Hope you had a great life! Goodbye!")
initial()
Here is the current output and error message:
>>> initial()
- O - - O - - O - -
- O - - O - - - O O
- O - - O - O O - O
O - - O - - O O O O
O - O O - - - O O -
O - O - O - O - O -
O - O O O O - - O -
- - - - O O O - - O
O O - O - - O - - -
- - O O O - O - - -
top left check complete
0
top mid check complete
0
top right check complete
0
mid left check complete
0
mid right check complete
0
bot left check complete
0
bot mid check complete
0
Traceback (most recent call last):
File "<pyshell#68>", line 1, in <module>
initial()
File "C:\Users\Ted\Desktop\combined.py", line 86, in initial
if(newgrid[r+1][c+1] == 'O'):
IndexError: list index out of range
As I iterate through the random array to see what the neighbors are, it seems to be fine up until it moves over to [0][1] while checking the bot right neighbor.
Also, the mid right neighbor should + 1 to count as it is alive. However, even with succession of the if statement, count remains 0?
Question 1: How can I possibly know that my if conditions witll suffice for every instance of [r][c] for all sides of the array?
Question 2: Is my current method of checking out of bounds the best for my situation? Is there a way to make a "check all for out of bounds" before I even check the value?
I am at my wit's end at this point. Thanks in advance for the time taken to help answer my questions
You are getting that index error because your newgrid only contains a single empty row. And your testing for neighbours in newgrid instead of in grid (as Blckknght mentions in the comments). I've made a few repairs, but there's a lot more that can be done to improve this code. It looks like it's working now, but it's hard to tell when you're working with random Life forms. :) I suggest giving your program some way of using known Life patterns like blinkers and gliders to see that they behave correctly.
The simplest way to ensure that newgrid is valid is to copy it from grid. If we just do newgrid = grid that simply makes newgrid another name for the grid object. To copy a list of lists properly we need to make copies of each of the internal lists. My new code does that with the copy_grid function.
I've fixed a couple of minor bugs that you had in the if tests in the section that counts neighbours, and I've simplified the logic that updates a cell from its neighbour count. I've also condensed the code that makes a random grid, and I've added a simple function that can read a Life pattern from a string and build a grid from it. This lets us test the code with a Glider. I've also added a function that makes an empty grid. The program doesn't currently use that function although I used it during my tests, and I guess it's a useful example. :)
import random
# Set a seed so that we get the same random numbers each time we run the program
# This makes it easier to test the program during development
random.seed(42)
numrows = 10
numcols = 10
glider = '''\
----------
--O-------
---O------
-OOO------
----------
----------
----------
----------
----------
----------
'''
# Make an empty grid
def empty_grid():
return [['-' for y in range(numcols)]
for x in range(numrows)]
# Make a random grid
def random_grid():
return [[random.choice('O-') for y in range(numcols)]
for x in range(numrows)]
# Make a grid from a pattern string
def pattern_grid(pattern):
return [list(row) for row in pattern.splitlines()]
# Copy a grid, properly!
def copy_grid(grid):
return [row[:] for row in grid]
# Print a grid
def show_grid(grid):
for row in grid:
print(*row)
print()
def run(grid):
show_grid(grid)
# Copy the grid to newgrid.
newgrid = copy_grid(grid)
while True:
for r in range(numrows):
for c in range(numcols):
# Count the neighbours, making sure that they are in bounds
count = 0
# Above this row
if(r-1 > -1 and c-1 > -1):
if(grid[r-1][c-1] == 'O'):
count += 1
if(r-1 > -1):
if(grid[r-1][c] == 'O'):
count += 1
if(r-1 > -1 and c+1 < numcols):
if(grid[r-1][c+1] == 'O'):
count += 1
# On this row
if(c-1 > -1):
if(grid[r][c-1] == 'O'):
count += 1
if(c+1 < numcols):
if(grid[r][c+1] == 'O'):
count += 1
# Below this row
if(r+1 < numrows and c-1 > -1):
if(grid[r+1][c-1] == 'O'):
count += 1
if(r+1 < numrows):
if(grid[r+1][c] == 'O'):
count += 1
if(r+1 < numrows and c+1 < numcols):
if(grid[r+1][c+1] == 'O'):
count += 1
# Update the cell in the new grid
if grid[r][c] == '-':
if count == 3:
newgrid[r][c] ='O'
else:
if count < 2 or count> 3:
newgrid[r][c] = '-'
# Copy the newgrid to grid
grid = copy_grid(newgrid)
show_grid(grid)
answer = input("Continue? [Y/n]: ")
if not answer in 'yY':
print(" Hope you had a great life! Goodbye!")
break
#grid = random_grid()
grid = pattern_grid(glider)
run(grid)
This code does work correctly, but there is still plenty of room for improvement. For example, here's an improved version of run() that condenses the neighbour counting section by using a couple of loops.
def run(grid):
show_grid(grid)
# Copy the grid to newgrid.
newgrid = copy_grid(grid)
while True:
for r in range(numrows):
for c in range(numcols):
# Count the neighbours, making sure that they are in bounds
# This includes the cell itself in the count
count = 0
for y in range(max(0, r - 1), min(r + 2, numrows)):
for x in range(max(0, c - 1), min(c + 2, numcols)):
count += grid[y][x] == 'O'
# Update the cell in the new grid
if grid[r][c] == '-':
if count == 3:
newgrid[r][c] ='O'
else:
# Remember, this count includes the cell itself
if count < 3 or count > 4:
newgrid[r][c] = '-'
# Copy the newgrid to grid
grid = copy_grid(newgrid)
show_grid(grid)
answer = input("Continue? [Y/n]: ")
if not answer in 'yY':
print(" Hope you had a great life! Goodbye!")
break
To count the neighbors, just follow this simple rule. You will have the row and col of the current cell. Using that form two arrays, one for rows and another for columns. Below would be your logic.
You can find the detailed implementation with a demo video here.
this.neibhours = function() {
var rows = [row-1, row, row+1];
var cols = [col-1, col, col+1];
neibhourCells = [];
for(var i=0; i < rows.length; i++) {
for(var j=0; j < cols.length; j++) {
if(!(col === cols[j] && row === rows[i])) {
var cell = new Cell(rows[i], cols[j])
if(cell.isOnGrid()) {
neibhourCells.push(cell);
}
}
}
}
return neibhourCells;
}

How to use a variable to find another variable in a list - python

(Python 3.x)
z=[]
x=0
while 1==1:
x=x+1
y=1
z.append(x)
while y==1:
a = 0
b = 0
if z(a)==x:
print(x)
y = 2
elif x%z(a)!= 0:
a = a+1
elif b == 2:
y = 2
else:
b = b+1
So, I made a code to find all the prime numbers until python crashes. However, it relies on z(a) for it to work. The idea is that as "a" changes, it moves on in the list.
"z(a)" is where the error lies, so does anyone know a way to fix this?
z is a list. You can approach values inside it by indexes using the z[a] operator (and not z(a) which assumes a function call with a as parameter).
I've taken the liberty of using boolean variables, += operators and unpacking values:
z = []
x = 0
while True:
x += 1
y = True
z.append(x)
while y:
a, b = 0, 0
if z[a] == x:
print(x)
y = False
elif x % z[a]: a += 1
elif b == 2: y = False
else: b += 1
I believe that's what you want to achieve (infinitely incrementing prime generator):
def prime (n): return not any([n % i == 0 for i in range(2, n)])
x = 0
while True:
if prime(x): print(x)
x += 1

Conway's Game of Life with Python

I took a liking to Conway's Game of Life and began to try and write it in python. At this moment I have yet to write any code for the borders of the program so I am just asking for help with what I have right now. I seem to have trouble when initialization a "blinker" formation. Instead of oscillating like it should, it seems to turn itself into a cube.
#File: gameoflife.py
#Description: This is a basic "life" simulation that follows three distinct rules:
#1.Any live cell with fewer than two live neighbours dies
#2.Any live cell with two or three live neighbours lives
#3.Any live cell with more than three live neighbours dies
#4.Any dead cell with exactly three live neighbours becomes a live cell
#A neighbor is deemed as any cell directly horizantal/vertical/diagonal
#meaning there are 9 neighbors to a cell at any time
from graphics import *
import random
from time import sleep
def initial(D,M,win):
#Creates the white board background
for i in range (11):
m = [] # rectangle list
for j in range (11):
rec = Rectangle(Point(6 + 4 * i, 6 + 4 * j), Point(10 + 4 * i, 10 + 4 * j))
D[i][j] = 0
rec.setFill("white")
rec.draw(win)
m.append(rec)
M.append(m)
def check(x,y,D):
#Checks all 9 adjacent neihbors to see if "alive" and checks this number
#means the cell should stay alive(1), die(0), or if already dead come
#back alive(2)
counter = 0
if D[x+1][y] == 1:
counter += 1
if D[x-1][y] == 1:
counter += 1
if D[x][y+1] == 1:
counter += 1
if D[x][y-1] == 1:
counter +=1
if D[x+1][y+1] == 1:
counter+=1
if D[x+1][y-1] == 1:
counter+= 1
if D[x-1][y-1] == 1:
counter += 1
if D[x-1][y+1] == 1:
counter +=1
if counter<2 or counter>3:
return 0
if counter == 2:
return 1
if counter == 3:
return 2
def main():
win = GraphWin("Game of Life", 700, 600)
win. setCoords(0, 0, 70, 60)
#Initialize two dimesion arrays.
#D records color of grids, M records rectangles
D = []
M = []
C = []
#initialize the grids to create all "white"
for i in range(11):
d = []
c = []
for j in range(11):
d.append(0)
c.append(0)
D.append(d)
C.append(c)
initial(D,M,win)
#Initialzes three "alive" units
D[5][5],D[4][5] ,D[6][5]= 1,1,1
C[5][5],C[4][5] ,C[6][5]= 1,1,1
M[5][5].setFill("Black")
M[4][5].setFill("Black")
M[6][5].setFill("Black")
#Contiually checking
while True:
#Purposfully not checking the "Borders" of the array
for i in range (len(D)-1):
for j in range(len(D[i])-1):
#If the cell is alive
if D[i][j] == 1:
#If the cell should die
if check(i,j,D) == 0:
sleep(1)
#Sets a temporary list to white
C[i][j] = 0
#Fills the cell white
M[i][j].setFill("White")
#if the cell is dead
if D[i][j] == 0:
#If the cell should be revived
if check(i,j,D) == 2:
sleep(1)
#Sets a temporary list to black
C[i][j] = 1
#Fills the cell black
M[i][j].setFill("Black")
#Sets the main list = to the temporary list
D = C
main()
You will need to swap D and C, and not just assign C to D. As it stands now, D and C will be referring to the same list after the first iteration.
Here is a simple algorithm to do Conway's Game of Life in python using a numpy array of arbitrary 2D size:
import numpy
# this function does all the work
def play_life(a):
xmax, ymax = a.shape
b = a.copy() # copy grid & Rule 2
for x in range(xmax):
for y in range(ymax):
n = numpy.sum(a[max(x - 1, 0):min(x + 2, xmax), max(y - 1, 0):min(y + 2, ymax)]) - a[x, y]
if a[x, y]:
if n < 2 or n > 3:
b[x, y] = 0 # Rule 1 and 3
elif n == 3:
b[x, y] = 1 # Rule 4
return(b)
# replace (5, 5) with the desired dimensions
life = numpy.zeros((5, 5), dtype=numpy.byte)
# place starting conditions here
life[2, 1:4] = 1 # a simple "spinner"
# now let's play
print(life)
for i in range(3):
life = play_life(life)
print(life)
This is not very efficient, but will certainly get the job done. Replace print(life) with whatever graphical calls you prefer.

Categories

Resources