井字棋

问题

井字棋是在一个九宫格中依次填入O或X,当横、竖、斜出现三个相同棋子时即获胜。以O先下为例,统计各自胜场和平局数。

完全穷举

讨论所有情况的胜率。

程序

class TicTacToeCompleteExhaustive:
    def __init__(self):
        self.total_states = 0
        self.o_wins = 0
        self.x_wins = 0
        self.draws = 0
        self.unfinished = 0
        self.total_moves = 0
        
        # 胜利条件
        self.win_conditions = [
            [0, 1, 2], [3, 4, 5], [6, 7, 8],  # 横线
            [0, 3, 6], [1, 4, 7], [2, 5, 8],  # 竖线
            [0, 4, 8], [2, 4, 6]              # 对角线
        ]
    
    def is_win(self, state, player):
        """判断某玩家是否获胜"""
        for condition in self.win_conditions:
            if all(state[i] == player for i in condition):
                return True
        return False
    
    def is_draw(self, state):
        """判断是否平局"""
        return 0 not in state and not self.is_win(state, 1) and not self.is_win(state, 2)
    
    def is_unfinished(self, state):
        """判断是否未结束"""
        return 0 in state and not self.is_win(state, 1) and not self.is_win(state, 2)
    
    def analyze_state(self, state):
        """分析一个状态并更新统计"""
        self.total_states += 1
        
        # 计算当前状态的步数(已下的棋子数)
        moves_in_state = state.count(1) + state.count(2)
        self.total_moves += moves_in_state
        
        if self.is_win(state, 1):  # O赢
            self.o_wins += 1
        elif self.is_win(state, 2):  # X赢
            self.x_wins += 1
        elif self.is_draw(state):  # 平局
            self.draws += 1
        elif self.is_unfinished(state):  # 未结束
            self.unfinished += 1
    
    def dfs(self, state, is_o_turn):
        """深度优先搜索所有可能的局面(完全穷举)"""
        # 分析当前状态
        self.analyze_state(state)
        
        # 如果游戏结束,不再继续搜索
        if self.is_win(state, 1) or self.is_win(state, 2) or self.is_draw(state):
            return
        
        # 继续搜索下一步
        for i in range(9):
            if state[i] == 0:
                state[i] = 1 if is_o_turn else 2
                self.dfs(state, not is_o_turn)
                state[i] = 0
    
    def analyze_all_states(self):
        """分析所有可能的游戏局面(完全穷举)"""
        print("开始分析井字棋所有可能局面(完全穷举)...")
        
        initial_state = [0] * 9  # 0表示空,1表示O,2表示X
        self.dfs(initial_state, True)  # O先手
        
        # 输出结果
        print("\n=== 井字棋穷举分析结果(完全穷举)===")
        print(f"总局面数: {self.total_states:,}")
        print(f"O 获胜局面数: {self.o_wins:,}")
        print(f"X 获胜局面数: {self.x_wins:,}")
        print(f"平局局面数: {self.draws:,}")
        print(f"未结束局面数: {self.unfinished:,}")
        
        # 验证总数
        total_calculated = self.o_wins + self.x_wins + self.draws + self.unfinished
        print(f"验证: {self.o_wins:,} + {self.x_wins:,} + {self.draws:,} + {self.unfinished:,} = {total_calculated:,}")
        print(f"与实际总数对比: {total_calculated} = {self.total_states}")
        
        print("\n=== 比例分析 ===")
        print(f"O 胜率: {self.o_wins/self.total_states*100:.4f}%")
        print(f"X 胜率: {self.x_wins/self.total_states*100:.4f}%")
        print(f"平局率: {self.draws/self.total_states*100:.4f}%")
        print(f"未结束率: {self.unfinished/self.total_states*100:.4f}%")
        
        # 计算平均深度
        if self.total_states > 0:
            avg_moves = self.total_moves / self.total_states
            print(f"\n平均步数: {avg_moves:.2f}")
        
        return self.total_states


