ragoo
Profilo di
Nome | ragoo |
---|---|
Indirizzo email | n/a |
Messaggi | 1 |
-
- 2023-09-27 12:48:13
- Creare tetromini
- Forum >> Programmazione Python >> Videogames
- Ehila. Per qualche ragione nutro il forte sospetto di essere stato sostanzialmente (e probabilmente giustamente) ostracizzato (leggasi sfanculato) sul forum in lingua inglese, quindi mi sembrava giusto cogliere l'occasione per esserlo anche qui.
Il problema che avevo era inizializzare tetronimi partendo da una serie di "dati grezzi". Prima di esaurire la disponibilita' dell'utenza, sono riuscito a sgraffignare una soluzione interessante, che costituisce un significativo miglioramento rispetto alla mia, ahem, implementazione:
import math import random from pyglet.shapes import Rectangle from pyglet.math import Vec2 import settings class Shape: """I know the details of a tetronimo shape. Creating an instance of Shape adds the shape to my dictionary. Use get(id) to get the Shape instance that matches the id, or use random() go get a random shape. """ dictionary = {} # Lookup Shape by id. default_kick = ( {3: ((-1, 0), (-1, -1), (0, 2), (-1, 2)), 1: ((1, 0), (1, -1), (0, 2), (1, 2))}, {0: ((-1, 0), (-1, 1), (0, -2), (-1, -2)), 2: ((-1, 0), (-1, 1), (0, -2), (-1, -2))}, {1: ((1, 0), (1, -1), (0, 2), (1, 2)), 3: ((-1, 0), (-1, -1), (0, 2), (-1, 2))}, {2: ((1, 0), (1, 1), (0, -2), (1, -2)), 0: ((1, 0), (1, 1), (0, -2), (1, -2))}, ) default_pivot = (-1, -2) def __init__(self, id, color, minos, pivot=None, wall_kick=None): pivot = self.default_pivot if pivot is None else pivot wall_kick = self.default_kick if wall_kick is None else wall_kick self.id = id self.color = color self.minos = [Vec2(*mino) for mino in minos] self.pivot = Vec2(*pivot) self.wall_kick = wall_kick self.dictionaryid = self @classmethod def get(cls, key): """Lookup shape by id.""" return cls.dictionarykey @classmethod def random(cls): """Return random shape.""" return random.choice(list(cls.dictionary.values())) class Mino(Rectangle): cell_size = 10 def __init__(self, grid_position, pivot, color, batch): self.grid_position = grid_position self.pivot = pivot x = self.grid_position.x * self.cell_size y = self.grid_position.y * self.cell_size super().__init__(x, y, self.cell_size, self.cell_size, color, batch) def rotate(self, angle): translated = self.grid_position - self.pivot rotated = translated.rotate(math.radians(angle)) self.grid_position = Vec2(*map(round, rotated + self.pivot)) self.update() def shift(self, vector): self.pivot += vector self.grid_position += vector self.update() def update(self): self.x = self.grid_position.x * self.width self.y = self.grid_position.y * self.height class Tetromino: """I am a tetronimo that the player is moving on the screen. Use random(location, rotation) to create a randomly shaped tetronimo. location is the coordinates of the origin. Rotation is 0..3. Use get(shape_id, location, rotation) to create a tetronimo of a specific shape. """ @classmethod def random(cls, location, batch): """Create a random Tetronimo.""" return cls(Shape.random(), location, batch) @classmethod def get(cls, shape_id, location, batch): """Create Tetronimo using shape with matching id.""" return cls(Shape.get(shape_id), location, batch) def __init__(self, shape, location, batch): self.minos = [ Mino(pos + location, shape.pivot + location, shape.color, batch) for pos in shape.minos ] self.wall_kicks = shape.wall_kick self.rotation = 0 self.old_rotation = 0 def get_positions(self): return (mino.grid_position for mino in self.minos) def get_wall_kicks(self): return self.wall_kicks[self.rotation][self.old_rotation] def rotate(self, angle): """Rotate tetronimo.""" self.old_rotation = self.rotation self.rotation = (self.rotation - int(math.copysign(1, angle))) % 4 for mino in self.minos: mino.rotate(angle) def shift(self, vector): """Move in x and y direction.""" for mino in self.minos: mino.shift(vector) # Define all the shapes Shape("I", settings.icolor, ((-2, -2), (-1, -2), (0, -2), (1, -2)), pivot=(-0.5, -2.5), wall_kick=( {3: ((1, 0), (-2, 0), (1, -2), (-2, 1)), 1: ((2, 0), (-1, 0), (2, 1), (-1, -2))}, {0: ((-2, 0), (1, 0), (-2, -1), (1, 2)), 2: ((1, 0), (-2, 0), (1, -2), (-2, 1))}, {1: ((-1, 0), (2, 0), (-1, 2), (2, -1)), 3: ((-2, 0), (1, 0), (-2, -1), (1, 2))}, {2: ((2, 0), (-1, 0), (2, 1), (-1, -2)), 0: ((-1, 0), (2, 0), (-1, 2), (2, -1))} ) ) Shape("O", settings.ocolor, ((-1, -2), (0, -2), (-1, -1), (0, -1)), (-0.5, -1.5), []) Shape("J", settings.jcolor, ((-2, -2), (-1, -2), (0, -2), (-2, -1))) Shape("L", settings.lcolor, ((-2, -2), (-1, -2), (0, -2), (0, -1))) Shape("S", settings.scolor, ((-2, -2), (-1, -2), (-1, -1), (0, -1))) Shape("T", settings.tcolor, ((-2, -2), (-1, -2), (0, -2), (-1, -1))) Shape("Z", settings.zcolor, ((-1, -2), (0, -2), (-2, -1), (-1, -1)))
Il problema e' che non mi piace inizializzare tutte queste Shape nel modulo, mi sembra un tantinello sciatto. Non sarebbe meglio incapsulare la logica in una classe deputata unicamente a questo scopo? Inoltre, non e' meglio utilizzare un Enumerator anziche' stringhe per le shape_id?
Con queste modifiche, questo sarebbe il risultato:
import math import random from enum import Enum from pyglet.shapes import Rectangle from pyglet.math import Vec2 import settings class ShapeID(Enum): I = 0 J = 1 L = 2 O = 3 S = 4 T = 5 Z = 6 class ShapeFactory: iminos = ((-2, -2), (-1, -2), (0, -2), (1, -2)) jminos = ((-2, -2), (-1, -2), (0, -2), (-2, -1)) lminos = ((-2, -2), (-1, -2), (0, -2), (0, -1)) ominos = ((-1, -2), (0, -2), (-1, -1), (0, -1)) sminos = ((-2, -2), (-1, -2), (-1, -1), (0, -1)) tminos = ((-2, -2), (-1, -2), (0, -2), (-1, -1)) zminos = ((-1, -2), (0, -2), (-2, -1), (-1, -1)) pivot = (-1, -2) ipivot = (-0.5, -2.5) opivot = (-0.5, -1.5) kicks = ( { 3: ((-1, 0), (-1, -1), (0, 2), (-1, 2)), 1: ((1, 0), (1, -1), (0, 2), (1, 2)), }, { 0: ((-1, 0), (-1, 1), (0, -2), (-1, -2)), 2: ((-1, 0), (-1, 1), (0, -2), (-1, -2)), }, { 1: ((1, 0), (1, -1), (0, 2), (1, 2)), 3: ((-1, 0), (-1, -1), (0, 2), (-1, 2)), }, { 2: ((1, 0), (1, 1), (0, -2), (1, -2)), 0: ((1, 0), (1, 1), (0, -2), (1, -2)), }, ) ikicks = ( { 3: ((1, 0), (-2, 0), (1, -2), (-2, 1)), 1: ((2, 0), (-1, 0), (2, 1), (-1, -2)), }, { 0: ((-2, 0), (1, 0), (-2, -1), (1, 2)), 2: ((1, 0), (-2, 0), (1, -2), (-2, 1)), }, { 1: ((-1, 0), (2, 0), (-1, 2), (2, -1)), 3: ((-2, 0), (1, 0), (-2, -1), (1, 2)), }, { 2: ((2, 0), (-1, 0), (2, 1), (-1, -2)), 0: ((-1, 0), (2, 0), (-1, 2), (2, -1)), }, ) def __init__(self): self.shapes = {} self.register_shape(ShapeID.I, settings.icolor, self.iminos, self.ipivot, self.ikicks) self.register_shape(ShapeID.J, settings.jcolor, self.jminos, self.pivot, self.kicks) self.register_shape(ShapeID.L, settings.lcolor, self.lminos, self.pivot, self.kicks) self.register_shape(ShapeID.O, settings.ocolor, self.ominos, self.opivot) self.register_shape(ShapeID.S, settings.scolor, self.sminos, self.pivot, self.kicks) self.register_shape(ShapeID.T, settings.tcolor, self.tminos, self.pivot, self.kicks) self.register_shape(ShapeID.Z, settings.zcolor, self.zminos, self.pivot, self.kicks) def register_shape(self, shape_id, color, minos, pivot, kicks=None): """Register a new shape with the factory.""" self.shapesshape id = Shape(color, minos, pivot, kicks) def get(self, shape_id): """Return a specific shape by ID.""" if shape_id in self.shapes: return self.shapesshape id else: raise ValueError(f"Invalid shape ID: {shape_id}") def random(self): """Return a random shape.""" shape_id = random.choice(tuple(self.shapes.keys())) return self.get(shape_id) class Shape: def __init__(self, color, minos, pivot, kicks=None): self.color = color self.minos = [Vec2(*mino) for mino in minos] self.pivot = Vec2(*pivot) self.kicks = kicks class Mino(Rectangle): cell_size = 10 def __init__(self, grid_position, pivot, color, batch): self.grid_position = grid_position self.pivot = pivot x = self.grid_position.x * self.cell_size y = self.grid_position.y * self.cell_size super().__init__(x, y, self.cell_size, self.cell_size, color, batch) def rotate(self, angle): translated = self.grid_position - self.pivot rotated = translated.rotate(math.radians(angle)) self.grid_position = Vec2(*map(round, rotated + self.pivot)) self.update() def shift(self, vector): self.pivot += vector self.grid_position += vector self.update() def update(self): self.x = self.grid_position.x * self.width self.y = self.grid_position.y * self.height class Tetromino: shape_factory = ShapeFactory() def __init__(self, shape, location, batch): self.minos = [ Mino(pos + location, shape.pivot + location, shape.color, batch) for pos in shape.minos ] self.wall_kicks = shape.kicks self.rotation = 0 self.old_rotation = 0 def get_positions(self): return (mino.grid_position for mino in self.minos) def get_wall_kicks(self): return self.wall_kicks[self.rotation][self.old_rotation] def rotate(self, angle): """Rotate tetromino.""" self.old_rotation = self.rotation self.rotation = (self.rotation - int(math.copysign(1, angle))) % 4 for mino in self.minos: mino.rotate(angle) def shift(self, vector): """Move in x and y direction.""" for mino in self.minos: mino.shift(vector) @classmethod def get(cls, shape_id, location, batch): """Create Tetromino using shape with matching id.""" shape = cls.shape_factory.get(shape_id) return cls(shape, location, batch) @classmethod def random(cls, location, batch): """Create a random Tetromino.""" shape = cls.shape_factory.random() return cls(shape, location, batch)
In questo modo continuo ad aderire al principio open-close (anche se non ne vedo un gran beneficio, dal momento che le forme in Tetris sono quelle), e come minimo il codice mi diventa piu' ordinato, e quindi leggibile. Per me almeno.
Non so se ho davvero bisogno di quella classe Shape, adesso. E' solo un container di dati di cui la classe Tetromino necessita. Potrei farne a meno e trasformare ShapeFactory in un TetrominoFactory...