使用Python写一个贪吃蛇AI

不久前在网上看到一张贪吃蛇的动态图片,蛇最后把所有的食物吃完并占满整个运动空间。 
觉得挺好玩的,于是用Python写了一个。
Blog:

http://hawstein.com/posts/snake-ai.html

Code:

https://github.com/Hawstein/snake-ai
标签: <无>

代码片段(1)

[代码] Python 贪吃蛇AI

001 # coding: utf-8
002  
003 import curses
004 from curses import KEY_RIGHT, KEY_LEFT, KEY_UP, KEY_DOWN
005 from random import randint
006  
007 # 蛇运动的场地长宽
008 HEIGHT = 10
009 WIDTH = 20
010 FIELD_SIZE = HEIGHT * WIDTH
011  
012 # 蛇头总是位于snake数组的第一个元素
013 HEAD = 0
014  
015 # 用来代表不同东西的数字,由于矩阵上每个格子会处理成到达食物的路径长度,
016 # 因此这三个变量间需要有足够大的间隔(>HEIGHT*WIDTH)
017 FOOD = 0
018 UNDEFINED = (HEIGHT + 1* (WIDTH + 1)
019 SNAKE = 2 * UNDEFINED
020  
021 # 由于snake是一维数组,所以对应元素直接加上以下值就表示向四个方向移动
022 LEFT = -1
023 RIGHT = 1
024 UP = -WIDTH
025 DOWN = WIDTH
026  
027 # 错误码
028 ERR = -1111
029  
030 # 用一维数组来表示二维的东西
031 # board表示蛇运动的矩形场地
032 # 初始化蛇头在(1,1)的地方,第0行,HEIGHT行,第0列,WIDTH列为围墙,不可用
033 # 初始蛇长度为1
034 board = [0* FIELD_SIZE
035 snake = [0* (FIELD_SIZE+1)
036 snake[HEAD] = 1*WIDTH+1
037 snake_size = 1
038 # 与上面变量对应的临时变量,蛇试探性地移动时使用
039 tmpboard = [0* FIELD_SIZE
040 tmpsnake = [0* (FIELD_SIZE+1)
041 tmpsnake[HEAD] = 1*WIDTH+1
042 tmpsnake_size = 1
043  
044 # food:食物位置(0~FIELD_SIZE-1),初始在(3, 3)
045 # best_move: 运动方向
046 food = 3 * WIDTH + 3
047 best_move = ERR
048  
049 # 运动方向数组
050 mov = [LEFT, RIGHT, UP, DOWN]
051 # 接收到的键 和 分数
052 key = KEY_RIGHT                                                   
053 score = 1 #分数也表示蛇长
054  
055 # 检查一个cell有没有被蛇身覆盖,没有覆盖则为free,返回true
056 def is_cell_free(idx, psize, psnake):
057     return not (idx in psnake[:psize])
058  
059 # 检查某个位置idx是否可向move方向运动
060 def is_move_possible(idx, move):
061     flag = False
062     if move == LEFT:
063         flag = True if idx%WIDTH > 1 else False
064     elif move == RIGHT:
065         flag = True if idx%WIDTH < (WIDTH-2else False
066     elif move == UP:
067         flag = True if idx > (2*WIDTH-1else False # 即idx/WIDTH > 1
068     elif move == DOWN:
069         flag = True if idx < (FIELD_SIZE-2*WIDTH) else False # 即idx/WIDTH < HEIGHT-2
070     return flag
071 # 重置board
072 # board_refresh后,UNDEFINED值都变为了到达食物的路径长度
073 # 如需要还原,则要重置它
074 def board_reset(psnake, psize, pboard):
075     for in xrange(FIELD_SIZE):
076         if == food:
077             pboard[i] = FOOD
078         elif is_cell_free(i, psize, psnake): # 该位置为空
079             pboard[i] = UNDEFINED
080         else# 该位置为蛇身
081             pboard[i] = SNAKE
082      
083 # 广度优先搜索遍历整个board,
084 # 计算出board中每个非SNAKE元素到达食物的路径长度
085 def board_refresh(pfood, psnake, pboard):
086     queue = []
087     queue.append(pfood)
088     inqueue = [0* FIELD_SIZE
089     found = False
090     # while循环结束后,除了蛇的身体,
091     # 其它每个方格中的数字代码从它到食物的路径长度
092     while len(queue)!=0:
093         idx = queue.pop(0)
094         if inqueue[idx] == 1continue
095         inqueue[idx] = 1
096         for in xrange(4):
097             if is_move_possible(idx, mov[i]):
098                 if idx + mov[i] == psnake[HEAD]:
099                     found = True
100                 if pboard[idx+mov[i]] < SNAKE: # 如果该点不是蛇的身体
101                      
102                     if pboard[idx+mov[i]] > pboard[idx]+1:
103                         pboard[idx+mov[i]] = pboard[idx] + 1
104                     if inqueue[idx+mov[i]] == 0:
105                         queue.append(idx+mov[i])
106  
107     return found
108  
109 # 从蛇头开始,根据board中元素值,
110 # 从蛇头周围4个领域点中选择最短路径
111 def choose_shortest_safe_move(psnake, pboard):
112     best_move = ERR
113     min = SNAKE
114     for in xrange(4):
115         if is_move_possible(psnake[HEAD], mov[i]) and pboard[psnake[HEAD]+mov[i]]<min:
116             min = pboard[psnake[HEAD]+mov[i]]
117             best_move = mov[i]
118     return best_move
119  
120 # 从蛇头开始,根据board中元素值,
121 # 从蛇头周围4个领域点中选择最远路径
122 def choose_longest_safe_move(psnake, pboard):
123     best_move = ERR
124     max = -1
125     for in xrange(4):
126         if is_move_possible(psnake[HEAD], mov[i]) and pboard[psnake[HEAD]+mov[i]]<UNDEFINED and pboard[psnake[HEAD]+mov[i]]>max:
127             max = pboard[psnake[HEAD]+mov[i]]
128             best_move = mov[i]
129     return best_move
130  
131 # 检查是否可以追着蛇尾运动,即蛇头和蛇尾间是有路径的
132 # 为的是避免蛇头陷入死路
133 # 虚拟操作,在tmpboard,tmpsnake中进行
134 def is_tail_inside():
135     global tmpboard, tmpsnake, food, tmpsnake_size
136     tmpboard[tmpsnake[tmpsnake_size-1]] = 0 # 虚拟地将蛇尾变为食物(因为是虚拟的,所以在tmpsnake,tmpboard中进行)
137     tmpboard[food] = SNAKE # 放置食物的地方,看成蛇身
138     result = board_refresh(tmpsnake[tmpsnake_size-1], tmpsnake, tmpboard) # 求得每个位置到蛇尾的路径长度
139     for in xrange(4): # 如果蛇头和蛇尾紧挨着,则返回False。即不能follow_tail,追着蛇尾运动了
140         if is_move_possible(tmpsnake[HEAD], mov[i]) andtmpsnake[HEAD]+mov[i]==tmpsnake[tmpsnake_size-1and tmpsnake_size>3:
141             result = False
142     return result
143  
144 # 让蛇头朝着蛇尾运行一步
145 # 不管蛇身阻挡,朝蛇尾方向运行
146 def follow_tail():
147     global tmpboard, tmpsnake, food, tmpsnake_size
148     tmpsnake_size = snake_size
149     tmpsnake = snake[:]
150     board_reset(tmpsnake, tmpsnake_size, tmpboard) # 重置虚拟board
151     tmpboard[tmpsnake[tmpsnake_size-1]] = FOOD # 让蛇尾成为食物
152     tmpboard[food] = SNAKE # 让食物的地方变成蛇身
153     board_refresh(tmpsnake[tmpsnake_size-1], tmpsnake, tmpboard) # 求得各个位置到达蛇尾的路径长度
154     tmpboard[tmpsnake[tmpsnake_size-1]] = SNAKE # 还原蛇尾
155  
156     return choose_longest_safe_move(tmpsnake, tmpboard) # 返回运行方向(让蛇头运动1步)
157  
158 # 在各种方案都不行时,随便找一个可行的方向来走(1步),
159 def any_possible_move():
160     global food , snake, snake_size, board
161     best_move = ERR
162     board_reset(snake, snake_size, board)
163     board_refresh(food, snake, board)
164     min = SNAKE
165  
166     for in xrange(4):
167         if is_move_possible(snake[HEAD], mov[i]) and board[snake[HEAD]+mov[i]]<min:
168             min = board[snake[HEAD]+mov[i]]
169             best_move = mov[i]
170     return best_move
171  
172 def shift_array(arr, size):
173     for in xrange(size, 0-1):
174         arr[i] = arr[i-1]
175  
176 def new_food():
177     global food, snake_size
178     cell_free = False
179     while not cell_free:
180         = randint(1, WIDTH-2)
181         = randint(1, HEIGHT-2)
182         food = * WIDTH + w
183         cell_free = is_cell_free(food, snake_size, snake)
184     win.addch(food/WIDTH, food%WIDTH, '@')
185  
186 # 真正的蛇在这个函数中,朝pbest_move走1步
187 def make_move(pbest_move):
188     global key, snake, board, snake_size, score
189     shift_array(snake, snake_size)
190     snake[HEAD] += pbest_move
191      
192  
193     # 按esc退出,getch同时保证绘图的流畅性,没有它只会看到最终结果
194     win.timeout(10)
195     event = win.getch()
196     key = key if event == -1 else event
197     if key == 27return
198  
199     = snake[HEAD]
200     win.addch(p/WIDTH, p%WIDTH, '*')
201  
202      
203     # 如果新加入的蛇头就是食物的位置
204     # 蛇长加1,产生新的食物,重置board(因为原来那些路径长度已经用不上了)
205     if snake[HEAD] == food:
206         board[snake[HEAD]] = SNAKE # 新的蛇头
207         snake_size += 1
208         score += 1
209         if snake_size < FIELD_SIZE: new_food()
210     else# 如果新加入的蛇头不是食物的位置
211         board[snake[HEAD]] = SNAKE # 新的蛇头
212         board[snake[snake_size]] = UNDEFINED # 蛇尾变为空格
213         win.addch(snake[snake_size]/WIDTH, snake[snake_size]%WIDTH, ' ')
214  
215 # 虚拟地运行一次,然后在调用处检查这次运行可否可行
216 # 可行才真实运行。
217 # 虚拟运行吃到食物后,得到虚拟下蛇在board的位置
218 def virtual_shortest_move():
219     global snake, board, snake_size, tmpsnake, tmpboard, tmpsnake_size, food
220     tmpsnake_size = snake_size
221     tmpsnake = snake[:] # 如果直接tmpsnake=snake,则两者指向同一处内存
222     tmpboard = board[:] # board中已经是各位置到达食物的路径长度了,不用再计算
223     board_reset(tmpsnake, tmpsnake_size, tmpboard)
224      
225     food_eated = False
226     while not food_eated:
227         board_refresh(food, tmpsnake, tmpboard)   
228         move = choose_shortest_safe_move(tmpsnake, tmpboard)
229         shift_array(tmpsnake, tmpsnake_size)
230         tmpsnake[HEAD] += move # 在蛇头前加入一个新的位置
231         # 如果新加入的蛇头的位置正好是食物的位置
232         # 则长度加1,重置board,食物那个位置变为蛇的一部分(SNAKE)
233         if tmpsnake[HEAD] == food:
234             tmpsnake_size += 1
235             board_reset(tmpsnake, tmpsnake_size, tmpboard) # 虚拟运行后,蛇在board的位置(label101010)
236             tmpboard[food] = SNAKE
237             food_eated = True
238         else# 如果蛇头不是食物的位置,则新加入的位置为蛇头,最后一个变为空格
239             tmpboard[tmpsnake[HEAD]] = SNAKE
240             tmpboard[tmpsnake[tmpsnake_size]] = UNDEFINED
241  
242 # 如果蛇与食物间有路径,则调用本函数
243 def find_safe_way():
244     global snake, board
245     safe_move = ERR
246     # 虚拟地运行一次,因为已经确保蛇与食物间有路径,所以执行有效
247     # 运行后得到虚拟下蛇在board中的位置,即tmpboard,见label101010
248     virtual_shortest_move() # 该函数唯一调用处
249     if is_tail_inside(): # 如果虚拟运行后,蛇头蛇尾间有通路,则选最短路运行(1步)
250         return choose_shortest_safe_move(snake, board)
251     safe_move = follow_tail() # 否则虚拟地follow_tail 1步,如果可以做到,返回true
252     return safe_move
253  
254  
255 curses.initscr()
256 win = curses.newwin(HEIGHT, WIDTH, 00)
257 win.keypad(1)
258 curses.noecho()
259 curses.curs_set(0)
260 win.border(0)
261 win.nodelay(1)
262 win.addch(food/WIDTH, food%WIDTH, '@')
263  
264      
265 while key != 27:
266     win.border(0)
267     win.addstr(02'S:' + str(score) + ' ')              
268     win.timeout(10)
269     # 接收键盘输入,同时也使显示流畅
270     event = win.getch()
271     key = key if event == -1 else event
272     # 重置矩阵
273     board_reset(snake, snake_size, board)
274      
275     # 如果蛇可以吃到食物,board_refresh返回true
276     # 并且board中除了蛇身(=SNAKE),其它的元素值表示从该点运动到食物的最短路径长
277     if board_refresh(food, snake, board):
278         best_move  = find_safe_way() # find_safe_way的唯一调用处
279     else:
280         best_move = follow_tail()
281              
282     if best_move == ERR:
283         best_move = any_possible_move()
284     # 上面一次思考,只得出一个方向,运行一步
285     if best_move != ERR: make_move(best_move)  
286     elsebreak       
287          
288 curses.endwin()
289 print("\nScore - " + str(score))
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值