基于深度强化学习的俄罗斯方块模拟训练器

1. 游戏机制介绍

gym-tetris 是一个将经典游戏《俄罗斯方块》(Tetris)集成到 OpenAI Gym 强化学习框架中的环境。它允许研究人员和开发者使用强化学习算法训练智能体来玩 Tetris,主要基于 NES(任天堂娱乐系统)版本的游戏。

gym-tetris 的核心特点

  • 平台基础:基于 nes-py 模拟器,模拟 NES 上的 Tetris 游戏。
  • OpenAI Gym 接口:通过标准的 env.step() 和 env.reset() 接口与环境交互。

动作空间:

  • 默认使用 NES 的 256 个离散动作。
  • 可使用简化动作列表(如 MOVEMENT 或 SIMPLE_MOVEMENT)来减少训练复杂度。

游戏模式:

  • A-type:标准耐力模式,持续游戏直到失败。
  • B-type:街机模式,目标是清除特定行数。

奖励机制:

  • 按得分变化奖励
  • 按清除行数奖励
  • 对堆叠高度增加进行惩罚

在这里插入图片描述

2. DQN模拟玩家控制游戏

import pdb
from nes_py.wrappers import JoypadSpace
from gym_tetris.actions import MOVEMENT

import gym_tetris
import torch
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
import torch.nn as nn
import torch.optim as optim
import numpy as np
import random
from collections import deque

class DQN(nn.Module):
    def __init__(self, state_dim, action_dim):
        super(DQN, self).__init__()
        self.net = nn.Sequential(
            nn.Linear(state_dim, 256),
            nn.ReLU(),
            nn.Linear(256, 128),
            nn.ReLU(),
            nn.Linear(128, action_dim)
        )
    def forward(self, x):
        return self.net(x)

def preprocess_state(state):
    # 假设 state 是 Box 类型,需展平成一维
    return np.array(state).flatten()

def select_action(state, policy_net, epsilon, action_dim):
    if random.random() < epsilon:
        return random.randrange(action_dim)
    with torch.no_grad():
        state = torch.FloatTensor(state)
        q_values = policy_net(state)
        return q_values.argmax().item()

def train():
    env = gym_tetris.make('TetrisA-v3')
    env = JoypadSpace(env, MOVEMENT)

    state = preprocess_state(env.reset())
    state_dim = state.shape[0]
    action_dim = env.action_space.n

    policy_net = DQN(state_dim, action_dim).to(device)
    target_net = DQN(state_dim, action_dim).to(device)
    target_net.load_state_dict(policy_net.state_dict())
    optimizer = optim.Adam(policy_net.parameters(), lr=1e-3)
    memory = deque(maxlen=10000)
    batch_size = 64
    gamma = 0.99
    epsilon = 1.0
    epsilon_min = 0.05
    epsilon_decay = 0.995
    update_target = 100

    for episode in range(300):
        state = preprocess_state(env.reset())
        total_reward = 0
        done = False
        while not done:
            action = select_action(state, policy_net, epsilon, action_dim)
            next_state, reward, done, info = env.step(action)
            next_state = preprocess_state(next_state)
            memory.append((state, action, reward, next_state, done))
            state = next_state
            total_reward += reward

            if len(memory) >= batch_size:
                batch = random.sample(memory, batch_size)
                states, actions, rewards, next_states, dones = zip(*batch)
                states = torch.from_numpy(np.array(states)).float().to(device)
                actions = torch.from_numpy(np.array(actions)).long().unsqueeze(1).to(device)
                rewards = torch.from_numpy(np.array(rewards)).float().unsqueeze(1).to(device)
                next_states = torch.from_numpy(np.array(next_states)).float().to(device)
                dones = torch.from_numpy(np.array(dones)).float().unsqueeze(1).to(device)

                q_values = policy_net(states).gather(1, actions)
                next_q_values = target_net(next_states).max(1)[0].unsqueeze(1)
                expected_q = rewards + gamma * next_q_values * (1 - dones)

                loss = nn.MSELoss()(q_values, expected_q)
                optimizer.zero_grad()
                loss.backward()
                optimizer.step()

            env.render()

        epsilon = max(epsilon_min, epsilon * epsilon_decay)
        if episode % update_target == 0:
            target_net.load_state_dict(policy_net.state_dict())
        print(f"Episode {episode}, Reward: {total_reward}")

    env.close()

if __name__ == "__main__":
    train()

3. 效果图展示

在这里插入图片描述

更多推荐