使用Selenium自动玩2048

2014年8月20日 01:24

前几天看summer师弟玩Selenium感觉挺有意思。便拿着时间风靡一时的游戏“2048”来练手,写了个简单的 AI。甚是欢乐!

Selenium

啥是selenimu,简单的说——Selenium也是一个用于Web应用程序测试的工具。Selenium测试直接运行在浏览器中,就像真正的用户在操作一样。支持的浏览器包括IE、Mozilla Firefox、Mozilla Suite等(来自 http://www.51testing.com/zhuanti/selenium.html)。

selenimu这里就不多介绍了,细节请看 selenium 的 python api 文档

2048策略

2048这个游戏地球人都知道就不介绍了。主要介绍下我的AI。我的AI对于每一个方向的评估有三个方面

  1. 移动导致合并的得分 score:这个就是游戏本身定义的得分,如 4和4合并得8分,128和128合并的256分
  2. 移动后每一行每一列的单调性 monotone:对于每一行(每一列)如果 line[i] <= line[i+1]则mon+= line[i]+line[i+1]否则,mon-=line[i]+line[i+1],monotone=sum(abs(mon))
  3. 移动后相邻块值相同的情况 adjoin:任意两个相邻块的值相同,如cells[i][j]=cells[i+1][j],则 adjoin+=cells[i][j]

最后的估值 estimation = score + monotone * 0.3 + adjoin。对于上下左右四个方向取estimation最大的方向操作

这种方法还可以,运气好的话,可以得到2048

程序结构

程序的源代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import os
import time
 
size = 4
 
class Estimator:
    def estimate(self, precells, postcells, action, score):
        for i in range(size):
            score += self.__estimate_line([postcells[i][j] for j in range(size)])
            score += self.__estimate_line([postcells[j][i] for j in range(size)])
        return score
 
    def __estimate_line(self, line):
        monotone, adjoin = 0, 0
        for i in range(size - 1):
            if line[i + 1] > line[i]:
                monotone += line[i + 1] + line[i]
            else:
                monotone -= line[i + 1] + line[i]
            if line[i + 1] == line[i]:
                adjoin += line[i]
        return abs(monotone) * .3 + adjoin
 
class Auto2048:
    def __init__(self, url, estimator):
        self.browser = webdriver.Firefox()
        self.browser.get(url)
        self.estimator = estimator
 
    def get_cells(self):
        tiles = self.browser.find_elements_by_class_name('tile')
        self.cells = [[0 for i in range(4)] for i in range(4)]
 
        for tile in tiles:
            attr = tile.get_attribute('class').split()
            value = int(attr[1].split('-')[1])
            x = int(attr[2].split('-')[3]) - 1
            y = int(attr[2].split('-')[2]) - 1
            self.cells[x][y] = value
 
    def AI(self):
 
        self.get_cells()
        self.Print(self.cells)
 
        action, actionname = '', ''
        moveable = False
         
        strategies = [    {'fun': self.try_up, 'action': Keys.UP, 'name': 'Up'},
                        {'fun': self.try_down, 'action': Keys.DOWN, 'name': 'Down'},
                        {'fun': self.try_left, 'action': Keys.LEFT, 'name': 'Left'},
                        {'fun': self.try_right, 'action': Keys.RIGHT, 'name': 'Right'}]
        for strategy in strategies:
            result = strategy['fun']()
            estimation = self.estimator.estimate(self.cells, result['cells'], strategy['name'], result['score'])
            if result['moveable'] and (moveable == False or max_estimation < estimation):
                action = strategy['action']
                max_estimation = estimation
                moveable = True
                actionname = strategy['name']
 
        if not moveable:
            return False
 
        self.browser.find_element_by_class_name('grid-container').send_keys(action)
        print 'Action: ', actionname
        return True
 
    def move_left(self, cells):
        moveable = False
        score = 0
        for x in range(size):
            pre = 0
            for y in range(size):
                if cells[x][y]:
                    cells[x][pre] = cells[x][y]
                    if y != pre:
                        moveable = True
                        cells[x][y] = 0
                    pre += 1
            for y in range(size - 1):
                if cells[x][y] and cells[x][y] == cells[x][y + 1]:
                    cells[x][y] += cells[x][y]
                    score += cells[x][y]
                    cells[x][y + 1] = 0
                    moveable = True
            pre = 0
            for y in range(size):
                if cells[x][y]:
                    cells[x][pre] = cells[x][y]
                    if y != pre:
                        moveable = True
                        cells[x][y] = 0
                    pre += 1
        return {'moveable': moveable, 'score': score, 'cells': cells}
     
    def try_left(self):
        cells = [[self.cells[i][j] for j in range(size)] for i in range(size)]
        return self.move_left(cells)
 
    def try_right(self):
        cells = [[self.cells[i][size - 1 - j] for j in range(size)] for i in range(size)]
        result = self.move_left(cells)
        result['cells'] = [[result['cells'][i][size - 1 - j] for j in range(size)] for i in range(size)]
        return result
 
    def try_up(self):
        cells = [[self.cells[j][i] for j in range(size)] for i in range(size)]
        result = self.move_left(cells)
        result['cells'] = [[result['cells'][j][i] for j in range(size)] for i in range(size)]
        return result
     
    def try_down(self):
        cells = [[self.cells[size - 1 - j][i] for j in range(size)] for i in range(size)]
        result = self.move_left(cells)
        result['cells'] = [[result['cells'][j][size - 1 - i] for j in range(size)] for i in range(size)]
        return result
 
    def __del__(self):
        self.browser.close()
 
    def Print(self, cells):
        print
        for x in range(size):
            for y in range(size):
                print '%5d' % cells[x][y],
            print
 
if __name__ == '__main__':
    url = 'file://' + os.path.abspath('2048/index.html')
    auto2048 = Auto2048(url, Estimator())
    while auto2048.AI():
        time.sleep(0.2)
    time.sleep(10)

源代码中有两个类

主逻辑类 Auto2048

使用 2048的url和估值类(AI逻辑)构造。测试过程中,用 wget 将 "http://gabrielecirulli.github.io/2048/" 的所有页面抓到本地分析(由于不懂js在网页中的工作原理,使用find_elements_by_class_name找当前cells的信息找了好久)

每一次操作调一次AI(),AI() 先获取当前页面的状态保存在 self.cells 中,然后对上下左右四个方向枚举,取估值最大的方向并使用send_keys进行操作。如果能操作则AI()返回True,否则返回False

估值类 Estimator

估值类只要实现 def estimate(self, precells, postcells, action, score): 估值方法即可。其中,precells为操作前状态,postcells为操作后状态,atcion为操作['Left', 'Right', 'Up', 'Down'],score为操作得分。返回值为估值estimation,值越到越好。

虽然我的估值方法运气好的话可以得到2048,但还是很粗糙的。你要有兴趣的话,可以写一个更好的Estimator得到更高的分。

源代码地址 https://github.com/xhSong/auto2048