# 运行分析
if __name__ == "__main__":
    analyzer = TicTacToeCompleteExhaustive()
    total = analyzer.analyze_all_states()

执行结果

image-20251102165232650

完全去重

去除因为顺序和对称性导致的重复情况

程序

import tkinter as tk
from tkinter import messagebox, ttk
import itertools

class TicTacToeAnalyzer:
    def __init__(self):
        self.states = {}  # 存储所有局面
        self.unique_states = set()  # 去重后的局面
        self.state_list = []  # 状态列表,用于浏览
        self.current_state_index = 0  # 当前浏览的状态索引
        
        self.o_wins = 0
        self.x_wins = 0
        self.draws = 0
        
        # 对称变换策略
        self.symmetry_strategies = [
            [0, 1, 2, 3, 4, 5, 6, 7, 8],  # 原始
            [2, 1, 0, 5, 4, 3, 8, 7, 6],  # 垂直翻转
            [6, 7, 8, 3, 4, 5, 0, 1, 2],  # 水平翻转
            [8, 5, 2, 7, 4, 1, 6, 3, 0],  # 主对角线翻转
            [0, 3, 6, 1, 4, 7, 2, 5, 8],  # 副对角线翻转
            [6, 3, 0, 7, 4, 1, 8, 5, 2],  # 旋转90度
            [8, 7, 6, 5, 4, 3, 2, 1, 0],  # 旋转180度
            [2, 5, 8, 1, 4, 7, 0, 3, 6],  # 旋转270度
        ]
        
        # 胜利条件
        self.win_conditions = [
            [0, 1, 2], [3, 4, 5], [6, 7, 8],  # 横线
            [0, 3, 6], [1, 4, 7], [2, 5, 8],  # 竖线
            [0, 4, 8], [2, 4, 6]              # 对角线
        ]
        
        self.setup_gui()
    
    def setup_gui(self):
        self.root = tk.Tk()
        self.root.title("井字棋穷举分析器")
        self.root.geometry("900x700")
        
        # 控制面板
        control_frame = tk.Frame(self.root)
        control_frame.pack(pady=10)
        
        tk.Button(control_frame, text="开始分析", command=self.start_analysis, 
                 bg="lightblue", font=("Arial", 12)).pack(side=tk.LEFT, padx=5)
        
        tk.Button(control_frame, text="重置", command=self.reset, 
                 bg="lightcoral", font=("Arial", 12)).pack(side=tk.LEFT, padx=5)
        
        # 统计信息
        stats_frame = tk.Frame(self.root)
        stats_frame.pack(pady=10)
        
        self.stats_text = tk.Text(stats_frame, height=6, width=80, font=("Courier", 10))
        self.stats_text.pack()
        
        # 进度条
        self.progress = ttk.Progressbar(self.root, orient=tk.HORIZONTAL, length=600, mode='determinate')
        self.progress.pack(pady=10)
        
        # 棋盘显示区域
        board_frame = tk.Frame(self.root)
        board_frame.pack(pady=10)
        
        # 创建棋盘
        self.canvas = tk.Canvas(board_frame, width=300, height=300, bg="white")
        self.canvas.pack()
        
        # 状态信息和导航
        nav_frame = tk.Frame(self.root)
        nav_frame.pack(pady=10)
        
        # 状态信息
        self.state_info = tk.Label(nav_frame, text="请点击'开始分析'进行穷举分析", 
                                  font=("Arial", 12), fg="blue")
        self.state_info.pack(pady=5)
        
        # 导航按钮
        nav_buttons = tk.Frame(nav_frame)
        nav_buttons.pack(pady=5)
        
        tk.Button(nav_buttons, text="第一个", command=self.first_state, 
                 font=("Arial", 10)).pack(side=tk.LEFT, padx=5)
        
        tk.Button(nav_buttons, text="上一个", command=self.prev_state, 
                 font=("Arial", 10)).pack(side=tk.LEFT, padx=5)
        
        tk.Button(nav_buttons, text="下一个", command=self.next_state, 
                 font=("Arial", 10)).pack(side=tk.LEFT, padx=5)
        
        tk.Button(nav_buttons, text="最后一个", command=self.last_state, 
                 font=("Arial", 10)).pack(side=tk.LEFT, padx=5)
        
        # 结果显示区域
        result_frame = tk.Frame(self.root)
        result_frame.pack(pady=10, fill=tk.BOTH, expand=True)
        
        # 创建滚动条
        scrollbar = tk.Scrollbar(result_frame)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        
        self.result_text = tk.Text(result_frame, height=10, width=80, font=("Courier", 9),
                                  yscrollcommand=scrollbar.set)
        self.result_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        scrollbar.config(command=self.result_text.yview)
    
    def draw_board(self, state):
        """绘制棋盘和棋子"""
        self.canvas.delete("all")
        
        # 绘制棋盘网格
        for i in range(1, 3):
            # 垂直线
            self.canvas.create_line(i * 100, 0, i * 100, 300, width=2)
            # 水平线
            self.canvas.create_line(0, i * 100, 300, i * 100, width=2)
        
        # 绘制棋子
        for i in range(9):
            row = i // 3
            col = i % 3
            x = col * 100 + 50
            y = row * 100 + 50
            
            if state[i] == 1:  # O
                self.canvas.create_oval(x-30, y-30, x+30, y+30, width=3, outline="blue")
            elif state[i] == 2:  # X
                self.canvas.create_line(x-30, y-30, x+30, y+30, width=3, fill="red")
                self.canvas.create_line(x-30, y+30, x+30, y-30, width=3, fill="red")
    
    def rearrange(self, state, strategy):
        """根据对称策略重新排列局面"""
        return [state[i] for i in strategy]
    
    def get_symmetric_states(self, state):
        """获取所有对称局面"""
        symmetric_states = []
        for strategy in self.symmetry_strategies:
            symmetric_states.append(self.rearrange(state, strategy))
        return symmetric_states
    
    def is_win(self, state, player):
        """判断某玩家是否获胜"""
        for condition in self.win_conditions:
            if all(state[i] == player for i in condition):
                return True
        return False
    
    def is_draw(self, state):
        """判断是否平局"""
        return 0 not in state and not self.is_win(state, 1) and not self.is_win(state, 2)
    
    def get_state_key(self, state):
        """获取状态的规范表示(用于去重)"""
        # 找到所有对称状态中最小的元组表示
        symmetric_states = self.get_symmetric_states(state)
        min_state = min(tuple(s) for s in symmetric_states)
        return min_state
    
    def dfs(self, state, is_o_turn):
        """深度优先搜索所有可能的局面"""
        state_key = self.get_state_key(state)
        
        # 如果已经访问过这个状态,直接返回
        if state_key in self.states:
            return
        
        # 记录这个状态
        self.states[state_key] = {
            'state': state.copy(),
            'o_win': self.is_win(state, 1),
            'x_win': self.is_win(state, 2),
            'draw': self.is_draw(state)
        }
        
        # 如果游戏结束,不再继续搜索
        if self.is_win(state, 1) or self.is_win(state, 2) or self.is_draw(state):
            return
        
        # 继续搜索下一步
        for i in range(9):
            if state[i] == 0:
                state[i] = 1 if is_o_turn else 2
                self.dfs(state, not is_o_turn)
                state[i] = 0
    
    def analyze_game(self):
        """分析所有可能的游戏局面"""
        self.states = {}
        initial_state = [0] * 9  # 0表示空,1表示O,2表示X
        self.dfs(initial_state, True)  # O先手
        
        # 将状态转换为列表,用于浏览
        self.state_list = list(self.states.keys())
        
        # 统计结果
        self.o_wins = sum(1 for data in self.states.values() if data['o_win'])
        self.x_wins = sum(1 for data in self.states.values() if data['x_win'])
        self.draws = sum(1 for data in self.states.values() if data['draw'])
        
        return len(self.states)
    
    def format_state(self, state):
        """格式化显示局面"""
        symbols = {0: ' ', 1: 'O', 2: 'X'}
        lines = []
        for i in range(0, 9, 3):
            line = ' | '.join(symbols[state[i+j]] for j in range(3))
            lines.append(line)
        return '\n---------\n'.join(lines)
    
    def display_current_state(self):
        """显示当前状态"""
        if not self.state_list:
            return
        
        state_key = self.state_list[self.current_state_index]
        state_data = self.states[state_key]
        state = state_data['state']
        
        # 绘制棋盘
        self.draw_board(state)
        
        # 更新状态信息
        status = "O胜" if state_data['o_win'] else "X胜" if state_data['x_win'] else "平局" if state_data['draw'] else "未结束"
        self.state_info.config(text=f"状态 {self.current_state_index+1}/{len(self.state_list)} - {status}")
        
        # 在结果区域显示详细信息
        self.result_text.delete(1.0, tk.END)
        self.result_text.insert(tk.END, f"局面 #{self.current_state_index+1}:\n")
        self.result_text.insert(tk.END, self.format_state(state))
        self.result_text.insert(tk.END, f"\n\n状态: {status}")
        self.result_text.insert(tk.END, f"\n局面代码: {state}")
    
    def first_state(self):
        """跳转到第一个状态"""
        if self.state_list:
            self.current_state_index = 0
            self.display_current_state()
    
    def prev_state(self):
        """跳转到上一个状态"""
        if self.state_list and self.current_state_index > 0:
            self.current_state_index -= 1
            self.display_current_state()
    
    def next_state(self):
        """跳转到下一个状态"""
        if self.state_list and self.current_state_index < len(self.state_list) - 1:
            self.current_state_index += 1
            self.display_current_state()
    
    def last_state(self):
        """跳转到最后一个状态"""
        if self.state_list:
            self.current_state_index = len(self.state_list) - 1
            self.display_current_state()
    
    def display_results(self):
        """显示分析结果"""
        total_states = len(self.states)
        
        stats_info = f"""
=== 井字棋穷举分析结果 ===
总局面数(去重后): {total_states}
O 获胜局面数: {self.o_wins}
X 获胜局面数: {self.x_wins}
平局局面数: {self.draws}
未结束局面数: {total_states - self.o_wins - self.x_wins - self.draws}

O 胜率: {self.o_wins/total_states*100:.2f}%
X 胜率: {self.x_wins/total_states*100:.2f}%
平局率: {self.draws/total_states*100:.2f}%
"""
        self.stats_text.delete(1.0, tk.END)
        self.stats_text.insert(1.0, stats_info)
        
        # 显示第一个状态
        if self.state_list:
            self.current_state_index = 0
            self.display_current_state()
    
    def update_progress(self, value):
        """更新进度条"""
        self.progress['value'] = value
        self.root.update_idletasks()
    
    def start_analysis(self):
        """开始分析"""
        self.stats_text.delete(1.0, tk.END)
        self.stats_text.insert(1.0, "正在分析井字棋所有可能局面...\n这可能需要几秒钟...")
        
        # 模拟进度更新
        for i in range(101):
            self.update_progress(i)
            self.root.after(10)  # 小延迟让界面更新
        
        # 执行分析
        total_states = self.analyze_game()
        
        # 显示结果
        self.display_results()
        
        messagebox.showinfo("分析完成", f"分析完成!共找到 {total_states} 个不同的局面。")
    
    def reset(self):
        """重置分析"""
        self.states = {}
        self.state_list = []
        self.current_state_index = 0
        self.o_wins = 0
        self.x_wins = 0
        self.draws = 0
        self.progress['value'] = 0
        self.stats_text.delete(1.0, tk.END)
        self.result_text.delete(1.0, tk.END)
        self.state_info.config(text="请点击'开始分析'进行穷举分析")
        self.canvas.delete("all")
    
    def run(self):
        """运行应用程序"""
        self.root.mainloop()

# 创建并运行分析器
if __name__ == "__main__":
    analyzer = TicTacToeAnalyzer()
    analyzer.run()

执行结果

image-20251102165907153