贪吃蛇的Gym环境搭建

写个贪吃蛇拿来入门实践深度强化学习感觉上是再合适不过了,不过得先把贪吃蛇写出来

一.游戏规则

也无意搞得多复杂,游戏规则就概括为两句话:

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)

运行之,然后你就能看到一个不受控制傻乎乎的贪吃蛇(还只是一个头,你甚至几乎不能看到变成蛇的样子,因为几乎吃不到食物)

很傻是吗?然而我并不打算做键盘控制(虽然我做了,但是这只是想玩玩而已,对实现目标并无任何裨益)

总之就写出来了,下一步就能准备编写深度强化学习的玩意了。

上一篇:实战讲解内网渗透思路


下一篇:ubuntu的kylin16版本安装OpenAI gym