用Python打造AI玩家:挑战2048,谁与争锋

发布于:2025-03-16 ⋅ 阅读:(18) ⋅ 点赞:(0)

文章目录

一、创作背景

二、效果图

三、准备工作

1. 安装Chrome和Chrome Driver

2. 安装Python库

四、代码说明

‌1. init_driver 函数‌

2. play_2048 函数‌

五、完整代码

六、改进版本

七、主要模块

八、核心算法分析

1. 棋盘状态获取

2. 位置权重系统

3. 连续性评估

4. 单调性评估

5. 移动模拟系统

九、评估系统

1. 评估标准

2. 决策机制

十、性能优化

1. 延迟控制

2. 错误处理

十一、完整代码

​编辑

十二、总结


一、创作背景

厌倦了手动滑动方块,在2048的海洋中挣扎?是时候让AI接管了!这是一个基于Python和Selenium的2048游戏AI程序,通过模拟浏览器操作实现自动玩游戏。下面我们一起构建游戏环境、设计AI算法,并最终见证AI如何以惊人的策略和速度,突破2048!

二、效果图

说明:模拟Chrome浏览器登入2048小游戏网站,AI将自动开始游戏,Pycharm终端输出实时棋盘

三、准备工作

1. 安装Chrome和Chrome Driver

‌下载与 Chrome 浏览器和与之版本匹配的 ChromeDriver,并将其路径添加到系统 PATH 中,或在代码中指定路径。

具体细节可参考这两篇文章:彻底解决 Selenium ChromeDriver 不匹配问题:Selenium ChromeDriver 最新版本下载安装教程_driver = webdriver.chrome-CSDN博客

ChromeDriver下载安装 - chenhongl - 博客园

2. 安装Python库

pip install selenium

四、代码说明

‌1. init_driver 函数‌

初始化 Chrome WebDriver

2. play_2048 函数‌

使用 driver.get(GAME_URL) 打开 2048 游戏网页。
等待游戏加载完成(这里使用简单的 time.sleep(2),可根据实际情况调整)。
通过 driver.find_element_by_tag_name('body') 获取游戏主体元素,用于发送键盘指令。
在无限循环中,随机选择一个移动方向,并发送键盘指令。
使用 time.sleep(random.uniform(*DELAY_RANGE)) 实现随机延迟,模拟人类操作。
检查游戏是否结束,这里通过尝试点击“重玩”按钮来判断。如果找到“重玩”按钮,则打印最终得分并点击按钮开始新游戏;如果找不到,则继续游戏循环。
捕获 KeyboardInterrupt 异常,允许用户通过 Ctrl+C 手动停止游戏。
在 finally 块中确保 WebDriver 正常关闭。

五、完整代码

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
import time
import random
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options

# 游戏网址
GAME_URL = 'https://2048game.com/'

# 指定 Chrome以及Chrome Driver可执行文件路径
chrome_path = {"Windows": r"你自己电脑上chrome可执行文件的绝对路径",}
options = Options()
options.binary_location = chrome_path["Windows"]
driver_path = r'你自己电脑上chrome Driver所在目录'
service = Service(executable_path=driver_path)

# 定义移动方向
MOVES = [Keys.ARROW_UP, Keys.ARROW_RIGHT, Keys.ARROW_DOWN, Keys.ARROW_LEFT]

# 随机延迟时间范围(秒)
DELAY_RANGE = (0.5, 1.5)

def init_driver():
    driver = webdriver.Chrome(service=service, options=options)
    return driver

def play_2048():
    driver = init_driver()
    try:
        driver.get(GAME_URL)
        # 等待游戏加载完成(可根据实际情况调整等待时间)
        time.sleep(2)
        game_board = driver.find_element(By.TAG_NAME, 'body')
        while True:
            # 随机选择一个移动方向
            move = random.choice(MOVES)
            game_board.send_keys(move)
            # 随机延迟
            time.sleep(random.uniform(*DELAY_RANGE))
            # 检查游戏是否结束(可根据页面元素调整检查逻辑)
            # 这里简单通过尝试点击“重玩”按钮来判断游戏是否结束,如果不存在则继续游戏
            try:
                replay_button = driver.find_element(By.CLASS_NAME, 'replay-button')
                print("游戏结束!最终得分:", driver.find_element(By.CLASS_NAME, 'score-board').text.split()[-1])
                replay_button.click()
                # 等待新游戏开始
                time.sleep(2)
            except Exception:
                # 如果找不到“重玩”按钮,则继续游戏循环
                continue
    except KeyboardInterrupt:
        print("用户手动停止游戏")
    finally:
        driver.quit()

