【问题标题】:Unexpected results in Conway's Game of Life康威生命游戏中的意外结果
【发布时间】:2014-02-03 16:52:23
【问题描述】:

我一直在尝试编写我自己版本的康威生命游戏作为 Python 使用 Pygame 的练习。我先写了初始化游戏场的函数,然后计算下一代。我使用控制台验证了它的功能,以打印结果并验证它们返回了预期的结果(在 5x5 网格上手动仅 2 代)。

关于我如何计算邻居的重要说明...我没有对整个数组执行 for 循环并执行 for 循环来计算每个单元格,而是实现了一个保存邻居计数的数组。仅在单元格状态更改时进行更改。这意味着我不会浪费时间计算未更改单元格的邻居。

当需要使用 Pygame 显示带有矩形的数组时,我编写了以下程序。起初我通过将整个屏幕填充为白色来绘制屏幕,​​然后将活动单元格绘制为黑色(这可以通过注释掉 update() 中的 else 语句来完成)。我希望这能正常工作,但当我运行程序时,我最终得到的只是屏幕全黑。

我对结果感到困惑,所以我为未填充的单元格绘制了白色矩形(使用 else 语句。并得到了更好看的结果,但不是单元格最终全部死亡,而是最终在整个屏幕上成倍增加。这是与我的预期相反,因为我预计它最终会稳定下来。

有人知道我做错了什么吗?我知道这不是编写这个程序的最佳方式,我欢迎 cmets 告诉我如何使它变得更好。


  • 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)

【问题讨论】:

  • 您是否尝试将您的结果与其他实现进行比较?
  • 最接近这一点的是我在以前的生活游戏中用 C++ 编写的。我在想也许我有错误的规则,但我用维基百科的文章验证了它。

标签: python pygame conways-game-of-life


【解决方案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]

import copy
b = copy.deepcopy(a)

无论哪种方式,它都会导致

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

【讨论】:

  • 谢谢!如果我要注释掉 update() 函数中的 else 语句,屏幕主要变黑。我希望它能够工作,因为我在将活细胞绘制为黑色之前将屏幕绘制为白色。你知道我做错了什么吗?
  • 你必须绘制单元格和空白空间,否则你永远看不到单元格死亡,因为你总是在同一个屏幕上重绘。
  • 好的,我认为填充屏幕白色是在照顾死细胞。
  • 是的,抱歉,我没看到那行。不过我也会有,这很奇怪。
  • 我想通了,我用错了pygame的矩形绘图功能。我以为我必须传入右下角的坐标,而实际上我必须给(topleft_x,topleft_y,width,height)
猜你喜欢
  • 1970-01-01
  • 2013-02-21
  • 1970-01-01
  • 2010-09-07
  • 2017-03-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多