TicTacToe 是一个简单的对抗游戏,棋盘大小为 3 × 3,谁先将棋子连成线(横、竖、斜),谁就获得胜利。(× 先手) 这里要求大家实现以下功能:
(1) 用数值的方式表示状态、动作、奖励(+1/0/-1 区分胜/平/负)。
(2) 环境类,环境能够根据智能体的动作给出反馈。即实现成员函数step(a)→s, r。
(3)智能体类,并包含一个随机策略,即从剩下的空位中随机采样一个位置下。函数形式 policy(s)→ a。
(4) 通过仿真的方式,大量对弈,统计在都执行随机策略的情况下,先手和后手胜利的概率。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import numpy as np
import random
BOARD_LEN = 3
class TicTacToeEnv(object):
def __init__(self):
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()
self.current_player = 1
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 # 中间过程都是0
def getCurrentPlayer(self): # 当前是谁在下棋
return self.current_player
def getWinner(self):
return self.winner
def switchPlayer(self): # 回顾下棋的每一个步骤,就能不遗漏,switchPlayer是很多对弈中都会有的,本质就是改变current_player
if self.current_player == 1:
self.current_player = -1
else:
self.current_player = 1
def checkState(self):
# 每次有人下棋,都要检查游戏是否结束
# 从而更新self.terminal和self.winner
# ----------------------------------
# 2019.08.21
# ----------------------------------
results = [] # 用一个list来存储行、列、对角线的检查结果
# 注意extend和append的区别
# 计算所有行的结果,插入到results中
results.extend(np.sum(self.data, 1).tolist())
# 计算所有列的结果,插入到results中
results.extend(np.sum(self.data, 0).tolist())
# 计算主对角线的结果,插入到results中
results.append(np.sum(np.diag(self.data)))
# 计算副对角线的结果,插入到results中
results.append(np.sum(np.diag(np.fliplr(self.data))))
# 检查是否有胜负结果
for v in results:
if int(v) == BOARD_LEN:
self.winner = 1
self.terminal = True
return
elif int(v) == -BOARD_LEN:
self.winner = -1
self.terminal = True
return
# 如果没有胜负结果,同时棋盘下满了,说明是平局
if np.sum(np.abs(self.data)) == BOARD_LEN*BOARD_LEN:
self.winner = 0
self.terminal = True
def step(self, action):
"""action: is a tuple or list [x, y]
Return:
state, reward, terminal
"""
# ----------------------------------
# 2019.08.21
# ----------------------------------
# 先根据action改变data
# 然后checkState,判断是否结束游戏,然后得到reward
if self.current_player == 1:
value = 1
else:
value = -1
self.data[action[0], action[1]] = value # 这里只管执行,action是否合理是在前面就得确定
self.checkState()
next_state = self.getState()
reward = self.getReward()
self.switchPlayer()
return next_state,reward,self.terminal
class RandAgent(object):
def policy(self, state):
"""
Return: action
"""
# ----------------------------------
# 2019.08.21
# ----------------------------------
# 根据现在的state,生成可以执行的action
available_action = np.where(state == 0) # where之后,是按维度分开的(坐标以tuple的形式给出,通常原数组有多少维,输出的tuple中就包含几个数组,分别对应符合条件元素的各维坐标。)
available_action = list(zip(*available_action)) # zip返回的是一个对象
action = random.sample(available_action, 1)[0] # 从列表里取出第一个元素,也就是一个元组
print('action',action)
return action
def main():
env = TicTacToeEnv()
agent1 = RandAgent()
agent2 = RandAgent()
state = env.reset()
# 这里给出了一次运行的代码参考
# 你可以按照自己的想法实现
# 多次实验,计算在双方随机策略下,先手胜/平/负的概率
while 1:
current_player = env.getCurrentPlayer()
if current_player == 1:
action = agent1.policy(state)
else:
action = agent2.policy(state)
next_state, reward, terminal = env.step(action)
print(next_state)
if terminal:
winner = 'Player1' if env.getWinner() == 1 else 'Player2'
print('Winner: {}'.format(winner))
break
state = next_state
env.reset()
if __name__ == "__main__":
main()
主要是通过这个实例理解强化学习中的环境与智能体,环境对智能体的奖励,智能体对历史的利用,智能体通过动作作用于环境,智能体的策略,环境模型等概念。