if __name__ == "__main__":
    play_2048()

六、改进版本

由于采用的策略是随机移动,因此游戏表现性能不稳定,得分不高。下面将介绍一种更智能的移动策略来提高游戏成绩。

主要特点包括:
- 采用启发式评估系统进行决策
- 实现了基于位置的权重系统
- 考虑了数字的连续性和单调性
- 具有完善的错误处理机制

七、主要模块

1. 浏览器控制模块
   - 初始化WebDriver
   - 页面元素定位
   - 键盘事件模拟

2. 游戏状态获取模块
   - 棋盘状态解析
   - 数据转换和存储

3. 决策系统模块
   - 移动模拟
   - 状态评估
   - 最优移动选择

4. 评估系统模块
   - 位置权重计算
   - 连续性评估
   - 单调性评估

八、核心算法分析

1. 棋盘状态获取

实现细节:
1)使用Selenium的find_elements方法获取所有tile元素
2)通过class属性解析每个tile的位置信息
3)使用try-except处理空值情况
4)返回4x4的二维数组表示棋盘状态

def get_board_state(driver):
    tiles = driver.find_elements(By.CLASS_NAME, 'tile')
    board = [[0] * 4 for _ in range(4)]
    for tile in tiles:
        classes = tile.get_attribute('class').split()
        for cls in classes:
            if cls.startswith('tile-position-'):
                position = cls.split('-')
                x = int(position[2]) - 1
                y = int(position[3]) - 1
                try:
                    value = int(tile.text) if tile.text else 0
                except ValueError:
                    value = 0
                board[y][x] = value
    return board

2. 位置权重系统

POSITION_WEIGHTS = [
    [4, 3, 2, 1],
    [3, 2, 1, 1],
    [2, 1, 1, 1],
    [1, 1, 1, 1]
]

实现细节:
1. 定义4x4的权重矩阵,角落权重最高
2. 遍历棋盘计算加权得分
3. 大数字在角落获得更高分数

def calculate_position_score(board):
    score = 0
    for i in range(4):
        for j in range(4):
            if board[i][j] != 0:
                score += board[i][j] * POSITION_WEIGHTS[i][j]
    return score

3. 连续性评估

实现细节:
1. 分别评估水平和垂直方向的连续性
2. 相同数字相邻获得双倍分数
3. 数字相差1获得额外奖励

def calculate_continuity(board):
    score = 0
    # 水平连续性
    for i in range(4):
        for j in range(3):
            if board[i][j] != 0 and board[i][j+1] != 0:
                if board[i][j] == board[i][j+1]:
                    score += board[i][j] * 2
                elif abs(board[i][j] - board[i][j+1]) == 1:
                    score += min(board[i][j], board[i][j+1])
    return score

4. 单调性评估

实现细节:
1. 评估数字是否按顺序排列
2. 计算符合单调性的相邻数字对
3. 鼓励数字的有序排列

def calculate_monotonicity(board):
    score = 0
    for i in range(4):
        for j in range(3):
            if board[i][j] >= board[i][j+1]:
                score += 1
    return score

5. 移动模拟系统

实现细节:
1. 深拷贝当前棋盘状态
2. 使用merged数组防止重复合并
3. 分别处理四个方向的移动
4. 实现数字的移动和合并逻辑

def simulate_move(board, move):
    new_board = [row[:] for row in board]
    merged = [[False] * 4 for _ in range(4)]
    # ... 移动逻辑实现

九、评估系统

1. 评估标准

def evaluate_move(board, move):
    score = (empty_cells * 200 +           # 空格权重
             max_tile * 100 +              # 最大数字权重
             position_score * 50 +         # 位置权重
             continuity_score * 30 +       # 连续性权重
             monotonicity_score * 20)      # 单调性权重

权重分配说明:
1. 空格数量:200
   - 保持棋盘有足够空间
   - 避免过早填满

2. 最大数字:100
   - 鼓励产生更大的数字
   - 提高游戏得分

3. 位置权重:50
   - 优化数字分布
   - 保持大数字在角落

4. 连续性:30
   - 提高合并效率
   - 保持数字相邻

5. 单调性:20
   - 保持数字有序
   - 便于后续合并

2. 决策机制

实现细节:
1. 遍历所有可能的移动方向
2. 评估每个移动的得分
3. 选择得分最高的移动
4. 默认返回向下移动

