Pygame实现自定义的生命游戏

仅是尝试,还有待提高(v0)。

主要目的:想了解如何交互,获取鼠标点击位置并进行相应操作。

此次生命游戏的规则:
每一轮用户通过鼠标点击增加一个细胞;
所有细胞向其八邻域中的一个分裂新细胞;
细胞的八邻域若多于3个细胞,则认为营养不足、毒素增多,该细胞凋亡;
细胞最多只能存活三轮。

1 引入需要的包
import os
import sys
import time
import pygame
import random
import numpy as np
2 定义生命游戏
# 生命游戏的规则,用numpy数组存储
def count_score(current_cells):
    '''
    现在存在的细胞总数
    '''
    return np.sum(current_cells)

def add_new_cell(x,y,current_cells):
    '''
    加入一个新细胞
    '''
    current_cells[x][y] = 1

def add_cells_lives(current_cells,cells_life):
    '''
    对所有细胞增加细胞周期
    '''
    for i in range(len(cells_life)):
        for j in range(len(cells_life[0])):
            if(current_cells[i][j]==1):
                cells_life[i][j] = cells_life[i][j]+1
            
def cells_death1(current_cells,cells_life):
    '''
    进行细胞凋亡操作(细胞活多于3周期,则死亡)
    '''
    for i in range(len(cells_life)):
        for j in range(len(cells_life[0])):
            if(cells_life[i][j] > 3):
                cells_life[i][j] = 0
                current_cells[i][j] = 0

def cells_death2(current_cells,cells_life):
    '''
    进行细胞凋亡操作(细胞周围八邻域多于3个细胞,则死亡)
    '''
    xn = [-1,-1,-1,0,1,1,1,0]
    yn = [1,0,-1,-1,-1,0,1,1]
    for i in range(len(current_cells)):
        for j in range(len(current_cells[0])):
            if(current_cells[i][j]==1):
                count_n_cells = 0
                for k in range(8):
                    xx = i+xn[k]
                    yy = j+yn[k]
                    if(xx>=0 and xx<len(current_cells) and yy>=0 and yy<len(current_cells[0]) and current_cells[xx][yy]==1):
                        count_n_cells += 1
                if(count_n_cells>3):
                    current_cells[i][j] = 0
                    cells_life[i][j] = 0                

def cells_division(current_cells):
    '''
    细胞分裂(细胞周围如果有空位,则分裂)(简便起见,先检测到哪就往哪分裂)
    '''
    xn = [-1,-1,-1,0,1,1,1,0]
    yn = [1,0,-1,-1,-1,0,1,1]
    for i in range(len(current_cells)):
        for j in range(len(current_cells[0])):
            if(current_cells[i][j]==1):
                for k in range(8):
                    xx = i+xn[k]
                    yy = j+yn[k]
                    if(xx >=0 and xx<len(current_cells) and yy>=0 and yy<len(current_cells[0]) and current_cells[xx][yy]==0):
                        current_cells[xx][yy] = 2    # 先标记为2,避免新分裂的细胞再次分裂
                        break
    for i in range(len(current_cells)):     
        for j in range(len(current_cells[0])):
            if(current_cells[i][j]==2):
                current_cells[i][j] = 1
                      

def one_circle(x,y,current_cells,cells_life):
    '''
    一次交互,增加细胞,细胞分裂,细胞凋亡
    '''
    if(current_cells[x][y]==1):   # 如果x,y位置已经有细胞,则返回
        return 
    else:
        add_new_cell(x, y, current_cells)
        cells_division(current_cells)
        add_cells_lives(current_cells,cells_life)
        cells_death1(current_cells,cells_life)
        cells_death2(current_cells,cells_life)
        
        score = count_score(current_cells)   # 可以将现存细胞数目打印出来
        print(score)
3 定义游戏的相关信息
# 定义游戏窗口宽高,行数列数等信息
WIDTH = 700
HEIGHT = 700
NUMGRID = 16  # 一行/列的方格数
GRIDSIZE = 36  # 方格大小
XMARGIN = (WIDTH - NUMGRID*GRIDSIZE) // 2  # 边缘大小
YMARGIN = (HEIGHT - NUMGRID*GRIDSIZE) // 2

# 初始化数组
score = 0 # 生存的细胞数目
current_cells = np.zeros((NUMGRID,NUMGRID))  # 记录格子是否有细胞,bool
cells_life = np.zeros((NUMGRID,NUMGRID))     # 记录细胞存活的周期

用于判断鼠标点击的方格位置:

# 记录矩形的信息,之后需要往窗口中贴图
BOARDRECTS = []
for x in range(WIDTH):
    BOARDRECTS.append([])
    for y in range(HEIGHT):
        r = pygame.Rect((XMARGIN+x*GRIDSIZE),(YMARGIN+y*GRIDSIZE),GRIDSIZE,GRIDSIZE)
        BOARDRECTS[x].append(r)

