PloicyAgent类为采用类似于A*策略的三子棋下法实现,大致思想是使用类似A*算法,在下棋时选择该棋盘情况下,获胜方案最多的地方下,即遍历所有空位置,判断在该局情况下在该位置有可能获胜的 方案的总数并得到对应得分,选取得分最高的位置下棋,但如果自己能立即获胜,则下在自己能立即获胜的位置,否则如果对方即将获胜,会优先阻止对方获胜。
具体可见那部分代码实现,当然此方法还有一点点局限性,可见总结
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import numpy as np
import random
BOARD_LEN = 3
class TicTacToeEnv(object):
def __init__(self): # 用数值的方式表示状态、动作、奖励(+1/0/-1 区分胜/平/负)
self.data = np.zeros((BOARD_LEN, BOARD_LEN)) # data 表示棋盘当前状态,1和-1分别表示x和o,0表示空位
self.winner = None # 1/0/-1表示玩家一胜/平局/玩家二胜,None表示未分出胜负
self.terminal = False # true表示游戏结束
self.current_player = 1 # 当前正在下棋的人是玩家1还是-1
def reset(self):
# 游戏重新开始,返回状态
self.data = np.zeros((BOARD_LEN, BOARD_LEN))
self.winner = None
self.terminal = False
state = self.getState()
return state
def getState(self):
# 注意到很多时候,存储数据不等同与状态,状态的定义可以有很多种,比如将棋的位置作一些哈希编码等
# 这里直接返回data数据作为状态
return self.data
def getReward(self):
"""Return (reward_1, reward_2)
"""
if self.terminal:
if self.winner == 1:
return 1, -1
elif self.winner == -1:
return -1, 1
return 0, 0
def getCurrentPlayer(self):
return self.current_player
def getWinner(self):
return self.winner
def switchPlayer(self):
if self.current_player == 1:
self.current_player = -1
else:
self.current_player = 1
def checkState(self):
# 每次有人下棋,都要检查游戏是否结束
# 从而更新self.terminal和self.winner
# ----------------------------------
# 实现自己的代码
# 将能够获胜的情况全部记录下来
over_states = [[1, 2, 3], [4, 5, 6], [7, 8, 9], [1, 5, 9], [2, 5, 8], [3, 5, 7], [1, 4, 7], [3, 6, 9]]
current_state = self.getState() # 获取当前棋盘状态
state_play1 = [] # 玩家1的下棋位置
state_play2 = [] # 玩家2的下棋位置
for ind_i, i in enumerate(current_state): # 找到玩家1和玩家2的下棋位置
for ind_j, j in enumerate(i):
if j == 1:
state_play1.append(ind_i * 3 + ind_j + 1)
elif j == 2:
state_play2.append(ind_i * 3 + ind_j + 1)
# 首先遍历所有获胜情况,查看是否有获胜的玩家
for i in over_states:
if all(item in state_play1 for item in i): # 判断是否有获胜的情况出现在玩家1或玩家2的棋子中,如果有则该玩家获胜
self.terminal = True
self.winner = 1
return self.terminal, self.winner
if all(item in state_play2 for item in i):
self.terminal = True
self.winner = -1
return self.terminal, self.winner
# 如果没有玩家获胜,查看是否是平局,如果棋盘满了,则为平局
count_zero = 0
for i in self.data:
for j in i:
if j != 0:
count_zero += 1
if count_zero == 9:
self.terminal = True
self.winner = 0
return self.terminal, self.winner
# ----------------------------------
# pass
def step(self, action):
"""action: is a tuple or list [x, y]
Return:
state, reward, terminal
"""
# ----------------------------------
# 实现自己的代码
current_player = self.getCurrentPlayer() # 首先当前下棋的玩家
i_ind = int((action - 1) / 3) # 进行坐标转换
j_ind = (action - 1) % 3 # 进行坐标转换
if current_player == 1: # 如果是玩家1在下棋
self.data[i_ind][j_ind] = 1 # 在该处下棋
else:
self.data[i_ind][j_ind] = 2
terminal, winner = self.checkState() # 检测是否有人获胜或平局
reward = self.getReward() # 获取奖励
next_state = self.getState() # 获取下一步的状态
self.switchPlayer() # 交换玩家
return next_state, reward, terminal # 返回需要的信息
# ----------------------------------
pass
# 随机策略,在空的位置随机挑一个下即可
class RandAgent(object):
def __init__(self, own_chess_piece):
self.own_chess_piece = own_chess_piece
def policy(self, state):
"""
Return: action
"""
# ----------------------------------
# 实现自己的代码
piece = self.own_chess_piece
left_space = [] # 棋盘为空的位置
own_space = [] # 棋盘中自己棋子的位置
other_space = [] # 棋盘中对方棋子的位置
# 遍历找到三种位置
for i in range(3):
for j in range(3):
if state[i][j] == 0:
left_space.append(i * 3 + j + 1)
elif state[i][j] == piece:
own_space.append(i * 3 + j + 1)
else:
other_space.append(i * 3 + j + 1)
best_action = random.choice(left_space) # 在空棋子位置随机挑取一个位置下棋即可
return best_action
# ----------------------------------
pass
# 对agent进行了优化,大致思想是使用类似A*算法,在下棋时选择该棋盘情况下,获胜方案最多的地方下,即遍历所有空位置,判断在该局情况下在该位置有可能获胜的
# 方案的总数并得到对应得分,选取得分最高的位置下棋,但如果自己能立即获胜,则下在自己能立即获胜的位置,否则如果对方即将获胜,会优先阻止对方获胜,
class PloicyAgent(object):
# 为了标志所下的棋,增加了初始化部分,传入参数为自己的棋子的代码,比如1或2
def __init__(self, own_chess_piece):
self.own_chess_piece = own_chess_piece
def policy(self, state):
"""
Return: action
"""
# ----------------------------------
# 实现自己的代码
piece = self.own_chess_piece # 首先确定自己的棋子是哪个
left_space = [] # 棋盘剩余可下位置
own_space = [] # 自己的棋已经下的位置
other_space = [] # 别人的棋已经下的位置
# 遍历棋盘,确定上述三类位置,棋盘编码为1 2 3/4 5 6/ 7 8 9,分别代表9个位置
for i in range(3):
for j in range(3):
if state[i][j] == 0: # 空位置
left_space.append(i * 3 + j + 1)
elif state[i][j] == piece: # 自己棋子的位置
own_space.append(i * 3 + j + 1)
else: # 对手棋子位置
other_space.append(i * 3 + j + 1)
all_action = [1 if i + 1 in left_space else 0 for i in
range(9)] # 所有可以下棋的位置初始化,并用以记录得分,不能下的位置即已经下棋的位置都为0,空位置为1,后面会取得分最高的位置下棋
over_states = [[1, 2, 3], [4, 5, 6], [7, 8, 9], [1, 5, 9], [2, 5, 8], [3, 5, 7], [1, 4, 7],
[3, 6, 9]] # 结束的状态,为了判断某位置在该局情况下是否可能获胜以及自己和对手是否即将获胜
best_action = 0 # 最好的下棋位置
all_choice_space = own_space + left_space # 所有可选择的下棋位置
len_all_choice_space = len(all_choice_space) # 列表长度
necessary_choice_space = other_space + left_space # 所有可选择的必要的下棋的位置,即为了防止对手获胜必须要下的
len_necessary_choice_space = len(necessary_choice_space)
# 利用A*思想搜索所有情况,在赢的方案最多处下棋,但如果自己能立即获胜,则下在能立即获胜的位置
# 遍历
for i in range(len_all_choice_space - 2):
for j in range(i + 1, len_all_choice_space - 1):
for k in range(j + 1, len_all_choice_space):
i_choice = all_choice_space[i]
j_choice = all_choice_space[j]
k_choice = all_choice_space[k]
count_over = 0 # 该是标记,是为了找到自己下棋必然会获胜的位置
for over_state in over_states: # 遍历所有获胜情况
if all(item in [i_choice, j_choice, k_choice] for item in over_state):
for p in [i_choice, j_choice, k_choice]: # 对在该选择的每一个位置,进行判断是否该处为空
if p in left_space: # 如果该处为空,则该位置得分加2,最后会选择得分最高的位置下棋
count_over += 1 # count_over++
best_action = p # 记录该处位置,为了如果该处能直接获胜可以直接返回该位置
all_action[p - 1] += 2 # 得分加2
if count_over == 1: # 该种情况三个位置只有一个地方为空,说明我方下此处必然能够获胜,则此步下在这里
return best_action # 直接下在这里便可直接获胜
# 如果自己不能立即获胜,则遍历所有的情况,查看是否有对手即将获胜的位置,堵住对手即将获胜的位置
for i in range(len_necessary_choice_space - 2):
for j in range(i + 1, len_necessary_choice_space - 1):
for k in range(j + 1, len_necessary_choice_space):
i_choice = necessary_choice_space[i]
j_choice = necessary_choice_space[j]
k_choice = necessary_choice_space[k]
count_over = 0 # 此处也是一个标记,是为了找到对手能立即获胜的位置
for over_state in over_states: # 遍历所有获胜的情况
if all(item in [i_choice, j_choice, k_choice] for item in over_state): # 如果这种情况有可能获胜,则进行后续操作
for p in [i_choice, j_choice, k_choice]: # 对在该选择的每一个位置,进行判断是否该处为空
if p in left_space: # 如果该处为空,则count_over++,且记录下该位置
count_over += 1
best_action = p # 记录该处位置,为了该处对手能直接获胜可以直接返回该位置
if count_over == 1: # 如果该种情况的三个棋子位置只有一个为空,则说明对手再下一次就能获胜,则必然要优先下在这里才能避免输
return best_action # 直接返回该位置即可
# 最后,如果上述情况都不存在,则找到得分最高的下棋位置中,随机选取一个下即可
best_action = max(all_action) # 找到最高的得分
best_action_index = []
for ind, i in enumerate(all_action): # 找到得分最高的下棋位置
if i == best_action:
best_action_index.append(ind + 1)
best_action = random.choice(best_action_index) # 在其中随机下一个位置即可
return best_action
# ----------------------------------
pass
def main(Agent_1, Agent_2):
env = TicTacToeEnv()
# agent1 = RandAgent()
# agent2 = RandAgent()
agent1 = Agent_1
agent2 = Agent_2
state = env.reset()
# 这里给出了一次运行的代码参考
# 你可以按照自己的想法实现
# 多次实验,计算在双方随机策略下,先手胜/平/负的概率
while 1:
current_player = env.getCurrentPlayer()
if current_player == 1:
action = agent1.policy(state)
else:
action = agent2.policy(state)
# print("action:",action) # 打印动作方便查看
next_state, reward, terminal = env.step(action)
# print(next_state) # 调试使用
if terminal: # 应该有三种情况,先手获胜,后手获胜和平局
# winner = 'Player1' if env.getWinner() == 1 else 'Player2'
if env.getWinner() == 1:
winner = 'Player1'
elif env.getWinner() == -1:
winner = 'Player2'
else:
winner = None # 平局
# print('Winner: {}'.format(winner)) # 大量对局方便给注释掉了
break
state = next_state
return winner
from tqdm import tqdm
if __name__ == "__main__":
# 分四种情况,即随机对弈随机,策略对弈随机(包括策略先下和随机先下),策略对弈策略四种进行实验。每种情况对局 10000次
epoch_num = 10000 # 一共10000次对局
Player1_won_times = 0 # 记录玩家1获胜次数
Player2_won_times = 0 # 记录玩家2获胜次数
None_won_times = 0 # 记录平局次数
Agent_1 = RandAgent(1) # 随机agent
Agent_2 = RandAgent(2) # 随机agent
for i in tqdm(range(epoch_num)):
result = main(Agent_1, Agent_2)
if result == "Player1": # 玩家1获胜
Player1_won_times += 1
elif result == "Player2": # 玩家2获胜
Player2_won_times += 1
else: # 平局
None_won_times += 1
print("When Agent_1 is RandAgent and Agent_2 is RandAgent with Agent_1 is first:") # 随机对弈随机
print("Player1_won_possibility:{:.5f}%".format(Player1_won_times / epoch_num * 100)) # 随机先手获胜概率为57.58%
print("Player2_won_possibility:{:.5f}%".format(Player2_won_times / epoch_num * 100)) # 随机后手获胜概率为29.15%
print("None_won_possibility:{:.5f}%".format(None_won_times / epoch_num * 100)) # 平局概率13.27%
epoch_num = 10000
Player1_won_times = 0
Player2_won_times = 0
None_won_times = 0
Agent_1 = RandAgent(1) # 随机agent
Agent_2 = PloicyAgent(2) # 策略agent
for i in tqdm(range(epoch_num)):
result = main(Agent_1, Agent_2)
if result == "Player1":
Player1_won_times += 1
elif result == "Player2":
Player2_won_times += 1
else:
None_won_times += 1
print("When Agent_1 is RandAgent and Agent_2 is PloicyAgent with Agent_1 is first:") # 随机对弈对弈策略,随机先下
print("Player1_won_possibility:{:.5f}%".format(Player1_won_times / epoch_num * 100)) # 随机先手获胜概率为1.76%
print("Player2_won_possibility:{:.5f}%".format(Player2_won_times / epoch_num * 100)) # 策略后手获胜概率为91.51%
print("None_won_possibility:{:.5f}%".format(None_won_times / epoch_num * 100)) # 平局概率6.73%
epoch_num = 10000
Player1_won_times = 0
Player2_won_times = 0
None_won_times = 0
Agent_1 = PloicyAgent(1) # 策略agent
Agent_2 = RandAgent(2) # 随机agent
for i in tqdm(range(epoch_num)):
result = main(Agent_1, Agent_2)
if result == "Player1":
Player1_won_times += 1
elif result == "Player2":
Player2_won_times += 1
else:
None_won_times += 1
print("When Agent_1 is PloicyAgent and Agent_2 is RandAgent with Agent_1 is first:") # 策略对弈随机,策略先下
print("Player1_won_possibility:{:.5f}%".format(Player1_won_times / epoch_num * 100)) # 策略先手获胜概率为98.72%
print("Player2_won_possibility:{:.5f}%".format(Player2_won_times / epoch_num * 100)) # 随机后手获胜概率为0%
print("None_won_possibility:{:.5f}%".format(None_won_times / epoch_num * 100)) # 平局概率1.28%
epoch_num = 10000
Player1_won_times = 0
Player2_won_times = 0
None_won_times = 0
Agent_1 = PloicyAgent(1) # 策略agent
Agent_2 = PloicyAgent(2) # 策略agent
for i in tqdm(range(epoch_num)):
result = main(Agent_1, Agent_2)
if result == "Player1":
Player1_won_times += 1
elif result == "Player2":
Player2_won_times += 1
else:
None_won_times += 1
print("When Agent_1 is PloicyAgent and Agent_2 is PloicyAgent with Agent_1 is first:") # 策略对弈策略
print("Player1_won_possibility:{:.5f}%".format(Player1_won_times / epoch_num * 100)) # 策略先手获胜概率为9.48%
print("Player2_won_possibility:{:.5f}%".format(Player2_won_times / epoch_num * 100)) # 策略后手获胜概率为0%
print("None_won_possibility:{:.5f}%".format(None_won_times / epoch_num * 100)) # 平局概率90.52%
# 总结:从实验结果来看,如果两个agent都是随机的,先手获胜概率约为后手获胜概率2倍,约近60%,平局概率约为13%,符合常理
# 如果使用我采用的策略agent对弈随机的agent,策略agent先手的话,其获胜概率为98.7%,随机agent获胜概率为0%,平局概率为1.28%,可见策略的有效性
# 但如果随机agent先手,则其获胜概率为1.76%,策略agent获胜概率为91.51%,平局概率为6.73%,策略agent的优势依然明显,经过反复推导,发现策略agent确实
# 有一定的局限性,如,如果随机agent先下在左上角,那么根据推理,策略agent会优先选取正中间下,如果此时随机agent下右下角,agent会选择其它任意位置下(因为此时
# 下在其它地方的获胜方案均只有一种),如果此时策略agent随机下在角,那么随机agent再碰巧下在最后一个角,那么策略agent无论如何都无法完全堵死随机agent,如下
# 1 0 2
# 0 2 0
# 1 0 1 此时策略agent会优先堵左边中间或下边中间,但为时已晚,有概率会输!这也解释了随机agent会获胜的原因,但策略agent仍优势明显
# 如果策略agent对弈策略agent,那么他们水平相当,但先下仍有一点优势,但是不明显,有90%的概率平局
# 仍有方案可以继续优化策略agent,但必然会增加复杂度,而此时的策略(即类似于A*搜索+优先下己方立即获胜或防止对手立即获胜位置的思想)并不复杂,也能收获很大的提高,性价比较高。
# 总结:从实验结果来看,如果两个agent都是随机的,先手获胜概率约为后手获胜概率2倍,约近60%,平局概率约为13%,符合常理 如果使用我采用的策略agent对弈随机的agent,策略agent先手的话,其获胜概率为98.7%,随机agent获胜概率为0%,平局概率为1.28%,可见策略的有效性 但如果随机agent先手,则其获胜概率为1.76%,策略agent获胜概率为91.51%,平局概率为6.73%,策略agent的优势依然明显,经过反复推导,发现策略agent确实有一定的局限性,如,如果随机agent先下在左上角,那么根据推理,策略agent会优先选取正中间下,如果此时随机agent下右下角,agent会选择其它任意位置下(因为此时下在其它地方的获胜方案均只有一种),如果此时策略agent随机下在角,那么随机agent再碰巧下在最后一个角,那么策略agent无论如何都无法完全堵死随机agent,如下 1 0 2 0 2 0 1 0 1 此时策略agent会优先堵左边中间或下边中间,但为时已晚,有概率会输!这也解释了随机agent会获胜的原因,但策略agent仍优势明显 如果策略agent对弈策略agent,那么他们水平相当,但先下仍有一点优势,但是不明显,有90%的概率平局 仍有方案可以继续优化策略agent,但必然会增加复杂度,而此时的策略(即类似于A*搜索+优先下己方立即获胜或防止对手立即获胜位置的思想)并不复杂,也能收获很大的提高,性价比较高。