def get_best_move(board):
    best_score = float('-inf')
    best_move = None
    for move in MOVES:
        score = evaluate_move(board, move)
        if score > best_score:
            best_score = score
            best_move = move

十、性能优化

1. 延迟控制

说明:
1)使用随机延迟避免固定模式
2)延迟范围0.5-1.5秒
3)模拟人类操作特征

DELAY_RANGE = (0.5, 1.5)
time.sleep(random.uniform(*DELAY_RANGE))

2. 错误处理

实现细节:
1. 处理空值情况
2. 处理数值转换异常
3. 确保程序稳定运行

try:
    value = int(tile.text) if tile.text else 0
except ValueError:
    value = 0

十一、完整代码

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
import time
import random
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
import numpy as np

# 游戏网址
GAME_URL = 'https://2048game.com/'

# 指定 Chrome以及Chrome Driver可执行文件路径
chrome_path = {"Windows": r"你自己电脑上chrome可执行文件的绝对路径",}
options = Options()
options.binary_location = chrome_path["Windows"]
driver_path = r'你自己电脑上chrome Driver所在目录'
service = Service(executable_path=driver_path)

# 定义移动方向
MOVES = [Keys.ARROW_UP, Keys.ARROW_RIGHT, Keys.ARROW_DOWN, Keys.ARROW_LEFT]

# 随机延迟时间范围(秒)
DELAY_RANGE = (0.5, 1.5)

# 位置权重矩阵(角落和边缘权重更高)
POSITION_WEIGHTS = [
    [4, 3, 2, 1],
    [3, 2, 1, 1],
    [2, 1, 1, 1],
    [1, 1, 1, 1]
]


def init_driver():
    driver = webdriver.Chrome(service=service, options=options)
    return driver


def get_board_state(driver):
    tiles = driver.find_elements(By.CLASS_NAME, 'tile')
    board = [[0] * 4 for _ in range(4)]
    for tile in tiles:
        classes = tile.get_attribute('class').split()
        for cls in classes:
            if cls.startswith('tile-position-'):
                position = cls.split('-')
                x = int(position[2]) - 1
                y = int(position[3]) - 1
                try:
                    value = int(tile.text) if tile.text else 0
                except ValueError:
                    value = 0
                board[y][x] = value
    return board


def print_board(board):
    for row in board:
        print('\t'.join(map(str, row)))
    print()


def calculate_position_score(board):
    """计算基于位置的得分"""
    score = 0
    for i in range(4):
        for j in range(4):
            if board[i][j] != 0:
                score += board[i][j] * POSITION_WEIGHTS[i][j]
    return score


def calculate_continuity(board):
    """计算数字的连续性"""
    score = 0
    # 水平连续性
    for i in range(4):
        for j in range(3):
            if board[i][j] != 0 and board[i][j + 1] != 0:
                if board[i][j] == board[i][j + 1]:
                    score += board[i][j] * 2
                elif abs(board[i][j] - board[i][j + 1]) == 1:
                    score += min(board[i][j], board[i][j + 1])

    # 垂直连续性
    for i in range(3):
        for j in range(4):
            if board[i][j] != 0 and board[i + 1][j] != 0:
                if board[i][j] == board[i + 1][j]:
                    score += board[i][j] * 2
                elif abs(board[i][j] - board[i + 1][j]) == 1:
                    score += min(board[i][j], board[i + 1][j])

    return score


def calculate_monotonicity(board):
    """计算单调性(数字是否按顺序排列)"""
    score = 0
    # 水平单调性
    for i in range(4):
        for j in range(3):
            if board[i][j] >= board[i][j + 1]:
                score += 1

    # 垂直单调性
    for i in range(3):
        for j in range(4):
            if board[i][j] >= board[i + 1][j]:
                score += 1

    return score


def evaluate_move(board, move):
    """评估移动后的棋盘状态"""
    new_board = simulate_move(board, move)
    if new_board == board:  # 如果移动没有改变棋盘,返回负无穷
        return float('-inf')

    score = 0
    empty_cells = 0
    max_tile = 0

    # 计算空格数量
    for row in new_board:
        empty_cells += row.count(0)

    # 找出最大数字
    for row in new_board:
        max_tile = max(max_tile, max(row))

    # 计算位置得分
    position_score = calculate_position_score(new_board)

    # 计算连续性得分
    continuity_score = calculate_continuity(new_board)

    # 计算单调性得分
    monotonicity_score = calculate_monotonicity(new_board)

    # 计算总分(调整权重)
    score = (empty_cells * 200 +  # 空格权重增加
             max_tile * 100 +  # 最大数字权重增加
             position_score * 50 +  # 位置权重
             continuity_score * 30 +  # 连续性权重
             monotonicity_score * 20)  # 单调性权重

    return score


