Pygame очень медленно запускает игру жизни

avatar
Ethan97
4 сентября 2021 в 14:41
108
2
3

Пара других вопросов, связанных с этим, была решена путем перемещения события рисования из цикла, в котором оно не должно быть. Однако у меня нет этой проблемы. Мы будем очень признательны за любую помощь!

Питон: 3.8

Pygame: 1.9.6

Если вам нужно протестировать воспроизведение:

Запустите игру. Рисуйте на экране, чтобы размещать живые клетки. Нажмите «R», чтобы начать. Вы также можете щелкнуть «S» после того, как начнете останавливаться, и снова рисовать, но вам придется подождать несколько поколений после нажатия, прежде чем он действительно остановится (из-за той же задержки, которую я полагаю).

import pygame
import numpy

class Game():
    def __init__(self):
        self.Run()

    def GetAdj(self, x, y):
        nb = 0
        for c in range (-1, 2):
            for r in range (-1, 2):
                if r == 0 and c == 0:
                    pass
                else:
                    nposx = x + r
                    nposy = y + c
                    if nposx < len(self.pixels):
                        if nposy < len(self.pixels[nposx]):
                            if self.pixels[nposx][nposy] == 1:
                                nb += 1
        return nb

    def NextGeneration(self):
        newGeneration = numpy.zeros(self.ScreenWidth//2, self.ScreenHeight//2, dtype=int)
        for x, c in enumerate(self.pixels):
            for y, cell in enumerate(c):
                nbrs = self.GetAdj(x, y)
                if cell == 1:
                    if nbrs in [2, 3]:
                        newGeneration[x][y] = 1
                else:
                    if nbrs == 3:
                        newGeneration[x][y] = 1
        self.pixels = newGeneration

    def DrawBG(self):
        black = (0,0,0)
        white = (255,255,255)
        self.bg.fill(black)
        for c in range(self.ScreenWidth // self.cellsize):
            for r in range(self.ScreenHeight // self.cellsize):
                if self.pixels[c][r] == 1:
                    pygame.draw.rect(self.bg, white, (c*self.cellsize, r*self.cellsize, self.cellsize, self.cellsize))

    def Run(self):
        pygame.init()
        self.ScreenHeight = 720
        self.ScreenWidth = 1280
        self.ScreenSize = (self.ScreenWidth, self.ScreenHeight)
        screen = pygame.display.set_mode(self.ScreenSize)
        self.bg = pygame.Surface(self.ScreenSize)
        clock = pygame.time.Clock()
        mousedown = False
        self.pixels = numpy.zeros(self.ScreenWidth//2, self.ScreenHeight//2, dtype=int)
        self.cellsize = 10
        stage = 'Draw'
        running = True
        while running:
            clock.tick(60)
            events = pygame.event.get()
            for event in events:
                if event.type == pygame.QUIT:
                    running = False
            if stage == 'Draw':
                for event in events:
                    if event.type == pygame.MOUSEBUTTONUP and mousedown:
                        mousedown = False
                    elif event.type == pygame.MOUSEBUTTONDOWN:
                        mousedown = True
                    elif event.type == pygame.MOUSEMOTION and mousedown and stage == 'Draw':
                        mposx, mposy = pygame.mouse.get_pos()
                        self.pixels[mposx//self.cellsize][mposy//self.cellsize] = 1
                    elif event.type == pygame.KEYDOWN and event.key == pygame.K_r:
                        stage = 'Run'
                        self.NextGeneration()

            elif stage == 'Run':
                for event in events:
                    if event.type == pygame.KEYDOWN and event.key == pygame.K_s:
                        stage = 'Draw'
                self.NextGeneration()

            self.DrawBG()
            screen.blit(self.bg, (0,0))
            pygame.display.flip()

if __name__ == "__main__":
    Game()
Источник

Ответы (2)

avatar
Rabbid76
4 сентября 2021 в 14:59
2

Массив слишком велик. Вы создаете массив для каждого пикселя, а не для каждой ячейки. Так что вычисляйте его не для каждой ячейки, а для каждого пикселя.

Изменить размер массива в методах NextGeneration и Run:

newGeneration = numpy.zeros(self.ScreenWidth//2, self.ScreenHeight//2, dtype=int)

newGeneration = numpy.zeros((self.ScreenWidth//self.cellsize, self.ScreenHeight//self.cellsize), dtype=int)

self.pixels = numpy.zeros(self.ScreenWidth//2, self.ScreenHeight//2, dtype=int)

self.cellsize = 10
self.pixels = numpy.zeros((self.ScreenWidth//self.cellsize, self.ScreenHeight//self.cellsize), dtype=int)

Метод GetAdj можно значительно упростить с помощью numpy.sum:

class Game():
    # [...]

    def GetAdj(self, x, y):
        x0, y0 = max(0, x-1), max(0, y-1)
        nb = numpy.sum(self.pixels[x0 : x+2, y0 : y+2]) - self.pixels[x, y]
        return nb

Производительность можно дополнительно повысить с помощью scipy.ndimage.convolve и таблицы поиска (см. Индексирование многомерных массивов):

import numpy
from scipy.ndimage import convolve

class Game():
    def __init__(self):
        self.kernel = numpy.array([[1,1,1], [1,0,1], [1,1,1]])
        self.lookup = numpy.array([0,0,0,1,0,0,0,0,0, 0,0,1,1,0,0,0,0,0])
        self.Run()

    def NextGeneration(self):
        adjacent = convolve(self.pixels, self.kernel, mode='constant')
        newGeneration = self.lookup[self.pixels * 9 + adjacent]
        self.pixels = newGeneration
avatar
Mat Gomes
6 сентября 2021 в 11:50
0

В дополнение к ответу @Rabbid76 вы также можете реализовать игру с разреженной структурой.

Как мы видим, наличие огромных массивов Numpy является узким местом вашей реализации. Если вы просто храните информацию о живых ячейках, вы, вероятно, можете использовать разреженную логическую матрицу SciPy, которая, по сути, просто инициализирует информацию о живых ячейках. Таким образом, даже если у вас есть сетка 1000x1000 с одной активной ячейкой, она не будет хранить все эти мертвые ячейки.

Но еще лучше, почему бы просто не использовать набор Python? Например, у вас может быть набор кортежей int, которые представляют ячейки, живые в настоящее время в каждой итерации. Я обнаружил, что это делает реализацию и логику намного проще, а накладные расходы на импорт и создание огромных массивов numpy исчезают.

Недавно я опубликовал реализацию Конвея Game Of Life, состоящую менее чем из 80 строк Python с использованием Pygame. Я использую наборы для хранения активных ячеек, так что взгляните и посмотрите, как можно упростить вашу реализацию с помощью этого подхода.

Конечно, если вы планируете постоянно иметь полноценную живую сотовую доску, то у этого подхода есть свои недостатки. Но я предполагаю, что, поскольку вы в первую очередь используете Python, вы не собираетесь использовать много ресурсоемких настроек (я бы рекомендовал использовать для этого нативный код со значительной оптимизацией).