井字棋
我们先实现一个最基本的使用控制台交互的井字棋游戏。
为了保持代码整洁,方便后续扩展,我们使用类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