技术路线
GUI的实现
- 使用PyQt技术作为基础。PyQt是一个支持多平台的客户端开发SDK,使用它实现的客户端可以运行在目前几乎所有主流平台之上。
- 使用PyQt,Qt设计器实现UI,通过
pyuic4 -x -o main_page.py untitled.ui
命令将设计好的xml文件转换为python程序。 - 继承纯UI的mainWindow类,重写setupUI方法,在UI建立完成之后进行控件的信号-槽的绑定。
- 在子类中完成各类事件的响应。
棋盘的绘制
- PyQt提供了丰富的控件,如TableView,虽然和棋盘有相似之处,但提供的API过于死板。所以我决定用Widget绘制一个棋盘。
- 建立一个QiPan类,继承于
QtGui.QWidget
实现它的paintEvent
接口,在此接口中可进行Widget的绘制。 - 将QiPan的长宽和棋盘格子数进行计算,绘制横纵的直线,完成棋盘的绘制。
- 将QiPan类实例化,得到的对象在主窗口setupUI的时候添加到上一层的容器内。
落子位置的判定
- 因为用户不可能每次恰好点中横纵线的交点处,所以要对用户点击的位置的坐标进行判定,判断用户想要落子到哪个点
- 如图,我将每个点的有效范围扩充至它到邻点的1/2组成的矩形。如图阴影区域:
- 如此,棋盘中每个点都能映射到对应的交点上。
胜利条件判定算法
- 因为每次落棋都需要判定当前状态是否有一方胜利,所以胜利条件的判定应当在每次落棋之后进行。
- 又因为每次落棋时,落子的一方只有(赢/还没赢)两种状态,所以只需判断落子的一方是否赢得比赛,另一方的棋子无需判断。所以只需从当前落子的棋的4条线上进行判定是否连珠即可。
4条线分为8个方向,两两对称。因为情况很多,所以我构造了两个数组,dx和dy,如图:
将数组从[0,3][4,7]分为两部分,分别对应四条线每条线的两个方向。
- 如此只需循环遍历8个方向,每个方向出发的4个棋子子,统计这些棋子有多少和中心棋子颜色相同,每个方向的除以4取余数的存储计算结果。
- 如果某个方向上第x个棋子已经与中心棋子不同,立即跳出循环,不再继续遍历该方向。
- 核心判断代码:
1 | def check_line(self, x, y, map, width, height): |
悔棋功能的实现
- 建立两个栈,分别用作黑棋和白棋的棋子状态,在每次落子时push进该棋子的状态。
- 当用户点击悔棋按钮时,从相应的栈中pop出来,恢复上一状态。
工程结构
采用MVC的模式
- build: 生成可执行文件目录
controller:UI的相关控制,相当于C的作用
- MyMainWindow.py:继承Ui_MainWindow类,主要控制主窗口
- QiPan.py:棋盘类,继承于QWidget,用于棋盘的相关控制
libs: 各项判定条件的算法,充当了一部分Model的作用
- AI.py:AI类,用来判定输赢条件
- BitmapTools.py:封装了对落子在棋盘map中的位置的判定方法
resource:图片资源文件夹
- ui:UI相关代码,View的作用
- main_page.py:UI界面的python代码
- untitled.ui:UI界面的xml代码
阿萨德
- app.py:主函数入口,用于启动MainWindow
设计模式
单例模式
AI类和BitMapTools都采用单例模式:
好处在于可以只用实例化一次,占用资源少。在棋盘初始化的时候就加载这两个工具类,将它们的实例保存为成员,以后可以频繁地使用这个实例。
1 | class BitMapTools: |
观察者模式
继承UI相关的类,并重写setupUI,在其中订阅各类点击事件,在相关的函数中处理这些事件发生之后的工作。
1 | self.father.restart_btn.clicked.connect(self.restart) |
这样每一个事件对应一个函数,使得结构更加清晰。