上一次,我们尝试了用Tkinter的GUI库来编写简单的游戏。今天我们进一步编写一个稍微复杂的游戏吧。我们今天要编写的是一个叫“生命游戏”的模拟游戏。这个游戏虽然简单,但是看起来很有趣,大家可以尝试一下。
什么是生命游戏?
这是一款模拟英国生物群体兴衰的环境游戏,由英国数学家约翰·霍顿·康威(John Horton Conway)设计。
详细介绍请看百度百科生命游戏
实际的运行效果如下:
如上图所示,在生命游戏中,生物(细胞)的生与死用二维矩形表示。红色圆圈是活细胞。并且有一个基本规则,即生物群体不得居住在人口稀少或人满为患的区域。
下一代生物的生死是通过检查生物的周围环境(8个方向)的活细胞个数来确定的。换句话说,生物的诞生和淘汰通过以下的简单规则来确定。
生活游戏规则如下:
1. 如果一个细胞周围有3个细胞为生(一个细胞周围共有8个细胞),则该细胞为生(即该细胞若原先为死,则转为生,若原先为生,则保持不变) 。
2. 如果一个细胞周围有2个细胞为生,则该细胞的生死状态保持不变;
3. 在其它情况下,该细胞为死(即该细胞若原先为生,则转为死,若原先为死,则保持不变)
开始编写游戏
那么接下来就让我们开始编写游戏吧。下面的程序中,当游戏启动时,我们随机生成初始化界面,然后根据圣明游戏的规则每300ms执行一次游戏。
from random import randint
from tkinter import *
# 变量・常量定义 --- (*1)
COLS, ROWS = [30, 20] # 定义舞台的大小
CW = 20 # 单元格的大小
data = [] # 保存舞台数据
for y in range(0, ROWS): # 随机初始化舞台
data.append([(randint(0, 9) == 0) for x in range(0, COLS)])
# 定义生命游戏的规则 --- (*2)
def check(x, y):
# 计算周围活细胞的数量
cnt = 0
tbl = [(-1, -1), (0, -1), (1, -1), (1, 0), (1, 1), (0, 1), (-1, 1), (-1, 0)]
for t in tbl:
xx, yy = [x + t[0], y + t[1]]
if 0 <= xx < COLS and 0 <= yy < ROWS:
if data[yy][xx]: cnt += 1
# 更具规则决定细胞的死活
if cnt == 3: return True # 诞生新细胞
if data[y][x]:
if 2 <= cnt <= 3: return True # 继续存活
return False # 细胞太少 or 细胞太多
return data[y][x]
# 下一代细胞数据的生成 --- (*3)
def next_turn():
global data
data2 = []
for y in range(0, ROWS):
data2.append([check(x, y) for x in range(0, COLS)])
data = data2 # 报数据替换成下一代细胞的数据
# 游戏界面 --- (*4)
win = Tk() # 生成主窗口
cv = Canvas(win, width = 600, height = 400) # キャンバスを作成
cv.pack()
# 绘制舞台 --- (*5)
def draw_stage():
cv.delete('all') # 清除原有界面内容
for y in range(0, ROWS):
for x in range(0, COLS):
if not data[y][x]: continue
x1, y1 = [x * CW, y * CW]
cv.create_oval(x1, y1, x1 + CW, y1 + CW,
fill="red", width=0) # 绘制活细胞
# 每隔300m重新绘制画面 --- (*6)
def game_loop():
next_turn() # 产生下一代细胞数据
draw_stage() # 绘制舞台
win.after(300, game_loop) # 指定重新绘制的时间
game_loop() # 循环执行游戏
win.mainloop() # 进入消息循环
将上述代码另存为“ lifegame-simple.py”。然后,命令行执行以下命令。
# Windows
python lifegame-simple.py
当运行程序,就能得到文章开头介绍的画面。
接下来就让我们看看具体的代码吧!
首先,在代码的开头(* 1),定义了变量和常量。其中,变量用于管理二维矩形舞台上细胞的生死状态。每一个单元格的初始状态是随机确定的。True代表活,False代表死亡。
在代码的(* 2)和(* 3)部分中,根据游戏规则确定下一代单元格的状态。(* 2)中的check()函数返回指定位置(x,y)的下一代细胞的生死状态。生死状态取决于该位置(8个方向)周围的活细胞数量。在这里,指定位置的8个方向的相对坐标保存在变量tbl中。然后,使用for.. in ..语法来循环计算每个单元格的生死状态。在(* 3)部分中,同样用check()函数来确认舞台上所有单元格的生死状态。
在代码的(* 4)部分中,创建一个窗口,并在其上放置可以绘制图形的画布(具体内容上一次已经介绍过了)。
在代码的(* 5)部分中,首先清除了所有现有图形内容,然后再重新绘制单元格。这里,使用create_oval()方法来绘制红色圆圈来代表每一个活细胞。
最后,在(* 6)部分中,每隔300毫米重新绘制游戏界面。after()函数的用法如下:
[语法格式] msec毫秒后、执行func函数
win.after(msec, func)
总结
今天,我们介绍了如何制作生命游戏。和之前的文章内容比较起来,可能稍微复杂了一点。
不过,总共的代码行数也不超过60行,所以,大家一边看代码说明,一边自己尝试编写,应该很快就能掌握。