感染效率 - Pygame

Efficiency in Infections - Pygame

我目前正在为我的 A-level 课程制作一个感染生存游戏,我正在努力研究如何使它更有效率。
当细胞被感染时,我需要检查越来越多的细胞,我的计算机科学老师建议我将感染保存为一个布尔值,因为我以后可以用它做更多的事情,但是由于我最终必须检查它,所以效率更低导致大量帧率问题的平方单元格数量。
我最初的想法是将未感染和感染的存储在单独的列表中,但我的 comp sci 老师说我把它复杂化了,但是这没有任何帧率问题。
我的很多代码都从这个问题中获得了灵感,尤其是在涉及到单元格移动时。

TLDR:我想让我的代码更有效率,但我想不出怎么做
我的代码:

import sys, random, pygame
import matplotlib.pyplot as plt
from pygame.locals import *
import time
pygame.init()

#Window details
windowWidth = 400
windowHeight = 400
pixSize = 2
FPS = 60
screen = pygame.display.set_mode((windowWidth, windowHeight))
pygame.display.set_caption("Infection Game")
class Cell:
    def __init__(self):
        self.xPos = random.randrange(1,windowWidth)
        self.yPos = random.randrange(1,windowHeight)
        self.speed = 2
        self.isInfected = False
        self.infectionRange = 5
        self.move = [None, None]
        self.direction = None
    def cellDraw(self):
        if self.isInfected == False:
            pygame.draw.rect(screen, (255,255,255), (self.xPos,self.yPos,pixSize,pixSize),0)
        else:
            pygame.draw.rect(screen, (0,255,0), (self.xPos,self.yPos,pixSize,pixSize),0)
    def cellMovement(self):
        directions = {"S":((-1,2),(1,self.speed)),"SW":((-self.speed,-1),(1,self.speed)),"W":((-self.speed,-1),(-1,2)),"NW":((-self.speed,-1),(-self.speed,-1)),"N":((-1,2),(-self.speed,-1)),"NE":((1,self.speed),(-self.speed,-1)),"E":((1,self.speed),(-1,2)),"SE":((1,self.speed),(1,self.speed))} #((min x, max x)(min y, max y))
        directionsName = ("S","SW","W","NW","N","NE","E","SE") #possible directions
        if random.randrange(0,5) == 2: #move about once every 5 frames
            if self.direction == None: #if no direction is set, set a random one
                self.direction = random.choice(directionsName)
            else:
                a = directionsName.index(self.direction) #get the index of direction in directions list
                b = random.randrange(a-1,a+2) #set the direction to be the same, or one next to the current direction
                if b > len(directionsName)-1: #if direction index is outside the list, move back to the start
                    b = 0
                self.direction = directionsName[b]

            self.move[0] = random.randrange(directions[self.direction][0][0],directions[self.direction][0][1]) + 0.35
            self.move[1] = random.randrange(directions[self.direction][1][0],directions[self.direction][1][1]) + 0.35
        if self.xPos < 5 or self.xPos > windowWidth - 5 or self.yPos < 5 or self.yPos > windowHeight - 5: #if cell is near the border of the screen, change direction
            if self.xPos < 5:
                self.direction = "E"
            elif self.xPos > windowWidth - 5:
                self.direction = "W"
            elif self.yPos < 5:
                self.direction = "S"
            elif self.yPos > windowHeight - 5:
                self.direction = "N"

            self.move[0] = random.randrange(directions[self.direction][0][0],directions[self.direction][0][1]) + 0.35
            self.move[1] = random.randrange(directions[self.direction][1][0],directions[self.direction][1][1]) + 0.35
        if self.move[0] != None: #add the relative coordinates to the cells coordinates
            self.xPos += self.move[0]
            self.yPos += self.move[1]
    def Infect(self):
        for i in cellList:
            if (self.xPos > i.xPos - self.infectionRange and self.xPos < i.xPos + self.infectionRange) and (self.yPos > i.yPos - self.infectionRange and self.yPos < i.yPos + self.infectionRange):
                i.isInfected = True
        
