【深入浅出强化学习-编程实战】2 马尔可夫决策过程
2.1 鸳鸯系统马尔可夫决策过程
左上为雄鸟,右上为雌鸟,中间有两道障碍物。目标:雄鸟找到雌鸟。
2.1.1 代码展示
main.py
import pygame
from resource.load import *
import math
import time
import random
import numpy as np
class YuanYangEnv:
# 初始化子函数
def __init__(self):
# 状态空间
self.states = []# 0-99
for i in range(0,100):
self.states.append(i)
self.actions=['e','w','n','s']
self.gamma = 0.8
# 值函数
self.value = np.zeros((10,10))# 10*10的表格
# 设置渲染属性
self.viewer = None # 一个渲染窗口
# 帧速率是指程序每秒在屏幕山绘制图像的数目,我们可以用FPS来表示它。一般的计算机都能达到每秒60帧的速度。如果我们把帧速率讲得比较低,那么游戏也会看上去较为卡顿。
self.FPSCLOCK = pygame.time.Clock()
# 屏幕大小
self.screen_size = (1200,900)
# 雄鸟当前位置
self.bird_position = (0,0)
# 雄鸟在x方向每走一次像素为120
self.limit_distance_x = 120
# 雄鸟在y方向每走一次像素为90
self.limit_distance_y = 90
# 每个障碍物大小为120像素*90像素
self.obstacle_size = [120,90]
# 一共有两个障碍物墙,每个障碍物墙由8个小障碍物组成
self.obstacle1_x = []
self.obstacle1_y = []
self.obstacle2_x = []
self.obstacle2_y = []
for i in range(8):
# 第一个障碍物
self.obstacle1_x.append(360)
if i <= 3:
self.obstacle1_y.append(90*i)
else:
self.obstacle1_y.append(90*(i+2))
# 第二个障碍物
self.obstacle2_x.append(720)
if i <= 4:
self.obstacle2_y.append(90*i)
else:
self.obstacle2_y.append(90*(i+2))
# 雄鸟初始位置
self.bird_male_init_position = [0,0]
# 雄鸟当前位置
self.bird_male_position = [0,0]
# 雌鸟初始位置
self.bird_female_init_position = [1080,0]
# 雄鸟碰撞检测子函数
def collide(self, state_position):
# 用标志flag,flag1,flag2分别表示是否与障碍物、障碍物墙1、障碍物墙2发生碰撞
flag = 1
flag1 = 1
flag2 = 1
# 检测雄鸟是否与第一个障碍物墙发生碰撞
# 找到雄鸟与第一个障碍物所有障碍物x方向和y方向最近的障碍物的坐标差
# 并判断最近的坐标差是否大于一个最小运动距离
# 如果大于等于 就不会发生碰撞
dx = []
dy = []
for i in range(8):
dx1 = abs(self.obstacle1_x[i] - state_position[0])
dx.append(dx1)
dy1 = abs(self.obstacle1_y[i] - state_position[1])
dy.append(dy1)
mindx = min(dx)
mindy = min(dy)
if mindx >= self.limit_distance_x or mindy >= self.limit_distance_y:
flag1 = 0 # 没碰
# 是否与第二个障碍物墙碰撞
second_dx = []
second_dy = []
for i in range(8):
dx2 = abs(self.obstacle2_x[i] - state_position[0])
second_dx.append(dx2)
dy2 = abs(self.obstacle2_y[i] - state_position[1])
second_dy.append(dy2)
mindx = min(second_dx)
mindy = min(second_dy)
if mindx >= self.limit_distance_x or mindy >= self.limit_distance_y:
flag2 = 0 # 没碰
if flag1 == 0 and flag2 == 0:
flag = 0 # 没碰
# 是否超出边界,如果是,也认为发生碰撞
if state_position[0] > 1080 or state_position[0] < 0 or state_position[1] > 810 or state_position[1] < 0:
flag = 1 # 碰了
# 返回碰撞标志位
return flag
# 雄鸟是否找到雌鸟子函数
def find(self,state_position):
# 设置标志位flag
# 判断雄鸟当前位置和雌鸟位置坐标差,雄安与最小运动距离则为找到
flag = 0
if abs(state_position[0] - self.bird_female_init_position[0]) < self.limit_distance_x and abs(
state_position[1] - self.bird_female_init_position[1]) < self.limit_distance_y:
flag = 1
return flag
# 状态转化为像素坐标子函数
def state_to_position(self, state):
i = int(state / 10)
j = state % 10
position = [0, 0]
position[0] = 120 * j
position[1] = 90 * i
return position
# 像素转化为状态坐标子函数
def position_to_state(self, position):
i = position[0] / 120
j = position[1] / 90
return int(i * 10 + j)
# 重置环境子函数
def reset(self):
# 随机产生一个合法的初始位置,不能在障碍物处,不能在雌鸟处,
flag1 = 1
flag2 = 1
while flag1 or flag2 == 1:
# 随机产生初始状态0-99,random.random()产生一个0-1的随机数
state = self.states[int(random.random()*len(self.states))]
state_position = self.state_to_position(state)
flag1 = self.collide(state_position)
flag2 = self.find(state_position)
return state
# 状态转移子函数
# 首先判断当前坐标是否与障碍物碰撞或是否是终点,如果是,结束转换
# 如果否,更具运动学进行像素位置坐标的转换
# 最后判断碰撞和是否找到
# 没找到:回报为0;找到:回报为1;碰撞:回报为-1
def transform(self,state,action):
# 将当前状态转化为坐标
current_position = self.state_to_position(state)
next_position = [0,0]
flag_collide = 0
flag_find = 0
# 判断是否与障碍物碰撞
flag_collide = self.collide(current_position)
# 判断是否为终点
flag_find = self.find(current_position)
if flag_collide==1 or flag_find ==1:
return state,0,True # 结束
# 状态转移
if action == 'e':
next_position[0]=current_position[0] + 120
next_position[1]=current_position[1]
if action == 'w':
next_position[0] = current_position[0] - 120
next_position[1] = current_position[1]
if action == 'e':
next_position[0] = current_position[0] + 120
next_position[1] = current_position[1]
if action == 'n':
next_position[0] = current_position[0]
next_position[1] = current_position[1] - 90
if action == 's':
next_position[0] = current_position[0]
next_position[1] = current_position[1] + 90
# 判断是否碰撞
flag_collide = self.collide(next_position)
# 如果碰撞,回报为-1,并结束
if flag_collide==1:
return self.position_to_state(current_position),-1,True # 结束
# 判断是否为终点
flag_find = self.find(next_position)
if flag_find == 1:
return self.position_to_state(next_position),1,True # 结束
return self.position_to_state(next_position),0,False # 不结束
# 游戏结束控制子函数
# 调用pygame中的事件用来结束游戏,如果结束,则pygame会结束渲染
def gameover(self):
for event in pygame.event.get():
if event.type == QUIT:
exit()
# 游戏渲染子函数
def render(self):
# 首先判断环境是否有一个游戏窗口,如果没有,调用pygame.display.set_mode设置一个游戏窗口
# 调用load_bird_male(),load_bird_female(),load_background(),load_obstacle()函数下载游戏环境需要的突破 这四个函数的实现在load.py中进行
# 下载完后,利用pygame自带的blit函数将图片画到窗口上
if self.viewer is None:
pygame.init()
# 画一个窗口
self.viewer = pygame.display.set_mode(self.screen_size,0,32)# 32表示深度
pygame.display.set_caption("yuanyang")
# 下载图片
self.bird_male = load_bird_male()
self.bird_female = load_bird_female()
self.background = load_background()
self.obstacle = load_obstacle()
# self.viewer.blit(self.bird_male,self.bird_male_init_position)
# 在幕布上画图片
self.viewer.blit(self.bird_female,self.bird_female_init_position)
self.viewer.blit(self.background,(0,0))
self.font = pygame.font.SysFont('times',15)# 字体大小15
# 如果环境中已经有窗口
# 则依次画:背景图,11条纵向直线,11条横向直线,第一个障碍物墙,第二个障碍物墙,雄鸟,值函数
# 画完后,调用pygame.display.update将这些原色更新到幕布中
# 调用子函数gameover()来检查是否要结束有游戏
self.viewer.blit(self.background,(0,0))
# 画直线
for i in range(11):
pygame.draw.lines(self.viewer,(255,255,255),True,((120*i,0),(120*i,900)),1)
pygame.draw.lines(self.viewer, (255, 255, 255), True, ((0, 90*i), (1200, 90*i)), 1)
self.viewer.blit(self.bird_female,self.bird_female_init_position)
# 画障碍物
for i in range(8):
self.viewer.blit(self.obstacle, (self.obstacle1_x[i], self.obstacle1_y[i]))
self.viewer.blit(self.obstacle, (self.obstacle2_x[i], self.obstacle2_y[i]))
# 画雄鸟
self.viewer.blit(self.bird_male,self.bird_male_position)
# 画值函数
for i in range(10):
for j in range(10):
surface = self.font.render(str(round(float(self.value[i,j]),3)),True,(0,0,0))
self.viewer.blit(surface,(120*i+5,90*j+70))
pygame.display.update()
self.gameover()
self.FPSCLOCK.tick(30)
# 主函数程序测试
# 首先声明一个鸳鸯环境的示例yy
# 然后调用渲染函数将鸳鸯系统绘制出来
# 最后调用循环语句检查是都要结束游戏渲染
if __name__ == "__main__":
yy = YuanYangEnv()
yy.render()
while True:
for event in pygame.event.get():
if event.type == QUIT:
exit()
load.py
import pygame
import os
from pygame.locals import *
from sys import exit
#得到当前工程目录
current_dir = os.path.split(os.path.realpath(__file__))[0]
print(current_dir)
#得到文件名
bird_file = r"F:\pythonProject\pythonProject\pythonProject8\resource\bird.png"
obstacle_file = r"F:\pythonProject\pythonProject\pythonProject8\resource\obstacle.png"
background_file = r"F:\pythonProject\pythonProject\pythonProject8\resource\background.png"
#创建小鸟,
def load_bird_male():
bird = pygame.image.load(bird_file).convert_alpha()
return bird
def load_bird_female():
bird = pygame.image.load(bird_file).convert_alpha()
return bird
#得到背景
def load_background():
background = pygame.image.load(background_file).convert()
return background
#得到障碍物
def load_obstacle():
obstacle = pygame.image.load(obstacle_file).convert()
return obstacle
文件放置位置如下:
图片:
结果展示:
2.1.2 部分代码解析
- line23
- 帧速率是指程序每秒在屏幕山绘制图像的数目,我们可以用FPS来表示它。一般的计算机都能达到每秒60帧的速度。如果我们把帧速率讲得比较低,那么游戏也会看上去较为卡顿。
pygame.time.Clock
对象帮助我们确定程序要以多少最大的帧速率运行,这个对象在游戏每一次迭代都会设置一个暂停,以防程序运行过快,有时候计算机的速度过于快速,我们就可以利用这个对象来让计算机在一个固定的速度运行,我们可以这样创建Clock对象:
FPSClock=pygame.time.Clock()
- 我们要把他放在结尾
pygame.display.update()
后,我们可以用tick对象来让计算机知道你要在每一次迭代后暂停多少时间:
FPSClock.tick(30)
- line199
self.viewer = pygame.display.set_mode(self.screen_size,0,32)
- 初始化一个准备显示的窗口或屏幕。
set_mode(resolution=(0,0), flags=0, depth=0) -> Surface
- 这个函数将创建一个
Surface
对象的显示界面。传入的参数用于指定显示类型。最终创建出来的显示界面将是最大可能地匹配当前操作系统。 resolution
参数是一个二元组,表示宽和高。flags
参数是附件选项的集合。depth
参数表示使用的颜色深度。- 返回的
Surface
对象可以像常规的Surface
对象那样去绘制,但发生的改变最终会显示到屏幕上。 - 如果没有传入
resolution
参数,或者使用默认设置 (0, 0),且Pygame
使用 SDL1.2.10 以上版本,那么创建出来的Surface
对象将与当前屏幕用户一样的分辨率。如果只有宽或高其中一项被设置为 0,那么Surface
对象将使用屏幕分辨率的宽或高代替它。如果 SDL 版本低于 1.2.10,那么将抛出异常。 - 通常来说,最好的做法是不要传递 depth 参数。因为默认
Pygame
会根据当前操作系统选择最好和最快的颜色深度。如果你的游戏确实需要一个特殊的颜色格式,那么你可以通过控制depth
参数来实现。Pygame
将为模拟一个非现成的颜色深度而耗费更多的时间。 - 当使用全屏显示模式的时候,有时候无法完全匹配到需要的分辨率。在这种情况下,
Pygame
将自动选择最匹配的分辨率使用,而返回的Surface
对象将保持与需求的分辨率一致。 flags
参数指定你想要的显示类型。提供几个选择给你,你可以通过位操作同时使用多个类型(管道操作符 “|”)。如果你传入 0 或没有传入flags
参数,默认会使用软件驱动窗口。这儿是flags
参数提供的几个可选项:
- line 210
self.font = pygame.font.SysFont('times',15)
- 从系统字体库创建一个
Font
对象。
SysFont(name, size, bold=False, italic=False) -> Font
- 从系统字体库中加载并返回一个新的字体对象。
- 该字体将会匹配
bold
(加粗)和italic
(斜体)参数的要求。 - 如果找不到一个合适的系统字体,该函数将会回退并加载默认的
pygame
字体。 - 尝试搜索的
name
参数可以是一个用逗号隔开的列表。
- line 219
pygame.draw.lines(self.viewer,(255,255,255),True,((120*i,0),(120*i,900)),1)
- 原型:
pygame.draw.lines(Surface, color, closed, pointlist, width=1): return Rect
- 用途:用于绘制一系列直线段。
closed
是一个布尔变量,如果closed
为真,那么表示需要把第一点和最后一点连接起来。这些点来自pointlist
,一个包含坐标点的列表。这个函数不会绘制线条的端点,也没有斜角连接(miter joints),而且角度小和线条粗的连线看起来会有点奇怪( Lines with sharp corners and wide line widths can have improper looking corners.)。
- line 232
surface = self.font.render(str(round(float(self.value[i,j]),3)),True,(0,0,0))
- 第一个参数是写的文字;第二个参数是个布尔值,是否开启抗锯齿,如果为True的话字体会比较平滑,不过相应的速度有一点点影响;第三个参数是字体的颜色;第四个是背景色,如果你想没有背景色(也就是透明),那么可以不加这第四个参数。
- 其中
round()
函数用于返回浮点数number的四舍五入值 - 语法:
round(number[, ndigits])
- 参数介绍:
number
— 数值表达式
ndigits
— 表示从小数点位数,其中 number 需要四舍五入,默认值为 0 - 返回值:
返回浮点数number的四舍五入值