强化学习实战 | 自定义Gym环境之井字棋
在文章 强化学习实战 | 自定义Gym环境 中 ,我们了解了一个简单的环境应该如何定义,并使用 print 简单地呈现了环境。在本文中,我们将学习自定义一个稍微复杂一点的环境——井字棋。回想一下井字棋游戏:
- 这是一个双人回合制博弈游戏,双方玩家使用的占位符是不一样的(圈/叉),动作编写需要区分玩家
- 双方玩家获得的终局奖励是不一样的,胜方+1,败方-1(除非平局+0),奖励编写需要区分玩家
- 终局的条件是:任意行 / 列 / 对角 占满了相同的占位符 or 场上没有空位可以占位
- 从单个玩家的视角看,当前状态 s 下采取动作 a 后,新的状态 s_ 并不是后继状态,而是一个等待对手动作的中间状态,真正的后继状态是对手动作之后产生的状态 s'(除非采取动作 a 后游戏直接结束),如下图所示:
除了游戏本身的机制,考虑到与gym的API接口格式的契合,通过外部循环控制游戏进程是较方便的,所以env本身定义时不必要编写控制游戏进程 / 切换行动玩家的代码。另外,我们还需要更生动的环境呈现方式,而不是print!那么,接下来我们就来实现上述的目标吧!
步骤1:新建文件
来到目录:D:\Anaconda\envs\pytorch1.1\Lib\site-packages\gym\envs\user,创建文件 __init__.py 和 TicTacToe_env.py(还记得吗?文件夹user是文章 强化学习实战 | 自定义Gym环境 中我们创建的用来存放自定义环境的文件夹)。
步骤2:编写 TicTacToe_env.py 和 __init__.py
gym内置了一个绘图工具rendering,不过功能并不周全,想要绘制复杂的东西非常麻烦。本文不打算深入研究,只借助rendering中基本的线条 / 方块 / 圆圈呈现环境(更生动的游戏表现我们完全可以通过pygame来实现)。rendering是单帧绘制的,当调用env.render()时,将呈现当前 self.viewer.geoms 中所记录的绘画元素。环境的基本要素设计如下:
- 状态:由二维的numpy.array表示,无占位符值为0,有蓝色占位符值为1,有红色占位符值为-1。
- 动作:设计为一个字典,有着格式:action = {'mark':'blue', 'pos':(x, y)},其中'mark'表示占位符的颜色,用以区分玩家,'pos'表示占位符的位置。
- 奖励:锁定蓝方视角,胜利+1,失败-1,平局+0。
TicTacToe_env.py 的整体代码如下:
import gym
import random
import time
import numpy as np
from gym.envs.classic_control import rendering class TicTacToeEnv(gym.Env):
def __init__(self):
self.state = np.zeros([3, 3])
self.winner = None
WIDTH, HEIGHT = 300, 300
self.viewer = rendering.Viewer(WIDTH, HEIGHT) def reset(self):
self.state = np.zeros([3, 3])
self.winner = None
self.viewer.geoms.clear() # 清空画板中需要绘制的元素
self.viewer.onetime_geoms.clear() def step(self, action):
# 动作的格式:action = {'mark':'circle'/'cross', 'pos':(x,y)}# 产生状态
x = action['pos'][0]
y = action['pos'][1]
if action['mark'] == 'blue':
self.state[x][y] = 1
elif action['mark'] == 'red':
self.state[x][y] = -1
# 奖励
done = self.judgeEnd()
if done:
if self.winner == 'blue':
reward = 1
else:
reward = -1
else: reward = 0
# 报告
info = {}
return self.state, reward, done, info def judgeEnd(self):
# 检查两对角
check_diag_1 = self.state[0][0] + self.state[1][1] + self.state[2][2]
check_diag_2 = self.state[2][0] + self.state[1][1] + self.state[0][2]
if check_diag_1 == 3 or check_diag_2 == 3:
self.winner = 'blue'
return True
elif check_diag_1 == -3 or check_diag_2 == -3:
self.winner = 'red'
return True
# 检查三行三列
state_T = self.state.T
for i in range(3):
check_row = sum(self.state[i]) # 检查行
check_col = sum(state_T[i]) # 检查列
if check_row == 3 or check_col == 3:
self.winner = 'blue'
return True
elif check_row == -3 or check_col == -3:
self.winner = 'red'
return True
# 检查整个棋盘是否还有空位
empty = []
for i in range(3):
for j in range(3):
if self.state[i][j] == 0: empty.append((i,j))
if empty == []: return True return False def render(self, mode='human'):
SIZE = 100
# 画分隔线
line1 = rendering.Line((0, 100), (300, 100))
line2 = rendering.Line((0, 200), (300, 200))
line3 = rendering.Line((100, 0), (100, 300))
line4 = rendering.Line((200, 0), (200, 300))
line1.set_color(0, 0, 0)
line2.set_color(0, 0, 0)
line3.set_color(0, 0, 0)
line4.set_color(0, 0, 0)
# 将绘画元素添加至画板中
self.viewer.add_geom(line1)
self.viewer.add_geom(line2)
self.viewer.add_geom(line3)
self.viewer.add_geom(line4)
# 根据self.state画占位符
for i in range(3):
for j in range(3):
if self.state[i][j] == 1:
circle = rendering.make_circle(30) # 画直径为30的圆
circle.set_color(135/255, 206/255, 250/255) # mark = blue
move = rendering.Transform(translation=(i * SIZE + 50, j * SIZE + 50)) # 创建平移操作
circle.add_attr(move) # 将平移操作添加至圆的属性中
self.viewer.add_geom(circle) # 将圆添加至画板中
if self.state[i][j] == -1:
circle = rendering.make_circle(30)
circle.set_color(255/255, 182/255, 193/255) # mark = red
move = rendering.Transform(translation=(i * SIZE + 50, j * SIZE + 50))
circle.add_attr(move)
self.viewer.add_geom(circle) return self.viewer.render(return_rgb_array=mode == 'rgb_array')
在 __init__.py 中引入类的信息,添加:
from gym.envs.user.TicTacToe_env import TicTacToeEnv
步骤3:注册环境
来到目录:D:\Anaconda\envs\pytorch1.1\Lib\site-packages\gym,打开 __init__.py,添加代码:
register(
id="TicTacToeEnv-v0",
entry_point="gym.envs.user:TicTacToeEnv",
max_episode_steps=20,
)
步骤4:测试环境
在测试代码中,我们在主循环中让游戏不断地进行。蓝红双方玩家以0.5s的间隔,随机选择空格子动作,代码如下:
import gym
import random
import time # 查看所有已注册的环境
# from gym import envs
# print(envs.registry.all()) def randomAction(env_, mark): # 随机选择未占位的格子动作
action_space = []
for i, row in enumerate(env_.state):
for j, one in enumerate(row):
if one == 0: action_space.append((i,j))
action_pos = random.choice(action_space)
action = {'mark':mark, 'pos':action_pos}
return action def randomFirst():
if random.random() > 0.5: # 随机先后手
first_, second_ = 'blue', 'red'
else:
first_, second_ = 'red', 'blue'
return first_, second_ env = gym.make('TicTacToeEnv-v0')
env.reset() # 在第一次step前要先重置环境 不然会报错
first, second = randomFirst()
while True:
# 先手行动
action = randomAction(env, first)
state, reward, done, info = env.step(action)
env.render()
time.sleep(0.5)
if done:
env.reset()
env.render()
first, second = randomFirst()
time.sleep(0.5)
continue
# 后手行动
action = randomAction(env, second)
state, reward, done, info = env.step(action)
env.render()
time.sleep(0.5)
if done:
env.reset()
env.render()
first, second = randomFirst()
time.sleep(0.5)
continue
效果如下图所示:
强化学习实战 | 自定义Gym环境之井字棋的更多相关文章
- 强化学习实战 | 表格型Q-Learning玩井字棋(一)
在 强化学习实战 | 自定义Gym环境之井子棋 中,我们构建了一个井字棋环境,并进行了测试.接下来我们可以使用各种强化学习方法训练agent出棋,其中比较简单的是Q学习,Q即Q(S, a),是状态动作 ...
- 强化学习实战 | 自定义Gym环境之扫雷
开始之前 先考虑几个问题: Q1:如何展开无雷区? Q2:如何计算格子的提示数? Q3:如何表示扫雷游戏的状态? A1:可以使用递归函数,或是堆栈. A2:一般的做法是,需要打开某格子时,再去统计周围 ...
- 强化学习实战 | 表格型Q-Learning玩井字棋(二)
在 强化学习实战 | 表格型Q-Learning玩井字棋(一)中,我们构建了以Game() 和 Agent() 类为基础的框架,本篇我们要让agent不断对弈,维护Q表格,提升棋力.那么我们先来盘算一 ...
- 强化学习实战 | 表格型Q-Learning玩井字棋(四)游戏时间
在 强化学习实战 | 表格型Q-Learning玩井字棋(三)优化,优化 中,我们经过优化和训练,得到了一个还不错的Q表格,这一节我们将用pygame实现一个有人机对战,机机对战和作弊功能的井字棋游戏 ...
- 强化学习实战 | 自定义gym环境之显示字符串
如果想用强化学习去实现扫雷.2048这种带有数字提示信息的游戏,自然是希望自定义 gym 环境时能把字符显示出来.上网查了很久,没有找到gym自带的图形工具Viewer可以显示字符串的信息,反而是通过 ...
- 强化学习实战 | 自定义Gym环境
新手的第一个强化学习示例一般都从Open Gym开始.在这些示例中,我们不断地向环境施加动作,并得到观测和奖励,这也是Gym Env的基本用法: state, reward, done, info = ...
- 强化学习实战 | 表格型Q-Learning玩井子棋(三)优化,优化
在 强化学习实战 | 表格型Q-Learning玩井字棋(二)开始训练!中,我们让agent"简陋地"训练了起来,经过了耗费时间的10万局游戏过后,却效果平平,尤其是初始状态的数值 ...
- [游戏学习22] MFC 井字棋 双人对战
>_<:太多啦,感觉用英语说的太慢啦,没想到一年做的东西竟然这么多.....接下来要加速啦! >_<:注意这里必须用MFC和前面的Win32不一样啦! >_<:这也 ...
- quick cocos2d-x 入门---井字棋
学习quick cocos2d-x 第二天 ,使用quick-x 做了一个井字棋游戏 . 我假设读者已经 http://wiki.quick-x.com/doku.php?id=zh_cn阅读了这个链 ...
随机推荐
- 零基础如何更好的学习Linux
本节旨在介绍对于初学者如何学习 Linux 的建议.如果你已经确定对 Linux 产生了兴趣,那么接下来我们介绍一下学习 Linux 的方法. 如何去学习 学习大多类似庖丁解牛,对事物的认识一般都是由 ...
- MySQL 的架构与组件
MySQL 的逻辑架构图设计图 连接/线程处理:管理客户端连接/会话[mysql threads] 解析器:通过检查SQL查询中的每个字符来检查SQL语法,并为每个SQL查询生成 SQL_ID. 此 ...
- hdu 2190 重建希望小学(数学,递推)
题意: N*3的教室,有2种砖,2*2.1*1. 问铺设教室的方案有多少种.(要铺满) 思路: 画一下图可以很快发现递推公式 代码: int main(){ int a[35]; mem(a,0); ...
- 微信小程序API接口封装
@ 目录 一,让我们看一下项目目录 二,让我们熟悉一下这三个文件目的(文件名你看着办) 三,页面js中如何使用 今天的API的封装,我们拿WX小程序开发中,对它的API (wx.request)对这个 ...
- 讲分布式唯一id,这篇文章很实在
分布式唯一ID介绍 分布式系统全局唯一的 id 是所有系统都会遇到的场景,往往会被用在搜索,存储方面,用于作为唯一的标识或者排序,比如全局唯一的订单号,优惠券的券码等,如果出现两个相同的订单号,对于用 ...
- LeetCode刷题 二分专题
二分专题 二分的题目类型 对于满足二段性的题目的两套模板 模板一 模板如下 模板二 模板如下 解决二分题目的一般流程 LeeCode实战 LC69.x的平方根 解法思路 LC35.搜索插入位置 解法思 ...
- 什么?还在用delete删除数据《死磕MySQL系列 九》
系列文章 五.如何选择普通索引和唯一索引<死磕MySQL系列 五> 六.五分钟,让你明白MySQL是怎么选择索引<死磕MySQL系列 六> 七.字符串可以这样加索引,你知吗?& ...
- 安装spark 后 NoClassDefFoundError
安装spark后,hive报 java.lang.NoClassDefFoundError: org/apache/hadoop/mapreduce/InputFormat trace 看是sqoop ...
- codeforces心得1---747div2
codeforces心得1---747div2 cf div2的前AB题一般是字符串or数论的找规律结论题 因此标程极为精简 1.小窍门是看样例或者自己打表或造数据找规律 2.一些不确定的操作,可以化 ...
- 痞子衡嵌入式:在IAR开发环境下RT-Thread工程函数重定向失效分析
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家分享的是在IAR开发环境下RT-Thread工程函数重定向失效分析. 痞子衡旧文 <在IAR下将关键函数重定向到RAM中执行的方法> ...