2013年11月10日星期日

Python Snake Code

这是哈尔滨工业大学《高级语言程序设计I》的进阶实验3.2 贪吃蛇游戏。感觉挺有意思,而且写好它也花了我不少时间,所以就发上来了。


简单地说,这就是一个用Python(+Tkinter)写的GUI界面贪吃蛇游戏。电脑需要安装有Python才能运行。


一. 实验目的:

1)  掌握GUI的基本控件

2)  掌握GUI的布局方法

3)  熟练掌握键盘、鼠标事件的处理方法

4)  掌握程序调试、测试方法

5)  掌握MVC程序设计方法



二. 实验内容:

snake


图1 贪吃蛇游戏界面


1)  利用GUI控件实现如图1所示用户界面,包括游戏窗口,显示得分。

2)  键盘上下左右箭头(或者ASWD键)控制贪吃蛇的方向。

3)  贪吃蛇初始长度为3个方块,每个大小为15*15像素,显示蛇的移动。

4)  在游戏窗口内随机生产食物的位置

5)  蛇碰到自己或者墙结束游戏

6)  根据上述要求首先设计贪吃蛇游戏程序模块和流程图,然后开始编写代码。

7)  为了统一评判,要求必须利用所提供的程序框架编写代码,(不用类开发也可以,但是函数名称和数目必须与框架一致)

选做内容(游戏等级功能)

8)  显示游戏的等级

9)  一个食物10分,每100分升级一个等级,

10)              游戏速度增加10毫秒

