Initial commit
This commit is contained in:
132
game.py
Normal file
132
game.py
Normal file
@ -0,0 +1,132 @@
|
||||
import sys
|
||||
|
||||
from random import randint
|
||||
from tkinter import Canvas, Tk
|
||||
from PIL import Image, ImageTk
|
||||
|
||||
from directions import Direction
|
||||
from snake import Block, Snake
|
||||
|
||||
ILLEGAL_DIRECTION_CHANGES = (
|
||||
set((Direction.NORTH, Direction.SOUTH)),
|
||||
set((Direction.EAST, Direction.WEST)),
|
||||
)
|
||||
|
||||
|
||||
class Game:
|
||||
def __init__(self, width: int, height: int, blocksize: int = 10):
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.blocksize = blocksize
|
||||
|
||||
# Window
|
||||
self._root = Tk()
|
||||
self._canvas = Canvas(
|
||||
self._root, width=width * blocksize, height=height * blocksize, bg="white"
|
||||
)
|
||||
self._canvas.pack()
|
||||
self._bitmap_tk = None
|
||||
|
||||
# Register inputs
|
||||
self._root.bind("<Left>", lambda e: self._key_event(Direction.WEST))
|
||||
self._root.bind("<Up>", lambda e: self._key_event(Direction.NORTH))
|
||||
self._root.bind("<Right>", lambda e: self._key_event(Direction.EAST))
|
||||
self._root.bind("<Down>", lambda e: self._key_event(Direction.SOUTH))
|
||||
|
||||
# Number of milliseconds between updates
|
||||
self._update_delta = 125
|
||||
|
||||
# Game state
|
||||
self.snake = Snake(width // 2, height // 2)
|
||||
self.direction = Direction.WEST
|
||||
self._queued_direction = None
|
||||
self.food = self._spawn_food()
|
||||
self.score = 0
|
||||
|
||||
# Helper functions
|
||||
def _key_event(self, direction: Direction) -> None:
|
||||
"""
|
||||
Queues the given direction.
|
||||
"""
|
||||
self._queued_direction = direction
|
||||
|
||||
# Game functions
|
||||
def _oob(self, block: int) -> bool:
|
||||
"""
|
||||
Returns true if the given block is out of bounds.
|
||||
"""
|
||||
return not (0 <= block.x < self.width and 0 <= block.y < self.height)
|
||||
|
||||
def _legal_direction_change(self) -> bool:
|
||||
"""
|
||||
Returns true if the queued direction is a valid direction change (not 180 degrees).
|
||||
"""
|
||||
s = set((self.direction, self._queued_direction))
|
||||
return not s in ILLEGAL_DIRECTION_CHANGES
|
||||
|
||||
def _spawn_food(self) -> Block:
|
||||
"""
|
||||
Returns a random block inside the current game window.
|
||||
"""
|
||||
for _ in range(100):
|
||||
food = Block(randint(0, self.width - 1), randint(0, self.height - 1))
|
||||
if food not in self.snake:
|
||||
break
|
||||
return food
|
||||
|
||||
# Game control
|
||||
def start(self, ticks: int = 8) -> None:
|
||||
"""
|
||||
Starts the game.
|
||||
"""
|
||||
self._update_delta = 1000 // ticks
|
||||
self._root.after(self._update_delta, self.tick)
|
||||
self._root.mainloop()
|
||||
|
||||
def draw(self) -> Image:
|
||||
"""
|
||||
Draws the current state onto an Image.
|
||||
"""
|
||||
bitmap = Image.new("RGB", size=(self.width, self.height), color=(255, 255, 255))
|
||||
for block in self.snake.body:
|
||||
bitmap.putpixel(block.to_tuple(), (0, 0, 0))
|
||||
bitmap.putpixel(self.food.to_tuple(), (255, 233, 124))
|
||||
return bitmap
|
||||
|
||||
def tick(self) -> None:
|
||||
"""
|
||||
Executes all game actions and updates the window.
|
||||
"""
|
||||
|
||||
# Update direction if possible
|
||||
if self._queued_direction is not None and self._legal_direction_change():
|
||||
self.direction = self._queued_direction
|
||||
self._queued_direction = None
|
||||
|
||||
# Check if move is allowed
|
||||
head = self.snake.preview(self.direction)
|
||||
if self._oob(head):
|
||||
sys.exit("Out ouf bounds!")
|
||||
if head in self.snake:
|
||||
sys.exit("Eaten itself!")
|
||||
fed = head == self.food
|
||||
|
||||
# Move and let snake eat
|
||||
self.snake.move(self.direction, extend=fed)
|
||||
if fed:
|
||||
self.food = self._spawn_food()
|
||||
self.score += 1
|
||||
|
||||
# Check if game is won
|
||||
if self.score == (self.width * self.height) - 1:
|
||||
sys.exit("You win!")
|
||||
|
||||
# Update image
|
||||
bitmap = self.draw().resize(
|
||||
(self.width * self.blocksize, self.height * self.blocksize),
|
||||
resample=Image.NEAREST,
|
||||
)
|
||||
self._bitmap_tk = ImageTk.PhotoImage(bitmap)
|
||||
self._canvas.create_image((0, 0), anchor="nw", image=self._bitmap_tk)
|
||||
|
||||
self._root.after(self._update_delta, self.tick)
|
||||
Reference in New Issue
Block a user