How to correctly update the grid in falling sand simulation? - python

So, recently I started doing some python programming, and came across a video on Youtube in which guy showcases some of his simulations made in pygame (https://www.youtube.com/watch?v=M39R2A8kuh8).
I decided to do the easiest one, the Falling Sand Simulation. I implemented eveything correctly, but when it came to updating the grid I just couldn't do it right. In the end cells are positioned correctly at the bottom of screen, but they don't fall slowly, instead they just instantly teleport there. That's happening because when for loop comes across the cell it is being updated and falling down one row down, then loop comes across that same cell once more and same thing happens
I tried fixing it with second array which holds old grid and for some reason it didn't work.
Here's the code (please ignore my bad code, just a beginner xd):
import pygame
import random
from time import sleep
pygame.init()
WIDTH, HEIGHT = 800, 800
WIN = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Falling Sand Simulation")
BLACK = (0, 0, 0)
ORANGE = (158, 103, 32)
class Grid:
def __init__(self, width, height):
self.rows = int(width / 2)
self.columns = int(width / 2)
self.PreviousGrid = [[0 for i in range(self.columns)] for j in range(self.rows)]
self.CurrentGrid = [[0 for i in range(self.columns)] for j in range(self.rows)]
def add_cell(self, xpos, ypos):
xcell = int(xpos / 2)
ycell = int(ypos / 2)
self.CurrentGrid[xcell][ycell] = 1
def update_grid(self):
self.PreviousGrid = self.CurrentGrid
for i in range(self.rows):
if (i+1) != self.rows:
for j in range(self.columns):
if (j+1) != self.columns:
if self.PreviousGrid[i][j] == 0:
pass
else:
if self.PreviousGrid[i][j+1] == 0:
self.CurrentGrid[i][j] = 0
self.CurrentGrid[i][j+1] = 1
elif self.PreviousGrid[i-1][j+1] == 0 and self.PreviousGrid[i+1][j+1] == 0:
self.CurrentGrid[i][j] = 0
choice = random.randint(0, 1)
if choice == 0:
self.CurrentGrid[i-1][j+1] = 1
else:
self.CurrentGrid[i+1][j+1] = 1
elif self.PreviousGrid[i-1][j+1] == 0:
self.CurrentGrid[i][j] = 0
self.CurrentGrid[i-1][j+1] = 1
elif self.PreviousGrid[i+1][j+1] == 0:
self.CurrentGrid[i][j] = 0
self.CurrentGrid[i+1][j+1] = 1
def draw_grid(self, win):
for i in range(self.rows):
for j in range(self.columns):
if self.CurrentGrid[i][j] == 0:
pass
elif self.CurrentGrid[i][j] == 1:
pygame.draw.rect(win, ORANGE, pygame.Rect(int(i*2), int(j*2), 4, 4))
def main():
run = True
clock = pygame.time.Clock()
grid = Grid(WIDTH, HEIGHT)
update_rate = 0.05
countdownMS = update_rate
paused = False
while run:
clock.tick(30)
WIN.fill(BLACK)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
sec = clock.get_rawtime()/100;
countdownMS -= sec;
if countdownMS < 0.0:
grid.update_grid()
countdownMS = update_rate
grid.draw_grid(WIN)
if pygame.mouse.get_pressed()[0]:
xpos, ypos = event.pos
grid.add_cell(xpos, ypos)
pygame.display.update()
pygame.quit()
if __name__ == '__main__':
main()

You have to create a new empty grid in update_grid. Copy the bottom line of the old grid and fill the rest of the new grid depending on the previous grid:
class Grid:
# [...]
def update_grid(self):
self.PreviousGrid = self.CurrentGrid
# create a new and empty grid
self.CurrentGrid = [[0 for i in range(self.columns)] for j in range(self.rows)]
for i in range(self.rows):
self.CurrentGrid[i][self.columns-1] = self.PreviousGrid[i][self.columns-1]
# fill the new grid depending on the previous grid
for i in range(self.rows):
if i+1 < self.rows:
for j in range(self.columns):
if j+1 < self.columns:
if self.PreviousGrid[i][j] == 1:
if self.PreviousGrid[i][j+1] == 0:
self.CurrentGrid[i][j+1] = 1
elif self.PreviousGrid[i-1][j+1] == 0 and self.PreviousGrid[i+1][j+1] == 0:
self.CurrentGrid[i+random.choice([-1, 1])][j+1] = 1
elif self.PreviousGrid[i-1][j+1] == 0:
self.CurrentGrid[i-1][j+1] = 1
elif self.PreviousGrid[i+1][j+1] == 0:
self.CurrentGrid[i+1][j+1] = 1
else:
self.CurrentGrid[i][j] = 1

Related

I can't find a method to prevent my program slowing down as it loads more sprites python

I have created a simple simulation to show evolution. It works through a simuple window that contains many squares representing single-celled organisms. The screen looks like this:
The single-celled organisms (dubbed amoebae for conciseness) move around randomly. If they collide with another amoebae they produce an offspring. However, to prevent them reproducing infinitely I introduced an age measure. Amoebae must attain a certain age before they reproduce and once they do their age is reset to 1.
Now for the evolution part. As you can see, the amoebae are different colours. This represents the 'gene' that is passed down to offspring through reproduction (there is a chance of mutation controlled by a constant called maturingSpeed, which I set very high to increase the speed of evolution). It's called maturingSpeed and it controls the speed at which the amoebae age, which means that amoebae that have a higher maturingSpeed with reproduce faster and pass on their gene. In this way, they should gradually evolve through natural selection so all of the amoebae have a very high maturingSpeed. A high maturingSpeed translates to a brighter colour on the screen.
There is one other thing I should mention, which is the life countdown on each amoeba. It starts out at 10000 and ticks down by one each time the amoeba is updated. This is to gradually kill off the old amoebae, also increasing the rate of evolution and making it more lifelike.
My problem is that before the amoebae all evolve to get a high maturingSpeed (the highest I've had is around 65%), they become too numerous and the simulation starts slowing down as it struggles to load them all. I need a method to make the amoebae die off faster as more of them are produced. I have tried to cull them if they are above a certain number, or increase their countdown rate based on the number of amoebae however all of these methods cause them to eventually stop reproducing and die off for some reason. I have deleted these sections from my code now because they didn't work but I could add them again if needed.
My source code:
import pygame
import random
import time
import itertools
from pygame.locals import (
QUIT
)
pygame.init()
SCREEN_WIDTH = 500
SCREEN_HEIGHT = 500
screen = pygame.display.set_mode([500, 500])
amoebas = pygame.sprite.Group()
all_sprites = pygame.sprite.Group()
idList = []
mutationConstant = 254
class Amoeba(pygame.sprite.Sprite):
id_iter = itertools.count()
def __init__(self, maturingSpeed, x, y):
super(Amoeba, self).__init__()
self.id = 'amoeba' + str(next(Amoeba.id_iter))
idList.append(self.id)
self.surf = pygame.Surface((10,10))
if maturingSpeed <= 0:
maturingSpeed = 1
elif maturingSpeed >= 255:
maturingSpeed = 254
print(maturingSpeed)
self.surf.fill((maturingSpeed, 0, 0))
self.rect = self.surf.get_rect(
center=(
x,
y,
)
)
self.speed = 2
self.age = 1
self.maturingSpeed = int(maturingSpeed)
self.life = 9999
def update(self):
if self.rect.left <= 0:
direction = 1
elif self.rect.right >= SCREEN_WIDTH:
direction = 2
elif self.rect.top <= 0:
direction = 3
elif self.rect.bottom >= SCREEN_HEIGHT:
direction = 4
else:
direction = random.randint(1, 4)
if direction == 1:
self.rect.move_ip(self.speed, 0)
elif direction == 2:
self.rect.move_ip(-self.speed, 0)
elif direction == 3:
self.rect.move_ip(0, self.speed)
elif direction == 4:
self.rect.move_ip(0, -self.speed)
self.life = self.life - 1
if self.life <= 0:
self.kill()
modMaturingSpeed = self.maturingSpeed / 1240
self.age = self.age + (1 * modMaturingSpeed)
#classmethod
def collide(cls):
global collisionSuccess
collisionSuccess = False
global posList
posList = [[amoeba.rect.left, amoeba.rect.bottom] for amoeba in amoebas]
length = len(posList)
for i in range(length):
for amoeba in amoebas:
if amoeba.id == str(idList[i]):
ageOne = getattr(amoeba, 'age')
for h in range(i+1, length):
for amoeba in amoebas:
if amoeba.id == str(idList[h]):
ageTwo = getattr(amoeba, 'age')
OneX = int(posList[i][0])
OneY = int(posList[i][1])
TwoX = int(posList[h][0])
TwoY = int(posList[h][1])
if ageOne >= 100 and ageTwo >= 100:
if (OneX < TwoX + 10 and OneX + 10 > TwoX
and OneY < TwoY + 10 and 10 + OneY > TwoY):
for amoeba in amoebas:
if amoeba.id == str(idList[i]):
setattr(amoeba, 'age', 1)
pOMSinitial = int(getattr(amoeba, 'maturingSpeed'))
for amoeba in amoebas:
if amoeba.id == str(idList[h]):
setattr(amoeba, 'age', 1)
pTMSinitial = int(getattr(amoeba, 'maturingSpeed'))
locationX = OneX + random.randint(-10, 10)
locationY = OneY + random.randint(-10, 10)
if pOMSinitial >= pTMSinitial:
pOMSfinal = pOMSinitial + mutationConstant
pTMSfinal = pTMSinitial - mutationConstant
newMaturingSpeed = random.randint(pTMSfinal, pOMSfinal)
else:
pOMSfinal = pOMSinitial - mutationConstant
pTMSfinal = pTMSinitial + mutationConstant
newMaturingSpeed = random.randint(pOMSfinal, pTMSfinal)
collisionSuccess = True
return cls(newMaturingSpeed, locationX, locationY)
screen.fill((255, 255, 255))
for i in range(15):
amoebaname = Amoeba(random.randint(100, 150), random.randint(0, SCREEN_WIDTH), random.randint(0, SCREEN_HEIGHT))
amoebas.add(amoebaname)
all_sprites.add(amoebaname)
p = 0
while True:
ageArray = [amoeba.age for amoeba in amoebas]
if p == 1000:
print(amoebas)
five = 0
four = 0
three = 0
two = 0
one = 0
for amoeba in amoebas:
if amoeba.maturingSpeed >= 200:
five = five + 1
elif amoeba.maturingSpeed >=150:
four = four + 1
elif amoeba.maturingSpeed >= 100:
three = three + 1
elif amoeba.maturingSpeed >= 50:
two = two + 1
else:
one = one + 1
total = one + two + three + four + five
DivFive = five / total
DivFour = four / total
DivThree = three / total
DivTwo = two / total
DivOne = one / total
print(DivFive, DivFour, DivThree, DivTwo, DivOne)
p = 0
else:
p = p + 1
time.sleep(0.0000001)
screen.fill((255, 255, 255))
for event in pygame.event.get():
if event.type == QUIT:
break
amoebas.update()
amoebaname = Amoeba.collide()
if collisionSuccess == True:
amoebas.add(amoebaname)
all_sprites.add(amoebaname)
for entity in all_sprites:
screen.blit(entity.surf, entity.rect)
pygame.display.flip()
pygame.quit()
Too many nested loops and unneeded data structures. I did some cleanup and it's faster now. And it seems that the mutation constant was far to high. I changed the value from 254 to 25.
import pygame
import random
import time
import itertools
from pygame.locals import (
QUIT
)
SCREEN_WIDTH = 500
SCREEN_HEIGHT = 500
MUTATION_CONSTANT = 25
pygame.init()
screen = pygame.display.set_mode([SCREEN_WIDTH, SCREEN_HEIGHT])
amoebas = pygame.sprite.Group()
class Amoeba(pygame.sprite.Sprite):
id_iter = itertools.count()
def __init__(self, maturing_speed, x, y):
super().__init__()
self.id = 'amoeba' + str(next(Amoeba.id_iter))
self.surf = pygame.Surface((10, 10))
self.maturing_speed = min(max(maturing_speed, 1), 254)
self.surf.fill((self.maturing_speed, 0, 0))
self.rect = self.surf.get_rect(center=(x, y,))
self.speed = 2
self.age = 1
self.life = 9999
def update(self):
if self.rect.left <= 0:
direction = 1
elif self.rect.right >= SCREEN_WIDTH:
direction = 2
elif self.rect.top <= 0:
direction = 3
elif self.rect.bottom >= SCREEN_HEIGHT:
direction = 4
else:
direction = random.randint(1, 4)
if direction == 1:
self.rect.move_ip(self.speed, 0)
elif direction == 2:
self.rect.move_ip(-self.speed, 0)
elif direction == 3:
self.rect.move_ip(0, self.speed)
elif direction == 4:
self.rect.move_ip(0, -self.speed)
self.life = self.life - 1
if self.life <= 0:
self.kill()
self.age = self.age + (1 * self.maturing_speed / 1240)
#classmethod
def collide(cls):
for amoeba_1, amoeba_2 in itertools.combinations(amoebas, 2):
if amoeba_1.age >= 100 and amoeba_2.age >= 100 and (
pygame.sprite.collide_rect(amoeba_1, amoeba_2)
):
amoeba_1.age = 1
amoeba_2.age = 1
location_x = amoeba_1.rect.left + random.randint(-10, 10)
location_y = amoeba_1.rect.bottom + random.randint(-10, 10)
speed_low = min(amoeba_1.maturing_speed, amoeba_2.maturing_speed) - MUTATION_CONSTANT
speed_high = max(amoeba_1.maturing_speed, amoeba_2.maturing_speed) + MUTATION_CONSTANT
new_maturing_speed = random.randint(speed_low, speed_high)
return cls(new_maturing_speed, location_x, location_y)
return None
def main():
screen.fill((255, 255, 255))
for i in range(25):
amoeba = Amoeba(random.randint(100, 150), random.randint(0, SCREEN_WIDTH), random.randint(0, SCREEN_HEIGHT))
amoebas.add(amoeba)
step_counter = 0
while True:
step_counter += 1
if step_counter % 100 == 0:
print(step_counter, amoebas)
five = 0
four = 0
three = 0
two = 0
one = 0
for amoeba in amoebas:
if amoeba.maturing_speed >= 200:
five = five + 1
elif amoeba.maturing_speed >= 150:
four = four + 1
elif amoeba.maturing_speed >= 100:
three = three + 1
elif amoeba.maturing_speed >= 50:
two = two + 1
else:
one = one + 1
total = one + two + three + four + five
print(f'{five/total:.4f} {four/total:.4f} {three/total:.4f} {two/total:.4f} {one/total:.4f}')
time.sleep(0.0000001)
screen.fill((255, 255, 255))
for event in pygame.event.get():
if event.type == QUIT:
break
amoebas.update()
amoeba = Amoeba.collide()
if amoeba:
amoebas.add(amoeba)
for amoeba in amoebas:
screen.blit(amoeba.surf, amoeba.rect)
pygame.display.flip()
pygame.quit()
if __name__ == '__main__':
main()

Conway's Game of Life Python / PyGame printing error [closed]

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
This question does not appear to be about programming within the scope defined in the help center.
Closed 2 years ago.
Improve this question
I'm new to python and PyGame and wanted to get some experience by doing what i thought would be a simple project. I can't tell if my error is in my game logic or my PyGame printing. I created two function, one that fills the grid with random values and one that fills the grid with a "Blinker". The program runs without error, however, the rules of the game are not followed. For example, When the "blinker" is set, the program's second frame clears the screen instead of rotating the "blinker".
Any help diagnosing this problem would be appreciated!
import pygame
import random
pygame.init()
# Colors
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
# Sizes
size = (600, 600)
width = 20
height = 20
margin = 1
x_size = 600 / width
y_size = 600 / height
def init_grid():
return [[0 for x in range(x_size)] for y in range(y_size)]
def make_spinner(grind):
grid[0][0] = 1
grid[10][10] = 1
grid[10][11] = 1
grid[10][12] = 1
def random_grid(grid):
for x in range(x_size):
for y in range(y_size):
grid[x][y] = random.randint(0, 1)
def print_grid(screen, grid):
for x in range(x_size):
for y in range(y_size):
if grid[x][y] == 1:
pygame.draw.rect(
screen, BLACK, (x * width, y * height, width, height))
else:
pygame.draw.rect(
screen, BLACK, (x * width, y * height, width, height), margin)
def count_neighbours(grid, x, y):
count = 0
for i in range(-1, 1):
for j in range(-1, 1):
count += grid[x + i][y + j]
return count - grid[x][y]
def update_grid(grid):
next_grid = init_grid()
for x in range(x_size):
for y in range(y_size):
if x == 0 or x == x_size - 1 or y == 0 or y == y_size - 1:
next_grid[x][y] = 0
else:
count = count_neighbours(grid, x, y)
value = grid[x][y]
if value == 1 and (count == 2 or count == 3):
next_grid[x][y] = 1
elif value == 0 and count == 3:
next_grid[x][y] = 1
else:
next_grid[x][y] = 0
return next_grid
# Initialise game engine
screen = pygame.display.set_mode(size)
pygame.display.set_caption("The Game of Life")
running = True
clock = pygame.time.Clock()
grid = init_grid()
# random_grid(grid)
make_spinner(grid)
# Game loop
while running:
# Check for exit
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
screen.fill(WHITE)
print_grid(screen, grid)
next_grid = update_grid(grid)
pygame.display.update()
grid = next_grid
clock.tick(2)
pygame.quit()
Your count_neighbors function doesn't iterate over the right cells. range(-1,1) iterates over {-1,0} not {-1,0,1}.
Instead, use:
def count_neighbours(grid, x, y):
count = 0
for i in range(-1,2):
for j in range(-1,2):
count += grid[x + i][y + j]
return count - grid[x][y]

Conway's game of life list index error

So I'm trying to make Conway's game of life in Python/pygame, and the first iteration of making the new grid works, but the second wont because of a list index out of range error. I have been trying to figure out what's wrong, but the list index shouldn't be out of range. This is my code, the mistake is supposedly in changevalue() but i suspect it isn't, since the first iteration works:
import pygame
import random
width = 400
height = 400
blocksize = 10
white = (255, 255, 255)
black = (0, 0, 0)
visual = pygame.display.set_mode((width, height))
clock = pygame.time.Clock()
IsOn = True
grid = []
templist = []
tempgrid = []
class square(object):
def __init__(self, x, y, alive):
self.x = x
self.y = y
self.alive = alive
for y in range(height/blocksize):
templist = []
for x in range(width/blocksize):
templist.append(square(x, y, random.choice([True, False, False, False])))
grid.append(templist)
def changevalue(cx, cy, cgrid):
neighbours = []
for dy in range(3):
ddy = dy - 1
for dx in range(3):
ddx = dx - 1
if not (dx - 1 == 0 and dy - 1 == 0):
#print cgrid[(cy + ddy)%len(cgrid)][(cx + ddx)%len(cgrid[y])].alive
#NO ERRORS
#print len(cgrid) > (cy + ddy)%len(cgrid), len(cgrid[y]) > (cx + ddx)%len(cgrid[cy])
#NO ERRORS
neighbours.append(cgrid[(cy + ddy)%len(cgrid)][(cx + ddx)%len(cgrid[cy])].alive)
return len(filter(lambda p: p == True, neighbours))
while IsOn:
for event in pygame.event.get():
if event.type == pygame.QUIT:
IsOn = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_c:
proceed = True
tempgrid = []
for times in range(len(grid)):
tempgrid.append([])
for ty in range(len(grid)):
for tx in range(len(grid[ty])):
if changevalue(tx, ty, grid) < 2 and grid[ty][tx].alive == True:
tempgrid[ty].append(square(tx, ty, False))
elif changevalue(tx, ty, grid) > 3 and grid[ty][tx].alive == True:
tempgrid[ty].append(square(tx, ty, False))
elif changevalue(tx, ty, grid) == 3 and grid[ty][tx].alive == False:
tempgrid[ty].append(square(tx, ty, True))
grid = list(tempgrid)
visual.fill(white)
for y in range(len(grid)):
for x in range(len(grid[y])):
if grid[y][x].alive == True:
pygame.draw.rect(visual, black, (grid[y][x].x*blocksize, grid[y][x].y*blocksize, blocksize, blocksize))
pygame.display.update()
clock.tick(2)
pygame.quit()
quit()
Thanks for your help!
You don't copy square which doesn't change value - so new rows have different length - and later you have problem with index
You need something like this
if changevalue ...:
...
elif changevalue ...:
...
elif changevalue ...:
...
else:
# copy other elements
tempgrid[ty].append(grid[ty][tx])

Conways Game of Life in PyGame

So I read about Conways Game of Life and tried to implement it with PyGame.
I tried to make it object-oriented. The way it works is that I have a list of cell-instances, which then check how many neighbours they have and then either stay alive or die, based on their neighbours. Then the process repeats itself.
The problem is that when I test it with some known starting patterns (e.g. in the code below (CELL_MAP)) it does not work the way it should.
I read the code over and over and I dont really get what I'm missing here. I posted the whole code below as I dont know where my mistake is, but I'd highly appreciate if someone would point me in the right direction.
Thanks in advance!
import pygame
class Cell:
def __init__(self, live, xcor, ycor):
self.alive = live
self.x = xcor
self.y = ycor
self.neighbours = 0
def checkNeighbours(self, cellList):
for cell in cellList:
#left
if cell.x == self.x-1 and cell.y == self.y and cell.alive == True:
self.neighbours += 1
#right
elif cell.x == self.x+1 and cell.y == self.y and cell.alive == True:
self.neighbours += 1
#upleft
elif cell.x == self.x-1 and cell.y == self.y-1 and cell.alive == True:
self.neighbours += 1
#up
elif cell.x == self.x and cell.y == self.y-1 and cell.alive == True:
self.neighbours += 1
#upright
elif cell.x == self.x+1 and cell.y == self.y-1 and cell.alive == True:
self.neighbours += 1
#downleft
elif cell.x == self.x-1 and cell.y == self.y+1 and cell.alive == True:
self.neighbours += 1
#down
elif cell.x == self.x and cell.y == self.y+1 and cell.alive == True:
self.neighbours += 1
#downright
elif cell.x == self.x+1 and cell.y == self.y+1 and cell.alive == True:
self.neighbours += 1
def breed(self):
if self.alive == False and self.neighbours == 3:
#dead cell ressurects if neighbours equals 3
self.alive = True
elif self.alive and self.neighbours < 2:
#die from loneliness
self.alive = False
elif self.alive and self.neighbours == 2:
#stay alive
pass
elif self.alive and self.neighbours == 3:
#stay alive
pass
elif self.alive and self.neighbours > 3:
#die from overpopulation
self.alive = False
def render(self, display):
if self.alive:
pygame.draw.rect(display, (0,0,0), [self.x*10, self.y*10, 10, 10])
elif self.alive == False:
pygame.draw.rect(display, (0,0,255), [self.x*10, self.y*10, 10, 10])
WID = 33
HEI = 20
CELL_MAP = [[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]]
CELL_LIST = []
xc = -1
yc = -1
for yList in CELL_MAP:
yc += 1
for x in yList:
xc += 1
if x == 0:
#create dead cell
newCell = Cell(False, xc, yc)
CELL_LIST.append(newCell)
elif x == 1:
#create alive cell
newCell = Cell(True, xc, yc)
CELL_LIST.append(newCell)
xc = -1
#pygame init
pygame.init()
(width, height) = (WID*10, HEI*10)
pygame.display.set_caption('Game of Life')
screen = pygame.display.set_mode((width, height))
#game loop
def gameLoop():
gameLoop = True
while gameLoop:
#check for exit
for event in pygame.event.get():
if event.type == pygame.QUIT:
gameLoop = False
pygame.quit()
#render cells
for cell in CELL_LIST:
cell.render(screen)
#check neighbours
for cell in CELL_LIST:
cell.checkNeighbours(CELL_LIST)
pygame.display.flip()
#breed
for cell in CELL_LIST:
cell.breed()
pygame.time.wait(5)
quit()
if __name__ == "__main__":
gameLoop()
I don't have pygame installed, so I can't run your code. However, the bug that's causing your error is that you don't reset a cell's neighbour count to zero after you've determined whether it'll be alive or dead in the next generation. So in each generation each cell's new neighbour count gets added to the previous accumulated neighbour count. You should probably do that resetting in the .breed method.
Here's a more compact version of that method:
def breed(self):
self.alive = self.neighbours == 3 or self.alive and self.neighbours == 2
self.neighbours = 0
I have a few more comments about your code.
Your checkNeighbours method is extremely inefficient: for each cell, it scans the entire grid looking for a cell's neighbours! A simple alternative is to store your cells in a 2D list so you can quickly locate a cell's neighbours.
Here's a more compact way to build your CELL_LIST than what your code currently does:
CELL_LIST = []
for y, row in enumerate(CELL_MAP):
for x, v in enumerate(row):
CELL_LIST.append(Cell(v == 1, x, y))
Here's the same thing as a list comprehension:
CELL_LIST = [Cell(bool(v), x, y)
for y, row in enumerate(CELL_MAP)
for x, v in enumerate(row)
]
But as I said earlier, it's probably a good idea to make CELL_LIST a 2D list:
cell_list = [[Cell(bool(v), x, y) for x, v in enumerate(row)]
for y, row in enumerate(CELL_MAP)]
Your CELL_MAP isn't a convenient way to put Life patterns into your program, but I guess it's ok for testing purposes. Take a look at this answer I wrote earlier this month for an alternative.
Eventually, you should give your program the ability to read the common RLE format used by many Life programs.
You may also like to check out this moderately efficient version I wrote that uses Numpy: numpy_life.py. Like the other version I linked it displays the output in the Linux terminal, but both versions should be easy to adapt to pygame or another GUI framework.

Pygame 2d tile scrolling edges don't load

Im trying to make a 2d game in pygame that kinda works like pokemon. Ive gotten really stuck on a problem now that I dont know how to solve.
When I move my character I scroll the map instead so the player stays centered. I've created an "animation" by offsetting the distance by 2 pixels at a time instead of moving the full tile size so I get smooth movemen. The problem I have is that when I move, the screen doesn't load the new tiles in the edges that i'm moving towards so the edges end up a white space until i've completed the full animation. I'll link my code and hopefully someone can help me :)
TILESIZE = 32
MAP_WIDTH = 25
MAP_HEIGHT = 25
class Player(pygame.sprite.Sprite):
def __init__(self, color, width, height):
# Call the parent class (Sprite) constructor
super().__init__()
self.name = "Player"
self.width = width
self.height = height
self.image = pygame.Surface([width, height])
self.image.fill(color)
self.rect = self.image.get_rect()
self.rect.x = int(TILESIZE * (MAP_WIDTH / 2)) - TILESIZE / 2
self.rect.y = int(TILESIZE * (MAP_HEIGHT / 2)) - TILESIZE / 2
class World:
def __init__(self):
self.shiftX = 0
self.shiftY = 0
self.tile_map = [ [DIRT for w in range(MAP_WIDTH)] for h in range(MAP_HEIGHT)]
for row in range(MAP_HEIGHT):
for column in range(MAP_WIDTH):
try:
if real_map[row + self.shiftY][column + self.shiftX] == 0:
tile = DIRT
elif real_map[row + self.shiftY][column + self.shiftX] == 1:
tile = GRASS
elif real_map[row + self.shiftY][column + self.shiftX] == 2:
tile = WATER
else:
tile = DIRT
self.tile_map[row][column] = tile
except:
self.tile_map[row][column] = WATER
def shiftWorld(self):
for row in range(MAP_HEIGHT):
for column in range(MAP_WIDTH):
try:
if real_map[row + self.shiftY][column + self.shiftX] == 0:
tile = DIRT
elif real_map[row + self.shiftY][column + self.shiftX] == 1:
tile = GRASS
elif real_map[row + self.shiftY][column + self.shiftX] == 2:
tile = WATER
else:
tile = DIRT
self.tile_map[row][column] = tile
except:
self.tile_map[row][column] = WATER
def okToMove(self, key):
if key[K_w]:
if self.tile_map[int(MAP_WIDTH/2 - 1)][int(MAP_HEIGHT/2)] != 2:
return True
elif key[K_s]:
if self.tile_map[int(MAP_WIDTH/2 + 1)][int(MAP_HEIGHT/2)] != 2:
return True
elif key[K_a]:
if self.tile_map[int(MAP_WIDTH/2)][int(MAP_HEIGHT/2) - 1] != 2:
return True
elif key[K_d]:
if self.tile_map[int(MAP_WIDTH/2)][int(MAP_HEIGHT/2) + 1] != 2:
return True
def start_game():
pygame.init()
clock = pygame.time.Clock()
#HÄR KAN VI MÅLA UPP MER
#SCREEN = pygame.display.set_mode((MAP_WIDTH*TILESIZE, MAP_HEIGHT*TILESIZE))
world = World()
SCREEN = pygame.display.set_mode((TILESIZE * (MAP_WIDTH-2), TILESIZE * (MAP_HEIGHT-4)))
running = True
player = Player(BLACK, 32, 32)
sprites = pygame.sprite.Group()
sprites.add(player)
movement = 0
offsetY = 0
offsetX = 0
animation_north = False
animation_south = False
animation_west = False
animation_east = False
while running:
for event in pygame.event.get():
if event.type==QUIT:
pygame.quit()
sys.exit()
elif event.type == pygame.KEYDOWN:
#Get keyinput and do whatever needs to be done
key = pygame.key.get_pressed()
if key[K_ESCAPE]:
pygame.quit()
sys.exit()
if animation_east or animation_north or animation_south or animation_west:
pass
else:
if key[K_w]:
okToMove = world.okToMove(key)
if okToMove == True:
animation_north = True
else:
pass
elif key[K_a]:
okToMove = world.okToMove(key)
if okToMove == True:
animation_west = True
elif key[K_s]:
okToMove = world.okToMove(key)
if okToMove == True:
animation_south = True
elif key[K_d]:
okToMove = world.okToMove(key)
if okToMove == True:
animation_east = True
if animation_north == True:
if movement == 32:
movement = 0
world.shiftY -= 1
world.shiftWorld()
offsetY = 0
animation_north = False
else:
offsetY += 4
movement += 4
if animation_south == True:
if movement == 32:
movement = 0
world.shiftY += 1
world.shiftWorld()
offsetY = 0
animation_south = False
intY = 0
else:
offsetY -= 4
movement += 4
if animation_west == True:
if movement == 32:
movement = 0
world.shiftX -= 1
world.shiftWorld()
offsetX = 0
animation_west = False
else:
offsetX += 4
movement += 4
if animation_east == True:
if movement == 32:
world.shiftX += 1
world.shiftWorld()
movement = 0
offsetX = 0
animation_east = False
else:
offsetX -= 4
movement += 4
SCREEN.fill(WHITE)
for row in range(MAP_HEIGHT):
for column in range(MAP_WIDTH):
SCREEN.blit(textures[world.tile_map[row][column]], (column*TILESIZE + offsetX, row*TILESIZE + offsetY))
sprites.draw(SCREEN)
pygame.display.update()
pygame.display.flip()
clock.tick(60)
start_game()
I am writing a similar game and I will share my logic with you.
my blocks are 32x32 so each block is 32 pixesl.
The outer border is the sprites screen and the inner square is the monitor.
You always have one sprite extra on all sides of the screen. now if you count the pixel movement on any side of the screen it's easy for you to keep track of when you need to draw the next row or column of sprites but not that they are always DRAWN OFF SCREEN. if my pixel movement is -8 (left movement) I draw a column of sprites on the right side just on the border os the screen but OUTSIDE the visible area. Same goes for the other side.
Here is some code from my program. This is the sprite adding code.
def add_sprites(self):
"""sprites are added to the group which appear on screen right. the column number is the value in ColDrawn. We selct columns from the list according to this value. Once the end of the column list is reached we start again from the first one. We cycle thru the list depending on the NumCycle[0] value."""
if self.ColDrawn < self.Columns_in_Dungeon - 1:
self.ColDrawn += 1
else: # all the columns drawn so increment the flag
self.ColDrawn = 0
self.NumCycle[1] += 1
if self.NumCycle[1] >= self.NumCycle[0]: # if the flag is equal to the number of cycles the screen is scrolled then set numcycle[2] to True
self.NumCycle[2] = True
else: # screen can be scrolled
spritecol = self.all_wall_sprite_columns_list[self.ColDrawn]
self.wallspritegroup.add(spritecol) # add column of sprites to the sprite group
return
and here is the sprite removing code.
def remove_sprites(self):
"""sprites are removed from the group as they exit from screen left."""
for sprt in self.wallspritegroup: # remove_basic sprites that move_basic off screen on left
if sprt.rect.x <= -48:
sprt.rect.x = self.screenw # reset the x position and
sprt.kill()
#spritegrp.remove_basic(sprt) # remove_basic the sprite from the sprite group
return
The code is quite easy to follow as I have commented them. Hope this helps.

Categories