吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 8413|回复: 9
收起左侧

[Python 原创] [原创]控制雷电模拟器玩2048游戏算法优化[附成果和源代码]

[复制链接]
zerglurker 发表于 2018-12-21 22:18
此篇帖子需要参考:
1 雷电模拟器说明
2 在雷电模拟器玩2048
这两篇帖子实现了在雷电模拟器上面玩2048,但是这是一个没有灵魂的程序,没有什么智能
所以本篇主要讨论算法,给这个程序以灵魂
按照之前两篇帖子的方法,思考深度即使加到5,也基本就是1000~3000分左右,基本就是小朋友的水平
观察算法,我们可以看到代码总体的思路是递归
每次考虑四个方向移动后的情况,然后计算空格的数量
努力在四个方向的推演矩阵中,找到那个空位最多的方向

但是这种方式有一个缺点:算法会优先合成两对2,而不会理会即将合成的64
实际在游戏的时候,如果人来决策,则会合成64,而不是2.因为2很容易合成,但是64却无法随时找到机会合成
从这个角度思考,应该优先合成出大的那个,而不是合成2

所以这个算法的关键在于评分函数,也就是get_null_count函数
如果让这个评分函数偏向于合成大数,那么就可以让脚本像人一样去移动
经过反复测试,目前我找到了一个得分最高的评分函数[见图片附件]
现在将源码展示如下:
[Python] 纯文本查看 复制代码
import time

from Dnconsole import Dnconsole
import cv2 as cv


def compare_status(st0: list, st1: list) -> bool:  # True相同 False不同
    if len(st0) != len(st1):
        return False
    for i in range(len(st0)):
        if len(st0[i]) != len(st1[i]):
            return False
        for j in range(len(st0[i])):
            if st0[i][j] != st1[i][j]:
                return False
    return True


def copy_statu(status: list) -> list:
    result = list()
    for line in status:
        result.append([line[0], line[1], line[2], line[3]])
    return result


def read_num(area: tuple, screen: str, template: list) -> int:
    scr = cv.imread(screen)
    cur_area = scr[area[1]:area[3], area[0]:area[2]]  # 前面是y轴 后面是x轴
    for i, tmp in enumerate(template):
        tp = cv.imread(tmp)
        try:
            result = cv.matchTemplate(cur_area, tp, cv.TM_SQDIFF_NORMED)
            min_val, max_val, min_loc, max_loc = cv.minMaxLoc(result)
            if min_val < 0.001:
                return int(template[i].replace('2048/', '').replace('.png', ''))
        except cv.error:
            continue
    return -1


def get_cur_status() -> list:
    result = list()
    result.append([0, 0, 0, 0])
    result.append([0, 0, 0, 0])
    result.append([0, 0, 0, 0])
    result.append([0, 0, 0, 0])
    pos = [30, 260, 240, 470]  # , [300, 260, 510, 470], [570, 260, 780, 470], [840, 260, 1050, 470]]
    template = ['2048/0.png', '2048/2.png', '2048/4.png', '2048/8.png', '2048/16.png', '2048/32.png',
                '2048/64.png', '2048/128.png', '2048/256.png', '2048/512.png', '2048/1024.png']
    screen = 'E:/Project/PyCharmProj/Script/2048/share/2048.png'
    for i in range(4):
        for j in range(4):
            area = (pos[0] + 270 * j, pos[1] + 270 * i, pos[2] + 270 * j, pos[3] + 270 * i)
            result[i][j] = read_num(area, screen, template)
    return result


def swipe_up():
    Dnconsole.swipe(0, (540, 1280), (540, 260))
    print('上移')


def swipe_down():
    Dnconsole.swipe(0, (540, 260), (540, 1280))
    print('下移')


def swipe_left():
    Dnconsole.swipe(0, (1000, 770), (100, 770))
    print('左移')


def swipe_right():
    Dnconsole.swipe(0, (100, 770), (1000, 770))
    print('右移')


def get_score(status: list) -> int:
    score = 0
    if len(status) == 0:
        return score
    for row in status:
        for node in row:
            if node == 0:
                continue
            score += int(node * node)
    return score


def get_null_count(status: list) -> tuple:
    if len(status) == 0:
        # print('此方向移动无效')
        return -1, -1
    result = 0
    max_num = 0
    for row in status:
        for node in row:
            if node == 0:
                result += 1
            if node > max_num:
                max_num = node
    return result, max_num


def get_sum(line: list) -> int:
    total = 0
    for i in range(len(line)):
        total += line[i]
    return total


def compress_line(line: list) -> list:
    # 压实一条数据
    length = len(line)
    result = line[:]
    if get_sum(line) == 0:  # 全零则直接返回
        return line
    for i in range(length):
        if get_sum(result[i:]) == 0:
            break
        while result[i] == 0:
            for j in range(i, length - 1):
                result[j] = result[j + 1]
                result[j + 1] = 0
    return result


def combine_line(line: list) -> list:
    # 合并一条数据
    length = len(line)
    result = line[:]
    for i in range(length - 1):
        if result[i] == result[i + 1]:
            result[i] *= 2
            result[i + 1] = 0
    return result


