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.
Related
cells not "dying" in may logocal loop. New cells geretion is normal, i think..
so cells not die
all calculations are into one numpy array, i dont have "second/old/new etc" arrays
sory for my bad English
def updateCells(grid: np.ndarray):
for i in range(GRID_WIDTH):
for j in range(GRID_HEIGHT):
#neighbours list contains 0, 1
neighbours = getNeighbours(i, j, grid)
#sum of alive neighbours
count = sum(neighbours)
if grid[i][j] == 1:
if count < 2:
grid[i][j] = 0
continue
if count == 2 or count == 3:
grid[i][j] = 1
continue
if count > 3:
grid[i][j] = 0
continue
if grid[i][j] == 0:
if count == 3:
grid[i][j] = 1
continue
function for getting neighbours of one cell:
def getNeighbours(i, j, arr):
ax_0_y = np.size(arr, 1)
ax_1_x = np.size(arr, 0)
n = []
ofssets = [-1, 0, 1]
for x in ofssets:
for y in ofssets:
if x == 0 and y == 0: continue
if y+j < 0 or y+j == ax_0_y: continue
if x+i < 0 or x+i == ax_1_x: continue
n.append(arr[x+i][y+j])
return n
main loop:
def main():
grid = np.zeros((GRID_WIDTH, GRID_HEIGHT), dtype=numpy.short)
....
drawGrid(display) #drawing cells line separators
randomise(grid) #random gereation alive cells
while True:
for i in pg.event.get():
if i.type == pg.QUIT:
quit()
updateCells(grid)
drawCells(display, grid) #drawing squares
pg.display.update()
fpsClock.tick(UPDATE_TIME)
if __name__ == "__main__":
main()
You can’t update the grid as you go, because then cells you get to later in the scan will have some neighbors from the current generation and some from the new one and will get the wrong neighbor count.
You have to do the whole grid as if it all updates at the same time, which means either having a second grid to build the new generation in or creating a list of changes to make to the grid at the end of the scan.
Some of the cells might not die due to you changing the field before the checks, like mentioned in the previous answer. You also seem to have quiet a bit of unneccessary code, like
if count == 2 or count == 3:
grid[i][j] = 1
continue
For example.
If there is no dying cells at all, i would like you to show us the code that calls your function.
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)`
I am trying to create a function that finds if a move is winning on an NxN board where the winning condition is M pieces in a row in Python 3.
I am pretty new to programming and in my specific case I am creating a Gomoku game (15x15 board with 5 pieces in a row to win). To get it working I created 6 for loops to check vertical, horizontal and 4 diagonals. See the code below for examples on the 2 options for left to right digonals. This takes way too long though when I need to loop through it many times (8) for computer to find if I can win or if it has a winning move.
end_row = 15
for j in range(11):
end_row -= 1
counter = 0
for i in range(end_row):
if board[i+j][i] == board[i+1+j][i+1] and board[i+j][i] != ' ':
counter += 1
if counter == 4:
winning_line = [(i+j-3, i-3), (i+j-2, i-2), (i+j-1, i-1), (i+j, i), (i+1+j, i+1)]
winner = True
break
else:
counter = 0
# Top left to bottom right, lower side
end_row = 15
for j in range(11):
end_row -= 1
counter = 0
for i in range(end_row):
if board[i][i+j] == board[i+1][i+1+j] and board[i][i+j] != ' ':
counter += 1
if counter == 4:
winning_line = [(i-3, i+j-3), (i-2, i+j-2), (i-1, i+j-1), (i, i+j), (i+1, i+1+j)]
winner = True
break
else:
counter = 0
# What I want to do instead, where x and y are coordinates of last move:
# Horizontal
counter = 0
for i = x - (n - 1) to x + (n - 1):
if board[i][y] == board[x][y] :
counter++
else :
counter = 0
if counter == n:
return true
The problem with the lower part of the code is that if I place a piece on e.g. position (0, 0) the program will complain when trying to reach board[-4][0] in the first looping. I will have to place lots of if statements when I get close to the edge, which is not an elegant solution.
I thought of making a 3*15 x 3*15 board instead, where the actual board is the inner 15x15 part and the rest just contains placeholders:
15x15 || 15x15 || 15x15
15x15 || board || 15x15
15x15 || 15x15 || 15x15
This to avoid getting outside of my list of lists when looping through. Not an elegant solution either, but takes less space in the code.
Any suggestions on how to solve this problem? Thank you in advance from a beginner programmer!
As #MePsyDuck mentioned in comments, you can use min and max functions to limit the range to only reference valid squares in the board matrix.
Furthermore, you could make a generic function that does the count-job on any given list of values. Then you can call that generic function four times: once for every direction (horizontal, vertical, diagonal \ and diagonal /)
Here is how that could work:
def is_win(board, n, x, y):
end_row = len(board)
color = board[x][y]
def check(values):
counter = 0
for value in values:
if value == color:
counter += 1
else:
counter = 0
if counter == n:
return True
return False
return (check([board[i][y] for i in range(max(0, x - n + 1), min(end_row, x + n))])
or check([board[x][i] for i in range(max(0, y - n + 1), min(end_row, y + n))])
or check([board[x+i][y+i] for i in range(max(-x, -y, 1 - n), min(end_row - x, end_row - y, n))])
or check([board[x+i][y-i] for i in range(max(-x, y - end_row + 1, 1 - n), min(end_row - x, y + 1, n))]))
Instead of looping from 0 to 14, just loop from 0 to (board_size - winning_length).
Here's an example for a 1-dimensional board:
BOARD_SIZE = 15
WINNING_LENGTH = 5
for x in range(BOARD_SIZE - WINNING_LENGTH):
players_here = set()
for pos in range(x, x + WINNING_LENGTH):
players_here.add(board[pos])
if len(players_here) == 1:
# Exactly 1 player occupies every position in this line, so they win
Here is my very rough implementation of Conway's Game of Life simulation.
LIVE = 1
DEAD = 0
def board(canvas, width, height, n):
for row in range(n+1):
for col in range(n+1):
canvas.create_rectangle(row*height/n,col*width/n,(row+1)*height/n,(col+1)*width/n,width=1,fill='black',outline='green')
n = int(raw_input("Enter the dimensions of the board: "))
width = n*25
height = n*25
from Tkinter import *
import math
window=Tk()
window.title('Game of Life')
canvas=Canvas(window,width=width,height=height,highlightthickness=0)
canvas.grid(row=0,column=0,columnspan=5)
board = [[DEAD for row in range(n)] for col in range(n)]
rect = [[None for row in range(n)] for col in range(n)]
for row in range(n):
for col in range(n):
rect[row][col] = canvas.create_rectangle(row*height/n,col*width/n,(row+1)*height/n,(col+1)*width/n,width=1,fill='black',outline='green')
#canvas.itemconfigure(rect[2][3], fill='red') #rect[2][3] is rectangle ID
#print rect
f = open('filename','r') #filename is whatever configuration file is chosen that gives the step() function to work off of for the first time
for line in f:
parsed = line.split()
print parsed
if len(parsed)>1:
row = int(parsed[0].strip())
col = int(parsed[1].strip())
board[row][col] = LIVE
board[row][col] = canvas.itemconfigure(rlist[row][col], fill='red')
def surrounding(row,col):
count = 0
if board[(row-1) % n][(col-1) % n] == LIVE:
count += 1
if board[(row-1) % n][col % n] == LIVE:
count += 1
if board[(row-1) % n][(col+1) % n] == LIVE:
count += 1
if board[row % n][(col-1) % n] == LIVE:
count += 1
if board[row % n][(col+1) % n] == LIVE:
count += 1
if board[(row+1) % n][(col-1) % n] == LIVE:
count +=1
if board[(row+1) % n ][col % n] == LIVE:
count += 1
if board[(row+1) % n][(col+1) % n] == LIVE:
count += 1
print count
return count
surrounding(1,1)
def round():
board_copy = board
for row in range(n):
for col in range(n):
if surrounding(row,col) == 3:
board_copy[row][col] = LIVE
board_copy[row][col] = canvas.itemconfigure(rect[row][col],fill='red')
elif surrounding(row,col) > 3 or getNeighbors(row,col) < 2:
board_copy[row][col] = DEAD
board_copy[row][col] = canvas.itemconfigure(rect[row][col],fill='black')
board = board_copy
def start():
global alarm
alarm = window.after(500,round)
def stop():
window.after.cancel(alarm)
So I have a function to count how many surrounding squares around a certain square had the value LIVE (where LIVE = 1).
Originally, all squares were initialized to DEAD. The configuration file determines which squares are assigned the value LIVE. Made a configuration file so that the 8 squares surrounding the square at 1,1 would return a value of 8, but I am getting back a value of 0 consistently, no matter the configuration or the tested square.
UPDATE: Changed the counter to begin at 1 and the return value was 1. I'm guessing my function isn't actually counting (or reading the values of the surrounding squares on the board), but I can't see why it would not.
Also, for my step() function, I need a function that makes a copy of the board and then changes the fill on the copy depending on a count being performed on the original board. If a rectangle on the original board has 3 rectangles with the values of LIVE, then the rectangle with the same indices on the board copy will have a LIVE value and turn red. Same for DEAD, except for different case. At the end the copied board (with changes) replaces the original board so that when we run the function again, it will be based off the copy.
I'm assuming board_copy = board is sufficient in making the copy; what do I write so that on the Tkinter window, after the function is performed, the board_copy is what appears?
The format of the config file is
rownum colnum
rownum colnum
So the config file I used if I wanted to get 8 in countNeighbors function (if it worked) for the first round would be:
0 0
0 1
0 2
1 0
1 2
2 0
2 1
2 2
I'm assuming board_copy = board is sufficient in making the copy;
No, board_copy = board is not sufficient. This will just bind the name board_copy to the exact same list of list as board, i.e. everything you change in board_copy is also changed in board.
Instead, you could use the deepcopy function from the copy module:
import copy
board_copy = copy.deepcopy(board)
Or use a list-comprehension to create a deep copy of the nested list:
board_copy = [[x for x in row] for row in board]
The use of board = board_copy at the end of the method is okay, since here you just want to bind the new board to board. Note, however, that you will also need to add global board at the top of the method to access the globally defined board.
Also, note that you define both a function board (def board(...): and a global variable board (board = [...]). The latter will shadow the first, i.e. you will not be able to use the board function. You should rename the function to e.g. def draw_board(...).
There might be more problems with your code, but I can not execute it sinceit seems to be incomplete (where is start called?) and I do not know the content of 'filename'.
This question already has answers here:
Why does foo.append(bar) affect all elements in a list of lists?
(3 answers)
Closed 4 years ago.
I seem to have a problem with my maze generating program made in Python. I'm trying to randomly create a path that branches out at select points, with the points getting stored as it goes along. When the maze gets to a dead end, it will sort back through the visited points by testing the top value than popping that and going to the next one, until it gets to a spot where it isn't a dead end. However, when I try to append items to the list I'm using to save the spaces I've been to, something strange happens, I've never seen it before actually. Here's the code, and the best way to see it is to run it a through times until it goes all the way through. I haven't really found a way to counter the dead end problem, so if anyone could help me with that also, that would be great.
import random
width = 8
def check(x,y):
"""Figures out the directions that Gen can move while"""
if x-1 == -1:
maze[x][y][3] = 0
if x+1 == 8:
maze[x][y][1] = 0
if y+1 == 8:
maze[x][y][2] = 0
if y-1 == -1:
maze[x][y][0] = 0
if x + 1 in range(0,8) and visited[x+1][y] == False:
maze[x][y][1] = 2
if x - 1 in range(0,8) and visited[x-1][y] == False:
maze[x][y][3] = 2
if y + 1 in range(0,8) and visited[x][y+1] == False:
maze[x][y][2] = 2
if y - 1 in range(0,8) and visited[x][y-1] == False:
maze[x][y][0] = 2
def Gen(x,y):
visited[x][y] = True
past.append(current)
dirs = []
check(x,y)
print current
if maze[x][y][0] == 2:
dirs.append(0)
if maze[x][y][1] == 2:
dirs.append(1)
if maze[x][y][2] == 2:
dirs.append(2)
if maze[x][y][3] == 2:
dirs.append(3)
pos = random.choice(dirs)
print dirs
maze[x][y][pos] = 1
if pos == 0:
current[1] -= 1
if pos == 1:
current[0] += 1
if pos == 2:
current[1] += 1
if pos == 3:
current[0] -= 1
if maze[x][y][0] == 4:
maze[x][y][0] = 1
if maze[x][y][1] == 4:
maze[x][y][1] = 1
if maze[x][y][2] == 4:
maze[x][y][2] = 1
if maze[x][y][3] == 4:
maze[x][y][3] = 1
print maze[x][y]
print past, '\n'
#Build the initial values for the maze to be replaced later
maze = []
current = [0,0]
visited = []
past = []
#Generate empty 2d list with a value for each of the xy coordinates
for i in range(0,width):
maze.append([])
for q in range(0, width):
maze[i].append([])
for n in range(0, 4):
maze[i][q].append(4)
#Makes a list of falses for all the non visited places
for x in range(0, width):
visited.append([])
for y in range(0, width):
visited[x].append(False)
#Generates the walls
#for q in range(0, width):
# for i in range(0, width):
# check(q, i)
current = [0,0]
while current != [7,7]:
Gen(current[0], current[1])
print maze
As you can see, it starts at 0,0 and then figures out the possible paths to take. It randomly selects from those and sets the value for that side of the room in 0,0 to 1, which means a passage. 2 means wall and 0 means out of bounds. 4 is just a placeholder as all values should be filled by the time the maze is completely generated.
If anyone could help me, that would be great and very appreciated. Thanks in advance.
I believe the current list is simply copied multiple times into past. So you have multiple copies of the same list.
To fix: in the line past.append(current) (two lines below def Gen(x,y):), change it to past.append(current[:]).
The notation list[:] creates a copy of the list. Technically, you are creating a slice of the whole list.
By the way, a better solution would be to not use a global current variable :)
Yeah this is correct while List Comprehension in Python you need to append by strip other wise it will replace multiple times