|
| 1 | +# coding: utf-8 |
| 2 | + |
| 3 | +import curses |
| 4 | +from curses import KEY_RIGHT, KEY_LEFT, KEY_UP, KEY_DOWN |
| 5 | +from random import randint |
| 6 | + |
| 7 | +# 蛇运动的场地长宽 |
| 8 | +HEIGHT = 10 |
| 9 | +WIDTH = 20 |
| 10 | +FIELD_SIZE = HEIGHT * WIDTH |
| 11 | + |
| 12 | +# 蛇头总是位于snake数组的第一个元素 |
| 13 | +HEAD = 0 |
| 14 | + |
| 15 | +# 用来代表不同东西的数字,由于矩阵上每个格子会处理成到达食物的路径长度, |
| 16 | +# 因此这三个变量间需要有足够大的间隔(>HEIGHT*WIDTH) |
| 17 | +FOOD = 0 |
| 18 | +UNDEFINED = (HEIGHT + 1) * (WIDTH + 1) |
| 19 | +SNAKE = 2 * UNDEFINED |
| 20 | + |
| 21 | +# 由于snake是一维数组,所以对应元素直接加上以下值就表示向四个方向移动 |
| 22 | +LEFT = -1 |
| 23 | +RIGHT = 1 |
| 24 | +UP = -WIDTH |
| 25 | +DOWN = WIDTH |
| 26 | + |
| 27 | +# 错误码 |
| 28 | +ERR = -1111 |
| 29 | + |
| 30 | +# 用一维数组来表示二维的东西 |
| 31 | +# board表示蛇运动的矩形场地 |
| 32 | +# 初始化蛇头在(1,1)的地方,第0行,HEIGHT行,第0列,WIDTH列为围墙,不可用 |
| 33 | +# 初始蛇长度为1 |
| 34 | +board = [0] * FIELD_SIZE |
| 35 | +snake = [0] * (FIELD_SIZE+1) |
| 36 | +snake[HEAD] = 1*WIDTH+1 |
| 37 | +snake_size = 1 |
| 38 | +# 与上面变量对应的临时变量,蛇试探性地移动时使用 |
| 39 | +tmpboard = [0] * FIELD_SIZE |
| 40 | +tmpsnake = [0] * (FIELD_SIZE+1) |
| 41 | +tmpsnake[HEAD] = 1*WIDTH+1 |
| 42 | +tmpsnake_size = 1 |
| 43 | + |
| 44 | +# food:食物位置(0~FIELD_SIZE-1),初始在(3, 3) |
| 45 | +# best_move: 运动方向 |
| 46 | +food = 3 * WIDTH + 3 |
| 47 | +best_move = ERR |
| 48 | + |
| 49 | +# 运动方向数组 |
| 50 | +mov = [LEFT, RIGHT, UP, DOWN] |
| 51 | +# 接收到的键 和 分数 |
| 52 | +key = KEY_RIGHT |
| 53 | +score = 1 #分数也表示蛇长 |
| 54 | + |
| 55 | +# 检查一个cell有没有被蛇身覆盖,没有覆盖则为free,返回true |
| 56 | +def is_cell_free(idx, psize, psnake): |
| 57 | + #return not (idx in psnake[:]) #错!psnake后面还有很多没用上的单元 |
| 58 | + for i in xrange(psize): |
| 59 | + if idx == psnake[i]: return False |
| 60 | + return True |
| 61 | + |
| 62 | + |
| 63 | +# 检查某个位置idx是否可向move方向运动 |
| 64 | +def is_move_possible(idx, move): |
| 65 | + flag = False |
| 66 | + if move == LEFT: |
| 67 | + flag = True if idx%WIDTH > 1 else False |
| 68 | + elif move == RIGHT: |
| 69 | + flag = True if idx%WIDTH < (WIDTH-2) else False |
| 70 | + elif move == UP: |
| 71 | + flag = True if idx > (2*WIDTH-1) else False # 即idx/WIDTH > 1 |
| 72 | + elif move == DOWN: |
| 73 | + flag = True if idx < (FIELD_SIZE-2*WIDTH) else False # 即idx/WIDTH < HEIGHT-2 |
| 74 | + return flag |
| 75 | +# 重置board |
| 76 | +# board_refresh后,UNDEFINED值都变为了到达食物的路径长度 |
| 77 | +# 如需要还原,则要重置它 |
| 78 | +def board_reset(psnake, psize, pboard): |
| 79 | + for i in xrange(FIELD_SIZE): |
| 80 | + # f.write('hello ' + str(len(pboard)) +' ' +str(i)+'\n') |
| 81 | + if i == food: |
| 82 | + pboard[i] = FOOD |
| 83 | + elif is_cell_free(i, psize, psnake): # 该位置为空 |
| 84 | + pboard[i] = UNDEFINED |
| 85 | + else: # 该位置为蛇身 |
| 86 | + pboard[i] = SNAKE |
| 87 | + |
| 88 | +# def board_refresh(pfood, psnake, pboard): |
| 89 | +# board_changed = True |
| 90 | +# found = False |
| 91 | +# while board_changed: # 一直更新board,直到每个格子上的数都对应到达食物的步数,不能再改变为止 |
| 92 | +# board_changed = False |
| 93 | +# for i in xrange(FIELD_SIZE): |
| 94 | +# if pboard[i] < SNAKE: |
| 95 | +# min = pboard[i] |
| 96 | +# for j in xrange(4): |
| 97 | +# if is_move_possible(i, mov[j]) and pboard[i+mov[j]] < min: |
| 98 | +# min = pboard[i+mov[j]] |
| 99 | +# if pboard[i] > min+1: |
| 100 | +# pboard[i] = min + 1 |
| 101 | +# board_changed = True |
| 102 | + |
| 103 | +# if ~board_changed: break # 不再改变,退出 |
| 104 | +# board_changed = False |
| 105 | + |
| 106 | +# for i in xrange(FIELD_SIZE-1, -1, -1): # [FIELD_SIZE-1, 0] |
| 107 | +# if pboard[i] < SNAKE: |
| 108 | +# min = pboard[i] |
| 109 | +# for j in xrange(4): |
| 110 | +# if is_move_possible(i, mov[j]) and pboard[i+mov[j]] < min: |
| 111 | +# min = pboard[i+mov[j]] |
| 112 | +# if pboard[i] > min+1: |
| 113 | +# pboard[i] = min + 1 |
| 114 | +# board_changed = True |
| 115 | +# for i in xrange(4): |
| 116 | +# if is_move_possible(psnake[HEAD], mov[i]) and pboard[psnake[HEAD]+mov[i]]<UNDEFINED: |
| 117 | +# flag = True |
| 118 | + |
| 119 | +# return flag |
| 120 | + |
| 121 | +# 广度优先搜索遍历整个board, |
| 122 | +# 计算出board中每个非SNAKE元素到达食物的路径长度 |
| 123 | +def board_refresh(pfood, psnake, pboard): |
| 124 | + queue = [] |
| 125 | + queue.append(pfood) |
| 126 | + inqueue = [0] * FIELD_SIZE |
| 127 | + found = False |
| 128 | + # while循环结束后,除了蛇的身体, |
| 129 | + # 其它每个方格中的数字代码从它到食物的路径长度 |
| 130 | + #f.write('bfs begin:\n') |
| 131 | + while len(queue)!=0: |
| 132 | + idx = queue.pop(0) |
| 133 | + if inqueue[idx] == 1: continue |
| 134 | + #f.write(str(idx)+'\n') |
| 135 | + inqueue[idx] = 1 |
| 136 | + for i in xrange(4): |
| 137 | + if is_move_possible(idx, mov[i]): |
| 138 | + if idx + mov[i] == psnake[HEAD]: |
| 139 | + found = True |
| 140 | + if pboard[idx+mov[i]] < SNAKE: # 如果该点不是蛇的身体 |
| 141 | + |
| 142 | + if pboard[idx+mov[i]] > pboard[idx]+1: |
| 143 | + pboard[idx+mov[i]] = pboard[idx] + 1 |
| 144 | + if inqueue[idx+mov[i]] == 0: |
| 145 | + queue.append(idx+mov[i]) |
| 146 | + |
| 147 | + #f.write('bfs end\n') |
| 148 | + #f.write('found: ' + str(found) +'\n') |
| 149 | + return found |
| 150 | + |
| 151 | +# 从蛇头开始,根据board中元素值, |
| 152 | +# 从蛇头周围4个领域点中选择最短路径 |
| 153 | +def choose_shortest_safe_move(psnake, pboard): |
| 154 | + best_move = ERR |
| 155 | + min = SNAKE |
| 156 | + for i in xrange(4): |
| 157 | + if is_move_possible(psnake[HEAD], mov[i]) and pboard[psnake[HEAD]+mov[i]]<min: |
| 158 | + min = pboard[psnake[HEAD]+mov[i]] |
| 159 | + best_move = mov[i] |
| 160 | + return best_move |
| 161 | + |
| 162 | +# 从蛇头开始,根据board中元素值, |
| 163 | +# 从蛇头周围4个领域点中选择最远路径 |
| 164 | +def choose_longest_safe_move(psnake, pboard): |
| 165 | + best_move = ERR |
| 166 | + max = -1 |
| 167 | + for i in xrange(4): |
| 168 | + if is_move_possible(psnake[HEAD], mov[i]) and pboard[psnake[HEAD]+mov[i]]<UNDEFINED and pboard[psnake[HEAD]+mov[i]]>max: |
| 169 | + max = pboard[psnake[HEAD]+mov[i]] |
| 170 | + best_move = mov[i] |
| 171 | + return best_move |
| 172 | + |
| 173 | +# 检查是否可以追着蛇尾运动,即蛇头和蛇尾间是有路径的 |
| 174 | +# 为的是避免蛇头陷入死路 |
| 175 | +# 虚拟操作,在tmpboard,tmpsnake中进行 |
| 176 | +def is_tail_inside(): |
| 177 | + global tmpboard, tmpsnake, food, tmpsnake_size |
| 178 | + tmpboard[tmpsnake[tmpsnake_size-1]] = 0 # 虚拟地将蛇尾变为食物(因为是虚拟的,所以在tmpsnake,tmpboard中进行) |
| 179 | + tmpboard[food] = SNAKE # 放置食物的地方,看成蛇身 |
| 180 | + result = board_refresh(tmpsnake[tmpsnake_size-1], tmpsnake, tmpboard) # 求得每个位置到蛇尾的路径长度 |
| 181 | + for i in xrange(4): # 如果蛇头和蛇尾紧挨着,则返回False。即不能follow_tail,追着蛇尾运动了 |
| 182 | + if is_move_possible(tmpsnake[HEAD], mov[i]) and tmpsnake[HEAD]+mov[i]==tmpsnake[tmpsnake_size-1] and tmpsnake_size>3: |
| 183 | + result = False |
| 184 | + return result |
| 185 | + |
| 186 | +# 让蛇头朝着蛇尾运行一步 |
| 187 | +# 不管蛇身阻挡,朝蛇尾方向运行 |
| 188 | +def follow_tail(): |
| 189 | + global tmpboard, tmpsnake, food, tmpsnake_size |
| 190 | + tmpsnake_size = snake_size |
| 191 | + tmpsnake = snake[:] |
| 192 | + board_reset(tmpsnake, tmpsnake_size, tmpboard) # 重置虚拟board |
| 193 | + tmpboard[tmpsnake[tmpsnake_size-1]] = FOOD # 让蛇尾成为食物 |
| 194 | + tmpboard[food] = SNAKE # 让食物的地方变成蛇身 |
| 195 | + board_refresh(tmpsnake[tmpsnake_size-1], tmpsnake, tmpboard) # 求得各个位置到达蛇尾的路径长度 |
| 196 | + tmpboard[tmpsnake[tmpsnake_size-1]] = SNAKE # 还原蛇尾 |
| 197 | + |
| 198 | + # return choose_longest_safe_move(tmpsnake, tmpboard) # 返回运行方向(让蛇头运动1步) |
| 199 | + return choose_longest_safe_move(tmpsnake, tmpboard) |
| 200 | + |
| 201 | +# 在各种方案都不行时,随便找一个可行的方向来走(1步), |
| 202 | +def any_possible_move(): |
| 203 | + global food , snake, snake_size, board |
| 204 | + best_move = ERR |
| 205 | + board_reset(snake, snake_size, board) |
| 206 | + board_refresh(food, snake, board) |
| 207 | + min = SNAKE |
| 208 | + |
| 209 | + for i in xrange(4): |
| 210 | + if is_move_possible(snake[HEAD], mov[i]) and board[snake[HEAD]+mov[i]]<min: |
| 211 | + min = board[snake[HEAD]+mov[i]] |
| 212 | + best_move = mov[i] |
| 213 | + return best_move |
| 214 | + |
| 215 | +def shift_array(arr, size): |
| 216 | + for i in xrange(size, 0, -1): |
| 217 | + arr[i] = arr[i-1] |
| 218 | + |
| 219 | +def new_food(): |
| 220 | + global food, snake_size |
| 221 | + if snake_size >= FIELD_SIZE-1: return |
| 222 | + cell_free = False |
| 223 | + while not cell_free: |
| 224 | + w = randint(1, WIDTH-2) |
| 225 | + h = randint(1, HEIGHT-2) |
| 226 | + food = h * WIDTH + w |
| 227 | + cell_free = is_cell_free(food, snake_size, snake) |
| 228 | + win.addch(food/WIDTH, food%WIDTH, '@') |
| 229 | + |
| 230 | +# 真正的蛇在这个函数中,朝pbest_move走1步 |
| 231 | +def make_move(pbest_move): |
| 232 | + global key, snake, board, snake_size, score |
| 233 | + shift_array(snake, snake_size) |
| 234 | + snake[HEAD] += pbest_move |
| 235 | + |
| 236 | + |
| 237 | + # 按esc退出,getch同时保证绘图的流畅性,没有它只会看到最终结果 |
| 238 | + win.timeout(10) |
| 239 | + event = win.getch() |
| 240 | + key = key if event == -1 else event |
| 241 | + if key == 27: return |
| 242 | + |
| 243 | + p = snake[HEAD] |
| 244 | + win.addch(p/WIDTH, p%WIDTH, '*') |
| 245 | + |
| 246 | + |
| 247 | + # 如果新加入的蛇头就是食物的位置 |
| 248 | + # 蛇长加1,产生新的食物,重置board(因为原来那些路径长度已经用不上了) |
| 249 | + if snake[HEAD] == food: |
| 250 | + board[snake[HEAD]] = SNAKE # 新的蛇头 |
| 251 | + snake_size += 1 |
| 252 | + score += 1 |
| 253 | + if snake_size < FIELD_SIZE: new_food() |
| 254 | + #board_reset(snake, board) # |
| 255 | + #return True |
| 256 | + else: # 如果新加入的蛇头不是食物的位置 |
| 257 | + board[snake[HEAD]] = SNAKE # 新的蛇头 |
| 258 | + board[snake[snake_size]] = UNDEFINED # 蛇尾变为空格 |
| 259 | + win.addch(snake[snake_size]/WIDTH, snake[snake_size]%WIDTH, ' ') |
| 260 | + #return False |
| 261 | + # test 打印tmpboard |
| 262 | + for i in xrange(HEIGHT): |
| 263 | + for j in xrange(WIDTH): |
| 264 | + k = board[i*WIDTH+j] |
| 265 | + f.write(str(k)+' ') |
| 266 | + f.write('\n') |
| 267 | + # print symbol |
| 268 | + for i in xrange(HEIGHT): |
| 269 | + for j in xrange(WIDTH): |
| 270 | + k = board[i*WIDTH+j] |
| 271 | + if k == UNDEFINED: |
| 272 | + f.write('# ') |
| 273 | + elif k == SNAKE: |
| 274 | + if i*WIDTH+j == snake[HEAD]: |
| 275 | + f.write('+ ') # 蛇头 |
| 276 | + elif i*WIDTH+j == snake[snake_size-1]: |
| 277 | + f.write('- ') # 蛇尾 |
| 278 | + else: |
| 279 | + f.write('* ') |
| 280 | + else: |
| 281 | + f.write(str(k)+' ') |
| 282 | + f.write('\n') |
| 283 | + f.write('\n') |
| 284 | + |
| 285 | +# 虚拟地运行一次,然后在调用处检查这次运行可否可行 |
| 286 | +# 可行才真实运行。 |
| 287 | +# 虚拟运行吃到食物后,得到虚拟下蛇在board的位置 |
| 288 | +def virtual_shortest_move(): |
| 289 | + global snake, board, snake_size, tmpsnake, tmpboard, tmpsnake_size, food |
| 290 | + #f.write('i am in virtual\n') |
| 291 | + tmpsnake_size = snake_size |
| 292 | + tmpsnake = snake[:] # 如果直接tmpsnake=snake,则两者指向同一处内存 |
| 293 | + tmpboard = board[:] # board中已经是各位置到达食物的路径长度了,不用再计算 |
| 294 | + board_reset(tmpsnake, tmpsnake_size, tmpboard) |
| 295 | + |
| 296 | + |
| 297 | + # test 打印tmpboard |
| 298 | + # for i in xrange(HEIGHT): |
| 299 | + # for j in xrange(WIDTH): |
| 300 | + # k = tmpboard[i*WIDTH+j] |
| 301 | + # f.write(str(k)+' ') |
| 302 | + # f.write('\n') |
| 303 | + |
| 304 | + food_eated = False |
| 305 | + while not food_eated: |
| 306 | + board_refresh(food, tmpsnake, tmpboard) |
| 307 | + move = choose_shortest_safe_move(tmpsnake, tmpboard) |
| 308 | + shift_array(tmpsnake, tmpsnake_size) |
| 309 | + tmpsnake[HEAD] += move # 在蛇头前加入一个新的位置 |
| 310 | + #f.write('snake head: '+str(tmpsnake[HEAD])+'\n') |
| 311 | + # 如果新加入的蛇头的位置正好是食物的位置 |
| 312 | + # 则长度加1,重置board,食物那个位置变为蛇的一部分(SNAKE) |
| 313 | + if tmpsnake[HEAD] == food: |
| 314 | + tmpsnake_size += 1 |
| 315 | + board_reset(tmpsnake, tmpsnake_size, tmpboard) # 虚拟运行后,蛇在board的位置(label101010) |
| 316 | + tmpboard[food] = SNAKE |
| 317 | + food_eated = True |
| 318 | + else: # 如果蛇头不是食物的位置,则新加入的位置为蛇头,最后一个变为空格 |
| 319 | + tmpboard[tmpsnake[HEAD]] = SNAKE |
| 320 | + tmpboard[tmpsnake[tmpsnake_size]] = UNDEFINED |
| 321 | + #f.write('i am out virtual\n') |
| 322 | + |
| 323 | +# 如果蛇与食物间有路径,则调用本函数 |
| 324 | +def find_safe_way(): |
| 325 | + global snake, board |
| 326 | + safe_move = ERR |
| 327 | + # 虚拟地运行一次,因为已经确保蛇与食物间有路径,所以执行有效 |
| 328 | + # 运行后得到虚拟下蛇在board中的位置,即tmpboard,见label101010 |
| 329 | + virtual_shortest_move() # 该函数唯一调用处 |
| 330 | + if is_tail_inside(): # 如果虚拟运行后,蛇头蛇尾间有通路,则选最短路运行(1步) |
| 331 | + return choose_shortest_safe_move(snake, board) |
| 332 | + safe_move = follow_tail() # 否则虚拟地follow_tail 1步,如果可以做到,返回true |
| 333 | + return safe_move |
| 334 | + |
| 335 | + |
| 336 | +curses.initscr() |
| 337 | +win = curses.newwin(HEIGHT, WIDTH, 0, 0) |
| 338 | +win.keypad(1) |
| 339 | +curses.noecho() |
| 340 | +curses.curs_set(0) |
| 341 | +win.border(0) |
| 342 | +win.nodelay(1) |
| 343 | +win.addch(food/WIDTH, food%WIDTH, '@') |
| 344 | + |
| 345 | +f = file('log', 'w') |
| 346 | + |
| 347 | +while key != 27: |
| 348 | + win.border(0) |
| 349 | + win.addstr(0, 2, 's:' + str(score) + ' ') |
| 350 | + #win.addstr(0, WIDTH/2-3, ' SNAKE ') |
| 351 | + win.timeout(10) |
| 352 | + # 接收键盘输入,同时也使显示流畅 |
| 353 | + event = win.getch() |
| 354 | + key = key if event == -1 else event |
| 355 | + if snake_size >= FIELD_SIZE: continue |
| 356 | + # 重置矩阵 |
| 357 | + board_reset(snake, snake_size, board) |
| 358 | + |
| 359 | + # 如果蛇可以吃到食物,board_refresh返回true |
| 360 | + # 并且board中除了蛇身(=SNAKE),其它的元素值表示从该点运动到食物的最短路径长 |
| 361 | + if board_refresh(food, snake, board): |
| 362 | + best_move = find_safe_way() # find_safe_way的唯一调用处 |
| 363 | + f.write('find safe way: ' + str(best_move) + '\n') |
| 364 | + else: |
| 365 | + best_move = follow_tail() |
| 366 | + f.write('follow tail: ' + str(best_move) +'\n') |
| 367 | + |
| 368 | + if best_move == ERR: |
| 369 | + best_move = any_possible_move() |
| 370 | + f.write('any possible move: ' + str(best_move) + '\n') |
| 371 | + # 上面一次思考,只得出一个方向,运行一步 |
| 372 | + if best_move != ERR: make_move(best_move) |
| 373 | + else: break |
| 374 | + |
| 375 | +f.close() |
| 376 | +curses.endwin() |
| 377 | +print("\nScore - " + str(score)) |
| 378 | + |
0 commit comments