11)              每增加一个等级,同时显示的食物数量增加一个。
from Tkinter import *
import random
class SnakeGame:
  """
  This class contains a integrated snake game.\n
  Use SnakeGame() and it will start automatically.
  """

  def __init__(self):
  # moving step for snake and food
  self.step=15
  # game score
  self.gamescore=-10

  # to initialize the snake in the range of (x1,y1,x2,y1)
  # the number should be calculated cautiously
  r=random.randrange(54, 336, step = 15)
  self.snakeX=[r, r+self.step, r+2*self.step]
  self.snakeY=[r-2, r-2, r-2]

  # to initialize food list
  self.foodX=[0]
  self.foodY=[0]
  self.eat = 0
  # to initialize the moving direction
  # move[] one element = one self.step
  self.snakeDirection = 'Right'
  self.snakeMove = [1, 0]
  self.moveSpeed = 510
  # to draw the game frame
  window = Tk()
  window.geometry("600x400+300+100")
  window.maxsize(600,400)
  window.minsize(600,400)
  window.title("Snake game")

  #create frames to contain different widgets
  self.frame1=Frame(window, borderwidth = 4, relief = RIDGE)
  self.frame2=Frame(window, bg = 'white', borderwidth = 2, relief = RAISED)
  self.canvas=Canvas(self.frame1, bg = 'yellow', width = 600, height = 360)
  self.score_label=Label(self.frame2, height = 40)

  #pack widgets
  self.frame1.pack()
  self.frame2.pack(fill=BOTH)
  self.score_label.pack(side=LEFT)
  self.canvas.pack(fill=BOTH)

  #draw game scene
  self.draw_wall()
  self.draw_score()
  self.draw_food("eat", self.eat)
  self.draw_snake()

  #start game
  self.play()

  window.mainloop()

  "=== View Part ==="
  def draw_wall(self):
  """
  It creates a wall:
  (8,6) <- 575(38*15,9~583) -> (584,6)
  =
  345
  (23*15,7~351)
  =
  (8,352) (584,352)
  """
  self.canvas.create_rectangle(6, 4, 583, 355, outline = "blue", width=6)

  def draw_score(self):
  """
  It updates the score(+10) and then displays in the label.
  """
  self.score() # score model
  self.score_label.config(text = "Score: " \
  + str(self.gamescore) + "\tLevel: " + str(self.gamescore/100)) # score view

  def draw_food(self, mode="eat", eatno=-1):
  """
  It gets a random position for the food and draws it.\n
  mode "eat" is for when the snake eats a food.
  call it like draw_food("eat", 3).\n
  mode "new" is for when the snake upgrades.
  call it like draw_food("new")
  """
  if mode == "eat":
  self.canvas.delete("food"+str(eatno))
  self.foodX[eatno], self.foodY[eatno] = self.random_food() #food model
  self.canvas.create_rectangle(self.foodX[eatno],self.foodY[eatno],
  self.foodX[eatno]+self.step, self.foodY[eatno]+self.step,\
  fill="red" ,tags="food"+str(eatno)) #food view

  elif mode == "new":
  x, y= self.random_food()
  self.foodX.append(x)
  self.foodY.append(y)
  no = len(self.foodX)-1
  self.canvas.create_rectangle(self.foodX[no],self.foodY[no],
  self.foodX[no]+self.step, self.foodY[no]+self.step,\
  fill="red" ,tags="food"+str(no))

  def draw_snake(self, mode = "normal"):
  """
  It lets the snake go one step forward and then draws the snake.\n
  *I don't want to see the snake head going through the wall,
  so I don't allow drawing that.\n
  *To make changing directions quicker, I made this function
  support going back(mode = "back"), as the snack always go forward one more step before turning(
  the 'one more step' can be only observed when the snakeSpeed is very low, so
  with initial time interval smaller than 300(that's relatively fast,
  which is normally given in the template), you will not even notice it.
  I noticed it because I begin with a low speed.)
  """
  if mode == "back":
  x,y=self.snake("back") # snake model
  #we don't need to re-draw it, just correcting the data is enough
  else:
  self.canvas.delete("snake")
  x,y=self.snake() # snake model
  for i in range(len(x)): # snake view
  if 9<=x[i]<=564 and 7<=y[i]<=351: #no going through!!
  self.canvas.create_rectangle(x[i],y[i],x[i]+self.step,y[i]+self.step, fill="orange",tags='snake')
  "=== Model Part ==="
  # food model
  def random_food(self):
  """
  It returns a randomized position which is not in the snake body.
  """
  while True:
  x = random.randrange(9,569,self.step)
  y = random.randrange(7,337,self.step)
  if not(x in self.snakeX and y in self.snakeY):
  #if this position isn't in the snake body
  return x, y

  # snake model
  def snake(self, mode = "normal"):
  """
  This function changes self.snakeX[] and self.snakeY[] for the next
  position, and returns this two list. \n
  As is explained in draw_snake(), we need to add mode"normal" to make
  the reactions on turnings faster.
  """
  length = len(self.snakeX)
  if mode == "back":
  for i in range(length-1, 0, -1):
  self.snakeX[i] = self.snakeX[i-1]
  self.snakeY[i] = self.snakeY[i-1]
  self.snakeX[0] -= self.snakeMove[0] * self.step
  self.snakeY[0] -= self.snakeMove[1] * self.step
  elif mode == "normal":
  for i in range(0, length-1):
  self.snakeX[i] = self.snakeX[i+1]
  self.snakeY[i] = self.snakeY[i+1]
  self.snakeX[length-1] += self.snakeMove[0] * self.step
  self.snakeY[length-1] += self.snakeMove[1] * self.step
  return self.snakeX, self.snakeY
  #score model
  def score(self):
  """
  This function runs when the snake gets score(eats food) and also
  """
  level = self.gamescore / 100
  if level < 0:
  level = 0
  self.gamescore += 10
  if self.gamescore / 100 > level:
  #if upgrade
  #change speed
  self.moveSpeed -= 10
  if self.moveSpeed <= 0:
  #make sure speed > 0
  self.moveSpeed = 10
  #add food
  self.draw_food("new")

  "=== Control Part ==="
  def iseated(self):
  #if self.snakeX[len(self.snakeX)-1]+self.snakeMove[0]*self.step == self.foodx and self.snakeY[len(self.snakeY)-1]+self.snakeMove[1]*self.step == self.foody:
  for i in range(0, len(self.foodX)):
  if self.snakeX[len(self.snakeX)-1] == self.foodX[i] and self.snakeY[len(self.snakeY)-1] == self.foodY[i]:
  self.eat = i
  return True
  return False

  def isdead(self):
  """
  If the snake eats itself or hits the wall, return True\n
  else return False
  """
  l = len(self.snakeX)

  if self.snakeX[l-1] < 9 or self.snakeX[l-1] > 564 or self.snakeY[l-1] < 7 or self.snakeY[l-1] > 351:
  return True
  for i in range(0, l-1):
  if self.snakeX[l-1] == self.snakeX[i] and self.snakeY[l-1] == self.snakeY[i]:
  return True
  return False
  def move(self, event):
  #avoid senseless direction changing
  u=(event.keysym == 'Up' or event.keysym == 'w') and self.snakeDirection != 'Up' and self.snakeDirection != 'Down'
  d=(event.keysym == 'Down' or event.keysym == 's') and self.snakeDirection != 'Up' and self.snakeDirection != 'Down'
  l=(event.keysym == 'Left' or event.keysym == 'a') and self.snakeDirection != 'Left' and self.snakeDirection != 'Right'
  r=(event.keysym == 'Right' or event.keysym == 'd') and self.snakeDirection != 'Left' and self.snakeDirection != 'Right'
  #avoid unreasonable movements
  if u or d or l or r:
  if u:
  self.draw_snake("back")
  self.snakeDirection = 'Up'
  self.snakeMove = [0, -1]

  if d:
  self.draw_snake("back")
  self.snakeDirection = 'Down'
  self.snakeMove = [0, 1]

  if l:
  self.draw_snake("back")
  self.snakeDirection = 'Left'
  self.snakeMove = [-1, 0]

  if r:
  self.draw_snake("back")
  self.snakeDirection = 'Right'
  self.snakeMove = [1, 0]
  self.canvas.unbind("<Key>")
  '''
  Because I've made the reaction on turnings so fast,
  I have to use an extra iseated() to eat food directly when turning.
  Otherwise, things will be that when a food is beside your head(but not in
  your direction), you press a key to turn to eat it,
  but you just end up going through it, leaving it where it was!!
  snake snake snake snake => snake snake , not snake snake
  food snake food(also snake of course)
  snake snake
  '''
  if self.iseated():
  self.snakeX.append(self.foodx)
  self.snakeY.append(self.foody)
  self.draw_food("eat", self.eat)
  self.draw_score()
  self.draw_snake()
  self.canvas.update()

  def play(self):

  self.canvas.config(highlightthickness = 0)
  self.canvas.focus_set()
  while not self.isdead():
  self.canvas.bind("<Key>", self.move)
  if self.iseated():
  self.snakeX.append(self.foodX[self.eat])
  self.snakeY.append(self.foodY[self.eat])
  self.draw_food("eat", self.eat)
  self.draw_score()
  self.draw_snake("back")#if not, the snake will go forward two steps
  self.draw_snake()
  self.canvas.update()
  self.canvas.after(self.moveSpeed)
  self.gameover()

  def gameover(self):
  self.canvas.unbind("<Key>")
  self.canvas.create_text(300,100,text = "Game Over! Score: {}, Level: {}.\
  \nPress ESC to quit.\nPress any other key to restart.".format(self.gamescore, self.gamescore/100), font = "Helvetica -30 bold")
  self.canvas.bind("<Key>", self.restart)
  def restart(self,event):
  if event.keysym == 'Escape':
  quit()
  else:
  self.canvas.delete(ALL)
  self.gamescore=-10
  # to initialize the snake in the range of (x1,y1,x2,y1)
  # the number should be calculated cautiously
  r=random.randrange(54, 336, step = 15)
  self.snakeX=[r, r+self.step, r+2*self.step]
  self.snakeY=[r-2, r-2, r-2]

  # to initialize food list
  self.foodX=[0]
  self.foodY=[0]
  self.eat = 0
  # to initialize the moving direction
  # move[] one element = one self.step
  self.snakeDirection = 'Right'
  self.snakeMove = [1, 0]
  self.moveSpeed = 510

  self.draw_wall()
  self.draw_score()
  self.draw_snake()
  self.draw_food("eat", self.eat)
  self.play()
SnakeGame()

下载:
Snake