fix(game): no more duplicates

This commit is contained in:
Kristofers Solo 2024-01-01 00:04:47 +02:00
parent 276ed26aee
commit 54787e727b
4 changed files with 55 additions and 52 deletions

View File

@ -12,7 +12,7 @@ from .utils import Direction, grid_pos
class Block(pygame.sprite.Sprite): 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""" """Initialize a block"""
super().__init__() super().__init__()
self.image = pygame.Surface((Config.BLOCK_SIZE, Config.BLOCK_SIZE)) 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.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.font = pygame.font.SysFont(Config.FONT_FAMILY, Config.FONT_SIZE)
self.group = group
self.update() self.update()
def _draw_value(self) -> None: def _draw_value(self) -> None:
@ -46,7 +47,9 @@ class Block(pygame.sprite.Sprite):
else: else:
return return
self.group.remove(self)
self.rect.topleft = new_x, new_y self.rect.topleft = new_x, new_y
self.group.add(self)
def _calc_new_pos(self, direction: Direction) -> tuple[int, int]: def _calc_new_pos(self, direction: Direction) -> tuple[int, int]:
"""Calculate the new position of the block.""" """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: def _has_collision(self, x: int, y: int) -> bool:
"""Checks whether the block has a collision with any other block.""" """Checks whether the block has a collision with any other block."""
groups = self.groups() return any(block.rect.collidepoint(x, y) for block in self.group if block != self)
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)
def _get_collided_block(self, x: int, y: int) -> Union["Block", None]: def _get_collided_block(self, x: int, y: int) -> Union["Block", None]:
"""Get the block that collides with the given block.""" """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: def _merge(self, other: "Block") -> None:
"""Merge the block with another block.""" """Merge the block with another block."""
self.group.remove(other)
self.group.remove(self)
self.value += other.value self.value += other.value
self.update() self.update()
logger.debug(f"Merging block({id(self)}) with block({id(other)})") logger.debug(f"Merging block({id(self)}) with block({id(other)})")
self.groups()[0].remove(other) self.group.add(self)
def update(self) -> None: def update(self) -> None:
"""Update the block""" """Update the block"""
@ -100,7 +102,7 @@ class Block(pygame.sprite.Sprite):
def __repr__(self) -> str: def __repr__(self) -> str:
"""Return a string representation of the block""" """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: def __str__(self) -> str:
"""Return a string representation of the block""" """Return a string representation of the block"""

42
src/py2048/board.py Normal file
View File

@ -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

View File

@ -3,9 +3,10 @@ import sys
import pygame import pygame
from loguru import logger from loguru import logger
from .board import Board
from .colors import COLORS from .colors import COLORS
from .config import Config from .config import Config
from .grid import Grid
from .logger import setup_logger from .logger import setup_logger
from .utils import Direction from .utils import Direction
@ -18,7 +19,7 @@ class Game:
pygame.init() pygame.init()
self.screen: pygame.Surface = pygame.display.set_mode((Config.WIDTH, Config.HEIGHT)) self.screen: pygame.Surface = pygame.display.set_mode((Config.WIDTH, Config.HEIGHT))
pygame.display.set_caption("2048") pygame.display.set_caption("2048")
self.blocks = Grid() self.blocks = Board()
self.blocks.generate_block(Config.INITIAL_BLOCK_COUNT) self.blocks.generate_block(Config.INITIAL_BLOCK_COUNT)
# self.blocks.generate_block(2, (1, 1), (1, 3)) # self.blocks.generate_block(2, (1, 1), (1, 3))

View File

@ -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