使用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对于每一个方向的评估有三个方面
- 移动导致合并的得分 score:这个就是游戏本身定义的得分,如 4和4合并得8分,128和128合并的256分
- 移动后每一行每一列的单调性 monotone:对于每一行(每一列)如果 line[i] <= line[i+1]则mon+= line[i]+line[i+1]否则,mon-=line[i]+line[i+1],monotone=sum(abs(mon))
- 移动后相邻块值相同的情况 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 = "http://gabrielecirulli.github.io/2048/" 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得到更高的分。