生命游戏-覆盖当前一代而不是更新到下一代

时间:2019-05-28 15:40:16

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

下面,我添加了我的人生游戏代码。规则定义正确,并且运行平稳。但是,该游戏无法正常运行。它没有更新到下一代,但似乎覆盖了当前一代。例如:下一代应该将三个水平的点变成三个垂直的点,但这不会发生。

解决方案: 我有两代人,现在和下一代。它必须将规则应用于当前一代,并在下一代中进行更新。然后,它必须一次性用下一代覆盖当前一代,而不是逐个单元地覆盖。我该如何解决?

import tkinter as tk
import itertools, os, platform, pygame, random

# Defining the grid dimensions.
GRID_SIZE = WIDTH, HEIGHT = 750, 1000

# Defining the cell size and the number of cells in the X and Y direction.
CELL_SIZE = 10
X_CELLS = int(WIDTH/CELL_SIZE)
Y_CELLS = int(HEIGHT/CELL_SIZE)

# Defining the number and color for dead and living cells.
COLOR_DEAD = 0
COLOR_ALIVE = 1
colors = []
colors.append((0, 0, 0))  # Black
colors.append((0, 128, 128))  # blue

# Defining two lists: current generation and next generation.
current_generation = [[COLOR_DEAD for y in range(Y_CELLS)] for x in range(X_CELLS)]
next_generation = [[COLOR_DEAD for y in range(Y_CELLS)] for x in range(X_CELLS)]

# Defining the max frames per second/speed of the game.
FPS_MAX = 10

