井字棋
我们先实现一个最基本的使用控制台交互的井字棋游戏。
为了保持代码整洁,方便后续扩展,我们使用类Board
来实现棋盘。除了常规的初始化方法__init__和字符串方法__str__,我们还要判断游戏的胜负、棋子位置的合理性。在main中,我们在while循环中实现两个玩家的交替下棋,直到一方胜利或棋盘满了为止。
代码整体比较简单,只是在判断胜利时需要处理好多个条件。
class Board: def __init__(self, size=3): self.board = [['_' for j in range(size)] for i in range(size)] self.size = size def __str__(self): res = "" for row in self.board: temp_row = "" for col in row: temp_row = temp_row + col + '\t' res += str(temp_row) + "\n" return res def display(self): print(self) def can_set(self, row, col): return 0 <= row < self.size and 0 <= col < self.size and self.board[row][col] == '_' def set_board(self, row, col, mark): if self.can_set(row, col): self.board[row][col] = mark else: print("Invalid input...,") def check_win(self): for row in self.board: if all(x == 'X' for x in row): return "Player 1 wins" elif all(x == 'O' for x in row): return "Player 2 wins" for col in range(self.size): if all(self.board[row][col] == 'X' for row in range(self.size)): return "Player 1 wins" elif all(self.board[row][col] == 'O' for row in range(self.size)): return "Player 2 wins" # Diagonal if all(self.board[i][i] == 'X' for i in range(self.size)) or all(self.board[i][self.size-1-i] == 'X' for i in range(self.size)): return "Player 1 wins" elif all(self.board[i][i] == 'O' for i in range(self.size)) or all(self.board[i][self.size-1-i] == 'O' for i in range(self.size)): return "Player 2 wins" return "No one wins" def is_full(self): return not any(self.board[i][j] == '_' for i in range(self.size) for j in range(self.size)) if __name__ == '__main__': size = int(input("Please input the size of the board: ")) board = Board(size) board.display() # player 1:X player 2: O marks = ['X', 'O'] player = 0 while not board.is_full() and board.check_win() == "No one wins": index = input(f"Please player {player+1} input coordinates (row, col): ") try: row, col = index.split(',') row, col = int(row)-1, int(col)-1 except ValueError: print("Invalid input. Please input two integers separated by a comma.") continue if board.can_set(row, col): board.set_board(row, col, marks[player]) else: print("This cell is already occupied. Please choose another one.") continue player = (player + 1) % 2 board.display() print(board.check_win())
使用tkinter创建图形界面
在这个程序中,使用了tkinter模块来创建窗口和按钮。每个按钮都由Button类创建,并通过grid()方法来定位。当按钮被点击时,clicked()方法被调用,将其设置为当前玩家的标记,并切换到下一个玩家。每次下棋后,程序会检查游戏是否已结束。如果游戏结束,程序会调用show_winner()方法来显示获胜者或平局信息,并禁用所有按钮。
import tkinter as tk class TicTacToeGUI: def __init__(self): self.root = tk.Tk() self.root.title("Tic Tac Toe") # 创建9个按钮 self.buttons = [] for i in range(3): row = [] for j in range(3): button = tk.Button(self.root, text="", font=("Helvetica", 24), width=5, height=2, command=lambda row=i, col=j: self.clicked(row, col)) button.grid(row=i, column=j) row.append(button) self.buttons.append(row) self.current_player = "X" self.board = [ ["-", "-", "-"], ["-", "-", "-"], ["-", "-", "-"] ] def clicked(self, row, col): """ 当一个按钮被点击时,将其设置为当前玩家的标记,并切换到下一个玩家。 """ if self.board[row][col] == "-": self.buttons[row][col].config(text=self.current_player) self.board[row][col] = self.current_player if self.current_player == "X": self.current_player = "O" else: self.current_player = "X" self.check_game_over() def check_game_over(self): """ 检查游戏是否已结束 """ for i in range(3): # 检查行 if self.board[i][0] != "-" and self.board[i][0] == self.board[i][1] and self.board[i][1] == self.board[i][2]: self.show_winner(self.board[i][0]) return # 检查列 if self.board[0][i] != "-" and self.board[0][i] == self.board[1][i] and self.board[1][i] == self.board[2][i]: self.show_winner(self.board[0][i]) return # 检查对角线 if self.board[0][0] != "-" and self.board[0][0] == self.board[1][1] and self.board[1][1] == self.board[2][2]: self.show_winner(self.board[0][0]) return if self.board[0][2] != "-" and self.board[0][2] == self.board[1][1] and self.board[1][1] == self.board[2][0]: self.show_winner(self.board[0][2]) return # 检查是否有空格 for row in self.board: for cell in row: if cell == "-": return # 如果没有空格,平局 self.show_winner("Tie") def show_winner(self, winner): """ 显示获胜者或平局信息,并禁用所有按钮。 """ if winner == "Tie": message = "It's a tie!" else: message = f"Player {winner} wins!" for row in self.buttons: for button in row: button.config(state="disabled") self.root.title(message) def start(self): """ 开始游戏。 """ self.root.mainloop() # 运行游戏 game = TicTacToeGUI() game.start()
人机对战版
这是一个井字游戏的 Python 代码,分别有三个类:Board
,AIPlayer
,Game
。
Board
类
这个类代表了游戏的棋盘,有以下方法:
__init__(self, size=3)
:构造函数,初始化棋盘为给定大小的二维列表,初始值为 ‘_’。__str__(self)
:返回当前棋盘的字符串表示形式。display(self)
:打印当前棋盘的字符串表示形式。can_set(self, row, col)
:判断给定位置能否放置棋子。set_board(self, row, col, mark)
:将给定位置放置给定的标记(‘X’ 或 ‘O’)。check_win(self)
:判断当前棋局是否已分出胜负,返回 “Player 1 wins”、“Player 2 wins” 或 “No one wins”。is_full(self)
:判断当前棋盘是否已满。copy(self)
:返回当前棋盘的副本。
AIPlayer
类
这个类代表了游戏中的 AI 玩家,有以下方法:
__init__(self, mark)
:构造函数,初始化 AI 玩家的标记(‘X’ 或 ‘O’)。
get_best_move(self, board)
:计算当前 AI 玩家应该下的最佳位置。minimax(self, board, is_maximizing, alpha, beta)
:计算给定棋盘状态下当前玩家的最大(或最小)得分。
Game
类
这个类代表了游戏本身,有以下方法:
__init__(self, size=3, ai_mode=False)
:构造函数,初始化游戏的棋盘大小和是否启用 AI 模式。start(self)
:开始游戏。
- 在 start 方法中,游戏会循环进行,直到棋盘已满或有一方获胜。每次循环,根据当前玩家是否是 AI,分别提示玩家输入位置或计算 AI 玩家应该下的位置,然后更新棋盘,交换当前玩家。
class Board: def __init__(self, size=3): self.board = [['_' for j in range(size)] for i in range(size)] self.size = size def __str__(self): res = "" for row in self.board: temp_row = "" for col in row: temp_row = temp_row + col + '\t' res += str(temp_row) + "\n" return res def display(self): print(self) def can_set(self, row, col): return 0 <= row < self.size and 0 <= col < self.size and self.board[row][col] == '_' def set_board(self, row, col, mark): if self.can_set(row, col): self.board[row][col] = mark else: print("Invalid input...,") def check_win(self): for row in self.board: if all(x == 'X' for x in row): return "Player 1 wins" elif all(x == 'O' for x in row): return "Player 2 wins" for col in range(self.size): if all(self.board[row][col] == 'X' for row in range(self.size)): return "Player 1 wins" elif all(self.board[row][col] == 'O' for row in range(self.size)): return "Player 2 wins" # Diagonal if all(self.board[i][i] == 'X' for i in range(self.size)) or all( self.board[i][self.size - 1 - i] == 'X' for i in range(self.size)): return "Player 1 wins" elif all(self.board[i][i] == 'O' for i in range(self.size)) or all( self.board[i][self.size - 1 - i] == 'O' for i in range(self.size)): return "Player 2 wins" return "No one wins" def is_full(self): return not any(self.board[i][j] == '_' for i in range(self.size) for j in range(self.size)) def copy(self): new_board = Board(self.size) for i in range(self.size): for j in range(self.size): new_board.board[i][j] = self.board[i][j] return new_board class AIPlayer: def __init__(self, mark): self.mark = mark def get_best_move(self, board): best_score = -1000 best_move = None for row in range(board.size): for col in range(board.size): if board.can_set(row, col): board_copy = board.copy() board_copy.set_board(row, col, self.mark) score = self.minimax(board_copy, False, -1000, 1000) if score > best_score: best_score = score best_move = (row, col) return best_move def minimax(self, board, is_maximizing, alpha, beta): result = board.check_win() if result == "Player 1 wins": return -1 elif result == "Player 2 wins": return 1 elif board.is_full(): return 0 if is_maximizing: best_score = -1000 for row in range(board.size): for col in range(board.size): if board.can_set(row, col): board_copy = board.copy() board_copy.set_board(row, col, 'O') score = self.minimax(board_copy, False, alpha, beta) best_score = max(best_score, score) alpha = max(alpha, best_score) if beta <= alpha: break return best_score else: best_score = 1000 for row in range(board.size): for col in range(board.size): if board.can_set(row, col): board_copy = board.copy() board_copy.set_board(row, col, 'X') score = self.minimax(board_copy, True, alpha, beta) best_score = min(best_score, score) beta = min(beta, best_score) if beta <= alpha: break return best_score class Game: def __init__(self, size=3, ai_mode=False): self.board = Board(size) self.ai_mode = ai_mode self.player_marks = ['X', 'O'] self.player_turn = 0 if ai_mode: self.ai_player = AIPlayer(self.player_marks[1]) def start(self): print("Starting the game...") self.board.display() while not self.board.is_full() and self.board.check_win() == "No one wins": current_player_mark = self.player_marks[self.player_turn] if self.player_turn == 1 and self.ai_mode: print("AI is thinking...") row, col = self.ai_player.get_best_move(self.board) print(f"AI placed {current_player_mark} at row {row + 1}, col {col + 1}") self.board.set_board(row, col, current_player_mark) else: index = input(f"Please player {self.player_turn + 1} input coordinates (row, col): ") try: row, col = index.split(',') row, col = int(row) - 1, int(col) - 1 except ValueError: print("Invalid input. Please input two integers separated by a comma.") continue if not self.board.can_set(row, col): print("This cell is already occupied. Please choose another one.") continue self.board.set_board(row, col, current_player_mark) self.board.display() self.player_turn = (self.player_turn + 1) % 2 print(self.board.check_win()) if __name__ == '__main__': game = Game(size=3, ai_mode=True) game.start()
说明
第二部分(人机对战)全部由ChatGPT
生成(中间经过多次调整),第一部分的代码是ChatGPT在我的代码基础上优化后的结果。
对于简单的问题,ChatGPT通常能给出完美的回答。但对于复杂问题则相对差一些,需要多次修改。此外,ChatGPT无法保证内容的正确性,有时候会给出似是而非的误导性回答。值得一提的是,ChatGPT对代码的理解能力特别强,能很好地给自己的代码加注释(大概是因为代码比较规范,流程相对固定,比较容易学习)。
ChatGPT能提高写代码及其它文字的效率,可以用来
- 注释代码
- 写一段简单函数
- 提供灵感(比如起标题)
- 写格式比较固定的文字(邮件等)
但是,ChatGPT对事实类问题的回答比较离谱:
更多ChatGPT的案例可以看看:
ChatGPT评测观察之对话能力|语义理解较准,尚难以摆脱知识整合和逻辑推理困境 https://mp.weixin.qq.com/s/ZjZgMZIXiD966hcFua_gew
ChatGPT是一种生成式预训练transformer(generative pre-trained transformer, GPT),使用有监督学习和强化学习对GPT-3.5进行微调。在两种方法中,人类训练者都用于提高模型的性能。在有监督学习中,模型被提供了对话,训练者在其中扮演了用户和AI助手的双方角色。在强化学习步骤中,人类训练者首先对模型先前创建的响应进行排名。这些排名被用于创建“奖励模型”,…
此外,OpenAI继续收集来自ChatGPT用户的数据,这些数据可以用于进一步训练和微调ChatGPT。用户可以赞或踩他们从ChatGPT收到的响应;在赞或踩时,他们还可以填写一个文本字段以提供额外的反馈。
更多细节可以参考:
维基百科ChatGPT: https://en.wikipedia.org/wiki/ChatGPT
InstructGPT论文: Training language models to follow instructions with human feedback
最后提一下,井字棋的最优策略竟是先占角!
https://www.guokr.com/article/4754