1. 介绍
井字棋是一种非常受欢迎的人机博弈游戏,两个参与者轮流在3×3的棋盘上下棋,最先在横、竖、对角线上达成三子连珠者获胜。在这篇文章中,我们将使用Python编写一个纯函数版本的井字棋游戏。
2. 设计
2.1. 游戏状态
井字棋游戏最基本的元素是游戏状态。游戏状态可以表示为一个含有9个元素的列表,每个元素代表棋盘上的一个位置是否有棋子。
那么,我们可以把棋盘分成九个位置,编号如下:
board = [
0, 1, 2,
3, 4, 5,
6, 7, 8
]
游戏状态中每个位置分别对应这个列表中的元素,当某个位置有棋子时,对应位置的元素值为1。当某个位置没有棋子时,对应位置的元素值为0。
例如,如果当前游戏状态为:
[
1, 0, 0,
0, -1, 0,
0, 0, 1
]
那么棋盘上的状态为:
X | |
---+---+---
| O |
---+---+---
| | X
其中,X表示先手玩家下的棋子,O表示后手玩家下的棋子。
2.2. 对手和游戏结束状态
在井字棋游戏中,先手玩家下的棋子为X,后手玩家下的棋子为O。
当游戏结束时,有三种情况:
先手玩家获胜
后手玩家获胜
平局
我们可以使用字母X和O表示先手和后手玩家,使用数字1和-1表示玩家在对应位置下的棋子。因此,在游戏结束时,可以根据游戏状态得到对应的对手和游戏结束状态。
def opponent(player):
return -player
def game_over(board, player):
lines = [
[board[0], board[1], board[2]],
[board[3], board[4], board[5]],
[board[6], board[7], board[8]],
[board[0], board[3], board[6]],
[board[1], board[4], board[7]],
[board[2], board[5], board[8]],
[board[0], board[4], board[8]],
[board[2], board[4], board[6]],
]
for line in lines:
if all(position == player for position in line):
return player
elif all(position == opponent(player) for position in line):
return opponent(player)
if 0 not in board:
return 0
return None
函数opponent
返回对手的数字表示。函数game_over
在游戏结束时返回胜者的数字表示,如果平局返回0,否则返回None。
3. 实现
3.1. 游戏操作
现在我们可以实现游戏操作。
首先,我们需要一个函数来检查是否可以在指定位置下棋。
def is_move_valid(board, move):
return board[move] == 0
如果指定位置没有棋子,返回True
,否则返回False
。
然后,我们需要一个函数来执行玩家的下棋操作,并返回下个玩家。
def make_move(board, move, player):
new_board = board.copy()
new_board[move] = player
return new_board, opponent(player)
函数make_move
把当前的游戏状态复制一份,并把指定位置下成玩家的棋子,接着返回新的游戏状态和下一个玩家。
3.2. 游戏循环
现在我们已经有了足够的代码来实现井字棋游戏循环了。
def play_game():
board = [
0, 0, 0,
0, 0, 0,
0, 0, 0
]
player = 1
while True:
print_board(board)
move = get_move(board, player)
board, player = make_move(board, move, player)
winner = game_over(board, player)
if winner is not None:
print_board(board)
if winner == 0:
print("平局")
else:
print("玩家{}获胜".format(winner))
return
在这个游戏循环中,我们从一个初始状态开始,打印当前的棋盘状态,获取玩家要下的位置,执行下棋操作,并检查是否游戏结束。如果游戏结束,则输出结果并结束循环。
3.3. 策略
一个好的AI程序需要一个合适的下棋策略。我们将使用Minimax算法来实现这个策略。
在实现Minimax算法之前,我们需要一个函数来计算当前游戏状态的分数。在本文中,我们将使用简单的评分函数。对于每行、每列和每条对角线,我们计算有多少棋子属于当前玩家,减去有多少棋子属于对手。
def score(board, player):
lines = [
[board[0], board[1], board[2]],
[board[3], board[4], board[5]],
[board[6], board[7], board[8]],
[board[0], board[3], board[6]],
[board[1], board[4], board[7]],
[board[2], board[5], board[8]],
[board[0], board[4], board[8]],
[board[2], board[4], board[6]],
]
score = 0
for line in lines:
count = line.count(player)
opponent_count = line.count(opponent(player))
if count == 3:
score += 100
elif count == 2 and opponent_count == 0:
score += 10
elif count == 1 and opponent_count == 2:
score -= 10
return score
现在,我们可以实现Minimax算法了。
3.4. Minimax算法
Minimax算法是一种递归算法,用于确定游戏的最优决策。在每个玩家的回合,他们都会尝试最大化可能的利益,而在对手的回合,他们则会尝试最小化玩家的利益。
我们可以使用以下函数来计算当前游戏状态的最优决策:
def minimax(board, player, depth=8):
if depth == 0:
return None, score(board, player)
if game_over(board, player) is not None:
return None, game_over(board, player)
scores = []
moves = []
for move in range(9):
if is_move_valid(board, move):
new_board, new_player = make_move(board, move, player)
_, move_score = minimax(new_board, new_player, depth - 1)
scores.append(move_score)
moves.append(move)
if player == 1:
max_score_index = scores.index(max(scores))
return moves[max_score_index], scores[max_score_index]
else:
min_score_index = scores.index(min(scores))
return moves[min_score_index], scores[min_score_index]
函数minimax
使用递归方法计算当前游戏状态的最优决策。如果当前深度达到了设定的限制,或者游戏结束了,函数就会返回分数。否则,函数就会对每个合法位置进行尝试,并记录对应的分数。如果是玩家的回合,就返回分数最高的决策;如果是对手的回合,就返回分数最低的决策。
3.5. AI玩家
我们已经有了足够的代码来实现一个AI玩家了。
def get_move(board, player):
if player == 1:
print("玩家X的回合")
move = int(input("请输入要下棋子的位置:"))
else:
print("玩家O的回合")
move, _ = minimax(board, player)
while not is_move_valid(board, move):
print("无效的位置,请重新输入")
move = int(input("请输入要下棋子的位置:"))
return move
函数get_move
获取玩家要下的位置,并使用Minimax算法计算AI要下的位置。
3.6. 打印棋盘
最后,我们需要一个函数来打印当前的棋盘状态。
def print_board(board):
symbols = {
-1: "O",
0: " ",
1: "X"
}
row = " {} | {} | {} "
hr = "\n-----------\n"
print((row + hr + row + hr + row).format(*(symbols[position] for position in board)))
函数print_board
定义了三种不同的符号,用于打印X、O和空格。函数接受当前游戏状态作为输入,并打印出网格中的符号。
4. 运行
现在我们可以运行代码并开始玩井字棋游戏了!
play_game()
结论
在这篇文章中,我们一步步地学习了如何实现一个使用Minimax算法的井字棋游戏。我们实现了一个纯函数版本的井字棋游戏,并实现了一个基本的评分函数和一个简单的Minimax算法,让AI程序可以玩井字棋游戏并表现出不错的战略水平。