Tic-Tac-Toe可能棋局遍历的实现(python)

news2024/12/30 3:47:05

目录

1. 前言

2. 算法流程

3. 代码实现

4. 一个思考题:代码实现中的一个坑

5. 结果正确吗?


1. 前言

        在上一篇博客中:Tic-Tac-Toe可能棋局搜索的实现(python)_笨牛慢耕的博客-CSDN博客Tic-Tac-Toe中文常译作井字棋,即在3 x 3的棋盘上,双方轮流落子,先将3枚棋子连成一线的一方获得胜利。Tic-Tac-Toe变化简单,可能的局面和棋局数都很有限(相比中国象棋、日本象棋、围棋等来说连九牛一毛都不到!具体有多少可能的局面以及可能的棋局数,本系列完成以后就可以给出答案了),因此常成为和搜寻的教学例子,同时也是的一道好题目。本系列考虑实现一个Tic-Tac-Toe AI,以由浅入深循序渐进的方式来逐步完成这个实现。https://blog.csdn.net/chenxy_bwave/article/details/128506352        实现了搜索Tic-Tac-Toe游戏的某个棋局的python程序。

        接下来的问题是,在Tic-Tac-Toe游戏中总共有多少种可能的棋局呢?注意,棋局是指在两个player交替下棋直到终局的过程中所导致的棋盘状态变化的序列。所以,即便所包含的棋盘状态集合完全相同,但是如果棋盘状态出现的顺序不同的话,也是不同的棋局。

        游戏规则、基本表示方法等参考上一篇博客,本文不做赘述。

        本文在上一篇的基础上进一步实现搜索Tic-Tac-Toe游戏的所有可能棋局的实现。

2. 算法流程

        如果只需要搜索某一个可能的棋局,采用深度优先搜索或者广度优先搜索都可以。但是,如果要遍历所有可能的棋局,则需要采用深度优先搜索的方式来实现遍历,也称深度优先路径遍历。

Dfs(path):

       s = path[-1] # 取路径列表中最后一个状态作为当前状态

       查找s的所有邻接节点(即从s状态再下一手棋可能到达的状态)à neighbor_list

       For neighbor in neighbor_list:  # 遍历s所有邻接节点

              If neighbor not in path:     # 如果该节点已经在path中则跳过

                     Append neighbor to the end of path

                     If neighbor 是终局状态:

                            将path加入到path_list中去  # path_list是一个全局变量

                     Else:

                            递归调用:dfs(path)

                     Path.pop()  # 这个不能少!

        主程序中以初始状态开始调用dfs()即可求出所有的路径(Tic-Tac-Toe的所有可能棋局)。 

        这个算法流程中其实是留了一个坑。。。

3. 代码实现

# -*- coding: utf-8 -*-
"""
Created on Sun Jan  1 16:15:19 2023

@author: chenxy
"""

# -*- coding: utf-8 -*-
"""
Created on Sat Dec 31 12:53:10 2022

@author: chenxy
"""

import random
from collections import deque
import time
import itertools

win_comb  = ((0,1,2),(3,4,5),(6,7,8),(0,3,6),(1,4,7),(2,5,8),(0,4,8),(2,4,6))
path_list = [] # used to hold all the possible paths.
path_cnt  = 0
dfs_cnt   = 0

def is_endofgame(s):

def find_neighbor(s):

def print_board(s):

def dfs(path):
    global path_list
    global path_cnt
    global dfs_cnt
    
    dfs_cnt = dfs_cnt + 1
    # if dfs_cnt < 10: # for debug
        # print('dfs_cnt = {0}: path = {1}'.format(dfs_cnt,path))
    
    s = path[-1]
    neighbors = find_neighbor(s)

    for neighbor in neighbors:
        if neighbor not in path:
            
            ## This segment is wrong!
            # path.append(neighbor)
            # end_flag, winner = is_endofgame(neighbor)
            # if end_flag:
            #     path_list.append(path)
            #     path_cnt = path_cnt + 1
            #     if path_cnt < 10:
            #         # print('path_cnt = {0}, path = {1}, len(path_list) = {2}'.format(path_cnt,path,len(path_list)))
            #         print('path_cnt = {0}, path = {1}, path_list = {2}'.format(path_cnt,path,path_list))
            # else:
            #     dfs(path)
            # path.pop() # pop-out the last added node to return to the upper layer

            end_flag, winner = is_endofgame(neighbor)
            if end_flag:
                # path_list.append(tuple([path,winner]))
                path_list.append(path + [neighbor])
                path_cnt = path_cnt + 1
                # if path_cnt < 10: # for debug
                    # print('path_cnt = {0}, path = {1}, path_list = {2}'.format(path_cnt,path,path_list))
            else:
                dfs(path + [neighbor])

    # return path_list

