生命游戏框架开发

介绍

用简单逻辑模拟生命运行规律,感觉很有趣。新建文件夹开新坑防止反悔弃坑。

开发思路

  1. 图形可视化开发测试,计划使用Tkinter制作网格UI
  2. 主体逻辑开发,list[rows][columns]管理格子状态
  3. UI控制界面大小,时间等状态,逻辑主体和UI控制脱离
  4. 绘制class diagram和logic diagram,方便维护

lib调用

主要由TKinter生成GUI,如下

1
2
from tkinter import *
from tkinter import ttk

TKinter 介绍:(https://pipirima.top/python/TKinter-71b0c27c039d/#more)

cell类说明

init里有7大variables:

Variable Type Explanation
x1,y1 Int rectangle左上角坐标
x2,y2 Int rectangle右下角坐标
Canvas Tkinter.Canvas cell所属Canvas object
neighbor Int cell附件neighbor数量
liveCell Bool 记录cell是否是存活状态

和该class 7大方法:

Name Usage Call for
_ init _(self,x1,y1,x2,y2,color,Canvas) 初始化class determine_state(),print()
switch_state(self) 转换状态 print()
ShiftPosition(self,dx,dy) 改变两组(x,y)坐标平移一个cell print()
ZoomPosition(self,dx,dy,rate) 以一定rate缩放cell,并平移dx,dy使鼠标所指点不变 print()
change_color(self,color) 使目标cell改变为输入的color的state print()
determine_state(self,color) 通过cell颜色判断存活状态 None
print(self) 根据liveCell判断颜色并在Canvas上print此cell None

所有cell会统一存储在global variable: cell_list[row][column]中管理
存活的cell会额外存储[row,column]到liveCell_list
每一轮新的cell会先放在newBorn_list,然后在loop结尾和liveCell_list合并

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
class cell:
def __init__(self,x1,y1,x2,y2,color,Canvas):
self.x1=x1
self.y1=y1
self.x2=x2
self.y2=y2
self.Canvas=Canvas
self.neighbor=0
self.liveCell=False
self.determine_state(color)
#self.print()

def determine_state(self,color):
if color == "white":
self.liveCell=True
else:
self.liveCell=False

def switch_state(self):
self.liveCell=not self.liveCell
self.print()

def ShiftPosition(self,dx,dy):
self.x1=self.x1+dx
self.y1=self.y1+dy
self.x2=self.x2+dx
self.y2=self.y2+dy
#self.print()

def ZoomPosition(self,dx,dy,rate):
self.x1=self.x1*rate+dx
self.y1=self.y1*rate+dy
self.x2=self.x2*rate+dx
self.y2=self.y2*rate+dy
#self.print()

def change_color(self,color):
#self.Canvas.delete(self)
if color=="white":
self.liveCell=True
else:
self.liveCell=False
self.print()

def print(self):
if self.liveCell:
self.Canvas.create_rectangle(int(self.x1), int(self.y1), int(self.x2), int(self.y2), fill = "white", outline="")
else:
self.Canvas.create_rectangle(int(self.x1), int(self.y1), int(self.x2), int(self.y2), fill = "grey", outline="")

Global Variables

1
2
3
4
5
6
7
8
9
10
11
12
13
14
x_pos=0 #mouse posiotn recording
y_pos=0

liveCell_list=[] #life cell list
newBorn_list=[] #has 3 neighbors but no live cell right now (new live cell in next round)

cell_list=[] #2-D list memory
cell_length=10 #length of singal cell
cell_length_max=40
cell_length_min=3
cell_gap=0.1*cell_length #gap in two cells
row_max=0
column_max=0
beginning=False

基础界面生成和操作

GUI界面

按钮设置

Submit Button

直接Call create_cell_matrix(row,column,cell_length,Can)来更新cell_list并重绘Canvas

1
2
def submit_command(row,column,Can):
create_cell_matrix(row,column,cell_length,Can)

Reset Button

重置成程序刚启动的样子。恢复原始rows columns,停止游戏运行

1
2
3
4
5
6
7
def reset_command(row,column,Can):
global beginning

insert_enterbox(entry_row,int(canvas_height/(cell_length+cell_gap))-1)
insert_enterbox(entry_column,int(canvas_width/(cell_length+cell_gap))-1)
create_cell_matrix(row,column,cell_length,Can)
beginning=False

Start Button

标记start sign, 运行游戏主程序并生成最初的liveCell_list

1
2
3
4
5
6
7
8
9
10
11
12
def start_command():
global beginning
global test_count

global row_max
global column_max

global cell_list
global liveCell_list

beginning=True
start_game(test_count)

Stop Button

标记stop sign停止主程序运行

1
2
3
4
def stop_command():
global beginning

beginning=False

鼠标event设置

Right Key Pressing

重新记录global鼠标位置

1
2
3
4
5
6
7
def rightKey_press(event):
global x_pos
global y_pos

x_pos=event.x
y_pos=event.y
#print("right key pressing")

Right Key and moving

读取移动距离dx,dy移动cell位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def rightKey_moving(event):
global x_pos
global y_pos
global cell_list

Can.delete(ALL) #remove all components
dx=event.x-x_pos
dy=event.y-y_pos
x_pos=event.x
y_pos=event.y

for row in cell_list:
for cell in row:
cell.ShiftPosition(dx,dy) #editing and painting cell

#print("right key pressing and moving")

Wheel Rolling

以rate数值缩放cell,并平移dx,dy使鼠标所指canvas位置看起来不变。
当cell length到达最大/最小尺寸时停止缩放

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
def wheel_rolling(event):
global cell_list
direction=event.delta
rate=0.9

#check length border
if direction>0:
if abs(cell_list[0][0].x1-cell_list[0][0].x2)<cell_length_max: #not reach minimum length
Can.delete(ALL) #remove all components
rate=2-rate #1.1 times
dx=event.x*(1-rate) #mouse position shift, zoom figure out from where the mouse is pointed
dy=event.y*(1-rate)

for row in cell_list:
for cell in row:
cell.ZoomPosition(dx,dy,rate) #editing and painting cell
#print("Zoom out")
else:
print("maximum size")
elif direction<0:
if abs(cell_list[0][0].x1-cell_list[0][0].x2)>cell_length_min: #not reach maximum length
Can.delete(ALL) #remove all components
rate=rate #0.9 times
dx=event.x*(1-rate)
dy=event.y*(1-rate)

for row in cell_list:
for cell in row:
cell.ZoomPosition(dx,dy,rate) #editing and painting cell
#print("Zoom in")
else:
print("minimum size")

Left Key Pressing

查找cell坐标并switch指定cell颜色和状态,edit liveCell_list

1
2
3
4
5
6
7
8
9
10
11
def left_press(event):
global cell_list
global liveCell_list

[row,column]=find_cell(event) #get target cell coordinate

cell_list[row][column].switch_state()
if cell_list[row][column].liveCell: #live cell
liveCell_list.append([row,column])
else:
liveCell_list.remove([row,column])

Left Key and moving

切换所有鼠标扫过的cell color到白色

1
2
3
4
5
6
7
8
9
10
11
def leftKey_moving(event):
global cell_list
global liveCell_list

[row,column]=find_cell(event) #get target cell coordinate

cell_list[row][column].change_color("white")
if [row,column] not in liveCell_list:
liveCell_list.append([row,column])

#print("left key pressing and moving")

其他底层函数

创建cell list和object cell

清除canvas上内容,重新创建cell object并记录在一个2-D list内。cell 通过固定的 cell length 确定下一个cell coordinate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def create_cell_matrix(row_num,column_num,cell_length,Can):
global row_max
global column_max
global cell_list
row_max=row_num #record max row number into global variable
column_max=column_num

Can.delete(ALL)

new_cell_list=[[0 for _ in range(column_num)] for _ in range(row_num)] #2-D list init
shift_length=cell_length+cell_gap
pre_x=2 #start positoin on canvas
pre_y=2

for row in range(row_num):
for column in range(column_num):
new_cell_list[row][column]=cell(pre_x, pre_y, pre_x+cell_length, pre_y+cell_length, "grey", Can) #create cell object in 2-D list system
pre_x+=shift_length #move to next position
pre_x=2
pre_y+=shift_length #move to next position

cell_list=new_cell_list

通过绘制灰色background,和白色线条来绘制网格。如果 cell gap小于1 像素则取消画线。
先绘制横线,然后纵线

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
def redraw_matrix():
global row_max
global column_max
global cell_list
global liveCell_list

#grey background
Can.create_rectangle(cell_list[0][0].x1, cell_list[0][0].y1, cell_list[row_max-1][column_max-1].x2, cell_list[row_max-1][column_max-1].y2, fill = "grey", outline="")
line_width=cell_list[0][1].x1-cell_list[0][0].x2
cell_distance=cell_list[0][1].x1-cell_list[0][0].x1

if line_width<1:
pass

else:
#draw vertical lines
line_height=cell_list[row_max-1][column_max-1].y2
x1=cell_list[0][0].x1-line_width
for i in range(column_max):
Can.create_rectangle(x1, 0, x1+line_width, line_height, fill = "white", outline="")
x1+=cell_distance

#draw horizonal lines
line_height=cell_list[row_max-1][column_max-1].x2
y1=cell_list[0][0].y1-line_width
for j in range(row_max):
Can.create_rectangle(0, y1, line_height, y1+line_width, fill = "white", outline="")
y1+=cell_distance

#paint white cell
for row,column in liveCell_list:
cell_list[row][column].print()

寻找cell rows and columns

鼠标event里有鼠标的coordinate,通过对比第一个cell的存贮坐标位置来修正shift,并除以 (cell length+cell gap) 来直接得到cell的rows and columns。
example:
event.x=40
event.y=80
cell[0][0].x1=20
cell[0][0].y1=20
cell shift=cell length + cell gap=40

rows=(event.x-cell[0][0].x1)/cell shift=(40-20)/40=0
columns=(80-20)/40=1

Hence mouse 所指cell为 cell[0][1]

1
2
3
4
5
6
7
8
def find_cell(event):
global cell_list

cell_shift=cell_list[0][1].x1-cell_list[0][0].x1 #varying cell length
column=int((event.x-cell_list[0][0].x1)/cell_shift) #row value
row=int((event.y-cell_list[0][0].y1)/cell_shift)

return row,column

刷新Canvas

游戏暂时不清楚为什么运行一段时间会速度变慢,测试得到如果定时刷新canvas components会恢复最开始的速度。

1
2
3
4
5
6
7
8
9
def refresh_screen(Can):
global row_max
global column_max

Can.delete(ALL)

for row in cell_list:
for cell in row:
cell.print() #refresh matrix