class GameOfLife:
    """
    describe what the method does.
    """
    def __init__(self):
        # Initializing the interpreter and creating a root window and title.
        self.root = tk.Tk()
        self.root.title("Game of Life - Created by - Have fun")
        # Defining the main frame, left-side frame and right-side frame.
        self.frame = tk.Frame(self.root , width=1000, height=1000, highlightbackground='red')
        self.menu = tk.Frame(self.frame, width=250, height=1000, highlightbackground='#595959', highlightthickness=10)
        self.game_border = tk.Frame(self.frame, width=750, height=1000, highlightbackground='green', highlightthickness=10)
        # Packing the windows.
        self.frame.pack()
        self.frame.pack_propagate(0)
        self.menu.pack(side="left")
        self.menu.pack_propagate(0)
        self.game_border.pack()

        # Defining the buttons.
        self.button_start = tk.Button(self.menu, text="Start", height=5, width=20, fg="black", activeforeground="red", background="grey80", activebackground="grey80", command=self.start_button)
        self.button_stop = tk.Button(self.menu, text="Stop", height=5, width=20, fg="black", activeforeground="red", background="grey80", activebackground="grey80", command=self.stop_button)
        self.button_iteration = tk.Button(self.menu, text="Next iteration", height=5, width=20, fg="black", activeforeground="red", background="grey80", activebackground="grey80", command=self.create_next_gen)
        self.button_random = tk.Button(self.menu, text="Random", height=5, width=20, fg="black", activeforeground="red", background="grey80", activebackground="grey80", command=self.random_grid)
        self.button_reset = tk.Button(self.menu, text="Reset", height=5, width=20, fg="black", activeforeground="red", background="grey80", activebackground="grey80", command=self.reset_button)
        self.button_quit = tk.Button(self.menu, text="Quit", height=5, width=20, fg="black", activeforeground="red", background="grey80", activebackground="grey80", command=self.quit_button)
        # Packing the buttons.
        self.button_start.pack()
        self.button_stop.pack()
        self.button_iteration.pack()
        self.button_random.pack()
        self.button_reset.pack()
        self.button_quit.pack()
        # Placing the buttons.
        self.button_start.place(x=40, y=50)
        self.button_stop.place(x=40, y=200)
        self.button_iteration.place(x=40, y=350)
        self.button_random.place(x=40, y=500)
        self.button_reset.place(x=40, y=650)
        self.button_quit.place(x=40, y=800)

        # Defining the slider.
        self.slider_random = tk.Scale(self.menu, from_=0, to=100, orient="horizontal", command=self.slider_value)
        self.slider_random.set(50)
        # Packing the slider.
        self.slider_random.pack()
        # Placing the slider.
        self.slider_random.place(x=62, y=590)

        # Defining a dropdown menu for the form and color.
        """
        self.options_figures = [
            "circles",
            "squares",
            "surprise"
        ]
        self.var_figure = tk.StringVar(self.root)
        self.dropdown_figure = tk.OptionMenu(self.menu, self.var_figure,
                                             self.options_figures[0], self.options_figures[1],
                                             self.options_figures[2])
        self.var_figure.set(self.options_figures[0])
        #self.var_color.trace("w", FUNCTIONNAME)
        self.dropdown_figure.pack()

        # Dropdown menu for the cell color
        self.options_colors = [
            "blue",
            "red",
            "white",
            "green",
            "yellow",
            "purple",
            "grey",
            "pink"
        ]
        self.var_color = tk.StringVar(self.root)
        self.dropdown_colors = tk.OptionMenu(self.menu, self.var_color,
                                             self.options_colors[0], self.options_colors[1],
                                             self.options_colors[2], self.options_colors[3],
                                             self.options_colors[4], self.options_colors[5],
                                             self.options_colors[6], self.options_colors[7])
        self.var_color.set(self.options_colors[0])
        #self.var_color.trace("w", FUNCTION NAME)
        self.dropdown_colors.pack()
        """

        # Defining the labels that count the dead and living cells.
        """
        self.label_alive = tk.Label(self.menu, text="Living cells:"+" 1000", height=5, width=20, fg="black", background="grey80")
        self.label_dead = tk.Label(self.menu, text="Dead cells"+" 1000", height=1, width=20, fg="black", background="grey80")
        Packing the labels
        self.label_alive.pack()
        self.label_dead.pack()
        self.label_alive.place(x=40, y=900)
        self.label_alive.place(x=40, y=900)
        """

        # This embeds the pygame window in the tkinter frame.
        os.environ['SDL_WINDOWID'] = str(self.game_border.winfo_id())
        system = platform.system()
        if system == "Windows":
            os.environ['SDL_VIDEODRIVER'] = 'windib'
        elif system == "Linux":
            os.environ['SDL_VIDEODRIVER'] = 'x11'

        # Initializing pygame.
        pygame.init()
        self.screen = pygame.display.set_mode(GRID_SIZE)
        # Initializing the generations.
        self.init_gen(current_generation, COLOR_DEAD)
        # Defining a clock to set the FPS.
        self.fps_clock = pygame.time.Clock()
        # Setting variables for later use.
        self.next_iteration = False
        self.game_over = False

    # Get the slider value to change the % of randomness.
    def slider_value(self, value):
        self.value = value

    # Button functions.
    def start_button(self):
        self.next_iteration = True
    def stop_button(self):
        self.next_iteration = False
    def reset_button(self):
        self.next_iteration = False
        self.init_gen(next_generation, COLOR_DEAD)
    def quit_button(self):
        self.game_over = True

    # Initializing all the cells.
    def init_gen(self, generation, c):
        for y in range(Y_CELLS):
            for x in range(X_CELLS):
                generation[x][y] = c

    # Creates a random grid based on the slider value.
    def random_grid(self):
        self.next_iteration = False
        self.init_gen(next_generation, COLOR_DEAD)
        self.percentage_zero = list(itertools.repeat(0,
                                                     (100 - self.slider_random.get())))
        self.percentage_one = list(itertools.repeat(1,
                                                    (self.slider_random.get())))
        # print(self.percentage_zero)
        # print(self.percentage_one)
        for row in range(X_CELLS):
            for col in range(Y_CELLS):
                next_generation[row][col] = random.choice(self.percentage_zero + self.percentage_one)
                # print(next_generation[row][col])

    # Drawing the cells, color black or blue at location (x,y).
    def draw_cell(self, x, y, c):
        pos = (int(x * CELL_SIZE + CELL_SIZE / 2),
               int(y * CELL_SIZE + CELL_SIZE / 2))
        # pygame.draw.rect(screen, colors[c], pygame.Rect(x * CELL_SIZE, y * CELL_SIZE, CELL_SIZE-1, CELL_SIZE-1))
        # pygame.draw.circle(screen, colors[c], pos, CELL_SIZE, CELL_SIZE) #Weird form, can also be used instead of rectangles
        pygame.draw.circle(self.screen, colors[c], pos, 5, 0)

    # Updating the cells in the current generation.
    def update_gen(self):
        global current_generation
        for y in range(Y_CELLS):
            for x in range(X_CELLS):
                c = next_generation[x][y]
                self.draw_cell(x, y, c)
        current_generation = list(next_generation)

    # Activate a living cell.
    def activate_living_cell(self, x, y):
        global next_generation
        next_generation[x][y] = COLOR_ALIVE

    # Deactivate a living cell.
    def deactivate_living_cell(self, x, y):
        global next_generation
        next_generation[x][y] = COLOR_DEAD

    # Function to check neighbor cells.
    def check_cells(self, x, y):
        # Check the edges.
        if (x < 0) or (y < 0):
            return 0
        if (x >= X_CELLS) or (y >= Y_CELLS):
            return 0
        if current_generation[x][y] == COLOR_ALIVE:
            return 1
        else:
            return 0

    def check_cell_neighbors(self, row_index, col_index):
        # Get the number of alive cells surrounding the current cell.
        num_alive_neighbors = 0
        num_alive_neighbors += self.check_cells(row_index - 1, col_index - 1)
        num_alive_neighbors += self.check_cells(row_index - 1, col_index)
        num_alive_neighbors += self.check_cells(row_index - 1, col_index + 1)
        num_alive_neighbors += self.check_cells(row_index, col_index - 1)
        num_alive_neighbors += self.check_cells(row_index, col_index + 1)
        num_alive_neighbors += self.check_cells(row_index + 1, col_index - 1)
        num_alive_neighbors += self.check_cells(row_index + 1, col_index)
        num_alive_neighbors += self.check_cells(row_index + 1, col_index + 1)
        return num_alive_neighbors

    # Rules:
    # 1 Any live cell with fewer than two live neighbors dies, as if by underpopulation.
    # 2 Any live cell with two or three live neighbors lives on to the next generation.
    # 3 Any live cell with more than three live neighbors dies, as if by overpopulation.
    # 4 Any dead cell with exactly three live neighbors becomes a live cell, as if by reproduction.
    def create_next_gen(self):
        for y in range(Y_CELLS):
            for x in range(X_CELLS):
                n = self.check_cell_neighbors(x, y)  # Number of neighbors.
                c = current_generation[x][y]  # Current cell (either dead or alive).
                if c == COLOR_ALIVE:
                    if (n < 2):  # Rule number 1.
                        next_generation[x][y] = COLOR_DEAD
                    elif (n > 3):  # Rule number 3.
                        next_generation[x][y] = COLOR_DEAD
                    else:  # Rule number 2.
                        next_generation[x][y] = COLOR_ALIVE
                elif c == COLOR_DEAD:
                    if (n == 3):  # Rule number 4.
                        next_generation[x][y] = COLOR_ALIVE
                    else:
                        next_generation[x][y] = COLOR_DEAD
