写个贪吃蛇拿来入门实践深度强化学习感觉上是再合适不过了,不过得先把贪吃蛇写出来
一.游戏规则
也无意搞得多复杂,游戏规则就概括为两句话:
1.头部碰到边界或者身体游戏结束
2.吃到食物长度加一
二.准备文件
1.安装好gym和pygame
2.安装好之后,可以在anaconda prompt界面用pip show gym命令找到gym安装目录
3.新建文件Greedy_Snake.py
放在gym\envs\classic_control
中,方便调用同文件夹中的rendering.py文件.
4.在gym\envs
目录下的_init_.py
文件中加入:
register (
id= 'GreedySnake-v0',# id是调用所构建的环境的时候起的名字
entry_point='gym.envs.classic_control:SnakeEnv',# entry_point是环境文件所在的位置
max_episode_steps=200,
reward_threshold=100.0,
)
5.在gym\envs\classic_control
目录下的__init__.py
文件中加入:
from gym.envs.classic_control.Greedy_Snake import SnakeEnv
在这一切都准备好了后,就要开始在Greedy_Snake.py
文件中写SnakeEnv
类来实现我们的Gym环境了。
6.在Greedy_Snake.py
准备好Gym的标准框架:
class SnakeEnv(gym.Env):
metadata = {# 元数据
'render.modes': ['human', 'rgb_array'],
'video.frames_per_second': 2
}
# 初始化
def __init__(self):
#动力函数 最重要的部分
def step(self, action):# 输入为动作
return state, rewards, done, info# 输出为 状态, 反馈, 终结标志, 调试信息
# 将状态重置为初始状态
def reset(self):
return state# 返回初始状态
# 图形化的函数部分
def render(self, mode='human'):
# 关闭GUI的
def close(self):
三.编写贪吃蛇
终于开始写贪吃蛇了,别看上面的部分只有一点点(虽然确实只有一点点),但是查了好久的资料才理解和捋顺了的(可能是人傻)。
1.编写__init__
:
贪吃蛇的构造函数主要需要初始化两部分:一个是环境信息,一个是GUI信息
环境信息主要包括环境的行为空间和观测空间。
GUI信息主要包括pygame的初始化以及蛇的颜色和食物颜色以及像素大小和游戏边框等等
def __init__(self):
#游戏环境初始化
self.Size = 15
self.height = 50 # 高度
self.width = 60 # 宽度
self.bodyColor = (0,225,0)
self.foodColor = (0,0,225)
#环境初始化
self.action_space = spaces.Discrete(4) # 0:上 1:下 2:左 3:右
self.observation_space = spaces.Discrete(self.height * self.width) # 观测空间是整个游戏棋盘
惊讶的发现写出来后居然只有两句话就概括了,写的时候因为要考虑全局还是思考了有一会的。
2.编写step
:
这个函数是重中之重,基本上贪吃蛇的所有东西都在这个里面实现。
其逻辑是输入一个行动(在贪吃蛇里就是方向)。然后蛇就要跟着这个方向移动,同时如果吃到了食物,蛇的长度会加一,分数加一百,食物也会随机的重新生成,如果存活则分数加一(生存是第一准则嘛)。最后返回更新后的状态值,分数和是否结束游戏的标志。
放代码:
def step(self, action):
self.move(action)
self.eat()
self.state = self.getState()
self.score += 1
return self.state, self.score, self.is_done(), {}
然后我们把每段逻辑分成函数逐个实现。
2.1move
贪吃蛇的移动可以通过一个列表来存放每个身体的位置,列表首端就是蛇头,末端就是蛇尾,移动就是列表的首端插入移动后的蛇头位置,同时弹出末尾。
在移动的同时还要注意不能往反方向移动
def move(self,direction):
"""
移动
"""
# 上走height减一
if direction == 0 and self.direction != 1:
self.snake.insert(0,[self.snake[0][0] - 1,self.snake[0][1]])
self.snake.pop()
# 下走height加一
elif direction == 1 and self.direction != 0:
self.snake.insert(0, [self.snake[0][0] + 1, self.snake[0][1]])
self.snake.pop()
# 左走width减一
elif direction == 2 and self.direction != 3:
self.snake.insert(0, [self.snake[0][0], self.snake[0][1] - 1])
self.snake.pop()
# 右走width加一
elif direction == 3 and self.direction != 2:
self.snake.insert(0, [self.snake[0][0], self.snake[0][1] + 1])
self.snake.pop()
2.2eat
如果蛇头与食物重合,则视为“吃”,然后通过在尾部插入一个同尾部相同坐标的部分,下次移动时尾部弹出后会呈现未弹出的效果,即达到了增长的效果,吃了后还要生成新的食物。当然如果没得吃的话就什么都不做。
if self.snake[0] == self.food:
self.food = self.getFood()
self.socre += 100
self.snake.append(self.snake[-1])
2.2.1getFood
生成新食物要注意的就是不能在蛇身处生成,所以还要稍加拒绝,直到生成符合要求的坐标
def getFood(self):
"""
获取新的食物位置
"""
food = [random.randint(0,self.height - 1),random.randint(0,self.width - 1)]
while food in self.snake:
food = [random.randint(0, self.height - 1), random.randint(0, self.width - 1)]
return food
2.3getState
把游戏棋盘抽成一维数组
def getState(self):
board = np.array(self.getBoard())
board = board.reshape(3000)
return board
2.4is_done
这是用来判断是否为终局
如果蛇头在不该出现的位置就为终局(如蛇身,边界外)
def is_done(self):
head = self.snake[0]
if head in self.snake[1:]:
return True
if head[0] < 0 or head[0] >= self.height - 1 or head[1] < 0 or head[1] >= self.width - 1:
return True
return False
3.编写reset
:
reset
就是重置环境,主要用于以后训练的时候用(当然我因为懒,所以这里也被我寄予了初始化的重望)
def reset(self,is_GUI=True):
self.snake = [[random.randint(0,self.height - 1), random.randint(0,self.width - 1)]] # 蛇的所有身体位置
self.food = self.getFood() #食物位置
self.direction = random.choice([0,1,2,3]) #随机方向
self.score = 0
self.state = self.getState()
self.is_GUI = is_GUI
if is_GUI:
pygame.init()
size = self.width * self.Size, self.height * self.Size # 设置窗口大小
self.screen = pygame.display.set_mode(size) # 显示窗口
return self.state
4.编写render
:
这里就是编写图形界面的地方
其逻辑主要就是把屏幕涂黑,然后画出蛇和食物,打印出分数,刷新!
def render(self, mode='human'):
# 涂黑
self.screen.fill((0,0,0))
# 画
board = self.getBoard()
for h in range(len(board)):
for w in range(len(board[0])):
if board[h][w] == 1:
pygame.draw.rect(self.screen, self.bodyColor,
[w * self.Size, h * self.Size, self.Size, self.Size], 0)
elif board[h][w] == 2:
pygame.draw.rect(self.screen, self.foodColor,
[w * self.Size, h * self.Size, self.Size, self.Size], 0)
# 分数
font = pygame.font.SysFont("freesansbold.ttf", 30) # 30:font size
text = font.render(str(self.score), True, (225, 225, 225)) # (0,0,0) color of font
self.screen.blit(text, (10, 10)) # (10,10) rect left top
# 刷新
pygame.display.update()
return None
5.编写close
:
这个嘛,我直接没写,没关系!
四.编写测试文件
新建文件env_TEST.py
输入:
import random
import sys
import time
import gym
env = gym.make('GreedySnake-v0')
env.reset()
while True:
action = random.randint(0,3)
state, score, is_done, info = env.step(action)
env.render()
if is_done:
print(score)
sys.exit()
time.sleep(0.01)
运行之,然后你就能看到一个不受控制傻乎乎的贪吃蛇(还只是一个头,你甚至几乎不能看到变成蛇的样子,因为几乎吃不到食物)
很傻是吗?然而我并不打算做键盘控制(虽然我做了,但是这只是想玩玩而已,对实现目标并无任何裨益)
总之就写出来了,下一步就能准备编写深度强化学习的玩意了。