def simulate_move(board, move):
    """模拟移动并返回新的棋盘状态"""
    new_board = [row[:] for row in board]
    merged = [[False] * 4 for _ in range(4)]

    if move == Keys.ARROW_DOWN:
        # 向下移动
        for j in range(4):
            for i in range(2, -1, -1):
                if new_board[i][j] != 0:
                    row = i
                    while row < 3 and (new_board[row + 1][j] == 0 or
                                       (new_board[row + 1][j] == new_board[row][j] and
                                        not merged[row + 1][j])):
                        if new_board[row + 1][j] == 0:
                            new_board[row + 1][j] = new_board[row][j]
                            new_board[row][j] = 0
                        else:
                            new_board[row + 1][j] *= 2
                            new_board[row][j] = 0
                            merged[row + 1][j] = True
                        row += 1

    elif move == Keys.ARROW_LEFT:
        # 向左移动
        for i in range(4):
            for j in range(1, 4):
                if new_board[i][j] != 0:
                    col = j
                    while col > 0 and (new_board[i][col - 1] == 0 or
                                       (new_board[i][col - 1] == new_board[i][col] and
                                        not merged[i][col - 1])):
                        if new_board[i][col - 1] == 0:
                            new_board[i][col - 1] = new_board[i][col]
                            new_board[i][col] = 0
                        else:
                            new_board[i][col - 1] *= 2
                            new_board[i][col] = 0
                            merged[i][col - 1] = True
                        col -= 1

    elif move == Keys.ARROW_RIGHT:
        # 向右移动
        for i in range(4):
            for j in range(2, -1, -1):
                if new_board[i][j] != 0:
                    col = j
                    while col < 3 and (new_board[i][col + 1] == 0 or
                                       (new_board[i][col + 1] == new_board[i][col] and
                                        not merged[i][col + 1])):
                        if new_board[i][col + 1] == 0:
                            new_board[i][col + 1] = new_board[i][col]
                            new_board[i][col] = 0
                        else:
                            new_board[i][col + 1] *= 2
                            new_board[i][col] = 0
                            merged[i][col + 1] = True
                        col += 1

    elif move == Keys.ARROW_UP:
        # 向上移动
        for j in range(4):
            for i in range(1, 4):
                if new_board[i][j] != 0:
                    row = i
                    while row > 0 and (new_board[row - 1][j] == 0 or
                                       (new_board[row - 1][j] == new_board[row][j] and
                                        not merged[row - 1][j])):
                        if new_board[row - 1][j] == 0:
                            new_board[row - 1][j] = new_board[row][j]
                            new_board[row][j] = 0
                        else:
                            new_board[row - 1][j] *= 2
                            new_board[row][j] = 0
                            merged[row - 1][j] = True
                        row -= 1

    return new_board


def get_best_move(board):
    """获取最佳移动方向"""
    best_score = float('-inf')
    best_move = None

    # 评估每个可能的移动
    for move in MOVES:
        score = evaluate_move(board, move)
        if score > best_score:
            best_score = score
            best_move = move

    # 如果没有找到有效的移动,返回默认移动
    if best_move is None:
        return Keys.ARROW_DOWN

    return best_move


def play_2048():
    driver = init_driver()
    try:
        driver.get(GAME_URL)
        time.sleep(2)
        game_board = driver.find_element(By.TAG_NAME, 'body')

        while True:
            board = get_board_state(driver)
            print_board(board)

            # 获取最佳移动方向
            best_move = get_best_move(board)
            game_board.send_keys(best_move)
            time.sleep(random.uniform(*DELAY_RANGE))

            try:
                replay_button = driver.find_element(By.CLASS_NAME, 'replay-button')
                print("游戏结束!最终得分:", driver.find_element(By.CLASS_NAME, 'score-board').text.split()[-1])
                replay_button.click()
                time.sleep(2)
            except Exception:
                continue

    except KeyboardInterrupt:
        print("用户手动停止游戏")
    finally:
        driver.quit()


if __name__ == "__main__":
    play_2048()

十二、总结

程序能够实现基本的游戏功能,在复杂局面下的表现还有提升空间,只玩到1024,离2048还差一些,读者可以通过进一步优化算法和评估系统,提高游戏表现,自己试试吧。

喜欢这篇文章的话记得点赞收藏加关注哦!!