xgraph = []
ygraph = []
cellList = []
startTime = time.time()
for i in range(1000):
    cell = Cell()
    cellList.append(cell)
cellList[0].isInfected = True

def gameLoop():
    while True:
        infectList = []
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()
        screen.fill((0,0,0))
        for i in cellList:
            i.cellDraw()
            i.cellMovement()
        for i in cellList:
            if i.isInfected == True:
                i.Infect()
                infectList.append(i)
        xgraph.append(time.time()-startTime)
        ygraph.append(len(infectList))
        plt.plot(xgraph,ygraph)
        plt.xlabel('time (s)')
        plt.ylabel('infected')
        if len(infectList) == 1000:
            plt.show()
        pygame.display.update() #update display
        pygame.time.Clock().tick(FPS) #limit FPS

gameLoop()

首先,我更改了您的一些代码:

if self.isInfected == False:
if self.direction == None:

if not self.isInfected:
if self.direction is None:

只是这样读起来更好看。

其次,我对 Infect 函数进行了向量化:

uninfected = [i for i in cellList if not i.isInfected]
uninfected_array = np.array([[i.xPos, i.yPos] for i in uninfected])
indices = np.greater(uninfected_array[:, 0], self.xPos - self.infectionRange) * \
          np.greater(self.xPos + self.infectionRange, uninfected_array[:, 0]) * \
          np.greater(uninfected_array[:, 1], self.yPos - self.infectionRange) * \
          np.greater(self.yPos + self.infectionRange, uninfected_array[:, 1])

    
for i in np.where(indices)[0]:
    uninfected[i].isInfected = True

这个单元格数量需要相同的时间,但应该更好地缩放。

事实证明,创建数组几乎需要所有时间。因此,您可以创建一次,将其从循环中拉出并节省大量时间:

def Infect(self, uninfected, uninfected_array):
    indices = np.greater(uninfected_array[:, 0], self.xPos - self.infectionRange) * \
              np.greater(self.xPos + self.infectionRange, uninfected_array[:, 0]) * \
              np.greater(uninfected_array[:, 1], self.yPos - self.infectionRange) * \
              np.greater(self.yPos + self.infectionRange, uninfected_array[:, 1])

    for i in np.where(indices)[0]:
        uninfected[i].isInfected = True

uninfected = [i for i in cellList if not i.isInfected]
uninfected_array = np.array([[i.xPos, i.yPos] for i in uninfected])
# To prevent errors with empty arrays
if len(uninfected) > 0:
    for i in cellList:
        if i.isInfected:
            i.Infect(uninfected, uninfected_array)
# To prevent errors when everyone is infected
if infected == 0:
    infected = len(cellList) - len(uninfected)

最后,你似乎并没有真正使用infectList,所以我用一个计数器代替它:

infected = 0
if len(uninfected) > 0:
    for i in cellList:
        if i.isInfected:
            infected += 1

作为旁注,我会稍微更改 UI 控件,以便更容易绘制图形,而不是使用 sys.exit 退出,而是更好地跳出 while 循环。您也只绘制一次结果:

running = True
while running:
    infectList = []
    for event in pygame.event.get():
        if event.type == QUIT:
            running = False
    ...
pygame.quit()
plt.plot(xgraph, ygraph)
plt.xlabel('time (s)')
plt.ylabel('infected')
plt.show()

实施所有这些结果:

import random
import pygame
import matplotlib.pyplot as plt
from pygame.locals import *
import time
import numpy as np

pygame.init()

# Window details
windowWidth = 400
windowHeight = 400
pixSize = 2
FPS = 60
screen = pygame.display.set_mode((windowWidth, windowHeight))
pygame.display.set_caption("Infection Game")


