2048/src/py2048/board.py
2024-01-01 20:22:56 +02:00

85 lines
2.7 KiB
Python

import random
import pygame
from loguru import logger
from .block import Block
from .color import Color
from .config import Config
from .utils import Direction
class Board(pygame.sprite.Group):
def __init__(self, screen: pygame.Surface):
super().__init__()
self.screen = screen
self.generate_initial_blocks()
def move(self, direction: Direction):
blocks = self.sprites()
block: Block
match direction:
case Direction.UP:
blocks.sort(key=lambda block: block.rect.y)
case Direction.DOWN:
blocks.sort(key=lambda block: block.rect.y, reverse=True)
case Direction.LEFT:
blocks.sort(key=lambda block: block.rect.x)
case Direction.RIGHT:
blocks.sort(key=lambda block: block.rect.x, reverse=True)
for block in blocks:
block.move(direction)
if not self._is_full():
self.generate_random_block()
def generate_initial_blocks(self) -> None:
"""Generate the initial blocks."""
self.generate_block(Config.INITIAL_BLOCK_COUNT)
def generate_block(self, amount: int = 1, *pos: tuple[int, int]) -> None:
"""Generate `amount` number of blocks or at the specified positions."""
if pos:
for coords in pos:
x, y = coords[0] * Config.BLOCK_SIZE, coords[1] * Config.BLOCK_SIZE
self.add(Block(x, y, self))
return
for _ in range(amount):
self.generate_random_block()
def generate_random_block(self) -> None:
"""Generate a block with random coordinates aligned with the grid."""
while True:
# Generate random coordinates aligned with the grid
x = random.randint(0, 3) * Config.BLOCK_SIZE
y = random.randint(0, 3) * Config.BLOCK_SIZE
block = Block(x, y, self)
colliding_blocks = pygame.sprite.spritecollide(
block, self, False
) # check for collisions
if not colliding_blocks:
self.add(block)
logger.debug(f"Created block at {block.pos}")
break
def _is_full(self) -> bool:
"""Check if the board is full."""
return len(self.sprites()) == Config.GRID_SIZE**2
def _can_move(self) -> bool:
"""Check if any movement is possible on the board."""
block: Block
for block in self.sprites():
if block.can_move():
return True
return False
def is_game_over(self) -> bool:
"""Check if the game is over."""
return self._is_full() and not self._can_move()