井字棋
我们先实现一个最基本的使用控制台交互的井字棋游戏。
为了保持代码整洁,方便后续扩展,我们使用类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 = sizedef __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 resdef 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] = markelse: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"# Diagonalif 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: Omarks = ['X', 'O']player = 0while 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)-1except ValueError:print("Invalid input. Please input two integers separated by a comma.")continueif board.can_set(row, col):board.set_board(row, col, marks[player])else:print("This cell is already occupied. Please choose another one.")continueplayer = (player + 1) % 2board.display()print(board.check_win())
使用tkinter创建图形界面
在这个程序中,使用了tkinter
模块来创建窗口和按钮。每个按钮都由Button
类创建,并通过grid()
方法来定位。当按钮被点击时,clicked()
方法被调用,将其设置为当前玩家的标记,并切换到下一个玩家。每次下棋后,程序会检查游戏是否已结束。如果游戏结束,程序会调用show_winner()
方法来显示获胜者或平局信息,并禁用所有按钮。
import tkinter as tkclass 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_playerif 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])returnif 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 = sizedef __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 resdef 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] = markelse: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"# Diagonalif 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_boardclass AIPlayer:def __init__(self, mark):self.mark = markdef get_best_move(self, board):best_score = -1000best_move = Nonefor 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 = scorebest_move = (row, col)return best_movedef minimax(self, board, is_maximizing, alpha, beta):result = board.check_win()if result == "Player 1 wins":return -1elif result == "Player 2 wins":return 1elif board.is_full():return 0if is_maximizing:best_score = -1000for 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:breakreturn best_scoreelse:best_score = 1000for 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:breakreturn best_scoreclass Game:def __init__(self, size=3, ai_mode=False):self.board = Board(size)self.ai_mode = ai_modeself.player_marks = ['X', 'O']self.player_turn = 0if 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) - 1except ValueError:print("Invalid input. Please input two integers separated by a comma.")continueif not self.board.can_set(row, col):print("This cell is already occupied. Please choose another one.")continueself.board.set_board(row, col, current_player_mark)self.board.display()self.player_turn = (self.player_turn + 1) % 2print(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