程序员玩连连看的正确姿势

too young too simple

一、绝地反击

最近女票迷上了某平台的连连看对战小游戏,于是免不了要找哥哥我 PK 一翻,虽然是被迫卷入战争,但是以朕惊世骇俗的智商,那当然是胜券在握啦~~
没曾想,几个回合下来,竟被啪啪啪打脸,快把这个月的口粮都输光了(每把5块钱啊,肉疼!!)
哎,完全拼手速是没有希望的了,得想办法让连连看自动打

“连连看”都不会打的直男们,赶紧去怼一局

二、可行性

前段时间跳一跳火起来的时候,有人就通过 adb 截屏并发送到电脑分析,再求得距离然后计算出按键时长,最后通过 adb shell 自动按键,从而获得完美跳跳分,这一招用在连连看是否管用呢?
理论上,靠谱,分解如下:

  1. adb 截图传到电脑
  2. 将连连看的点击区域识别为一个二维矩阵,每一种小动物用一个数字表示
  3. 对二维矩阵求解,计算出每个位置的点击顺序数组
  4. 通过 adb shell 一把梭,一次性点掉所有

酱紫如果顺利的话并且不被女票发现,赢回三个月的口粮都很有希望呀~~

三、实施步骤

技术选型

从上一节的分析来看,方案的实施涉及到很多图片的分析处理,Python 可以方便的调用很多图片库,而且网上也有很多作业可以抄,所以选择基于 Python 来做

环境搭建

没有很具体的安装步骤,需要的咨询谷歌哥

1) 安装 adb 环境。安装完成后,用数据线连接一台 android 手机,执行一些简单的 adb 命令预热下

// 是否连接上
adb devices

// 可否截屏保存
adb shell /system/bin/screencap -p /sdcard/screenshot.png
adb pull /sdcard/screenshot.png /yourDocuments/screenshot.png

// 可否点击屏幕
adb shell input tap 100 100

2)安装 python 和相关的图片库,在安装 openCv 的时候还踩了个大坑,记录了下,仅供参考

图片处理

1)截屏保存
在终端,执行如上的两个 adb 命令就可以截屏保存了,也就是说,这里需要一个可以调用终端命令,同时可以等待返回的 Python 方法:

// 执行终端命令的方法
def sh(command):
    p = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    print p.stdout.read()

// 截屏保存
sh('adb shell /system/bin/screencap -p /sdcard/screenshot.png')
sh('adb pull /sdcard/screenshot.png /yourDocuments/screenshot.png')

2)裁剪有效区域、再等比切出小动物头像
如果不要求很通用只是对你的手机有效的话,那么只需要将第一步截下来的屏幕用工具来量一量(如 Mark Man),就可以用如下方式裁剪出有效区域

from PIL import Image
def cut (im, x, y, w, h, name):
  region = im.crop((x, y, x+w, y+h))
  region.save("./screenshot/" + name + ".png")

# 有效点击区域裁剪 (不通用的做法是,把这个矩形的坐标量出来)
gx = 43
gy = 401
gw = 993
gh = 1420
cut(Image.open("./screenshot/screenshot.png"), gx, gy, gw, gh, 'main')
有效区域

如果要做得通用一些,就需要计算图片的比例了(只用于打败女票的,完全没必要嘛)

然后再按照10行7列切成小块,并且根据二维数组的下标命名

# -*-coding:utf-8-*-
from PIL import Image
import cutImg
def cut ():
  im = Image.open("./screenshot/main.png")
  # 图片的宽度和高度
  img_size = im.size
  width = img_size[0]
  height = img_size[1]
  distanceW = width / 7
  distanceH = height / 10
  print(distanceW, distanceH)
  x = 0
  y = 0
  for num in range(0, 10):
    for i in range(0, 7):
      x = distanceW * i
      y = distanceH * num
      name = str(num) + str(i)
      cutImg.cut(im, x + 15, y + 15, distanceW - 20, distanceH - 20, name)
  return [distanceW, distanceH]

小动物头像

3)解析小动物头像输出数字二维矩阵(第一回合)

这一步着实需要下功夫,还踩了不少坑~~

首先想到的是通过求解图片的 hash 值,利用 hash 值来比对图片的相似度(例如感知 hash 算法)。网上有各种求 hash 值的算法,实现起来倒也简单,但是,比较的正确率只能达到百分之七、八十(这样我们分析出的点击路径,肯定打不过啦!!),主要是这些小动物头像在 hash 算法下显得都太相似了,拿感知哈希算法来说:
a) 缩小图片尺寸
b) 转为灰度图片
c) 计算灰度平均值
d) 比较像素的灰度
e) 计算哈希值
f) 对比图片指纹

小猪头

小猴头

想象一下,上面的小猪头和小猴头经过如上的变换后,还有多少差异呢?

转念一想,这个问题在机器学习领域,不过是那种最最简单的分类问题,so,完全可以先训练一个模型出来

4)解析小动物头像输出数字二维矩阵(第一回合)
Turicreate 是苹果开源的基于 python 机器学习框架,特点是轻量(只是分类相似的图片而已,当然是越简单越好),先安装之

然后将上面写好的截屏裁剪代码多执行几次,手工分类,准备好训练数据:


分类存储

0

给每种小动物创建一个文件夹,再将所有该种类的动物装进去

开始训练,并保存模型:

