""" Snake Game """ import tkinter as tk import random #### GLOBAL VARIALBES #### SNAKE_COLOR, FOOD_COLOR= "light green", "red" BG_COLOR, TEXT_COLOR = "black", "white" WIDTH = 40 #Width of squares in pixels. FOOD_WIDTH = WIDTH * 3 / 4 #Width of food squares in pixels. SPEED = 100 #Milliseconds between redraw. CUSHION = 1 #Applied so that adjacent squares don't overlap. SCREEN = WIDTH * 30, WIDTH * 17 #Screen size in pixels. FONT = "ms 70" MAX_T = 1000 // SPEED * 5 #Max steps until food disappears. FOOD_PROB = 0.04 #Probability a new food appears at any given step. CONTROLS = LEFT, RIGHT, UP, DOWN = "Left", "Right", "Up", "Down" EXIT, CHEAT, RESTART = "Escape", "space", "Return" ############################################################################### def apply_cushion(rect_def): """ Adjust bounds of a rectangular definition. rect_def: tuple, (x, y, x+width, y+width). """ x0, y0, x1, y1 = rect_def return x0 + CUSHION, y0 + CUSHION, x1 - CUSHION, y1 - CUSHION class Square(object): """ Controls one section of the snake. canvas: tk.Canvas object to draw on. rect_def: tuple, (x, y, x+WIDTH, y+WIDTH). """ def __init__(self, canvas, rect_def): self.square = canvas.create_rectangle(*rect_def, fill=SNAKE_COLOR) self.can, self.rect_def = canvas, rect_def self.behind, self.dx, self.dy = False, 1, 0 def draw(self, rect_def): dx = rect_def[0] - self.rect_def[0] dy = rect_def[1] - self.rect_def[1] self.dx = dx if not dx else dx // abs(dx) self.dy = dy if not dy else dy // abs(dy) self.can.coords(self.square, *rect_def) self.rect_def = rect_def if self.behind: r = self.get_behind_rect_def() self.behind.draw(r) def add_behind(self): if self.behind: self.behind.add_behind() else: r = self.get_behind_rect_def() self.behind = Square(self.can, r) def get_behind_rect_def(self): x0, y0, x1, y1 = self.rect_def r = ( x0 - self.dx * WIDTH, y0 - self.dy * WIDTH, x1 - self.dx * WIDTH, y1 - self.dy * WIDTH ) return r def get(self): return self.square def get_overlapping(self): return self.can.find_overlapping(*apply_cushion(self.rect_def)) def intersecting(self, overlapping): if self.behind: return any(( self.behind.get() in overlapping, self.behind.intersecting(overlapping) )) else: return False class Snake(object): """ Controls the movement of the snake. canvas: tk.Canvas to draw on. """ def __init__(self, canvas): self.x, self.y = 0, 0 r = self.x, self.y, self.x + WIDTH, self.y + WIDTH self.head = Square(canvas, r) self.direction = 1, 0 def update(self): self.x += WIDTH * self.direction[0] self.y += WIDTH * self.direction[1] r = self.x, self.y, self.x + WIDTH, self.y + WIDTH self.head.draw(r) def add_behind(self): self.head.add_behind() def get_overlapping(self): return self.head.get_overlapping() def is_valid(self): return all(( not self.head.intersecting(self.get_overlapping()), self.x >= 0, self.x + WIDTH <= SCREEN[0], self.y >= 0, self.y + WIDTH <= SCREEN[1] )) def change_direction(self, direction): """ direction: LEFT, RIGHT, UP, or DOWN. """ if direction == LEFT: self.direction = -1, 0 elif direction == RIGHT: self.direction = 1, 0 elif direction == UP: self.direction = 0, -1 elif direction == DOWN: self.direction = 0, 1 class Food(object): """ Stores information about the food on the screen. canvas: tk.Canvas object to draw on. rect_def: tuple, (x, y, x+w, y+w) """ def __init__(self, canvas, rect_def): self.food = canvas.create_rectangle(*rect_def, fill=FOOD_COLOR) self.can, self.t = canvas, 0 def update(self): self.t += 1 def is_well(self): return self.t < MAX_T def destroy(self): self.can.delete(self.food) def get(self): return self.food class Game(tk.Frame): """ Controls the game animation. master: tk.Tk window. """ def __init__(self, master): super().__init__(master) self.can = tk.Canvas(self, width=SCREEN[0], height=SCREEN[1], bg=BG_COLOR ) self.can.pack() self.snake, self.food = Snake(self.can), [] for key in CONTROLS: master.bind("<%s>" % key, lambda e: self.snake.change_direction(e.keysym) ) master.bind("<%s>" % CHEAT, lambda e: self.snake.add_behind()) self.master, self.score = master, 1 def next(self): self.snake.update() if random.random() < FOOD_PROB: self.add_food() overlapping = self.snake.get_overlapping() i = 0 while i < len(self.food): f = self.food[i] f.update() if f.get() in overlapping: self.snake.add_behind() f.destroy() self.food.pop(i) self.score += 1 elif not f.is_well(): f.destroy() self.food.pop(i) else: i += 1 self.master.title("Snake: %d" % self.score) if self.snake.is_valid(): self.after(SPEED, self.next) else: self.can.create_text(SCREEN[0] // 2, SCREEN[1] // 2, fill=TEXT_COLOR, font=FONT, text="Game Over: %d" % self.score ) def add_food(self): x = random.randint(0, SCREEN[0] - FOOD_WIDTH) y = random.randint(0, SCREEN[1] - FOOD_WIDTH) r = x, y, x + FOOD_WIDTH, y + FOOD_WIDTH if not self.can.find_overlapping(*r): self.food.append(Food(self.can, r)) else: self.add_food() def restart(self): self.destroy() self.__init__(self.master) self.pack() self.next() def main(): root = tk.Tk() root.title("Snake") root.resizable(0, 0) frame = Game(root) frame.pack() root.bind("<%s>" % RESTART, lambda e: frame.restart()) root.bind("<%s>" % EXIT, lambda e: root.destroy()) #root.eval('tk::PlaceWindow %s center'%root.winfo_pathname(root.winfo_id())) frame.next() root.mainloop() if __name__ == "__main__": main()