简体   繁体   English

康威《人生游戏》中出乎意料的结果

[英]Unexpected results in Conway's Game of Life

I have been trying to write my own version of Conway's Game of Life as practice for Python using Pygame. 我一直在尝试使用Pygame编写自己的版本的Conway的《人生游戏》作为Python的练习。 I first wrote the functions for initializing the game field, and then calculating the next generation. 我首先编写了用于初始化游戏场然后计算下一代的函数。 I verified it's functionality using the console to print the results and verify that they returned the expected results (just 2 generations deep by hand on a 5x5 grid). 我使用控制台验证了它的功能,以打印结果并验证它们是否返回了预期的结果(在5x5的网格上只有2代的深度)。

An important note of how I am calculating the neighbors... Instead of doing a for loop through the entire array and doing for loops to count for each cell, I have implemented an array that holds the neighbor counts. 关于如何计算邻居的重要说明...我没有实现遍历整个数组的for循环和对每个单元进行计数的for循环,而是实现了一个保存邻居计数的数组。 Only making changes when a cells status is changed. 仅在单元格状态更改时进行更改。 This means I don't waste time calculating neighbors for cells that have not changed. 这意味着我不会浪费时间为未更改的单元计算邻居。

When it came time to use Pygame to display the array with rectangles, I wrote the following program. 当使用Pygame显示带有矩形的数组时,我编写了以下程序。 At first I was drawing the screen by filling in the entire screen white, and then drawing the live cells as black (this can be done by commenting out the else statement in update()). 首先,我是通过将整个屏幕填充为白色来绘制屏幕,​​然后将活动单元绘制为黑色(这可以通过在update()中注释else语句来完成)。 I expected this to work as normal, but when I ran the program all I ended up with was the screen filling in black. 我希望它能正常工作,但是当我运行该程序时,最终得到的是黑色屏幕。

I was perplexed by the result so i drew white rectangles for the unpopulated cells (using the else statement. And got a better looking result, but instead of the cells eventually all dying, they eventually multiplied across the whole screen. This is opposite of what I expected, as I was expecting it to eventually stabilize. 我对结果感到困惑,于是我为未填充的单元格绘制了白色矩形(使用else语句。并得到了更好看的结果,但是最终单元格在整个屏幕上成倍增加,而不是单元格最终全部死亡。我期望,因为我期望它最终会稳定下来。

Anyone know what I am doing wrong? 有人知道我在做什么错吗? I know that this is not the best way of writing this program, I welcome comments of how I can make it better. 我知道这不是编写此程序的最佳方法,我欢迎提出有关如何使其变得更好的意见。


  • RETURN = run simulation RETURN =运行模拟
  • 'R' = randomize 'R'=随机
  • 'T' = tick one generation 'T'=勾选一代
  • 'C' = clear game field 'C'=清除比赛场地
  • 'N' = display neighbor map '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)

self.neighborsOld = self.neighbors does not copy the map, it only points to it. self.neighborsOld = self.neighbors不会复制地图,它只会指向地图。

See : 见:

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

You need to either make a copy ( a[:] ) for every row in a , or use the copy module and use deepcopy : 你需要要么使复印件( a[:] ),在每一行a ,或者使用copy模块,并使用deepcopy

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

or 要么

import copy
b = copy.deepcopy(a)

Either way, it results in 无论哪种方式,它都会导致

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM