Python Forum
[Pyglet] Making tetrominos
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[Pyglet] Making tetrominos
#1
Hi. I don't think this is really a game development problem, it's more of a design problem best I can tell.

I've written a base Tetromino class that is functional for every possible shape:

class Tetromino:
    def __init__(self, minos, pivot, wall_kick_data):
        self.minos = minos
        self.pivot = pivot
        self.wall_kick_data = wall_kick_data

        self.current_rotation = 0
        self.previous_rotation = 0

    def move_by(self, vector):
        self.pivot += vector
        for mino in self.minos:
            mino.move_by(vector)

    def rotate(self, angle):
        self.previous_rotation = self.current_rotation
        if angle > 0:
            self.current_rotation = (self.current_rotation - 1) % 4
        else:
            self.current_rotation = (self.current_rotation + 1) % 4

        for mino in self.minos:
            mino.rotate(self.pivot, angle)

    def update(self):
        for mino in self.minos:
            mino.update()
As you've probably noticed, it contains a list of minos objects (apparently that's the official name for the blocks that compose a tetromino), which in turn are a subclass of the pyglet.shape.Rectangle class:

from pyglet.shapes import Rectangle
from pyglet.math import Vec2


class Mino(Rectangle):
    def __init__(self, *pargs, **kargs):
        super().__init__(*pargs, **kargs)
        self.grid_position = Vec2(self.x // self.width, self.y // self.height)

    def move_by(self, vector):
        self.grid_position += vector

    def rotate(self, pivot, angle):
        translated = self.grid_position - pivot
        rotated = translated.rotate(angle)
        self.grid_position = Vec2(*map(round, rotated + pivot))

    def update(self):
        self.x = self.grid_position.x * self.width
        self.y = self.grid_position.y * self.height
Now, this is all well and good, by my standards at least. The problem is when it comes to actually build these tetrominos according to their shape. The client is going to pick up a random shape for the next tetromino whenever it's time to spawn a new one.

The sheer amount of data I need to sort through is overwhelming though.
For each shape I have:
  • the shape data, a tuple of grid coordinates for each mino.

    shapes_data = {
        'I': ((-2, -2), (-1, -2), (0, -2), (1, -2)),
        'J': ((-2, -2), (-1, -2), (0, -2), (-2, -1)),
        'L': ((-2, -2), (-1, -2), (0, -2), (0, -1)),
        'O': ((-1, -2), (0, -2), (-1, -1), (0, -1)),
        'S': ((-2, -2), (-1, -2), (-1, -1), (0, -1)),
        'T': ((-2, -2), (-1, -2), (0, -2), (-1, -1)),
        'Z': ((-1, -2), (0, -2), (-2, -1), (-1, -1))}
    Note that this just gives me the shape, then I actually need to translate them to the right place, adding each one of them to (grid.width // 2, grid.height).

  • the pivot point for each shape
    shapes_pivots = {
        'I': (-0.5, -2.5),
        'J': (-1, -2),
        'L': (-1, -2),
        'O': (-0.5, -1.5),
        'S': (-1, -2),
        'T': (-1, -2),
        'Z': (-1, -2)}
    This also needs to be translated to the actual spawn point, adding it to (grid.width // 2, grid.height).

  • the wall kick data. This one is nasty. It contains a list of coordinates the tetromino is supposed to be moved by, according to its rotation states, whenever a rotation fails (because of an obstacle, either the walls or tetrominos in the grid). Anyway, the logic is not the problem, I got that.

    standard_wall_kicks_data = {
        0: {
            3: ((-1, 0), (-1, -1), (0, 2), (-1, 2)),
            1: ((1, 0), (1, -1), (0, 2), (1, 2))},
        1: {
            0: ((-1, 0), (-1, 1), (0, -2), (-1, -2)),
            2: ((-1, 0), (-1, 1), (0, -2), (-1, -2))},
        2: {
            1: ((1, 0), (1, -1), (0, 2), (1, 2)),
            3: ((-1, 0), (-1, -1), (0, 2), (-1, 2))},
        3: {
            2: ((1, 0), (1, 1), (0, -2), (1, -2)),
            0: ((1, 0), (1, 1), (0, -2), (1, -2))}
    }
    
    i_wall_kicks_data = {
        0: {
            3: ((1, 0), (-2, 0), (1, -2), (-2, 1)),
            1: ((2, 0), (-1, 0), (2, 1), (-1, -2))},
        1: {
            0: ((-2, 0), (1, 0), (-2, -1), (1, 2)),
            2: ((1, 0), (-2, 0), (1, -2), (-2, 1))},
        2: {
            1: ((-1, 0), (2, 0), (-1, 2), (2, -1)),
            3: ((-2, 0), (1, 0), (-2, -1), (1, 2))},
        3: {
            2: ((2, 0), (-1, 0), (2, 1), (-1, -2)),
            0: ((-1, 0), (2, 0), (-1, 2), (2, -1))}
    }
    
    shapes_wall_kicks_data = {
        'I': i_wall_kicks_data,
        'J': standard_wall_kicks_data,
        'L': standard_wall_kicks_data,
        'O': standard_wall_kicks_data,
        'S': standard_wall_kicks_data,
        'T': standard_wall_kicks_data,
        'Z': standard_wall_kicks_data}
    Notice how I is the only tetromino that follows a different ruleset.

  • finally colors:
    colors = {
        'I': i_color,
        'J': j_color,
        'L': l_color,
        'O': o_color,
        'S': s_color,
        'T': t_color,
        'Z': z_color}

Now, my idea was to create a class whose job is to make tetrominos for the client. And I came up with this juggernaut:

from pyglet.math import Vec2

from mino import Mino
from tetromino import Tetromino


shapes_data = {
    'I': ((-2, -2), (-1, -2), (0, -2), (1, -2)),
    'J': ((-2, -2), (-1, -2), (0, -2), (-2, -1)),
    'L': ((-2, -2), (-1, -2), (0, -2), (0, -1)),
    'O': ((-1, -2), (0, -2), (-1, -1), (0, -1)),
    'S': ((-2, -2), (-1, -2), (-1, -1), (0, -1)),
    'T': ((-2, -2), (-1, -2), (0, -2), (-1, -1)),
    'Z': ((-1, -2), (0, -2), (-2, -1), (-1, -1))}

shapes_pivots = {
    'I': (-0.5, -2.5),
    'J': (-1, -2),
    'L': (-1, -2),
    'O': (-0.5, -1.5),
    'S': (-1, -2),
    'T': (-1, -2),
    'Z': (-1, -2)}

standard_wall_kicks_data = {
    0: {
        3: ((-1, 0), (-1, -1), (0, 2), (-1, 2)),
        1: ((1, 0), (1, -1), (0, 2), (1, 2))},
    1: {
        0: ((-1, 0), (-1, 1), (0, -2), (-1, -2)),
        2: ((-1, 0), (-1, 1), (0, -2), (-1, -2))},
    2: {
        1: ((1, 0), (1, -1), (0, 2), (1, 2)),
        3: ((-1, 0), (-1, -1), (0, 2), (-1, 2))},
    3: {
        2: ((1, 0), (1, 1), (0, -2), (1, -2)),
        0: ((1, 0), (1, 1), (0, -2), (1, -2))}
}

i_wall_kicks_data = {
    0: {
        3: ((1, 0), (-2, 0), (1, -2), (-2, 1)),
        1: ((2, 0), (-1, 0), (2, 1), (-1, -2))},
    1: {
        0: ((-2, 0), (1, 0), (-2, -1), (1, 2)),
        2: ((1, 0), (-2, 0), (1, -2), (-2, 1))},
    2: {
        1: ((-1, 0), (2, 0), (-1, 2), (2, -1)),
        3: ((-2, 0), (1, 0), (-2, -1), (1, 2))},
    3: {
        2: ((2, 0), (-1, 0), (2, 1), (-1, -2)),
        0: ((-1, 0), (2, 0), (-1, 2), (2, -1))}
}


shapes_wall_kicks_data = {
    'I': i_wall_kicks_data,
    'J': standard_wall_kicks_data,
    'L': standard_wall_kicks_data,
    'O': standard_wall_kicks_data,
    'S': standard_wall_kicks_data,
    'T': standard_wall_kicks_data,
    'Z': standard_wall_kicks_data}


class StandardRotationSystem:
    def __init__(self):
        self.shapes_data = shapes_data
        self.shapes_pivots = shapes_pivots
        self.shapes_wall_kicks_data = shapes_wall_kicks_data

    def _get_minos(self, shape, dx, dy, cell_size, color, batch):
        minos = list()
        positions = (((x + dx) * cell_size, (y + dy) * cell_size) for (x, y) in self.shapes_data[shape])
        for pos in positions:
            mino = Mino(*pos, cell_size, cell_size, color, batch)
            minos.append(mino)
        return minos

    def _get_pivot(self, shape, dx, dy):
        pivot = Vec2(*self.shapes_pivots[shape]) + Vec2(dx, dy)
        return pivot

    def _get_wall_kick_data(self, shape):
        return self.shapes_wall_kicks_data[shape]

    def make_tetromino(self, shape, dx, dy, cell_size, color, batch):
        minos = self._get_minos(shape, dx, dy, cell_size, color, batch)
        pivot = self._get_pivot(shape, dx, dy)
        wall_kick_data = self._get_wall_kick_data(shape)
        return Tetromino(minos, pivot, wall_kick_data)
But, c'mon, this can't be the right way to do this. What's the best way to tame this complexity?
Reply


Messages In This Thread
Making tetrominos - by ragoo - Sep-15-2023, 11:00 AM
RE: Making tetrominos - by deanhystad - Sep-15-2023, 03:34 PM
RE: Making tetrominos - by ragoo - Sep-15-2023, 06:28 PM
RE: Making tetrominos - by deanhystad - Sep-15-2023, 11:11 PM
RE: Making tetrominos - by ragoo - Sep-16-2023, 01:09 PM
RE: Making tetrominos - by deanhystad - Sep-17-2023, 02:38 AM
RE: Making tetrominos - by ragoo - Sep-18-2023, 10:09 AM
RE: Making tetrominos - by deanhystad - Sep-18-2023, 05:10 PM
RE: Making tetrominos - by ragoo - Sep-19-2023, 11:49 AM
RE: Making tetrominos - by deanhystad - Sep-19-2023, 06:20 PM
RE: Making tetrominos - by ragoo - Sep-26-2023, 07:13 PM
RE: Making tetrominos - by Benixon - Dec-07-2023, 02:44 AM

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020