#Problem: first counting, then next iteration.

    # Defines button and mouse clicks.
    def handle_events(self):
        for event in pygame.event.get():
            # Turns the mouse position into a position in the grid.
            posn = pygame.mouse.get_pos()
            x = int(posn[0] / CELL_SIZE)
            y = int(posn[1] / CELL_SIZE)
            # Pressing quit --> quit the game.
            if event.type == pygame.QUIT:
                self.game_over = True
            # Pressing the left mouse button to activate or deactivate a cell.
            if event.type == pygame.MOUSEBUTTONDOWN:
                if event.button == 1:
                    if next_generation[x][y] == COLOR_DEAD:
                        self.activate_living_cell(x, y)
                    else:
                        self.deactivate_living_cell(x, y)
            # Keeping the right mouse button pressed activates drawing mode.
            if event.type == pygame.MOUSEMOTION and event.buttons[2]:
                self.activate_living_cell(x, y)

            # Define the keyboard key presses for q, space, a, s, r.
            if event.type == pygame.KEYDOWN:
                # Quit the game.
                if event.unicode == 'q':
                    self.game_over = True
                    print("q")
                # Next iteration - manually.
                elif event.key == pygame.K_SPACE:
                    self.create_next_gen()
                    print("keypress")
                # Next iteration - automated.
                elif event.unicode == 'a':  # a to automate the iterations.
                    self.next_iteration = True
                    print("a")
                # Stop the automated iterations.
                elif event.unicode == 's':
                    self.next_iteration = False
                    print("s")
                # Empty the grid.
                elif event.unicode == 'r':
                    self.next_iteration = False
                    self.init_gen(next_generation, COLOR_DEAD)
                    print("r")

    # Runs the game loop
    def run(self):
        while not self.game_over:
            self.handle_events()
            if self.next_iteration:
                self.create_next_gen()
            self.update_gen()
            pygame.display.flip()
            self.fps_clock.tick(FPS_MAX)
            self.root.update()

if __name__ == "__main__":
    GAME = GameOfLife()
    GAME.run()

1 个答案:

答案 0 :(得分:1)

您必须将next_generation复制到current_generation。但是

current_generation = list(next_generation)

不执行您期望的操作,因为next_generation的元素也是一个列表。

要深度复制每个元素是数字列表的列表(不会复制对象),您必须:

current_generation = [list(e) for e in next_generation]

current_generation = [[i for i in j] for j in next_generation]

current_generation = [e[:] for e in next_generation]

由于方法GameOfLife.update_gen中存在嵌套循环,因此该问题也可以通过简单的赋值来解决:

class GameOfLife:

    # [...]

    # Updating the cells in the current generation.
    def update_gen(self):
        global current_generation
        for y in range(Y_CELLS):
            for x in range(X_CELLS):
                c = next_generation[x][y]
                self.draw_cell(x, y, c)
                current_generation[x][y] = next_generation[x][y] # assign element by element


还有一个问题,当动画运行并且用鼠标操纵游戏时。按下鼠标时,next_generation会被.activate_living_cell.deactivate_living_cell更改。
但是之后,next_generationcurrent_generation中的数据重新计算。

while not self.game_over:
     self.handle_events()       # change "next_generation" by click
     if self.next_iteration:
         self.create_next_gen() # compute "next_generation" from "current_generation"
     self.update_gen()          # copy "current_generation" from "next_generation"

该问题可以轻松解决。考虑到用鼠标操纵游戏时,current_generationnext_generation的内容相等。

在操作后要么更改current_generation而不是next_generation,要么更新current_generation

def handle_events(self):
    for event in pygame.event.get():

        # [...]        

        # Pressing the left mouse button to activate or deactivate a cell.
        if event.type == pygame.MOUSEBUTTONDOWN:
            if event.button == 1:
                if next_generation[x][y] == COLOR_DEAD:
                    self.activate_living_cell(x, y)
                else:
                   self.deactivate_living_cell(x, y)
                self.update_gen() # <----------------------------