def CheckValidClick(pos):
    '''
    检查点击是否在面板上
    '''
    for x in range(WIDTH):
        for y in range(HEIGHT):
            if(BOARDRECTS[x][y].collidepoint(pos[0],pos[1])):
                ans = [x,y]
                return ans
    return None

准备细胞图片和输出信息:

# 准备细胞图片
CELL = pygame.image.load('C:\\Users\\Administrator\\Desktop\\cell.jpg') # 加载图像
CELL = pygame.transform.scale(CELL, (GRIDSIZE-2,GRIDSIZE-2))  # 改变图像大小

# 定义需要显示的信息:当前细胞数目、已进行的轮数、总分
CELLNUMS = 0
ROUND = 0
SCORE = 0
POS1 = [20,20]  # 分别需要显示的位置
POS2 = [300,20]
POS3 = [520,20]
pygame.font.init() # 输出字体
font1 = pygame.font.Font(None,30)
4 游戏初始设置
# 初始化pygame,窗口大小,窗口名称
pygame.init()
screen = pygame.display.set_mode((WIDTH,HEIGHT))
pygame.display.set_caption('Life Game')

# 初始化背景和网格
screen.fill((255,255,220)) # 填充背景
for x in range(NUMGRID):  # 画网格
    for y in range(NUMGRID):
        rect = pygame.Rect((XMARGIN+x*GRIDSIZE,YMARGIN+y*GRIDSIZE,GRIDSIZE,GRIDSIZE))
        pygame.draw.rect(screen,(255,165,0),rect,2)
        pygame.display.flip()
        pygame.display.update()
5 游戏主循环
CLICKX = None   # 鼠标点击的位置,全局变量
CLICKY = None

while True:    # 游戏主循环

    # 初始化背景和网格,每次都初始化原因:细胞在不断变化,若不这样,已经死亡的细胞贴图会一直存在;为了简便,每次都初始化再重新贴图
    screen.fill((255,255,220))  # 填充窗口颜色
    for x in range(NUMGRID):  # 画网格
        for y in range(NUMGRID):
            rect = pygame.Rect((XMARGIN+x*GRIDSIZE,YMARGIN+y*GRIDSIZE,GRIDSIZE,GRIDSIZE))
            pygame.draw.rect(screen,(255,165,0),rect,2)
    
    ClickedSpace = None   # 鼠标点击的位置
    for event in pygame.event.get():  # 获取事件(如鼠标键盘点击)
        if(event.type==pygame.QUIT):  # 窗口关闭按钮被按下
            pygame.quit()
            sys.exit()                # 结束程序
        elif(event.type==pygame.MOUSEBUTTONDOWN): # 鼠标被按下
            CLICKX,CLICKY = event.pos   # 记录鼠标位置
            print(CLICKX,CLICKY)
        elif(event.type==pygame.MOUSEBUTTONUP):  # 鼠标抬起
            if(event.pos==(CLICKX,CLICKY)):  # 检查鼠标位置
                ClickedSpace = CheckValidClick(event.pos)  # 用之前写的函数检查点击是否在合法区域,返回属于哪个方格
                print('ClickedSpace: ',ClickedSpace)
   
    # 开始游戏
    if(ClickedSpace!=None):  # 方格位置合法
        if(current_cells[ClickedSpace[0]][ClickedSpace[1]]==1): # 若点击之处已有细胞,则跳出继续,这里是为了避免round数不合理增加
            continue
        one_circle(ClickedSpace[0],ClickedSpace[1], current_cells, cells_life) # 一次生命游戏交互
        CELLNUMS = count_score(current_cells)  # 计算并显示信息
        ROUND = ROUND+1
        SCORE = CELLNUMS + ROUND
        surface1 = font1.render('Cellnums: %d'%CELLNUMS,1,(125,125,255))
        surface2 = font1.render('Round: %d'%ROUND,1,(125,125,255))
        surface3 = font1.render('Score: %d'%SCORE,1,(125,125,255))
        screen.blit(surface1,POS1)
        screen.blit(surface2,POS2)
        screen.blit(surface3,POS3)
        pygame.display.update()
        # 遍历数组,在有细胞的地方换上细胞
        for i in range(NUMGRID):
            for j in range(NUMGRID):
                if(current_cells[i][j]==1):
                    screen.blit(CELL,(BOARDRECTS[i][j][0],BOARDRECTS[i][j][1]))
                    pygame.display.update()
                    pygame.display.flip()
不足之处:

1、游戏的规则可以继续改进:比如只改变点击点周围的细胞的情况等。不管怎么点都不会得到一个差的结果,规则设置得没有让人思考的欲望;
2、采用每个循环都重新绘制,可以寻找动态显示出细胞分裂、凋亡的方法。

学习到的点:

1、pygame需要在循环中不断获取事件不断更新,以达到动态游戏的目的。所以,如果没有这一步,窗口会显示无响应。
2、显示图像:先创建surface,再blit,再update。
3、获取鼠标点击相应位置的方法。