井字棋是一种在3 * 3格子上进行的连珠游戏,又称井字游戏。井字棋的游戏有两名玩家,其中一个玩家画圈,另一个玩家画叉,轮流在3 * 3格子上画上自己的符号,最先在横向、纵向、或斜线方向连成一条线的人为胜利方。如图1所示为画圈的一方为胜利者。
图1 井字棋
本任务要求编写程序,实现具有人机交互功能的井字棋。
分析
根据任务描述的井字棋游戏的规则,下面模拟一次游戏的流程如图2所示。
图2 井字棋游戏流程
图2中的描述的游戏流程如下:
- 重置棋盘数据,清理之前一轮的对局数据,为本轮对局做好准备。
- 显示棋盘上每个格子的编号,让玩家熟悉落子位置。
- 根据系统随机产生的结果确定先手玩家(先手使用X)。
- 当前落子一方落子。
- 显示落子后的棋盘。
- 判断落子一方是否胜利?若落子一方取 得胜利,修改玩家得分,本轮对局结束,跳转至第(9)步。
- 判断是否和棋?若出现和棋,本轮对局结束,跳转至第(9)步。
- 交换落子方,跳转至第(4)步,继续本轮游戏。
- 显示玩家当前对局比分。
以上流程中,落子是游戏中的核心功能,如何落子则是体现电脑智能的关键步骤,实现智能落子有策略可循的。按照井字棋的游戏规则:当玩家每次落子后,玩家的棋子在棋盘的水平、垂直或者对角线任一方向连成一条直线,则表示玩家获胜。因此,我们可以将电脑的落子位置按照优先级分成以下三种:
(1)必胜落子位置
我方在该位置落子会获胜。一旦出现这种情况,显然应该毫不犹豫在这个位置落子。
(2)必救落子位置
对方在该位置落子会获胜。如果我方暂时没有必胜落子位置,那么应该在必救落子位置落子,以阻止对方获胜。
(3)评估子力价值
评估子力价值,就是如果在该位置落子获胜的几率越高,子力价值就越大;获胜的几率越低,子力价值就越小。
如果当前的棋盘上,既没有必胜落子位置,也没有必救落子位置,那么就应该针对棋盘上的每一个空白位置寻找子力价值最高的位置落子。
要编写一个评估子力价值的程序,需要考虑诸多因素,这里我们选择了一种简单评估子力价值的方式——只考虑某个位置在空棋盘上的价值,而不考虑已有棋子以及落子之后的盘面变化。下面来看一下在空棋盘上不同位置落子的示意图,如图3所示。
图3 棋盘落子示意图
观察图3不难发现,玩家在空棋盘上落子的位置可分为以下3种情况:
- 中心点,这个位置共有4个方向可能和其它棋子连接成直线,获胜的几率最高。
- 4个角位,这4个位置各自有3个方向可能和其它棋子连接成直线,获胜几率中等。
- 4个边位,这4个位置各自有2个方向可能和其它棋子连接成直线,获胜几率最低。
综上所述,如果电脑在落子时,既没有必胜落子位置,也没有必救落子位置时,我们就可以让电脑按照胜率的高低来选择落子位置,也就是说,若棋盘的中心点没有棋子,则选择中心点作为落子位置;若中心点已有棋子,而角位没有棋子,则随机选择一个没有棋子的角位作为落子位置;若中心点和四个角位都有棋子,而边位没有棋子,则随机选择一个没有棋子的边位作为落子位置。
井字棋游戏一共需要设计4个类,不同的类创建的对象承担不同的职责,分别是:
(1)游戏类(Game):负责整个游戏流程的控制,是该游戏的入口。
(2)棋盘类(Board):负责显示棋盘、记录本轮对局数据、以及判断胜利等和对弈相关的处理工作。
(3)玩家类(Player):负责记录玩家姓名、棋子类型和得分、以及实现玩家在棋盘上落子。
(4)电脑玩家类(AIPlayer):是玩家类的子类。在电脑玩家类类中重写玩家类的落子方法,在重写的方法中实现电脑智能选择落子位置的功能。
设计后的类图如图4所示。
图4 类结构图
本任务中涉及到多个类,为保证程序具有清晰的结构,可以将每个类的相关代码分别放置到与其同名的py文件中。另外,由于Player和AIPlayer类具有继承关系,可以将这两个类的代码放置到player.py文件中。
运行结果: