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:
The sheer amount of data I need to sort through is overwhelming though.
For each shape I have:
Now, my idea was to create a class whose job is to make tetrominos for the client. And I came up with this juggernaut:
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.heightNow, 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?