Неожиданные результаты в "Игре жизни" Конвея

Я пытался написать свою собственную версию «Игры жизни» Конвея в качестве практики для Python с использованием Pygame. Сначала я написал функции для инициализации игрового поля, а затем для расчета следующего поколения. Я проверил его функциональность с помощью консоли, чтобы распечатать результаты и убедиться, что они вернули ожидаемые результаты (всего 2 поколения вручную на сетке 5x5).

Важное примечание о том, как я вычисляю соседей ... Вместо того, чтобы выполнять цикл for по всему массиву и делать циклы для подсчета для каждой ячейки, я реализовал массив, который содержит счетчики соседей. Вносить изменения только при изменении статуса ячеек. Это означает, что я не трачу время на вычисление соседей для ячеек, которые не изменились.

Когда пришло время использовать Pygame для отображения массива с прямоугольниками, я написал следующую программу. Сначала я рисовал экран, заполняя весь экран белым, а затем рисовал живые ячейки черным цветом (это можно сделать, закомментировав оператор else в update ()). Я ожидал, что это будет работать как обычно, но когда я запустил программу, все, что у меня получилось, это заливка экрана черным цветом.

Я был озадачен результатом, поэтому я нарисовал белые прямоугольники для незаселенных ячеек (используя оператор else. И получил более красивый результат, но вместо того, чтобы в конечном итоге все ячейки умирали, они в конечном итоге умножились по всему экрану. Это противоположно тому, что Я ожидал, так как ожидал, что в конечном итоге она стабилизируется.

Кто-нибудь знает, что я делаю не так? Я знаю, что это не лучший способ написания этой программы, я приветствую комментарии о том, как я могу его улучшить.


  • RETURN = запустить симуляцию
  • 'R' = рандомизировать
  • 'T' = отметьте одно поколение
  • 'C' = чистое игровое поле
  • 'N' = отображать карту соседей
import pygame
from pygame.locals import *
import numpy as np
from random import *
import copy

fieldSize = [100,50]
cellSize = 10  # size of >10 is recommended to see neighbor count
windowSize = [fieldSize[0]*cellSize, fieldSize[1]*cellSize]

# calculate the last cell in each axis so it is not done repeatedly
lastCell = [(fieldSize[0]-1), (fieldSize[1]-1)]

dX = float(windowSize[0])/float(fieldSize[0])
dY = float(windowSize[1])/float(fieldSize[1])

colorAlive = [0,125,0]
colorDead = [0, 0, 0]

# todo list
# 1. make cLife take in the field size
# 2. convert random functions to numpy.random.randint

class cLife():
    def randomize(self):
        self.neighbors = np.zeros(fieldSize)
        # fill in the game field with random numbers
        for x in range(fieldSize[0]):
            for y in range(fieldSize[1]):
                if(randint(0,99)<20):
                    self.gameField[x][y] = 1
                    self.updateNeighbors([x,y], True)
                else:
                    self.gameField[x][y] = 0

    def displayNeighbors(self, surface):
        self.drawField(surface)
        for x in range(fieldSize[0]):
            for y in range(fieldSize[1]):
                neighborCount=font.render(str(int(self.neighbors[x][y])), 1,(200,200,200))
                surface.blit(neighborCount, (x*dX+dX/3, y*dY+dY/3.5))
        pygame.display.flip()

    # This is the function to update the neighbor map, the game field is torroidal so the complexity is greatly
    # increased. I have handcoded each instruction to avoid countless if statements and for loops.
    # Hopefully, this has drastically improved the performance. Using this method also allows me to avoid calculating
    # the neighbor map for every single cell because the neighbor map is updated only for the cells affected by a change.
    def updateNeighbors(self, pos, status):
        if(status == True):
            change = 1
        else:
            change = -1

        # testing for the cells in the center of the field (most cells are in the center so this is first)
        # cells are filled in starting on the top-left corner going clockwise
        if((pos[0]>0 and pos[0]<lastCell[0])and(pos[1]>0 and pos[1]<lastCell[1])):
            self.neighbors[pos[0]-1][pos[1]-1] += change
            self.neighbors[pos[0]][pos[1]-1] += change
            self.neighbors[pos[0]+1][pos[1]-1] += change
            self.neighbors[pos[0]+1][pos[1]] += change
            self.neighbors[pos[0]+1][pos[1]+1] += change
            self.neighbors[pos[0]][pos[1]+1] += change
            self.neighbors[pos[0]-1][pos[1]+1] += change
            self.neighbors[pos[0]-1][pos[1]] += change

        elif(pos[0] == 0): # left edge
            if(pos[1] == 0): # top left corner
                self.neighbors[lastCell[0]][lastCell[1]] += change
                self.neighbors[0][lastCell[1]] += change
                self.neighbors[1][lastCell[1]] += change
                self.neighbors[1][0] += change
                self.neighbors[1][1] += change
                self.neighbors[0][1] += change
                self.neighbors[lastCell[0]][1] += change
                self.neighbors[lastCell[0]][0] += change
            elif(pos[1] == lastCell[1]): # bottom left corner
                self.neighbors[lastCell[0]][pos[1]-1] += change
                self.neighbors[0][pos[1]-1] += change
                self.neighbors[1][pos[1]-1] += change
                self.neighbors[1][pos[1]] += change
                self.neighbors[1][0] += change
                self.neighbors[0][0] += change
                self.neighbors[lastCell[0]][0] += change
                self.neighbors[lastCell[0]][pos[1]] += change
            else: # everything else
                self.neighbors[lastCell[0]][pos[1]-1] += change
                self.neighbors[0][pos[1]-1] += change
                self.neighbors[1][pos[1]-1] += change
                self.neighbors[1][pos[1]] += change
                self.neighbors[1][pos[1]+1] += change
                self.neighbors[0][pos[1]+1] += change
                self.neighbors[lastCell[0]][pos[1]+1] += change
                self.neighbors[lastCell[0]][pos[1]] += change

        elif(pos[0] == lastCell[0]): # right edge
            if(pos[1] == 0): # top right corner
                self.neighbors[pos[0]-1][lastCell[1]] += change
                self.neighbors[pos[0]][lastCell[1]] += change
                self.neighbors[0][lastCell[1]] += change
                self.neighbors[0][0] += change
                self.neighbors[0][1] += change
                self.neighbors[pos[0]][1] += change
                self.neighbors[pos[0]-1][1] += change
                self.neighbors[pos[0]-1][0] += change
            elif(pos[1] == lastCell[1]): # bottom right corner
                self.neighbors[pos[0]-1][pos[1]-1] += change
                self.neighbors[pos[0]][pos[1]-1] += change
                self.neighbors[0][pos[1]-1] += change
                self.neighbors[0][pos[1]] += change
                self.neighbors[0][0] += change
                self.neighbors[pos[0]][0] += change
                self.neighbors[pos[0]-1][0] += change
                self.neighbors[pos[0]-1][pos[1]] += change
            else: # everything else
                self.neighbors[pos[0]-1][pos[1]-1] += change
                self.neighbors[pos[0]][pos[1]-1] += change
                self.neighbors[0][pos[1]-1] += change
                self.neighbors[0][pos[1]] += change
                self.neighbors[0][pos[1]+1] += change
                self.neighbors[pos[0]][pos[1]+1] += change
                self.neighbors[pos[0]-1][pos[1]+1] += change
                self.neighbors[pos[0]-1][pos[1]] += change

        elif(pos[1] == 0): # top edge, corners already taken care of
            self.neighbors[pos[0]-1][lastCell[1]] += change
            self.neighbors[pos[0]][lastCell[1]] += change
            self.neighbors[pos[0]+1][lastCell[1]] += change
            self.neighbors[pos[0]+1][0] += change
            self.neighbors[pos[0]+1][1] += change
            self.neighbors[pos[0]][1] += change
            self.neighbors[pos[0]-1][1] += change
            self.neighbors[pos[0]-1][0] += change

        elif(pos[1] == lastCell[1]): # bottom edge, corners already taken care of
            self.neighbors[pos[0]-1][pos[1]-1] += change
            self.neighbors[pos[0]][pos[1]-1] += change
            self.neighbors[pos[0]+1][pos[1]-1] += change
            self.neighbors[pos[0]+1][pos[1]] += change
            self.neighbors[pos[0]+1][0] += change
            self.neighbors[pos[0]][0] += change
            self.neighbors[pos[0]-1][0] += change
            self.neighbors[pos[0]-1][pos[1]] += change

    def nextGeneration(self):
        # copy the neighbor map, because changes will be made during the update
        self.neighborsOld = copy.deepcopy(self.neighbors)

        for x in range(fieldSize[0]):
            for y in range(fieldSize[1]):
                # Any live cell with fewer than two live neighbours dies, as if caused by under-population.
                if(self.gameField[x][y] == 1 and self.neighborsOld[x][y] < 2):
                    self.gameField[x][y] = 0
                    self.updateNeighbors([x,y], False)
                # Any live cell with more than three live neighbours dies, as if by overcrowding.
                elif(self.gameField[x][y] == 1 and self.neighborsOld[x][y] >3):
                    self.gameField[x][y] = 0
                    self.updateNeighbors([x,y], False)
                # Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.
                elif(self.gameField[x][y] == 0 and self.neighborsOld[x][y] == 3):
                    self.gameField[x][y] = 1
                    self.updateNeighbors([x,y], True)

    def drawField(self, surface):
        surface.fill(colorDead)

        # loop through and draw each live cell
        for x in range(fieldSize[0]):
            for y in range(fieldSize[1]):
                if(self.gameField[x][y] == 1):
                    pygame.draw.rect(surface, colorAlive, [dX*x, dY*y, dX, dY])

        pygame.display.flip()

    def __init__(self):
        # initialize the game field and neighbor map with zeros
        self.gameField = np.zeros(fieldSize)
        self.neighbors = np.zeros(fieldSize)


# begining of the program
game = cLife()

pygame.init()
surface = pygame.display.set_mode(windowSize)
pygame.display.set_caption("Conway\'s Game of Life")
clock = pygame.time.Clock()
pygame.font.init()
font=pygame.font.Font(None,10)

surface.fill(colorDead)
game.randomize()
game.drawField(surface)
pygame.display.flip()

running = False

while True:
    #clock.tick(60)

    # handling events
    for event in pygame.event.get():
        if(event.type == pygame.MOUSEBUTTONDOWN):
            mousePos = pygame.mouse.get_pos()
            x = int(mousePos[0]/dX)
            y = int(mousePos[1]/dY)

            if(game.gameField[x][y] == 0):
                game.gameField[x][y] = 1
                game.updateNeighbors([x, y], True)
                game.drawField(surface)
            else:
                game.gameField[x][y] = 0
                game.updateNeighbors([x, y], False)
                game.drawField(surface)

        elif(event.type == pygame.QUIT):
            pygame.quit()
        elif(event.type == pygame.KEYDOWN):
            # return key starts and stops the simulation
            if(event.key == pygame.K_RETURN):
                if(running == False):
                    running = True
                else:
                    running = False
            # 't' key ticks the simulation forward one generation
            elif(event.key == pygame.K_t and running == False):
                game.nextGeneration()
                game.drawField(surface)
            # 'r' randomizes the playfield
            elif(event.key == pygame.K_r):
                game.randomize()
                game.drawField(surface)
            # 'c' clears the game field
            elif(event.key == pygame.K_c):
                running = False
                game.gameField = np.zeros(fieldSize)
                game.neighbors = np.zeros(fieldSize)
                game.drawField(surface)
            # 'n' displays the neighbor map
            elif(event.key == pygame.K_n):
                game.displayNeighbors(surface)

    if(running == True):
        game.nextGeneration()
        game.drawField(surface)

person user2388331    schedule 03.02.2014    source источник
comment
вы пытались сравнить свой результат с другой реализацией?   -  person njzk2    schedule 03.02.2014
comment
Ближе всего к этому я подошел к тому, что я видел в своей предыдущей игре жизни, которую я написал на C ++. Я думал, что, возможно, у меня были неправильные правила, но я проверил это со статьей в Википедии.   -  person user2388331    schedule 03.02.2014


Ответы (1)


self.neighborsOld = self.neighbors не копирует карту, а только указывает на нее.

Видеть :

a = [[1,2],[3,4]]
b = a
b[0][0] = 9
>>> a
[[9, 2], [3, 4]]

Вам нужно либо сделать копию (a[:]) для каждой строки в a, либо использовать модуль copy и использовать deepcopy:

b = [x[:] for x in a]

or

import copy
b = copy.deepcopy(a)

В любом случае это приводит к

b[0][0] = 9
>>> a
[[1, 2], [3, 4]]
person njzk2    schedule 03.02.2014
comment
Спасибо! Если бы я закомментировал оператор else в функции update (), экран стал бы в основном черным. Я ожидал, что это сработает, потому что я закрашиваю экран белым, прежде чем рисовать живые клетки черным. Вы хоть представляете, что я делаю не так? - person user2388331; 03.02.2014
comment
вы должны закрашивать ячейки и пустые пространства, потому что иначе вы никогда не увидите кубик ячейки, так как вы всегда перерисовываете на одном и том же экране. - person njzk2; 03.02.2014
comment
ок, я думал, что заливка экрана белым заботится о мертвых клетках. - person user2388331; 03.02.2014
comment
да, извините, я не видел эту строчку. Я бы тоже, это странно. - person njzk2; 03.02.2014
comment
Я понял это, я неправильно использовал функции рисования прямоугольников pygame. Я думал, что мне нужно передать координаты для правого нижнего угла, и это действительно то, что я должен был дать (topleft_x, topleft_y, width, height) - person user2388331; 04.02.2014