Forum >> Programmazione Python >> Videogames >> Creare tetromini

Pagina: 1

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


Pagina: 1



Esegui il login per scrivere una risposta.