基于Python实现的五子棋tkinter版小游戏 课程报告+源码

资源下载地址:https://download.csdn.net/download/sheziqiong/85677012
资源下载地址:https://download.csdn.net/download/sheziqiong/85677012

五子棋小游戏-tkinter版

一、实现内容

  • 图形界面
  • 局域网联机
  • 人机对战
  • 悔棋
  • 先后手
  • 重新开始
  • 导出/导入棋盘

游戏规则

假设俩个人轮流报数,可以报 1、2、3 这三个数,然后积分榜累加这俩个人报的数,最先加到 6 的人输

这个游戏存在先手优势,即谁最先报数,就有必胜的方案www.biyezuopin.vip

博弈树

博弈树的树叶表示游戏的结局

下图中方块表示乙报完数后的局面(此时甲要开始报数了),圆圈表示甲报完数后的局面,由图可知甲先报数

对于甲来说,第一次不能报 2 和 3,因为这样乙总有办法让甲输,即图中红色路线

如果甲报数 1,那么无论第二次乙报什么数,甲总有路线让乙输,即图中蓝色路线

极大极小搜索

报数游戏中,将甲(自己)获胜用 1 代替,将乙(对手)获胜用 -1 代替

以根节点(树的最顶端)作为第一个极大层(Max),极小层(Min)和极大层交替出现

极小层中选择子节点最小的数,极大层选择子节点最大的数

先手开始选择总是在偶数层(0、2、4…),而第二个选手开始选择总是在奇数层(1、3、5…),对应于先手位于极大层,第二个选手位于极小层,也就意味着,位于极大层的选手需要将自身利益最大化,会选择子节点中较大的那个,而位于极小层的选手会将对手的利益最小化,而选择子节点中最小的那个

根据上图可知,抽离博弈树的一部分后,这个子树的根节点是 -1,也就是说轮到乙选择了,局势变成这样的话,甲是不可能获胜的,因为乙肯定会选择对甲不利的 -1 这条路线

博弈树的最后结果

整个博弈树根节点的值所代表的就是先手在这场博弈中的结果(如果比赛双方都遵循最大最小值原则)

那些 -1 都是对甲不利的局面,也就代表了本场比赛的决胜权被对手掌握

井字游戏

打分函数

乙会尽力让评分降低,甲需要让评分更高,因此通过深度优先遍历,选择一条分值高的路径

打分函数根据日常经验来制定

令 α 为 -∞ 是为了让接下来任意一个比这个大的数可以替换掉 -∞

  1. 根据给定深度进行遍历(图中仅仅遍历 5 层)

    首先将父节点的 α、β 值传递到叶子节点

    negamax(board, candidates, role, i, MIN, MAX)
    

  2. 进行回溯,节点处于 Max 层,因此 α 变成 5

    if v["score"] > best["score"]:
        best = v
    # best在遍历子节点时选取分数最高f
    
  3. 继续遍历兄弟树

    从子节点中 7、4、5 选一个最小的 4 作为 β 的值

    由于该兄弟节点的评分较小,回溯时不会改变 Max 层的 α 值

  4. 继续回溯至第 1 层,由于是最小层,因此 β 改为 5(目前子节点最小只有 5)

  5. 继续遍历第一层第一个节点的右子树

  6. 以此类推,获得的最优选择是评分为 6 的路径

    这是全部遍历的情况,需要继续优化,参考α-β剪枝

代码实现

# minimax.py
def r(board, deep, alpha, beta, role, step, steps):
	# ...

    # 获取当前j棋盘分数
    _e = board.evaluate(role)

    leaf = {"score": _e, "step": step, "steps": steps}

    # 搜索到底(搜索范围:给定 depth ~ 0)或者已经胜利, 返回叶子节点
    if (deep <= 0 or func.greatOrEqualThan(_e, S["FIVE"])
            or func.littleOrEqualThan(_e, -S["FIVE"])):
        return leaf

    best = {"score": MIN, "step": step, "steps": steps}
    
    onlyThree = step > 1 if len(board.allSteps) > 10 else step > 3
    points = board.gen(role, onlyThree)  # 启发式评估, 获取整个棋盘下一步可能获胜的节点
    
    # 如果没有节点, 即当前节点是树叶, 直接返回
    if len(points) == 0:
        return leaf

    # 对可能获胜节点进行遍历
    for item in points:
        board.AIput(item["p"], role)  # 在可能获胜的节点上模拟落子
        _deep = deep - 1  # 深度减一

        _steps = steps.copy()  # 复制一下之前的步骤
        _steps.append(item)  # 步骤增加当前遍历的节点

        # 进行递归, 总步数加一
        v = r(board, _deep, -beta, -alpha, func.reverse(role), step + 1, _steps)

        # 下一步是对手, 对手分数越高, 代表自己分数越低, 所以分数取反
        v["score"] = - v["score"]

        board.AIremove(item["p"])  # 在棋盘上删除这个子(恢复原来的棋盘)
        
        if v["score"] > best["score"]:
            best = v
        
     # ...

    return best
