接python+pygame实现扫雷游戏之一,继续写游戏局的类:
五、
mineblock.py
# -*- coding: utf-8 -*-
import randomfrom blockstatus import *
from mine import *# 9*9-10 16*16-40 30*16-99 30*24-**
# BLOCK_WIDTH = 30
# BLOCK_HEIGHT = 16
# MINE_COUNT = 99 # 地雷数
BLOCK_WIDTH = 9
BLOCK_HEIGHT = 9
MINE_COUNT = 10 # 地雷数SIZE = 20 # 块大小#Mine构成游戏类MineBlock
class MineBlock: def __init__(self):self._block = [[Mine(i, j) for i in range(BLOCK_WIDTH)] for j in range(BLOCK_HEIGHT)]# 埋雷# 使用random.sample函数,它的作用是从指定序列中随机获取指定长度的片断并随机排列,结果以列表的形式返回,# 返回结果是无序的,省去了写循环读取随机数的工作。# sample函数不会修改原有序列。for i in random.sample(range(BLOCK_WIDTH * BLOCK_HEIGHT), MINE_COUNT):self._block[i // BLOCK_WIDTH][i % BLOCK_WIDTH].value = 1 #一维转二维def get_block(self):return self._block#定义属性blockblock = property(fget=get_block)def getmine(self, x, y):return self._block[y][x]def open_mine(self, x, y):# 踩到雷了if self._block[y][x].value:self._block[y][x].status = BlockStatus.bombreturn False# 先把状态改为 openedself._block[y][x].status = BlockStatus.openedaround = _get_around(x, y)_sum = 0for i, j in around:if self._block[j][i].value:_sum += 1self._block[y][x].around_mine_count = _sum# 如果周围没有雷,那么将周围8个未中未点开的递归算一遍# 这就能实现一点出现一大片打开的效果了if _sum == 0:for i, j in around:if self._block[j][i].around_mine_count == -1:self.open_mine(i, j)return Truedef double_mouse_button_down(self, x, y):if self._block[y][x].around_mine_count == 0:return Trueself._block[y][x].status = BlockStatus.doublearound = _get_around(x, y)sumflag = 0 # 周围被标记的雷数量for i, j in _get_around(x, y):if self._block[j][i].status == BlockStatus.flag:sumflag += 1# 周边的雷已经全部被标记result = Trueif sumflag == self._block[y][x].around_mine_count:for i, j in around:if self._block[j][i].status == BlockStatus.normal:if not self.open_mine(i, j):result = Falseelse:for i, j in around:if self._block[j][i].status == BlockStatus.normal:self._block[j][i].status = BlockStatus.hintreturn resultdef double_mouse_button_up(self, x, y):self._block[y][x].status = BlockStatus.openedfor i, j in _get_around(x, y):if self._block[j][i].status == BlockStatus.hint:self._block[j][i].status = BlockStatus.normaldef _get_around(x, y):"""返回(x, y)周围的点的坐标"""# 这里注意,range 末尾是开区间,所以要加 1return [(i, j) for i in range(max(0, x - 1), min(BLOCK_WIDTH - 1, x + 1) + 1)for j in range(max(0, y - 1), min(BLOCK_HEIGHT - 1, y + 1) + 1) if i != x or j != y]
由于这里有一定的逻辑,所以要看一下:
5.1 首先是一些常量的定义:
BLOCK_WIDTH = 9
BLOCK_HEIGHT = 9
MINE_COUNT = 10 # 地雷数
这里就是扫雷的初级模式
SIZE = 20 指的是每个格子的大小
5.2 在MineBlock类的__init__中,开始是初始化出一个二维的数组_block,数组大小是:BLOCK_WIDTH *BLOCK_HEIGHT,里面每一个元素就是一个Mine。同时利用random.sample函数产生MINE_COUNT 个地雷,将数组的相应元素的value赋值为1,表示地雷,其他的就不是地雷,其value值为默认值0.
5.3 接着定义了一个属性block,指向上面定义的二维数组
5.4 定义方法getmine,获取指定位置的Mine对象
5.5 这里先介绍在最后面的辅助的方法:_get_around(x, y): 返回(x, y)周围的点的坐标,因为当鼠标左右按键同时按下时;以及点击某点时,假如它恰好位于不是雷的地方,同时周围还没有雷,会开出一大片这两种时候,会去遍历(x,y)周围的8个方向,所以用_get_around函数获取周围点的坐标的集合。
5.6 主要逻辑函数是open_mine,由于这里不是界面元素,所以本质就是计算每个Mine对象的状态,并赋值给_block中相应下标的那个元素的status。(到这里,应该能够体会到前面Mine类中那样写属性的用处了。)就是这个逻辑:
5.6.1 先判断坐标处是不是雷,
5.6.1.1若是雷,就将状态设置为bomb,并返回False,结束该函数;
5.6.1.2否则,就先将该坐标处状态设置为opened,同时调用5.5的辅助方法_get_around获取 周围的坐标集合,计算这些集合对应的value之和,将这个和赋值给该点的属性 around_mine_count(注意,这个属性默认值是-1),表示该点坐标周围的雷的总和。
5.6.2 如果上述的雷的总和是0,表示周边没有雷,就遍历这个around集合,分别递归调用一下本 方法open_mine,执行完后返回True
5.7 有了5.5和5.6的基础,下面的鼠标左右双击逻辑就好理解了,它对应的是double_mouse_button_down函数和double_mouse_button_up函数,结合扫雷的实际操作很容易理解,将周边可能出现雷的地方显示一下就可以了(就是说如果已经标记了所有雷,则打开周围一圈, 如果还未标记完所有雷,则有一个周围一圈被同时按下的效果,状态如果是normal,就设置成hint),鼠标弹起up时恢复原状(状态如果是hint,就设置成normal)。
待续。。。
python+pygame实现扫雷游戏之三