if __name__ == '__main__':

    # Initialization
    s0     = tuple([0] * 9)
    path   = [s0]
    tStart = time.time()
    dfs(path)
    tStop  = time.time()
    
    state_set = set()
    # state_list = []
    # for path in path_list:
    for k in range(path_cnt):
        path = path_list[k]
        # print('k = {0}, path = {1}'.format(k,path))
        for s in path:
            state_set.add(s)
    # state_list = list(itertools.chain(*path_list))
    
    print('Totally there are {0} games'.format(len(path_list)))
    print('Totally there are {0} board states'.format(len(state_set)))
    print('Time cost:  {0:6.2f} seconds'.format(tStop-tStart))
                

        is_endofgame(s),find_neighbor(s),print_board(s)等三个函数的代码参见上一篇。

        运行结果:

        Totally there are 255168 games
        Totally there are 5478 board states

        也就是说,总共有255168中棋局,但是可能的盘面状态数要少得多,只有5478种状态。

        其中,几种可能的棋局如下所示(调用print_board可以打印出更容易看的盘面状态变化图):

path_list = [

[(0, 0, 0, 0, 0, 0, 0, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0, 1), (0, 0, 0, 0, 0, 0, 0, 2, 1), (0, 0, 0, 0, 0, 0, 1, 2, 1), (0, 0, 0, 0, 0, 2, 1, 2, 1), (0, 0, 0, 0, 1, 2, 1, 2, 1), (0, 0, 0, 2, 1, 2, 1, 2, 1), (0, 0, 1, 2, 1, 2, 1, 2, 1)],

[(0, 0, 0, 0, 0, 0, 0, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0, 1), (0, 0, 0, 0, 0, 0, 0, 2, 1), (0, 0, 0, 0, 0, 0, 1, 2, 1), (0, 0, 0, 0, 0, 2, 1, 2, 1), (0, 0, 0, 0, 1, 2, 1, 2, 1), (0, 0, 0, 2, 1, 2, 1, 2, 1), (0, 1, 0, 2, 1, 2, 1, 2, 1), (0, 1, 2, 2, 1, 2, 1, 2, 1), (1, 1, 2, 2, 1, 2, 1, 2, 1)],

[(0, 0, 0, 0, 0, 0, 0, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0, 1), (0, 0, 0, 0, 0, 0, 0, 2, 1), (0, 0, 0, 0, 0, 0, 1, 2, 1), (0, 0, 0, 0, 0, 2, 1, 2, 1), (0, 0, 0, 0, 1, 2, 1, 2, 1), (0, 0, 0, 2, 1, 2, 1, 2, 1), (0, 1, 0, 2, 1, 2, 1, 2, 1), (2, 1, 0, 2, 1, 2, 1, 2, 1), (2, 1, 1, 2, 1, 2, 1, 2, 1)],

[(0, 0, 0, 0, 0, 0, 0, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0, 1), (0, 0, 0, 0, 0, 0, 0, 2, 1), (0, 0, 0, 0, 0, 0, 1, 2, 1), (0, 0, 0, 0, 0, 2, 1, 2, 1), (0, 0, 0, 0, 1, 2, 1, 2, 1), (0, 0, 0, 2, 1, 2, 1, 2, 1), (1, 0, 0, 2, 1, 2, 1, 2, 1)]]

4. 一个思考题:代码实现中的一个坑

        前面说了算法流程(伪代码描述)留了一个坑。这个坑把我坑惨了,花了两个小时才终于看明白咋回事。

        一上来按照算法流程的描述,在dfs()函数里面是如下实现的:

            ## This segment is wrong!
            # path.append(neighbor)
            # end_flag, winner = is_endofgame(neighbor)
            # if end_flag:
            #     path_list.append(path)
            #     path_cnt = path_cnt + 1
            #     if path_cnt < 10:
            #         # print('path_cnt = {0}, path = {1}, len(path_list) = {2}'.format(path_cnt,path,len(path_list)))
            #         print('path_cnt = {0}, path = {1}, path_list = {2}'.format(path_cnt,path,path_list))
            # else:
            #     dfs(path)
            # path.pop() # pop-out the last added node to return to the upper layer

        然后,运行结果始终是不对的。。。经过了两个小时的奋斗,终于想明白了这个问题^-^。这里先不解开谜底。有兴趣的小伙伴可以自己那这段错误的代码运行一下然后看看能不能想明白它为什么错了。

