代码片段(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 - 2 ) else False |
066 | elif move = = UP: |
067 | flag = True if idx > ( 2 * WIDTH - 1 ) else 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 i in xrange (FIELD_SIZE): |
076 | if i = = 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] = = 1 : continue |
095 | inqueue[idx] = 1 |
096 | for i 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 i 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 i 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 i in xrange ( 4 ): # 如果蛇头和蛇尾紧挨着,则返回False。即不能follow_tail,追着蛇尾运动了 |
140 | if is_move_possible(tmpsnake[HEAD], mov[i]) and tmpsnake[HEAD] + mov[i] = = tmpsnake[tmpsnake_size - 1 ] and 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 i 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 i 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 | w = randint( 1 , WIDTH - 2 ) |
181 | h = randint( 1 , HEIGHT - 2 ) |
182 | food = h * 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 = = 27 : return |
198 |
199 | p = 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, 0 , 0 ) |
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( 0 , 2 , '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 | else : break |
287 | |
288 | curses.endwin() |
289 | print ( "\nScore - " + str (score)) |