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.