# minimax.py
def negmax(..., alpha, beta, ...):
    # 当前处于极大层(根节点), 对 candidates 里的落子点进行遍历
    # 找到最优解(alpha最大的)
    for item in candidates:
        # 在棋盘上落子
        board.AIput(item["p"], role)
        # 注意, alpha/beta 交换了, 因为每一层的 alpha 或 beta 只有一个会变
        # 但是递归时需要传那个不变的进去
        v = r(board, deep - 1, -beta, -alpha, func.reverse(role), 1, [item])
        v["score"] = -1 * v["score"]
        alpha = max(alpha, v["score"])
        # 从棋盘上移除这个子
        board.AIremove(item["p"])
        item["v"] = v
    return alpha

二、工作量

/(ㄒoㄒ)/~~ 左右互博和局域网联机做了我快一个星期, 一开始用的 pygame, 感觉按钮啊提示框啥的都要自己实现, 有点儿麻烦, 所以改用 tkinter了, 没想到这个也挺麻烦的, 网上的教程也很少

基本原理

根据评分表对某个位置进行评分

图中白子位置上

  • —:+++AO+++
  • |:+++AO+++
  • \:+++O+++
  • /:+++AO+++

A 代表敌方棋子,O 代表我方棋子

然后遍历棋盘的每一个位置,找到评分最高的位置落子,缺点是只顾眼前利益,电脑只能预测一步

Alpha Beta 剪枝

核心是固定深度

剪去 MAX 层叫 Alpha 剪枝

剪去 MIN 层叫 Beta 剪枝

触发剪枝的条件

  • 当极小层某节点的 α 大于等于 β 时不需要继续遍历其子节点

    下图中 α=5,说明我们存在一个使我们得分至少为 5 的情况,如果在遍历子节点的过程中,发现 β 小于 α 了,不会继续遍历后面的节点,因为后面的分数如果更大,对手不可能会选,如果后面的分数更小,对手肯定会选,那我们更加不能选这条路,因此不需要继续考虑了

    对于极大极小值搜索一章的博弈树进行剪枝可得

  • 当极大层某节点的 α 小于等于 β 时不需要继续遍历

    因为如果后面的分数更低,我们没必要选,如果后面的分数高,会导致这条路分数更高,对手不会选这条路,没必要继续考虑

代码实现

# minimax.
def r(..., alpha, ...):
    # ...

    # 将 alpha 值与子节点分数做比较, 选出最大的分数给 alpha
    alpha = max(best["score"], alpha)

    # alpha-beta 剪枝
    if func.greatOrEqualThan(a, beta):
        ABcut += 1  # 剪枝数加一
        v["score"] = MAX - 1  # 被剪枝的用极大值来记录, 但是必须比 MAX 小
        v["abcut"] = 1  # 剪枝标记
        return v

参考资料

  1. 极大极小值搜索和alpha-beta剪枝

Zobrist 散列算法

基本过程

不同的走法最终达到的局势相同, 则可以重复利用缓存中原来计算过的结果

根据 ABC = ACB 可知, 不同步骤只要进行异步运算的值相同, 则最终值相同, 利用 code 作字典的键值可以快速找到缓存中的数据

评分表

特征分数
活一10
活二100
活三1000
活四100000
连五10000000
眠一1
眠二10
眠三100
眠四10000


# 三、结果

1. 首页

   [![](https://img-blog.csdnimg.cn/img_convert/e308cadce5daff23b28ed54de3dbc2bf.png)](https://imgtu.com/i/IOnmWQ)

2. 本地开局

   ![](https://img-blog.csdnimg.cn/img_convert/3ae8f21a467aa7a88b9f4cd56100e1ba.png)

3. 获胜界面

   ![](https://img-blog.csdnimg.cn/img_convert/cf2320cd47c975deb1864953de693dc8.png)

4. 网络联机

   > 需要先运行 server.py

   ![](https://img-blog.csdnimg.cn/img_convert/b01454699e9922194f1ad69b866742ef.png)

   询问是否接受对战邀请

   ![](https://img-blog.csdnimg.cn/img_convert/a3fcf914f618c7c01e2e5b2e8e28200c.png)

   可边下棋边聊天

   ![](https://img-blog.csdnimg.cn/img_convert/0aa02686073b6d79ea7ad7bee533e43a.png)

   可拒绝/接受对方悔棋

   ![](https://img-blog.csdnimg.cn/img_convert/7d3f36ceaed84a653907ae13e490600b.png)

5. 人机模式

   ![](https://img-blog.csdnimg.cn/img_convert/92483faaa416b3e826c3f01ab2d2097c.png)

[资源下载地址](https://download.csdn.net/download/sheziqiong/85677012):https://download.csdn.net/download/sheziqiong/85677012
[资源下载地址](https://download.csdn.net/download/sheziqiong/85677012):https://download.csdn.net/download/sheziqiong/85677012


  • 2
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值