diff --git a/src/py2048/block.py b/src/py2048/block.py index 3517e6b..824fd4b 100644 --- a/src/py2048/block.py +++ b/src/py2048/block.py @@ -12,7 +12,7 @@ from .utils import Direction, grid_pos class Block(pygame.sprite.Sprite): - def __init__(self, x: int, y: int, value: int | None = 2): + def __init__(self, x: int, y: int, group: pygame.sprite.Group, value: int | None = 2): """Initialize a block""" super().__init__() self.image = pygame.Surface((Config.BLOCK_SIZE, Config.BLOCK_SIZE)) @@ -21,6 +21,7 @@ class Block(pygame.sprite.Sprite): self.value: int = value if value is not None else 2 if random.random() <= Config.BLOCK_VALUE_PROBABILITY else 4 self.font = pygame.font.SysFont(Config.FONT_FAMILY, Config.FONT_SIZE) + self.group = group self.update() def _draw_value(self) -> None: @@ -46,7 +47,9 @@ class Block(pygame.sprite.Sprite): else: return + self.group.remove(self) self.rect.topleft = new_x, new_y + self.group.add(self) def _calc_new_pos(self, direction: Direction) -> tuple[int, int]: """Calculate the new position of the block.""" @@ -59,22 +62,21 @@ class Block(pygame.sprite.Sprite): def _has_collision(self, x: int, y: int) -> bool: """Checks whether the block has a collision with any other block.""" - groups = self.groups() - if not groups or not groups[0]: - return False - return any(block.rect.collidepoint(x, y) for block in self.groups()[0] if block != self) + return any(block.rect.collidepoint(x, y) for block in self.group if block != self) def _get_collided_block(self, x: int, y: int) -> Union["Block", None]: """Get the block that collides with the given block.""" - return next((block for block in self.groups()[0] if block != self and block.rect.collidepoint(x, y)), None) + return next((block for block in self.group if block != self and block.rect.collidepoint(x, y)), None) def _merge(self, other: "Block") -> None: """Merge the block with another block.""" + self.group.remove(other) + self.group.remove(self) self.value += other.value self.update() logger.debug(f"Merging block({id(self)}) with block({id(other)})") - self.groups()[0].remove(other) + self.group.add(self) def update(self) -> None: """Update the block""" @@ -100,7 +102,7 @@ class Block(pygame.sprite.Sprite): def __repr__(self) -> str: """Return a string representation of the block""" - return f"Block({id(self)}): ({self.pos()})" + return f"Block({id(self)}): {self.pos()} num={self.value}" def __str__(self) -> str: """Return a string representation of the block""" diff --git a/src/py2048/board.py b/src/py2048/board.py new file mode 100644 index 0000000..bfe0602 --- /dev/null +++ b/src/py2048/board.py @@ -0,0 +1,42 @@ +import random + +import pygame + +from .block import Block +from .config import Config +from .utils import Direction + + +class Board(pygame.sprite.Group): + def move(self, direction: Direction): + blocks = self.sprites() + block: Block + + if direction in {Direction.DOWN, Direction.RIGHT}: + blocks.sort(key=lambda block: (block.rect.x, block.rect.y), reverse=True) + + for block in blocks: + block.move(direction) + + self.generate_block() + + def generate_block(self, amount: int = 1, *pos: tuple[int, int]) -> None: + """Generate `amount` number of blocks.""" + 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): + 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) + break diff --git a/src/py2048/game.py b/src/py2048/game.py index f0fcd5b..211acf5 100644 --- a/src/py2048/game.py +++ b/src/py2048/game.py @@ -3,9 +3,10 @@ import sys import pygame from loguru import logger +from .board import Board + from .colors import COLORS from .config import Config -from .grid import Grid from .logger import setup_logger from .utils import Direction @@ -18,7 +19,7 @@ class Game: pygame.init() self.screen: pygame.Surface = pygame.display.set_mode((Config.WIDTH, Config.HEIGHT)) pygame.display.set_caption("2048") - self.blocks = Grid() + self.blocks = Board() self.blocks.generate_block(Config.INITIAL_BLOCK_COUNT) # self.blocks.generate_block(2, (1, 1), (1, 3)) diff --git a/src/py2048/grid.py b/src/py2048/grid.py deleted file mode 100644 index 27412d8..0000000 --- a/src/py2048/grid.py +++ /dev/null @@ -1,42 +0,0 @@ -import random - -import pygame - -from .block import Block -from .config import Config -from .utils import Direction - - -class Grid(pygame.sprite.Group): - def move(self, direction: Direction): - blocks = list(self.sprites()) - - match direction: - case Direction.DOWN: - blocks.sort(key=lambda block: block.rect.y, reverse=True) - case Direction.RIGHT: - blocks.sort(key=lambda block: block.rect.x, reverse=True) - - for block in blocks: - block: Block - block.move(direction) - - self.generate_block() - - def generate_block(self, amount: int = 1, *pos: tuple[int, int]) -> None: - """Generate `amount` number of blocks.""" - if pos: - for coords in pos: - self.add(Block(coords[0] * Config.BLOCK_SIZE, coords[1] * Config.BLOCK_SIZE)) - return - for _ in range(amount): - while True: - x = random.randint(0, 3) * Config.BLOCK_SIZE # random column position - y = random.randint(0, 3) * Config.BLOCK_SIZE # random row position - block = Block(x, y) - - colliding_blocks = pygame.sprite.spritecollide(block, self, False) # check collision - - if not colliding_blocks: - self.add(block) - break