5. 结果正确吗?

        上面的运行结果表明有255168种棋局,有5478种盘面状态。但是,这个结果对吗?

        严格地来说是不对的。

        因为以上实现没有考虑Tic-Tac-Toe游戏的棋盘的对称性(包括90旋转对称以及对角线对称)。举个例子说,第一手下在四个角上的任意一个角上本质上都是一样的。考虑了对称性所导致的重复后,总的可能棋局数和盘面状态数会大幅度减小。

        但是,如何将对称性考虑进去进行去重(repetition removal)处理以得到真正的不同棋局数和盘面状态数结果呢?

        且听下回分解。。。

                 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/132369.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基础数学(五)——数值积分

文章目录考试要求基础概念代数精度&#xff08;必考题&#xff09;代数精度的定义求代数精度的例题&#xff08;期末考试数值积分第一个大题&#xff09;数值积分公式的构造插值型求积公式&#xff08;必考题&#xff09;插值型数值积分公式定理Newton-Cotes求积公式Cotes公式代…

webpack 学习

1.拆分、合并 webpack-merge devlopment production 2.webpack-dev-serve devServer: { port contentBase progress open compress proxy:{ xxx:{ target:..., pathRewrite:{ "^/api" }, changeOrigin:true } } } 3.处理样式 css module:{ rules:[ { test:/\.sc…

qt开关控件设计(手把手从零开始)

从零开始手把手教你设计自己的qt控件1 说明1.1 显示效果1.2 控件特性1.3 设计方法2 控件需求分析2.1 必要需求2.1 顺带需求&#xff08;锦上添花&#xff09;3 功能设计3.1 设计思路&#xff08;重点内容&#xff09;3.2 自适应大小3.3 开关动画3.4 控件绘制4 总体代码1 说明 …

推动新能源越野场景革命 坦克品牌开创越野新生态

近日&#xff0c;坦克品牌以“创领越野新生态”为主题&#xff0c;携多款车型登陆第二十届广州国际汽车展览会。秉持“以用户为中心”&#xff0c;坦克品牌围绕技术与生态双线出击&#xff0c;正式亮相坦克500 PHEV长续航版、生活方式共创平台TANK Life。技术创领&#xff0c;打…

docker-ui创建使用

首先需要安装docker: apt install docker.io composer也需要安装&#xff1a; apt install composer docker查找docker-ui镜像&#xff1a; docker search docker-ui 在列表里选一个镜这里就选第一个。 docker pull builtdock/docker-ui 然后直接docker run 使用的时候&#x…

DSL操作ElasticSearch基础命令

文章目录一、DSL操作ES-RESTful风格二、DSL操作索引库2.1 PUT 添加索引2.2 GET 查询索引2.3 DELETE 删除索引2.4 POST 打开/关闭索引库三、DSL操作映射3.1 数据类型3.1.1 简单数据类型3.1.2 复杂数据类型3.2 创建索引库并设置映射3.2.1 语法格式3.2.2 举例3.3 查询索引库映射3.…

python中的类型注解

目录 一.类型注解 变量的类型注解 类型注解的语法 类型注解主要功能在于: 函数方法的类型注解 函数&#xff08;方法&#xff09;形参进行类型注解 函数&#xff08;方法&#xff09;返回值进行类型注解 小结 Union类型 小结 一.类型注解 变量的类型注解 思考 为什么…

Android美团多渠道打包Walle集成

一、为什么使用美团多渠道打包的方式&#xff1f; 打包更加快速 传统的通过productFlavors渠道包的方式&#xff0c;渠道10个以内还可以接受&#xff0c;如果100个渠道包&#xff0c;每个包需要打5Min,就是将近10个小时的打包&#xff0c;而采用美团Walle多渠道打包的方式只需…

PyTorch 2.0 推理速度测试:与 TensorRT 、ONNX Runtime 进行对比

PyTorch 2.0 于 2022 年 12 月上旬在 NeurIPS 2022 上发布&#xff0c;它新增的 torch.compile 组件引起了广泛关注&#xff0c;因为该组件声称比 PyTorch 的先前版本带来更大的计算速度提升。 这对我们来说是一个好消息&#xff0c;训练时间改进的结果令人印象深刻。PyTorch 团…

JavaScript 入门基础 - 流程控制(四)

JavaScript 流程控制 - 分支和循环 文章目录JavaScript 流程控制 - 分支和循环1. 什么是流程控制2. 顺序流程控制3. 分支流程控制 之 if语句3.1 什么是分支结构3.2 if 语句3.2.1 if 语句基本理解3.2.2 if 语句执行流程3.2.3 if 语句案例3.3 if else语句&#xff08;双分支语句&…

Threejs实现鼠标点击人物行走/镜头跟随人物移动/鼠标点击动画/游戏第三人称/行走动作

1&#xff0c;功能介绍 Threejs获取鼠标点击位置、实现鼠标点击人物行走、人物头顶显示名称标签、镜头跟随人物移动并且镜头围绕人物旋转&#xff0c;类似游戏中第三人称、鼠标点击位置有动画效果&#xff0c;如下效果图 2&#xff0c;功能实现 获取鼠标点击位置&#xff0c;…

【Linux】进程间通信 - 匿名/命名管道与System V共享内存

目录 前言 一.管道 0.什么是管道 1).管道的概念 2).管道的本质 3).管道指令: "|" 1.匿名管道 1).如何创建匿名管道 2).如何使用匿名管道进行通信 3).匿名管道的特点总结 2.命名管道 0).指令级的命名管道的通信 1).如何在编程时创建命名管道 2).如何在…