#!/usr/bin/env python
#encoding=utf-8
import turicreate as tc
img_folder = 'data'
// 导入数据
data = tc.image_analysis.load_images(img_folder, with_path=True)
// 使用文件名来做标签
data['label'] = data['path'].apply(lambda path: path.split('/')[len(path.split('/')) - 2])
data.save('doraemon-walle.sframe')
// 百分之八十的数据用于训练,百分之二十用于测试
train_data, test_data = data.random_split(0.8, seed=2)
// 开始训练模型
model = tc.image_classifier.create(train_data, target='label')
// 测试模型
predictions = model.predict(test_data)
metrics = model.evaluate(test_data)
// 输出测试结果
print(metrics['accuracy'])
model.save('my_model_file')

执行到倒数第二行的时候,顺利输出1.0(百分百的正确率有木有):


正确率100%

使用训练好的模型,输出二维矩阵:

import turicreate as tc
loaded_model = tc.load_model('my_model_file')
def getDataset():
  data = tc.image_analysis.load_images('screenshot', with_path=True)
  arr = loaded_model.predict(data)
  result = []
  temp = []
  for index in range(len(arr)):
    if (index % 7 == 0):
      temp = []
    if ((index + 1) % 7 == 0):
      result.append(temp)
    // f 为 0,标记为未删除
    temp.append({'v': int(arr[index]), 'f': 0})
  return result

路径求解

1)判断两个动物图标可连
需要满足如下条件:
a) 相同的图标
b) 两种直接存在一条通路,它是一条只经过没有图案的地方、且转折点不超过2个的折线
具体代码实现可以看看这篇博文的分析(虽然是 C 版),这里我就不贴了,繁琐占篇幅

2) 搜索路径,最简单粗暴的一种做法
(1)从矩阵中挑出一个未被标记为删除的元素,(2)再从矩阵中余下的不被标记删除的元素寻找一个跟它一样的元素,判断是否可以相连,是则将两个元素标记为删除,并将点击坐标压入坐标数组,否则重复(2),(3)重复(1),知道找到所有的点击坐标点
但是这种做法是 O(nXn),很遗憾,暂时也没有想到更好的办法,只是想到了一个小小的优化策略,开始先遍历一轮,将所有挨着的相同图标消掉(显而易见的事情当然要先办啦),减小 N,节省一下算法的时间

然后在“盲狙”的过程中,因为循环停止的条件是找到所有的坐标点,假如游戏给了个无解的矩阵,或者咱们图片识别错了导致无解,就会陷入死循环(虽然这样的概率极低,没遇到过),所以要做一下循环保护

# 遍历消除(盲狙)
def commonBuild():
  global data
  global pos
  for num in range(0, 10):
      for i in range(0, 7):
        item = data[num][i]
        if (item['f'] == 1):
          continue
        for ix in range(0, 10):
          if (item['f'] == 1):
            break
          for iy in range(0, 7):
            item1 = data[ix][iy]
            if (item1['f'] == 1 or item1['v'] != item['v'] or (ix == num and iy == i)):
              continue
            if (remove.canRemove(num, i, ix, iy, data) == 1):
              item['f'] = 1
              item1['f'] = 1
              pos.append(getPos(i, num))
              pos.append(getPos(iy, ix))
              break
// 达到 70 也即所有的坐标都找到即停止
// 否则也最多循环十次
count = 0
while (len(pos) < 70 and count < 10):
  count = count + 1
  commonBuild()
print(count)

很幸运,经过优化后的算法,基本上每次 count 都输出为 1,不需要遍历太多次。假如真的出现了无解矩阵,循环了 10 次退出了,那该如何是好呢?这个时候自己将机器没有打完的点掉也应该没有难度了

adb 一把梭

克服艰难险阻把坐标数组计算出来之后,后面的事情就简单了,执行 adb 命令一把梭

for index in range(len(pos)):
  command = 'adb shell input tap ' + str(pos[index][0]) + ' ' + str(pos[index][1])
  print(command)
  os.system(command)

四、后来,我赢了么?

然而并没有!!!
因为每条 adb 命令的执行间隔基本差不多要到 1 秒,逐条执行完之后黄花菜都凉了,要知道正常人打完一局也就 30、40 秒,作为机器人,这打完居然要 1 分多钟,真是弱智机器人

尝试将命令写入一个 sh 文件,然后通过 adb shell 执行批处理文件,稍微快了一点点,但是依然还需要几十秒(在此之前还尝试写一个堡垒 app 来一次性接收坐标,然后再 android 系统中执行命令,都木有用)。然后优化分析算法的动力都木有了

不过话说回来,跟女票打游戏,还要用赢的么?

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,498评论 25 707
  • 在小岛最爱的就是海。 在小岛的第三个夏天,海边的短袖短裤短裙,商场出租车猛开的空调,下午六点的白昼。 夏天是从擦...
    一只温柔鬼啊阅读 372评论 0 1
  • 这是什么地方呢,是一个没有沙尘的地方,是我去过只去了机场的地方,是我从飞机上往下看的时候,我就在思考,可能从那以后...
    麻木木阅读 266评论 1 0
  • 在知乎、简书、豆瓣等平台常看到《要成功先自律》、《有些奖励只有自律之后才得到》、《自律后我的人生就像开挂一样精彩》...
    是舒格阅读 493评论 0 3
  • ①鹅妈妈磨耳朵30分钟 ②海尼曼两本。 ③《神奇的色彩女王》5遍 ④绘本《邻居》2遍 ⑤磁力片一个小时。 ⑥拼图1...
    韦钰阅读 168评论 0 0