class Cell:
    def __init__(self):
        self.xPos = random.randrange(1, windowWidth)
        self.yPos = random.randrange(1, windowHeight)
        self.speed = 2
        self.isInfected = False
        self.infectionRange = 5
        self.move = [None, None]
        self.direction = None

    def cellDraw(self):
        if not self.isInfected:
            pygame.draw.rect(screen, (255, 255, 255), (self.xPos, self.yPos, pixSize, pixSize), 0)
        else:
            pygame.draw.rect(screen, (0, 255, 0), (self.xPos, self.yPos, pixSize, pixSize), 0)

    def cellMovement(self):
        directions = {"S": ((-1, 2), (1, self.speed)), "SW": ((-self.speed, -1), (1, self.speed)),
                      "W": ((-self.speed, -1), (-1, 2)), "NW": ((-self.speed, -1), (-self.speed, -1)),
                      "N": ((-1, 2), (-self.speed, -1)), "NE": ((1, self.speed), (-self.speed, -1)),
                      "E": ((1, self.speed), (-1, 2)),
                      "SE": ((1, self.speed), (1, self.speed))}  # ((min x, max x)(min y, max y))
        directionsName = ("S", "SW", "W", "NW", "N", "NE", "E", "SE")  # possible directions
        if random.randrange(0, 5) == 2:  # move about once every 5 frames
            if self.direction is None:  # if no direction is set, set a random one
                self.direction = random.choice(directionsName)
            else:
                a = directionsName.index(self.direction)  # get the index of direction in directions list
                b = random.randrange(a - 1,
                                     a + 2)  # set the direction to be the same, or one next to the current direction
                if b > len(directionsName) - 1:  # if direction index is outside the list, move back to the start
                    b = 0
                self.direction = directionsName[b]

            self.move[0] = random.randrange(directions[self.direction][0][0], directions[self.direction][0][1]) + 0.35
            self.move[1] = random.randrange(directions[self.direction][1][0], directions[self.direction][1][1]) + 0.35
        if self.xPos < 5 or self.xPos > windowWidth - 5 or self.yPos < 5 or self.yPos > windowHeight - 5:  # if cell is near the border of the screen, change direction
            if self.xPos < 5:
                self.direction = "E"
            elif self.xPos > windowWidth - 5:
                self.direction = "W"
            elif self.yPos < 5:
                self.direction = "S"
            elif self.yPos > windowHeight - 5:
                self.direction = "N"

            self.move[0] = random.randrange(directions[self.direction][0][0], directions[self.direction][0][1]) + 0.35
            self.move[1] = random.randrange(directions[self.direction][1][0], directions[self.direction][1][1]) + 0.35
        if self.move[0] is not None:  # add the relative coordinates to the cells coordinates
            self.xPos += self.move[0]
            self.yPos += self.move[1]

    def Infect(self, uninfected, uninfected_array):
        indices = np.greater(uninfected_array[:, 0], self.xPos - self.infectionRange) * \
                  np.greater(self.xPos + self.infectionRange, uninfected_array[:, 0]) * \
                  np.greater(uninfected_array[:, 1], self.yPos - self.infectionRange) * \
                  np.greater(self.yPos + self.infectionRange, uninfected_array[:, 1])

        for i in np.where(indices)[0]:
            uninfected[i].isInfected = True


xgraph = []
ygraph = []
cellList = []
startTime = time.time()
for i in range(1000):
    cell = Cell()
    cellList.append(cell)
cellList[0].isInfected = True


def gameLoop():
    running = True
    while running:
        infectList = []
        for event in pygame.event.get():
            if event.type == QUIT:
                running = False
        screen.fill((0, 0, 0))
        for i in cellList:
            i.cellDraw()
            i.cellMovement()
        infected = 0

        uninfected = [i for i in cellList if not i.isInfected]
        uninfected_array = np.array([[i.xPos, i.yPos] for i in uninfected])
        if len(uninfected) > 0:
            for i in cellList:
                if i.isInfected:
                    i.Infect(uninfected, uninfected_array)
                    infected += 1
        if infected == 0:
            infected = len(cellList) - len(uninfected)
        xgraph.append(time.time() - startTime)
        ygraph.append(infected)
        pygame.display.update()  # update display
        pygame.time.Clock().tick(FPS)  # limit FPS
    pygame.quit()

    # figured this is what you wanted to do ;)
    plt.plot(xgraph, ygraph)
    plt.xlabel('time (s)')
    plt.ylabel('infected')
    plt.show()


gameLoop()

而且运行流畅