Python实现TicTacToe游戏模拟,理解强化学习基本概念

1. 问题描述

TicTacToe 是一个简单的对抗游戏,棋盘大小为 3 × 3,谁先将棋子连成线(横、竖、斜),谁就获得胜利。(× 先手) 这里要求大家实现以下功能:
(1) 用数值的方式表示状态、动作、奖励(+1/0/-1 区分胜/平/负)。
(2) 环境类,环境能够根据智能体的动作给出反馈。即实现成员函数step(a)→s, r。
(3)智能体类,并包含一个随机策略,即从剩下的空位中随机采样一个位置下。函数形式 policy(s)→ a。
(4) 通过仿真的方式,大量对弈,统计在都执行随机策略的情况下,先手和后手胜利的概率。

2. 代码实现

#!/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()

3. 总结思考

主要是通过这个实例理解强化学习中的环境与智能体,环境对智能体的奖励,智能体对历史的利用,智能体通过动作作用于环境,智能体的策略,环境模型等概念。

  • 环境与智能体是强化学习的两大主体,界定好环境和智能体是强化学习的第一步。在这里环境用TicTacToeEnv类来描述,智能体用RandAgent类来描述。
  • 环境类里面包含有环境模型,主要是要将使得这个游戏在环境中能够运行下去,并且环境设置得要能对智能体的动作有所响应,也就是动作要能够对环境起到一定的作用。
  • 环境对智能体的作用,体现在奖励上,这里的奖励只有最后赢了才有,
  • 状态是智能体对历史的利用,不同的情况下,智能体对历史的利用程度是不同的。
  • 智能体在某种状态下,通过动作作用于环境,可能对环境造成一些改变。
  • 策略是智能体的核心,强化学习的目的也是找到一个策略,策略就是从状态到动作的映射,策略有确定性的也有不确定性的。
  • 环境模型就是描述环境的改变的模型。如果环境模型是未知的,那么智能体就不知道自己的动作会对环境造成什么样的影响,只能通过奖励去学习。

你可能感兴趣的:(Python实现TicTacToe游戏模拟,理解强化学习基本概念)