在研究BlockBlast,发现确实很难,越研究越难。难在哪里,看起来很简单的游戏。但是其实有6、7万种的方块组合,放在8x8的地图上又有多种方式,合计组合+位置其实是个天文数字,达到上亿种组合放置的方法。
很难做,现在才知道了,但是我会迎难而上,争取做好。难点主要在时间花费上,组合太大太大了,核心在与怎么减少计算的时间。

BlockBlast的部分算法

import pygame
import random
import sys
from typing import List, Tuple, Optional

# Dev Constants
DEBUG = True

# Constants
SCREEN_SIZE = (720, 1280)
GRID_SIZE = 8
GRID_LINE_WIDTH = 3
BLOCK_SIZE = (SCREEN_SIZE[0] // (GRID_SIZE + 2)) - 1  # Slightly smaller to fit next pieces
NEXT_PIECES_COUNT = 3

# Colors
GRID_LINE_COLOR = (30, 37, 72)
BACKGROUND_COLOR = (39, 42, 83)
SCORE_COLOR = (255, 255, 255)

# Initialize Pygame
pygame.init()
FONT = pygame.font.Font("./Assets/Lato-Black.ttf", 90)
UI_FONT = pygame.font.Font("./Assets/Lato-Black.ttf", 30)

# Load block textures
BLOCK_TEXTURE = {
    "Blue": pygame.image.load("Assets/Blocks/Blue.png"),
    "Green": pygame.image.load("Assets/Blocks/Green.png"),
    "LightBlue": pygame.image.load("Assets/Blocks/LightBlue.png"),
    "Orange": pygame.image.load("Assets/Blocks/Orange.png"),
    "Purple": pygame.image.load("Assets/Blocks/Purple.png"),
    "Red": pygame.image.load("Assets/Blocks/Red.png"),
    "Yellow": pygame.image.load("Assets/Blocks/Yellow.png"),
}

BLOCK_SIZE_SMALL = (BLOCK_SIZE // 1.75)
BLOCK_TEXTURE_SMALL = {
    "Blue": pygame.transform.scale(BLOCK_TEXTURE["Blue"], (BLOCK_SIZE_SMALL, BLOCK_SIZE_SMALL)),
    "Green": pygame.transform.scale(BLOCK_TEXTURE["Green"], (BLOCK_SIZE_SMALL, BLOCK_SIZE_SMALL)),
    "LightBlue": pygame.transform.scale(BLOCK_TEXTURE["LightBlue"], (BLOCK_SIZE_SMALL, BLOCK_SIZE_SMALL)),
    "Orange": pygame.transform.scale(BLOCK_TEXTURE["Orange"], (BLOCK_SIZE_SMALL, BLOCK_SIZE_SMALL)),
    "Purple": pygame.transform.scale(BLOCK_TEXTURE["Purple"], (BLOCK_SIZE_SMALL, BLOCK_SIZE_SMALL)),
    "Red": pygame.transform.scale(BLOCK_TEXTURE["Red"], (BLOCK_SIZE_SMALL, BLOCK_SIZE_SMALL)),
    "Yellow": pygame.transform.scale(BLOCK_TEXTURE["Yellow"], (BLOCK_SIZE_SMALL, BLOCK_SIZE_SMALL)),
}

# Base block shapes
BASE_BLOCKS = [
    [[1, 1]],  # 2x1 block
    [[1, 1, 1]],  # 3x1 block
    [[1, 1, 1, 1]],  # 4x1 block
    [[1, 1, 1, 1, 1]],  # 5x1 block
    [[1, 1], [1, 1]],  # 2x2 block
    [[1, 1, 1, 1], [1, 1, 1, 1]],  # 4x2 block
    [[1, 1, 1], [1, 1, 1], [1, 1, 1]],  # 3x3 block
    [[1, 1, 1], [1, 0, 0], [1, 0, 0]],  # 3x3 - 2x2 block
    [[1, 1], [1, 0]],  # 2x2 - 2x1 block
    [[1, 1, 1], [1, 0, 0]],  # Tetris L block
    [[1, 1, 1], [0, 0, 1]],  # Tetris J block
    [[1, 1, 1], [0, 1, 0]],  # Tetris T block
    [[1, 1], [0, 1], [0, 1]],  # Tetris S block
    [[1, 1], [1, 0], [1, 0]],  # Tetris Z block
    [[1, 0], [0, 1]], # Diagonal 2x2 block
    [[1, 0, 0], [0, 1, 0], [0, 0, 1]], # Diagonal 3x3 block
]


class TextBox:
    def __init__(self, x, y, w, h, text=''):
        self.rect = pygame.Rect(x, y, w, h)
        self.color = (255, 255, 255)
        self.text = text
        self.font = pygame.font.Font(None, 32)
        self.txt_surface = self.font.render(text, True, self.color)
        self.active = False

    def handle_event(self, event):
        if event.type == pygame.MOUSEBUTTONDOWN:
            # If the user clicked on the input_box rect.
            if self.rect.collidepoint(event.pos):
                # Toggle the active variable.
                self.active = not self.active
            else:
                self.active = False
            # Change the current color of the input box.
            self.color = (255, 0, 0) if self.active else (255, 255, 255)
        if event.type == pygame.KEYDOWN:
            if self.active:
                if event.key == pygame.K_RETURN:
                    print(self.text)
                    self.text = ''
                elif event.key == pygame.K_BACKSPACE:
                    self.text = self.text[:-1]
                else:
                    self.text += event.unicode
                # Re-render the text.
                self.txt_surface = self.font.render(self.text, True, self.color)

    def update(self):
        # Resize the box if the text is too long.
        width = max(200, self.txt_surface.get_width()+10)
        self.rect.w = width

    def draw(self, screen):
        # Blit the text.
        screen.blit(self.txt_surface, (self.rect.x+5, self.rect.y+5))
        # Blit the rect.
        pygame.draw.rect(screen, self.color, self.rect, 2)


class Block:
    def __init__(self, shape: List[List[int]], color: str):
        """Initialize a new block with a given shape and color."""
        self.shape = shape
        self.color = color

        # Randomly rotate the block
        for _ in range(random.randint(0, 3)):
            self.rotate()

    def rotate(self):
        """Rotate the block 90 degrees clockwise."""
        self.shape = [list(reversed(col)) for col in zip(*self.shape)]

    def draw(self, x: int, y: int, small=False, given_screen=None):
        """Draw the block on the center of the given coordinates."""
        block_size = BLOCK_SIZE_SMALL if small else BLOCK_SIZE
        block_texture = BLOCK_TEXTURE_SMALL[self.color] if small else BLOCK_TEXTURE[self.color]

        # Make a container for the blocks
        block_surface = pygame.Surface((len(self.shape[0]) * block_size, len(self.shape) * block_size), pygame.SRCALPHA)

        # Add a red background for debugging
        if (DEBUG):
            block_surface.fill((255, 0, 0, 100))

        # Draw the blocks onto the container
        for i in range(len(self.shape)):
            for j in range(len(self.shape[i])):
                if self.shape[i][j] == 1:
                    block_surface.blit(block_texture, (j * block_size, i * block_size))

        # Draw the container onto the screen
        which_screen = self.screen if given_screen is None else given_screen

        block_surface_rect = block_surface.get_rect(center=(x, y))
        which_screen.blit(block_surface, block_surface_rect)

class GridBlock:
    def __init__(self, color: str, pos: Tuple[int, int]):
        """Initialize a new grid block with a given color."""
        self.color = color
        self.pos = pos

    def draw(self, x: int, y: int, screen: pygame.Surface):
        """Draw the block at given coordinates."""
        screen.blit(BLOCK_TEXTURE[self.color], (4 + x - BLOCK_SIZE // 2, 4 + y - BLOCK_SIZE // 2))

class Grid:
    def __init__(self):
        """Initialize a new empty grid."""
        self.grid: List[List[Optional[GridBlock]]] = [[None for _ in range(GRID_SIZE)] for _ in range(GRID_SIZE)]

    def draw(self, surface):
        """Draw the grid onto the given surface."""
        grid_width = BLOCK_SIZE * GRID_SIZE
        grid_height = grid_width
        grid_x = (SCREEN_SIZE[0] - grid_width) // 2
        grid_y = (SCREEN_SIZE[1] - grid_height) // 2

        pygame.draw.rect(surface, BACKGROUND_COLOR, (grid_x, grid_y, grid_width, grid_height))

        # Draw the grid lines
        for i in range(GRID_SIZE + 1):
            pygame.draw.line(surface, GRID_LINE_COLOR,
                             (grid_x, grid_y + i * BLOCK_SIZE),
                             (grid_x + grid_width, grid_y + i * BLOCK_SIZE), GRID_LINE_WIDTH)
            pygame.draw.line(surface, GRID_LINE_COLOR,
                             (grid_x + i * BLOCK_SIZE, grid_y),
                             (grid_x + i * BLOCK_SIZE, grid_y + grid_height), GRID_LINE_WIDTH)

        # Draw the blocks
        for i in range(GRID_SIZE):
            for j in range(GRID_SIZE):
                if self.grid[i][j] is not None:
                    self.grid[i][j].draw(grid_x + (j + 0.5) * BLOCK_SIZE, grid_y + (i + 0.5) * BLOCK_SIZE, surface)


class Game:
    def __init__(self):
        """Initialize the game, the screen, and the game clock."""
        self.screen = pygame.display.set_mode(SCREEN_SIZE)
        pygame.display.set_caption("Block Blast! ML")
        self.clock = pygame.time.Clock()
        self.grid = Grid()
        self.score = 0
        self.next_pieces: List[Optional[Block]] = [None] * NEXT_PIECES_COUNT

        self.input_boxes = [TextBox(100, 100, 140, 32),
                            TextBox(100, 150, 140, 32),
                            TextBox(100, 200, 140, 32)]

    def spawn_piece(self) -> Block:
        """Spawn a new block with a random shape and color."""
        shape = random.choice(BASE_BLOCKS)
        color = random.choice(list(BLOCK_TEXTURE.keys()))
        return Block(shape, color)

    def check_game_over(self) -> bool:
        """Check if any of the next pieces can be placed on the grid."""
        for piece in self.next_pieces:
            if piece is None:
                continue
            for y in range(GRID_SIZE):
                for x in range(GRID_SIZE):
                    if self.can_place_piece(piece, x, y):
                        return False
        return True

    def run(self):
        """Run the main game loop."""
        running = True
        while running:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    running = False
                for box in self.input_boxes:
                    box.handle_event(event)

            if all(box.text for box in self.input_boxes):  # Check if all boxes have text
                try:
                    idx = int(self.input_boxes[0].text)
                    x = int(self.input_boxes[1].text)
                    y = int(self.input_boxes[2].text)
                    self.place_piece(idx-1, x-1, y-1)  # Place the piece
                    for box in self.input_boxes:  # Clear the text boxes
                        box.text = ''
                        box.txt_surface = box.font.render(box.text, True, box.color)
                except ValueError:
                    pass  # Handle invalid input


            self.manage_next_pieces()

            # Check for any lines cleared
            self.clear_lines()

            # Check for game over
            if self.check_game_over():
                print("Game Over!")
                running = False

            self.draw()

            pygame.display.update()
            self.clock.tick(60)  # Cap the framerate to 60 FPS
        pygame.quit()
        sys.exit()

    def clear_lines(self):
        """Check if any lines have been cleared."""
        row_index_to_clear = []
        col_index_to_clear = []

        # Check for rows to clear
        for i in range(GRID_SIZE):
            row = self.grid.grid[i]
            if all(block is not None for block in row):
                row_index_to_clear.append(i)

        # Check for columns to clear
        for j in range(GRID_SIZE):
            col = [self.grid.grid[i][j] for i in range(GRID_SIZE)]
            if all(block is not None for block in col):
                col_index_to_clear.append(j)

        # Clear the rows first since they are easier to clear
        for i in row_index_to_clear:
            self.grid.grid[i] = [None for _ in range(GRID_SIZE)]

        # Clear the columns
        for j in col_index_to_clear:
            for i in range(GRID_SIZE):
                self.grid.grid[i][j] = None

        # Add the score
        self.score += len(row_index_to_clear) * 100 + len(col_index_to_clear) * 100

    def draw(self):
        """Draw the game onto the screen."""
        self.screen.fill((65, 83, 146))
        self.grid.draw(self.screen)
        self.draw_score()
        self.draw_next_pieces()

        for box in self.input_boxes:
            box.draw(self.screen)

        pygame.display.update()

    def draw_score(self):
        """Draw the current score on the screen."""
        score_text = FONT.render(f"{self.score}", True, SCORE_COLOR)
        score_text_rect = score_text.get_rect(center=(SCREEN_SIZE[0] // 2, SCREEN_SIZE[1] * 0.2))
        self.screen.blit(score_text, score_text_rect)

    def manage_next_pieces(self):
        """Manage the next pieces, spawning new ones if necessary."""
        # Only spawn new pieces if all pieces are None
        if all(piece is None for piece in self.next_pieces):
            for i in range(NEXT_PIECES_COUNT):
                self.next_pieces[i] = self.spawn_piece()

    def draw_next_pieces(self):
        """Draw the next pieces on the screen."""
        spacing = SCREEN_SIZE[0] * 0.1

        # Create a container for the next pieces
        # First, find the total width and height of the container
        total_width = 0
        total_height = 0
        for piece in self.next_pieces:
            if piece is not None:
                total_width += len(piece.shape[0]) * BLOCK_SIZE_SMALL
                total_height = max(total_height, len(piece.shape) * BLOCK_SIZE_SMALL)

        # Add the spacing between the pieces
        total_width += spacing * (NEXT_PIECES_COUNT - 1)

        # Then, create the container
        container = pygame.Surface((total_width, total_height), pygame.SRCALPHA)

        # Make the container's rect
        container_rect = container.get_rect(center=(SCREEN_SIZE[0] // 2, SCREEN_SIZE[1] * 0.85))

        # Draw the next pieces onto the container
        x = 0
        for piece in self.next_pieces:
            if piece is not None:
                piece.draw(x + len(piece.shape[0]) * BLOCK_SIZE_SMALL // 2, total_height // 2, small=True, given_screen=container)
                x += len(piece.shape[0]) * BLOCK_SIZE_SMALL + spacing

        # Draw the container onto the screen
        self.screen.blit(container, container_rect)

    def place_piece(self, pieceIdx: int, x: int, y: int):
        """Place the given piece on the grid at the given coordinates."""
        piece = self.next_pieces[pieceIdx]
        if piece is None:
            return

        if not self.can_place_piece(piece, x, y):
            print("Cannot place piece here, space occupied.")
            return

        # Place the piece on the grid
        for i in range(len(piece.shape)):
            for j in range(len(piece.shape[i])):
                if piece.shape[i][j] == 1:
                    self.grid.grid[y + i][x + j] = GridBlock(piece.color, (x + j, y + i))

        # Remove the piece from the next pieces
        self.next_pieces[pieceIdx] = None

    def can_place_piece(self, piece: Block, x: int, y: int) -> bool:
        """Check if the given piece can be placed at the given coordinates."""
        # Check if the piece is out of bounds
        for i in range(len(piece.shape)):
            for j in range(len(piece.shape[i])):
                if piece.shape[i][j] == 1:
                    if y + i >= GRID_SIZE or x + j >= GRID_SIZE or self.grid.grid[y + i][x + j] is not None:
                        return False
        return True

if __name__ == "__main__":
    game = Game()
    game.run()

标签: none

添加新评论