Initial commit

This commit is contained in:
2022-10-10 00:19:56 +02:00
commit 0b32b08cb1
4 changed files with 214 additions and 0 deletions

132
game.py Normal file
View 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)