def deduction_up(status: list) -> list:
    result = copy_statu(status)
    # 先把数据压实,再合并
    for i in range(4):
        line = [result[0][i], result[1][i], result[2][i], result[3][i]]
        line = compress_line(line)  # 压实(去掉为0的格子)
        line = combine_line(line)  # 合并
        line = compress_line(line)  # 压实
        result[0][i], result[1][i], result[2][i], result[3][i] = line[0], line[1], line[2], line[3]
    if compare_status(result, status):
        return []
    return result


def deduction_down(status: list) -> list:
    result = copy_statu(status)
    # 先把数据压实,再合并
    for i in range(4):
        line = [result[3][i], result[2][i], result[1][i], result[0][i]]
        line = compress_line(line)  # 压实(去掉为0的格子)
        line = combine_line(line)  # 合并
        line = compress_line(line)  # 压实
        result[3][i], result[2][i], result[1][i], result[0][i] = line[0], line[1], line[2], line[3]
    if compare_status(result, status):
        return []
    return result


def deduction_left(status: list) -> list:
    result = copy_statu(status)
    # 先把数据压实,再合并
    for i in range(4):
        line = [result[i][0], result[i][1], result[i][2], result[i][3]]
        line = compress_line(line)  # 压实(去掉为0的格子)
        line = combine_line(line)  # 合并
        line = compress_line(line)  # 压实
        result[i][0], result[i][1], result[i][2], result[i][3] = line[0], line[1], line[2], line[3]
    if compare_status(result, status):
        return []
    return result


def deduction_right(status: list) -> list:
    result = copy_statu(status)
    # 先把数据压实,再合并
    for i in range(4):
        line = [result[i][3], result[i][2], result[i][1], result[i][0]]
        line = compress_line(line)  # 压实(去掉为0的格子)
        line = combine_line(line)  # 合并
        line = compress_line(line)  # 压实
        result[i][3], result[i][2], result[i][1], result[i][0] = line[0], line[1], line[2], line[3]
    if compare_status(result, status):
        return []
    return result


def get_direction(status: list, deep: int) -> tuple:
    func = [deduction_up, deduction_down, deduction_left, deduction_right]
    score = 0
    direction = -1
    for i in range(4):
        result = func[i](status)
        if compare_status(result, status):
            result = []
        s = get_score(result)  # 获取本次操作的评价
        if deep > 0 and len(result) > 0:
            d, s0 = get_direction(result, deep - 1)  # 基于本次操作,获取下次移动的评价
            s += s0
        if s > score:
            direction = i
            score = s
    return direction, score


def run():
    if Dnconsole.is_running(0) is False:
        Dnconsole.launch(0)
    for i in range(120):
        if Dnconsole.is_running(0):
            time.sleep(10)
            break
        time.sleep(1)
    Dnconsole.invokeapp(0, 'com.digiplex.game')
    end_status = [[-1, -1, -1, -1], [-1, -1, -1, -1], [-1, -1, -1, -1], [-1, -1, -1, -1]]
    if Dnconsole.wait_activity(0, 'com.digiplex.game/.MainActivity', 20) is True:
        while True:
            time.sleep(1)
            Dnconsole.dnld(0, 'screencap -p /sdcard/Pictures/2048.png')
            status = get_cur_status()
            if compare_status(end_status, status):
                print('结束')
                break
            print('status=', status)
            for line in status:
                print(line)
            direction, score = get_direction(status, 5)
            if direction == -1:
                print('结束')
                return
            swipe = [swipe_up, swipe_down, swipe_left, swipe_right]
            swipe[direction]()

后续的改进方向:
一个是调整得分函数get_score
目前仅是一个逻辑,单纯追求合成更大的数
还可以在这个逻辑下,追加一个空格数更大的逻辑
另外一个是采用机器学习来替代这个得分函数
但是这个需要大量的标注数据,或者一个受控制的游戏(一个能够每秒中运行上百次的游戏)
如果有人知道2048游戏产生零散方块的算法,可以联系我,我后面可以考虑引入TensorFlow来进行机器学习解答,如果能够达到更高的分数,我会分享出代码
Screenshot_2018-12-21-21-53-39.png

免费评分

参与人数 1吾爱币 +5 热心值 +1 收起 理由
苏紫方璇 + 5 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

Longshadow 发表于 2018-12-21 22:55
我还在学习中,这代码有些长,不过谢谢楼主,我会再深入学习!
麦米尔加弗德 发表于 2018-12-21 23:32
今晚优化爬虫然后死机的人路过,膜拜一下大佬 =-=
qxzzxw 发表于 2018-12-22 01:04
我记得这个2048的理念是空位随机产生数字(50%为2,50%为4),这个用机器学习来做我也不知道行不行(考虑训练数据),训练数据的random的seed需要随机化(并且主要每个人写的程序这个概率可能设置的不一样?)
wangqiustc 发表于 2018-12-22 01:11
这操作简直完美
小黑LLB 发表于 2019-3-5 21:40
厉害 太强了  感谢分享 好棒
Light紫星 发表于 2019-3-15 10:39
感谢楼主分享
nicewell 发表于 2019-7-28 15:26
萌新看不懂。。。。
ghoob321 发表于 2019-7-29 08:44
这个能夶哦什么水平?
 楼主| zerglurker 发表于 2019-7-29 20:51
ghoob321 发表于 2019-7-29 08:44
这个能夶哦什么水平?

12000多分
10000多分大概是普通人玩上个把小时后的水平
12000多分大概是熟练玩家的水平
16000多分可以合成出1024,接近2048
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-16 12:33

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表