1、游戏规则
地雷随机埋设在“棋盘”方格里,挖到地雷为败,挖光全部无雷方格为胜。
2、游戏的空间表示
游戏发生在棋盘上,游戏的场景、规则,都体现在棋盘上。
棋盘,由“场景盘”和“逻辑盘”共同组成。
“场景盘”是玩家挖雷面对的棋盘。
“逻辑盘”是实现游戏规则所需的“雷区盘”、“空区盘”和“提示盘”。
“逻辑盘”由数组表示。
棋盘、游戏规则之类,由模块game_scene.py进行设置、表示等处理。
3、游戏的起点开端
挖雷游戏,始于game_scene.py的class GameScene的start()。
每一盘新游戏的开始,都是通过调用这个函数。其中包括:
整个程序启动之初,main.py的MainWindow的init()中的调用;
菜单选择新开一局时,on_action_New_triggered()中的调用;
菜单设置游戏时,on_action_Setup_triggered()中的调用;
棋盘上端按钮按下后,槽函数me()中的调用。
def start(self): self.setStatus(RUNNING) #设置棋局状态 self.mine_map = random_map(self.map_size, self.mines) # 布置地雷 self.hint_map = hint_map(self.mine_map) # 提示雷情 self.flag_map = np.zeros(self.map_size, dtype=np.bool) # bug 删除 self.open_map = np.zeros(self.map_size, dtype=np.bool) # 挖开的空白区 self.update() # 场景盘更新
注意!其中一句,如下所示,是有害无益的bug。
self.flag_map = np.zeros(self.map_size, dtype=np.bool)
程序中,所有与self.flag_map相关的语句,都应删除。
第一句,设置棋局状态,和最后一句,“场景盘”更新,放在后面说。
除去删除的bug,中间3句,分别是在“逻辑盘”上布雷、标志雷情、布置空白区域。
4、在逻辑盘上随机埋设地雷
def random_map(size, count): mine_map = np.zeros(size, dtype=np.bool) x, y = size while count > 0: rx = random.randint(0, x-1) ry = random.randint(0, y-1) if not mine_map[rx, ry]: count -= 1 mine_map[rx, ry] = True return mine_map
random_map是模块级全局函数。size是表示棋盘长宽的元组,count表示地雷数目。
从lib.py导入的np,即numpy。这里的np.zeros返回一个二维数组,
数组大小由size确定,如(3,3);数组的9个成员都是False。例如:
>>> mine_map = np.zeros((3,3), dtype=np.bool) >>> mine_map array([[False, False, False], [False, False, False], [False, False, False]], dtype=bool) >>> mine_map[1,1] False >>>
rx和ry是随机产生的整数。0 <= rx <= x-1,ry类似。
while循环的意思是说:如果地雷数目count > 0,
随机生成rx和ry作为坐标,并检测地雷逻辑盘mine_map[rx,ry]处是否有雷;
如果没有地雷,即该处值等于False,则将其值赋予True,并将地雷计数器减1;
如果此处有雷,即该处值等于True,则继续循环,生成新坐标布雷,直至全部完成。
5、在逻辑盘上标注提示
def hint_map(mine_map): x, y = mine_map.shape hint_map = np.zeros((x+2, y+2), dtype=np.uint8) for dx in range(x): for dy in range(y): if mine_map[dx, dy]: for tx in range(3): for ty in range(3): hint_map[dx+tx, dy+ty] += 1 return hint_map[1:x+1,1:y+1]
逻辑盘上的标注提示,就是在空白格上标注,其四周共埋着多少地雷。
这个函数的算法,比较巧妙,值得学习参考。
注意!mine_map是numpy建立的数组,具有shape属性。Python的列表,没有该属性。
提示盘hint_map比布雷盘mine_map整整大一圈。若mine_map = (10,10),则hint_map = (12,12)
这相当于把mine_map置放在hint_map的中间,避免了检测判断数组边界的麻烦,简化了具体算法。
外层的2个for循环,是为了遍历整个逻辑盘mine_map;
如果在盘格mine_map[dx,dy]处发现地雷,即值为True,则开始内层循环,
以dx,dy为原点,将它自身所处行列,及其右2列,其下2行,共计9个盘格中标注的地雷数目加1。
这句return hint_map[1:x+1,1:y+1],通过数据剪切,巧妙地把hint_map上移一行,左移一列,
与mine_map的实际情况完全一致了。
6、逻辑盘空白区域的标注
这是不能事先设置的。因为它是玩家博弈时,鼠标点击产生的结果。