你好2023-使用msys64 openssl 制作QSslSocket实验所需证书

2023年开始了&#xff0c;第一篇&#xff0c;记录最近帮朋友制作QSslSocket所需证书的过程。 使用传统的TCP连接依旧是很多工业软件的常见通信方法。但如果恰好不希望别人通过抓包等方法研究上位机和控制器模块之间的协议格式&#xff0c;那使用SSL连接是一种掩耳盗铃的好办法&…

Pyinstaller - 你的“神”队友

哈哈&#xff01;今天是我在2023年发布的第一篇文章呀&#xff01; 这两天&#xff0c;我在做一个爬虫项目。因为我做好后准备给我的朋友看看&#xff0c;但我朋友没有 Python 环境。所以&#xff0c;只好想办法把 .py 打包成 .exe 。 在网上搜了一下&#xff0c;发现目前相对…

设计模式 ——工厂模式

前言 有一些重要的设计原则在开篇和大家分享下&#xff0c;这些原则将贯通全文&#xff1a; 面向接口编程&#xff0c;而不是面向实现。这个很重要&#xff0c;也是优雅的、可扩展的代码的第一步&#xff0c;这就不需要多说了吧。 职责单一原则。每个类都应该只有一个单一的功…

第三十一讲:神州路由器策略路由的配置

从局域网去往广域网的流量有时需要进行分流&#xff0c;即区别了不同用户又进行了负载分担&#xff0c;有时这种目标是通过对不同的源地址进行区别对待完成的&#xff0c;通过策略路由的方法可以解决此问题。 实验拓扑图如下所示 R1 R2 R3 F0/0 1.1.3.1/24 F0/0 1.1.3.2…

【AcWing每日一题】4261. 孤独的照片

Farmer John 最近购入了 N 头新的奶牛&#xff0c;每头奶牛的品种是更赛牛&#xff08;Guernsey&#xff09;或荷斯坦牛&#xff08;Holstein&#xff09;之一。 奶牛目前排成一排&#xff0c;Farmer John 想要为每个连续不少于三头奶牛的序列拍摄一张照片。 然而&#xff0c…

java多线程(11):线程协作

1 线程通信 应用场景 : 生产者和消费者问题 假设仓库中只能存放一件产品 , 生产者将生产出来的产品放入仓库 , 消费者将仓库中产品取走消费 如果仓库中没有产品 , 则生产者将产品放入仓库 , 否则停止生产并等待 , 直到仓库中的产品被消费者取走为止 如果仓库中放有产品 ,…

Chrome Extension 基础篇

Extensions are software programs, built on web technologies (such as HTML, CSS, and JavaScript) that enable users to customize the Chrome browsing experience. 扩展程序是基于 Web 技术&#xff08;例如 HTML、CSS 和 JavaScript&#xff09;构建的软件程序&#xf…

C语言递归

递归指的是在函数的定义中使用函数自身的方法。 举个例子&#xff1a; 从前有座山&#xff0c;山里有座庙&#xff0c;庙里有个老和尚&#xff0c;正在给小和尚讲故事呢&#xff01;故事是什么呢&#xff1f;"从前有座山&#xff0c;山里有座庙&#xff0c;庙里有个老和尚…