Forum
>>
Programmazione Python
>>
Videogames
>>
Creare tetromini
Pagina: 1
Esegui il login per scrivere una risposta.
Pagina: 1
|
Scritto da ragoo |
2023-09-27 12:48:13 - Creare tetromini
|
|
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... |
|
|
Scritto da Vilions |
2025-01-22 15:01:19 - Re: Creare tetromini
|
Pagina: 1
Esegui il login per scrivere una risposta.
