Use nuitka for compiling, reorganize py files

This commit is contained in:
adrienmalin 2018-08-19 23:28:22 +02:00
parent 8139bf8154
commit 54bce41d50
13 changed files with 2140 additions and 2176 deletions

File diff suppressed because it is too large Load Diff

BIN
dist/Tetris2000.exe vendored

Binary file not shown.

View File

@ -1,43 +0,0 @@
# -*- mode: python -*-
block_cipher = None
a = Analysis(['Tetris2000.py'],
pathex=[],
binaries=[],
datas=[
("backgrounds/*", "backgrounds"),
("fonts/*.ttf", "fonts"),
("fonts/*.otf", "fonts"),
("icons/*.ico", "icons"),
("icons/splash_screen.png", "icons"),
("locale/*.qm", "locale"),
("musics/*.mp3", "musics"),
("sfx/*.wav", "sfx")
],
hiddenimports=[],
hookspath=[],
runtime_hooks=[],
excludes=["PyQt4", "PySide", "PySide2"],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
exclude_binaries=True,
name='Tetris2000',
debug=False,
strip=False,
upx=False,
console=False,
icon='icons\icon.ico')
coll = COLLECT(exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=False,
name='Tetris2000')

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
__author__ = "Adrien Malingrey" __author__ = "Adrien Malingrey"
__title__ = "Tetris 2000" __title__ = "Tetris 2000"
__version__ = "0.3" __version__ = "0.3"

View File

@ -1,107 +1,108 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import os import os
from qt5 import QtGui from .qt5 import QtGui
# Paths # Paths
PATH = os.path.dirname(os.path.abspath(__file__)) PATH = os.path.dirname(os.path.abspath(__file__))
ICON_PATH = os.path.join(PATH, "icons", "icon.ico") PATH = os.path.dirname(PATH)
BG_IMAGE_DIR = os.path.join(PATH, "backgrounds") ICON_PATH = os.path.join(PATH, "icons", "icon.ico")
START_BG_IMAGE_NAME = "01-spacefield_a-000.png" BG_IMAGE_DIR = os.path.join(PATH, "backgrounds")
MUSICS_DIR = os.path.join(PATH, "musics") START_BG_IMAGE_NAME = "01-spacefield_a-000.png"
SFX_DIR = os.path.join(PATH, "sfx") MUSICS_DIR = os.path.join(PATH, "musics")
LINE_CLEAR_SFX_PATH = os.path.join(SFX_DIR, "line_clear.wav") SFX_DIR = os.path.join(PATH, "sfx")
TETRIS_SFX_PATH = os.path.join(SFX_DIR, "tetris.wav") LINE_CLEAR_SFX_PATH = os.path.join(SFX_DIR, "line_clear.wav")
ROTATE_SFX_PATH = os.path.join(SFX_DIR, "rotate.wav") TETRIS_SFX_PATH = os.path.join(SFX_DIR, "tetris.wav")
HARD_DROP_SFX_PATH = os.path.join(SFX_DIR, "hard_drop.wav") ROTATE_SFX_PATH = os.path.join(SFX_DIR, "rotate.wav")
WALL_SFX_PATH = os.path.join(SFX_DIR, "wall.wav") HARD_DROP_SFX_PATH = os.path.join(SFX_DIR, "hard_drop.wav")
LOCALE_PATH = os.path.join(PATH, "locale") WALL_SFX_PATH = os.path.join(SFX_DIR, "wall.wav")
FONTS_DIR = os.path.join(PATH, "fonts") LOCALE_PATH = os.path.join(PATH, "locale")
STATS_FONT_PATH = os.path.join(FONTS_DIR, "PixelCaps!.otf") FONTS_DIR = os.path.join(PATH, "fonts")
STATS_FONT_NAME = "PixelCaps!" STATS_FONT_PATH = os.path.join(FONTS_DIR, "PixelCaps!.otf")
MATRIX_FONT_PATH = os.path.join(FONTS_DIR, "maass slicer Italic.ttf") STATS_FONT_NAME = "PixelCaps!"
MATRIX_FONT_NAME = "Maassslicer" MATRIX_FONT_PATH = os.path.join(FONTS_DIR, "maass slicer Italic.ttf")
MATRIX_FONT_NAME = "Maassslicer"
SPLASH_SCREEN_PATH = os.path.join(PATH, "icons", "splash_screen.png")
SPLASH_SCREEN_PATH = os.path.join(PATH, "icons", "splash_screen.png")
# Coordinates and direction
L, R, U, D = -1, 1, -1, 1 # Left, Right, Up, Down # Coordinates and direction
CLOCKWISE, COUNTERCLOCKWISE = 1, -1 L, R, U, D = -1, 1, -1, 1 # Left, Right, Up, Down
CLOCKWISE, COUNTERCLOCKWISE = 1, -1
# Delays in milliseconds
ANIMATION_DELAY = 67 # Delays in milliseconds
INITIAL_SPEED = 1000 ANIMATION_DELAY = 67
ENTRY_DELAY = 80 INITIAL_SPEED = 1000
LINE_CLEAR_DELAY = 80 ENTRY_DELAY = 80
LOCK_DELAY = 500 LINE_CLEAR_DELAY = 80
TEMPORARY_TEXT_DURATION = 1000 LOCK_DELAY = 500
AFTER_LVL_15_ACCELERATION = 0.9 TEMPORARY_TEXT_DURATION = 1000
AFTER_LVL_15_ACCELERATION = 0.9
# Block Colors
BLOCK_BORDER_COLOR = QtGui.QColor(0, 159, 218, 255) # Block Colors
BLOCK_FILL_COLOR = QtGui.QColor(0, 159, 218, 25) BLOCK_BORDER_COLOR = QtGui.QColor(0, 159, 218, 255)
BLOCK_GLOWING_BORDER_COLOR = None BLOCK_FILL_COLOR = QtGui.QColor(0, 159, 218, 25)
BLOCK_GLOWING_FILL_COLOR = QtGui.QColor(186, 211, 255, 70) BLOCK_GLOWING_BORDER_COLOR = None
BLOCK_LIGHT_COLOR = QtGui.QColor(242, 255, 255, 40) BLOCK_GLOWING_FILL_COLOR = QtGui.QColor(186, 211, 255, 70)
BLOCK_TRANSPARENT = QtGui.QColor(255, 255, 255, 0) BLOCK_LIGHT_COLOR = QtGui.QColor(242, 255, 255, 40)
BLOCK_GLOWING = 0 BLOCK_TRANSPARENT = QtGui.QColor(255, 255, 255, 0)
BLOCK_INITIAL_SIDE = 20 BLOCK_GLOWING = 0
BLOCK_INITIAL_SIDE = 20
GHOST_BLOCK_BORDER_COLOR = QtGui.QColor(135, 213, 255, 255)
GHOST_BLOCK_FILL_COLOR = None GHOST_BLOCK_BORDER_COLOR = QtGui.QColor(135, 213, 255, 255)
GHOST_BLOCK_GLOWING_FILL_COLOR = QtGui.QColor(201, 149, 205, 255) GHOST_BLOCK_FILL_COLOR = None
GHOST_BLOCK_GLOWING = 1 GHOST_BLOCK_GLOWING_FILL_COLOR = QtGui.QColor(201, 149, 205, 255)
GHOST_BLOCK_GLOWING = 1
# Grid
GRID_INVISIBLE_ROWS = 3 # Grid
GRID_DEFAULT_ROWS = 4 GRID_INVISIBLE_ROWS = 3
GRID_DEFAULT_COLUMNS = 6 GRID_DEFAULT_ROWS = 4
GRID_GRIDLINE_COLOR = QtGui.QColor(255, 255, 255, 60) GRID_DEFAULT_COLUMNS = 6
GRID_HARD_DROP_MOVEMENT = 0.2 GRID_GRIDLINE_COLOR = QtGui.QColor(255, 255, 255, 60)
GRID_SPOTLIGHT = 0, 0 GRID_HARD_DROP_MOVEMENT = 0.2
GRID_SPOTLIGHT = 0, 0
# Matrix
MATRIX_ROWS = 20 # Matrix
MATRIX_COLUMNS = 10 MATRIX_ROWS = 20
MATRIX_TEXT_COLOR = QtGui.QColor(204, 255, 255, 128) MATRIX_COLUMNS = 10
MATRIX_TEXT_COLOR = QtGui.QColor(204, 255, 255, 128)
# Next Queue
NEXT_QUEUE_ROWS = 16 # Next Queue
NEXT_QUEUE_COLUMNS = 6 NEXT_QUEUE_ROWS = 16
NEXT_QUEUE_COLUMNS = 6
# Stats frame
STATS_ROWS = 15 # Stats frame
STATS_COLUMNS = 6 STATS_ROWS = 15
STATS_TEXT_COLOR = QtGui.QColor(0, 159, 218, 128) STATS_COLUMNS = 6
SCORES = ( STATS_TEXT_COLOR = QtGui.QColor(0, 159, 218, 128)
{"name": "", "": 0, "Mini T-Spin": 1, "T-Spin": 4}, SCORES = (
{"name": "Single", "": 1, "Mini T-Spin": 2, "T-Spin": 8}, {"name": "", "": 0, "Mini T-Spin": 1, "T-Spin": 4},
{"name": "Double", "": 3, "T-Spin": 12}, {"name": "Single", "": 1, "Mini T-Spin": 2, "T-Spin": 8},
{"name": "Triple", "": 5, "T-Spin": 16}, {"name": "Double", "": 3, "T-Spin": 12},
{"name": "Tetris", "": 8}, {"name": "Triple", "": 5, "T-Spin": 16},
) {"name": "Tetris", "": 8},
)
# Default settings
DEFAULT_WINDOW_SIZE = 839, 807 # Default settings
# Key mapping DEFAULT_WINDOW_SIZE = 839, 807
DEFAULT_MOVE_LEFT_KEY = "Left" # Key mapping
DEFAULT_MOVE_RIGHT_KEY = "Right" DEFAULT_MOVE_LEFT_KEY = "Left"
DEFAULT_ROTATE_CLOCKWISE_KEY = "Up" DEFAULT_MOVE_RIGHT_KEY = "Right"
DEFAULT_ROTATE_COUNTERCLOCKWISE_KEY = "Control" DEFAULT_ROTATE_CLOCKWISE_KEY = "Up"
DEFAULT_SOFT_DROP_KEY = "Down" DEFAULT_ROTATE_COUNTERCLOCKWISE_KEY = "Control"
DEFAULT_HARD_DROP_KEY = "Space" DEFAULT_SOFT_DROP_KEY = "Down"
DEFAULT_HOLD_KEY = "Shift" DEFAULT_HARD_DROP_KEY = "Space"
DEFAULT_PAUSE_KEY = "Escape" DEFAULT_HOLD_KEY = "Shift"
# Delays in milliseconds DEFAULT_PAUSE_KEY = "Escape"
DEFAULT_AUTO_SHIFT_DELAY = 170 # Delays in milliseconds
DEFAULT_AUTO_REPEAT_RATE = 20 DEFAULT_AUTO_SHIFT_DELAY = 170
# Volume in percent DEFAULT_AUTO_REPEAT_RATE = 20
DEFAUT_MUSIC_VOLUME = 25 # Volume in percent
DEFAULT_SFX_VOLUME = 50 DEFAUT_MUSIC_VOLUME = 25
# Other DEFAULT_SFX_VOLUME = 50
DEFAULT_SHOW_GHOST = True # Other
DEFAULT_SHOW_NEXT_QUEUE = True DEFAULT_SHOW_GHOST = True
DEFAULT_HOLD_ENABLED = True DEFAULT_SHOW_NEXT_QUEUE = True
DEFAULT_HOLD_ENABLED = True

1520
source/game_gui.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,48 +1,48 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from consts import CLOCKWISE from .consts import CLOCKWISE
from qt5 import QtCore from .qt5 import QtCore
from propertize import propertize, rename_attributes, snake_case from .propertize import propertize, rename_attributes, snake_case
@propertize("", "set_") @propertize("", "set_")
@rename_attributes(snake_case) @rename_attributes(snake_case)
class Point(QtCore.QPoint): class Point(QtCore.QPoint):
""" """
Point of coordinates (x, y) Point of coordinates (x, y)
""" """
def rotate(self, center, direction=CLOCKWISE): def rotate(self, center, direction=CLOCKWISE):
""" Returns the Point image of the rotation of self """ Returns the Point image of the rotation of self
through 90° CLOKWISE or COUNTERCLOCKWISE around center""" through 90° CLOKWISE or COUNTERCLOCKWISE around center"""
if self == center: if self == center:
return self return self
p = self - center p = self - center
p = Point(-direction * p.y, direction * p.x) p = Point(-direction * p.y, direction * p.x)
p += center p += center
return p return p
def __add__(self, o): def __add__(self, o):
return Point(self.x + o.x, self.y + o.y) return Point(self.x + o.x, self.y + o.y)
def __sub__(self, o): def __sub__(self, o):
return Point(self.x - o.x, self.y - o.y) return Point(self.x - o.x, self.y - o.y)
def __mul__(self, k): def __mul__(self, k):
return Point(k * self.x, k * self.y) return Point(k * self.x, k * self.y)
def __truediv__(self, k): def __truediv__(self, k):
return Point(self.x / k, self.y / k) return Point(self.x / k, self.y / k)
__radd__ = __add__ __radd__ = __add__
__rsub__ = __sub__ __rsub__ = __sub__
__rmul__ = __mul__ __rmul__ = __mul__
__rtruediv__ = __truediv__ __rtruediv__ = __truediv__
def __repr__(self): def __repr__(self):
return "Point({}, {})".format(self.x, self.y) return "Point({}, {})".format(self.x, self.y)
__str__ = __repr__ __str__ = __repr__

View File

@ -1,29 +1,29 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import sys import sys
import os import os
try: try:
from PyQt5 import QtWidgets, QtCore, QtGui, QtMultimedia from PyQt5 import QtWidgets, QtCore, QtGui, QtMultimedia
except ImportError as pyqt5_error: except ImportError as pyqt5_error:
try: try:
from PySide2 import QtWidgets, QtCore, QtGui, QtMultimedia from PySide2 import QtWidgets, QtCore, QtGui, QtMultimedia
except ImportError as pyside2_error: except ImportError as pyside2_error:
sys.exit( sys.exit(
"This program require a Qt5 library.\n" "This program require a Qt5 library.\n"
"You can install PyQt5 (recommended) :\n" "You can install PyQt5 (recommended) :\n"
" pip3 install --user PyQt5\n" " pip3 install --user PyQt5\n"
" pip3 install --user qdarkstyle\n" " pip3 install --user qdarkstyle\n"
"or PySide2 :\n" "or PySide2 :\n"
" pip3 install --user PySide2\n" " pip3 install --user PySide2\n"
+ pyqt5_error.msg + pyqt5_error.msg
+ "\n" + "\n"
+ pyside2_error.msg + pyside2_error.msg
) )
else: else:
os.environ["QT_API"] = "pyside2" os.environ["QT_API"] = "pyside2"
else: else:
os.environ["QT_API"] = "pyqt5" os.environ["QT_API"] = "pyqt5"
QtCore.Signal = QtCore.pyqtSignal QtCore.Signal = QtCore.pyqtSignal

View File

@ -1,422 +1,422 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import random import random
import consts from . import consts
from consts import L, R, U, D, CLOCKWISE, COUNTERCLOCKWISE from .consts import L, R, U, D, CLOCKWISE, COUNTERCLOCKWISE
from point import Point from .point import Point
from qt5 import QtCore, QtGui from .qt5 import QtCore, QtGui
class Block: class Block:
""" """
Mino or block Mino or block
Mino : A single square-shaped building block of a shape called a Tetrimino. Mino : A single square-shaped building block of a shape called a Tetrimino.
Four Minos arranged into any of their various connected patterns is known as a Tetrimino Four Minos arranged into any of their various connected patterns is known as a Tetrimino
Block : A single block locked in a cell in the Grid Block : A single block locked in a cell in the Grid
""" """
# Colors # Colors
BORDER_COLOR = consts.BLOCK_BORDER_COLOR BORDER_COLOR = consts.BLOCK_BORDER_COLOR
FILL_COLOR = consts.BLOCK_FILL_COLOR FILL_COLOR = consts.BLOCK_FILL_COLOR
GLOWING_BORDER_COLOR = consts.BLOCK_GLOWING_BORDER_COLOR GLOWING_BORDER_COLOR = consts.BLOCK_GLOWING_BORDER_COLOR
GLOWING_FILL_COLOR = consts.BLOCK_GLOWING_FILL_COLOR GLOWING_FILL_COLOR = consts.BLOCK_GLOWING_FILL_COLOR
LIGHT_COLOR = consts.BLOCK_LIGHT_COLOR LIGHT_COLOR = consts.BLOCK_LIGHT_COLOR
TRANSPARENT = consts.BLOCK_TRANSPARENT TRANSPARENT = consts.BLOCK_TRANSPARENT
GLOWING = consts.BLOCK_GLOWING GLOWING = consts.BLOCK_GLOWING
side = consts.BLOCK_INITIAL_SIDE side = consts.BLOCK_INITIAL_SIDE
def __init__(self, coord, trail=0): def __init__(self, coord, trail=0):
self.coord = coord self.coord = coord
self.trail = trail self.trail = trail
self.border_color = self.BORDER_COLOR self.border_color = self.BORDER_COLOR
self.fill_color = self.FILL_COLOR self.fill_color = self.FILL_COLOR
self.glowing = self.GLOWING self.glowing = self.GLOWING
def paint(self, painter, top_left_corner, spotlight): def paint(self, painter, top_left_corner, spotlight):
p = top_left_corner + self.coord * Block.side p = top_left_corner + self.coord * Block.side
block_center = Point(Block.side / 2, Block.side / 2) block_center = Point(Block.side / 2, Block.side / 2)
self.center = p + block_center self.center = p + block_center
spotlight = top_left_corner + Block.side * spotlight + block_center spotlight = top_left_corner + Block.side * spotlight + block_center
self.glint = 0.15 * spotlight + 0.85 * self.center self.glint = 0.15 * spotlight + 0.85 * self.center
if self.trail: if self.trail:
start = ( start = (
top_left_corner + (self.coord + Point(0, self.trail * U)) * Block.side top_left_corner + (self.coord + Point(0, self.trail * U)) * Block.side
) )
stop = top_left_corner + (self.coord + Point(0, 2 * D)) * Block.side stop = top_left_corner + (self.coord + Point(0, 2 * D)) * Block.side
fill = QtGui.QLinearGradient(start, stop) fill = QtGui.QLinearGradient(start, stop)
fill.setColorAt(0, self.LIGHT_COLOR) fill.setColorAt(0, self.LIGHT_COLOR)
fill.setColorAt(1, self.GLOWING_FILL_COLOR) fill.setColorAt(1, self.GLOWING_FILL_COLOR)
painter.setBrush(fill) painter.setBrush(fill)
painter.setPen(QtCore.Qt.NoPen) painter.setPen(QtCore.Qt.NoPen)
painter.drawRoundedRect( painter.drawRoundedRect(
start.x, start.x,
start.y, start.y,
Block.side, Block.side,
Block.side * (1 + self.trail), Block.side * (1 + self.trail),
20, 20,
20, 20,
QtCore.Qt.RelativeSize, QtCore.Qt.RelativeSize,
) )
if self.glowing: if self.glowing:
fill = QtGui.QRadialGradient(self.center, self.glowing * Block.side) fill = QtGui.QRadialGradient(self.center, self.glowing * Block.side)
fill.setColorAt(0, self.TRANSPARENT) fill.setColorAt(0, self.TRANSPARENT)
fill.setColorAt(0.5 / self.glowing, self.LIGHT_COLOR) fill.setColorAt(0.5 / self.glowing, self.LIGHT_COLOR)
fill.setColorAt(1, self.TRANSPARENT) fill.setColorAt(1, self.TRANSPARENT)
painter.setBrush(QtGui.QBrush(fill)) painter.setBrush(QtGui.QBrush(fill))
painter.setPen(QtCore.Qt.NoPen) painter.setPen(QtCore.Qt.NoPen)
painter.drawEllipse( painter.drawEllipse(
self.center.x - self.glowing * Block.side, self.center.x - self.glowing * Block.side,
self.center.y - self.glowing * Block.side, self.center.y - self.glowing * Block.side,
2 * self.glowing * Block.side, 2 * self.glowing * Block.side,
2 * self.glowing * Block.side, 2 * self.glowing * Block.side,
) )
painter.setBrush(self.brush()) painter.setBrush(self.brush())
painter.setPen(self.pen()) painter.setPen(self.pen())
painter.drawRoundedRect( painter.drawRoundedRect(
p.x + 1, p.x + 1,
p.y + 1, p.y + 1,
Block.side - 2, Block.side - 2,
Block.side - 2, Block.side - 2,
20, 20,
20, 20,
QtCore.Qt.RelativeSize, QtCore.Qt.RelativeSize,
) )
def brush(self): def brush(self):
if self.fill_color is None: if self.fill_color is None:
return QtCore.Qt.NoBrush return QtCore.Qt.NoBrush
fill = QtGui.QRadialGradient(self.glint, 1.5 * Block.side) fill = QtGui.QRadialGradient(self.glint, 1.5 * Block.side)
fill.setColorAt(0, self.fill_color.lighter()) fill.setColorAt(0, self.fill_color.lighter())
fill.setColorAt(1, self.fill_color) fill.setColorAt(1, self.fill_color)
return QtGui.QBrush(fill) return QtGui.QBrush(fill)
def pen(self): def pen(self):
if self.border_color is None: if self.border_color is None:
return QtCore.Qt.NoPen return QtCore.Qt.NoPen
border = QtGui.QRadialGradient(self.glint, Block.side) border = QtGui.QRadialGradient(self.glint, Block.side)
border.setColorAt(0, self.border_color.lighter()) border.setColorAt(0, self.border_color.lighter())
border.setColorAt(1, self.border_color.darker()) border.setColorAt(1, self.border_color.darker())
return QtGui.QPen(QtGui.QBrush(border), 1, join=QtCore.Qt.RoundJoin) return QtGui.QPen(QtGui.QBrush(border), 1, join=QtCore.Qt.RoundJoin)
def shine(self, glowing=2, delay=None): def shine(self, glowing=2, delay=None):
self.border_color = Block.GLOWING_BORDER_COLOR self.border_color = Block.GLOWING_BORDER_COLOR
self.fill_color = Block.GLOWING_FILL_COLOR self.fill_color = Block.GLOWING_FILL_COLOR
self.glowing = glowing self.glowing = glowing
if delay: if delay:
QtCore.QTimer.singleShot(delay, self.fade) QtCore.QTimer.singleShot(delay, self.fade)
def fade(self): def fade(self):
self.border_color = Block.BORDER_COLOR self.border_color = Block.BORDER_COLOR
self.fill_color = Block.FILL_COLOR self.fill_color = Block.FILL_COLOR
self.glowing = 0 self.glowing = 0
self.trail = 0 self.trail = 0
class GhostBlock(Block): class GhostBlock(Block):
""" """
Mino of the ghost piece Mino of the ghost piece
""" """
BORDER_COLOR = consts.GHOST_BLOCK_BORDER_COLOR BORDER_COLOR = consts.GHOST_BLOCK_BORDER_COLOR
FILL_COLOR = consts.GHOST_BLOCK_FILL_COLOR FILL_COLOR = consts.GHOST_BLOCK_FILL_COLOR
GLOWING_FILL_COLOR = consts.GHOST_BLOCK_GLOWING_FILL_COLOR GLOWING_FILL_COLOR = consts.GHOST_BLOCK_GLOWING_FILL_COLOR
GLOWING = consts.GHOST_BLOCK_GLOWING GLOWING = consts.GHOST_BLOCK_GLOWING
class MetaTetro(type): class MetaTetro(type):
""" """
Save the different shapes of Tetrominoes Save the different shapes of Tetrominoes
""" """
def __init__(cls, name, bases, dico): def __init__(cls, name, bases, dico):
type.__init__(cls, name, bases, dico) type.__init__(cls, name, bases, dico)
Tetromino.classes.append(cls) Tetromino.classes.append(cls)
Tetromino.nb_classes += 1 Tetromino.nb_classes += 1
class Tetromino: class Tetromino:
""" """
Geometric Tetris® shape formed by four Minos connected along their sides. Geometric Tetris® shape formed by four Minos connected along their sides.
A total of seven possible Tetriminos can be made using four Minos. A total of seven possible Tetriminos can be made using four Minos.
""" """
COORDS = NotImplemented COORDS = NotImplemented
SUPER_ROTATION_SYSTEM = ( SUPER_ROTATION_SYSTEM = (
{ {
COUNTERCLOCKWISE: ((0, 0), (R, 0), (R, U), (0, 2 * D), (R, 2 * D)), COUNTERCLOCKWISE: ((0, 0), (R, 0), (R, U), (0, 2 * D), (R, 2 * D)),
CLOCKWISE: ((0, 0), (L, 0), (L, U), (0, 2 * D), (L, 2 * D)), CLOCKWISE: ((0, 0), (L, 0), (L, U), (0, 2 * D), (L, 2 * D)),
}, },
{ {
COUNTERCLOCKWISE: ((0, 0), (R, 0), (R, D), (0, 2 * U), (R, 2 * U)), COUNTERCLOCKWISE: ((0, 0), (R, 0), (R, D), (0, 2 * U), (R, 2 * U)),
CLOCKWISE: ((0, 0), (R, 0), (R, D), (0, 2 * U), (R, 2 * U)), CLOCKWISE: ((0, 0), (R, 0), (R, D), (0, 2 * U), (R, 2 * U)),
}, },
{ {
COUNTERCLOCKWISE: ((0, 0), (L, 0), (L, U), (0, 2 * D), (L, 2 * D)), COUNTERCLOCKWISE: ((0, 0), (L, 0), (L, U), (0, 2 * D), (L, 2 * D)),
CLOCKWISE: ((0, 0), (R, 0), (R, U), (0, 2 * D), (R, 2 * D)), CLOCKWISE: ((0, 0), (R, 0), (R, U), (0, 2 * D), (R, 2 * D)),
}, },
{ {
COUNTERCLOCKWISE: ((0, 0), (L, 0), (L, D), (0, 2 * U), (L, 2 * U)), COUNTERCLOCKWISE: ((0, 0), (L, 0), (L, D), (0, 2 * U), (L, 2 * U)),
CLOCKWISE: ((0, 0), (L, 0), (L, D), (0, 2 * D), (L, 2 * U)), CLOCKWISE: ((0, 0), (L, 0), (L, D), (0, 2 * D), (L, 2 * U)),
}, },
) )
classes = [] classes = []
nb_classes = 0 nb_classes = 0
random_bag = [] random_bag = []
def __new__(cls): def __new__(cls):
""" """
Return a Tetromino using the 7-bag Random Generator Return a Tetromino using the 7-bag Random Generator
Tetris uses a bag system to determine the sequence of Tetriminos Tetris uses a bag system to determine the sequence of Tetriminos
that appear during game play. that appear during game play.
This system allows for equal distribution among the seven Tetriminos. This system allows for equal distribution among the seven Tetriminos.
The seven different Tetriminos are placed into a virtual bag, The seven different Tetriminos are placed into a virtual bag,
then shuffled into a random order. then shuffled into a random order.
This order is the sequence that the bag feeds the Next Queue. This order is the sequence that the bag feeds the Next Queue.
Every time a new Tetrimino is generated and starts its fall within the Matrix, Every time a new Tetrimino is generated and starts its fall within the Matrix,
the Tetrimino at the front of the line in the bag is placed at the end of the Next Queue, the Tetrimino at the front of the line in the bag is placed at the end of the Next Queue,
pushing all Tetriminos in the Next Queue forward by one. pushing all Tetriminos in the Next Queue forward by one.
The bag is refilled and reshuffled once it is empty. The bag is refilled and reshuffled once it is empty.
""" """
if not cls.random_bag: if not cls.random_bag:
cls.random_bag = random.sample(cls.classes, cls.nb_classes) cls.random_bag = random.sample(cls.classes, cls.nb_classes)
return super().__new__(cls.random_bag.pop()) return super().__new__(cls.random_bag.pop())
def __init__(self): def __init__(self):
self.orientation = 0 self.orientation = 0
self.t_spin = "" self.t_spin = ""
def insert_into(self, matrix, position): def insert_into(self, matrix, position):
self.matrix = matrix self.matrix = matrix
self.minoes = tuple(Block(Point(*coord) + position) for coord in self.COORDS) self.minoes = tuple(Block(Point(*coord) + position) for coord in self.COORDS)
def _try_movement(self, next_coords_generator, trail=0, update=True): def _try_movement(self, next_coords_generator, trail=0, update=True):
""" """
Test if self can fit in the Grid with new coordinates, Test if self can fit in the Grid with new coordinates,
i.e. all cells are empty. i.e. all cells are empty.
If it can, change self's coordinates and return True. If it can, change self's coordinates and return True.
Else, make no changes and return False Else, make no changes and return False
Update the Grid if there is no drop trail Update the Grid if there is no drop trail
""" """
futures_coords = [] futures_coords = []
for p in next_coords_generator: for p in next_coords_generator:
if not self.matrix.is_empty_cell(p): if not self.matrix.is_empty_cell(p):
return False return False
futures_coords.append(p) futures_coords.append(p)
for block, future_coord in zip(self.minoes, futures_coords): for block, future_coord in zip(self.minoes, futures_coords):
block.coord = future_coord block.coord = future_coord
block.trail = trail block.trail = trail
if update: if update:
self.matrix.update() self.matrix.update()
return True return True
def move(self, horizontally, vertically, trail=0, update=True): def move(self, horizontally, vertically, trail=0, update=True):
""" """
Try to translate self horizontally or vertically Try to translate self horizontally or vertically
The Tetrimino in play falls from just above the Skyline one cell at a time, The Tetrimino in play falls from just above the Skyline one cell at a time,
and moves left and right one cell at a time. and moves left and right one cell at a time.
Each Mino of a Tetrimino snaps to the appropriate cell position at the completion of a move, Each Mino of a Tetrimino snaps to the appropriate cell position at the completion of a move,
although intermediate Tetrimino movement appears smooth. although intermediate Tetrimino movement appears smooth.
Only right, left, and downward movement are allowed. Only right, left, and downward movement are allowed.
Movement into occupied cells and Matrix walls and floors is not allowed Movement into occupied cells and Matrix walls and floors is not allowed
Update the Grid if there is no drop trail Update the Grid if there is no drop trail
""" """
return self._try_movement( return self._try_movement(
(block.coord + Point(horizontally, vertically) for block in self.minoes), (block.coord + Point(horizontally, vertically) for block in self.minoes),
trail, trail,
update update
) )
def rotate(self, direction=CLOCKWISE): def rotate(self, direction=CLOCKWISE):
""" """
Try to rotate self through 90° CLOCKWISE or COUNTERCLOCKWISE around its center Try to rotate self through 90° CLOCKWISE or COUNTERCLOCKWISE around its center
Tetriminos can rotate clockwise and counterclockwise using the Super Rotation System. Tetriminos can rotate clockwise and counterclockwise using the Super Rotation System.
This system allows Tetrimino rotation in situations that This system allows Tetrimino rotation in situations that
the original Classic Rotation System did not allow, the original Classic Rotation System did not allow,
such as rotating against walls. such as rotating against walls.
Each time a rotation button is pressed, Each time a rotation button is pressed,
the Tetrimino in play rotates 90 degrees in the clockwise or counterclockwise direction. the Tetrimino in play rotates 90 degrees in the clockwise or counterclockwise direction.
Rotation can be performed while the Tetrimino is Auto-Repeating left or right. Rotation can be performed while the Tetrimino is Auto-Repeating left or right.
There is no Auto-Repeat for rotation itself. There is no Auto-Repeat for rotation itself.
""" """
rotated_coords = tuple( rotated_coords = tuple(
mino.coord.rotate(self.minoes[0].coord, direction) for mino in self.minoes mino.coord.rotate(self.minoes[0].coord, direction) for mino in self.minoes
) )
for movement in self.SUPER_ROTATION_SYSTEM[self.orientation][direction]: for movement in self.SUPER_ROTATION_SYSTEM[self.orientation][direction]:
if self._try_movement(coord + Point(*movement) for coord in rotated_coords): if self._try_movement(coord + Point(*movement) for coord in rotated_coords):
self.orientation = (self.orientation + direction) % 4 self.orientation = (self.orientation + direction) % 4
return True return True
return False return False
def soft_drop(self): def soft_drop(self):
""" """
Causes the Tetrimino to drop at an accelerated rate (s.AUTO_REPEAT_RATE) Causes the Tetrimino to drop at an accelerated rate (s.AUTO_REPEAT_RATE)
from its current location from its current location
""" """
return self.move(0, D, trail=1) return self.move(0, D, trail=1)
def hard_drop(self, show_trail=True, update=True): def hard_drop(self, show_trail=True, update=True):
""" """
Causes the Tetrimino in play to drop straight down instantly from its Causes the Tetrimino in play to drop straight down instantly from its
current location and Lock Down on the first Surface it lands on. current location and Lock Down on the first Surface it lands on.
It does not allow for further player manipulation of the Tetrimino in play. It does not allow for further player manipulation of the Tetrimino in play.
""" """
trail = 0 trail = 0
while self.move(0, D, trail=trail, update=update): while self.move(0, D, trail=trail, update=update):
if show_trail: if show_trail:
trail += 1 trail += 1
return trail return trail
class TetroI(Tetromino, metaclass=MetaTetro): class TetroI(Tetromino, metaclass=MetaTetro):
""" """
Tetromino shaped like a capital I Tetromino shaped like a capital I
four minoes in a straight line four minoes in a straight line
""" """
COORDS = (L, 0), (2 * L, 0), (0, 0), (R, 0) COORDS = (L, 0), (2 * L, 0), (0, 0), (R, 0)
SUPER_ROTATION_SYSTEM = ( SUPER_ROTATION_SYSTEM = (
{ {
COUNTERCLOCKWISE: ((0, D), (L, D), (2 * R, D), (L, U), (2 * R, 2 * D)), COUNTERCLOCKWISE: ((0, D), (L, D), (2 * R, D), (L, U), (2 * R, 2 * D)),
CLOCKWISE: ((R, 0), (L, 0), (2 * R, 0), (L, D), (2 * R, 2 * U)), CLOCKWISE: ((R, 0), (L, 0), (2 * R, 0), (L, D), (2 * R, 2 * U)),
}, },
{ {
COUNTERCLOCKWISE: ((L, 0), (R, 0), (2 * L, 0), (R, U), (2 * L, 2 * D)), COUNTERCLOCKWISE: ((L, 0), (R, 0), (2 * L, 0), (R, U), (2 * L, 2 * D)),
CLOCKWISE: ((0, D), (L, D), (2 * R, D), (L, U), (2 * R, 2 * D)), CLOCKWISE: ((0, D), (L, D), (2 * R, D), (L, U), (2 * R, 2 * D)),
}, },
{ {
COUNTERCLOCKWISE: ((0, U), (R, U), (2 * L, U), (R, D), (2 * L, 2 * U)), COUNTERCLOCKWISE: ((0, U), (R, U), (2 * L, U), (R, D), (2 * L, 2 * U)),
CLOCKWISE: ((L, 0), (R, 0), (2 * L, 0), (R, U), (2 * L, 2 * D)), CLOCKWISE: ((L, 0), (R, 0), (2 * L, 0), (R, U), (2 * L, 2 * D)),
}, },
{ {
COUNTERCLOCKWISE: ((R, 0), (L, 0), (2 * R, 0), (L, D), (2 * R, 2 * U)), COUNTERCLOCKWISE: ((R, 0), (L, 0), (2 * R, 0), (L, D), (2 * R, 2 * U)),
CLOCKWISE: ((0, U), (R, U), (2 * L, U), (R, D), (2 * L, 2 * U)), CLOCKWISE: ((0, U), (R, U), (2 * L, U), (R, D), (2 * L, 2 * U)),
}, },
) )
class TetroT(Tetromino, metaclass=MetaTetro): class TetroT(Tetromino, metaclass=MetaTetro):
""" """
Tetromino shaped like a capital T Tetromino shaped like a capital T
a row of three minoes with one added above the center a row of three minoes with one added above the center
Can perform a T-Spin Can perform a T-Spin
""" """
COORDS = (0, 0), (L, 0), (0, U), (R, 0) COORDS = (0, 0), (L, 0), (0, U), (R, 0)
T_SLOT_A = ((L, U), (R, U), (R, D), (L, D)) T_SLOT_A = ((L, U), (R, U), (R, D), (L, D))
T_SLOT_B = ((R, U), (R, D), (L, D), (L, U)) T_SLOT_B = ((R, U), (R, D), (L, D), (L, U))
T_SLOT_C = ((L, D), (L, U), (R, U), (R, D)) T_SLOT_C = ((L, D), (L, U), (R, U), (R, D))
T_SLOT_D = ((R, D), (L, D), (L, U), (R, U)) T_SLOT_D = ((R, D), (L, D), (L, U), (R, U))
def __init__(self): def __init__(self):
super().__init__() super().__init__()
def rotate(self, direction=CLOCKWISE): def rotate(self, direction=CLOCKWISE):
""" """
Detects T-Spins: Detects T-Spins:
this action can be achieved by first landing a T-Tetrimino, this action can be achieved by first landing a T-Tetrimino,
and before it Locks Down, rotating it in a T-Slot and before it Locks Down, rotating it in a T-Slot
(any Block formation such that when the T-Tetrimino is spun into it, (any Block formation such that when the T-Tetrimino is spun into it,
any three of the four cells diagonally adjacent to the center of self any three of the four cells diagonally adjacent to the center of self
are occupied by existing Blocks.) are occupied by existing Blocks.)
""" """
rotated = super().rotate(direction) rotated = super().rotate(direction)
if rotated: if rotated:
center = self.minoes[0].coord center = self.minoes[0].coord
pa = center + Point(*self.T_SLOT_A[self.orientation]) pa = center + Point(*self.T_SLOT_A[self.orientation])
pb = center + Point(*self.T_SLOT_B[self.orientation]) pb = center + Point(*self.T_SLOT_B[self.orientation])
pc = center + Point(*self.T_SLOT_C[self.orientation]) pc = center + Point(*self.T_SLOT_C[self.orientation])
pd = center + Point(*self.T_SLOT_D[self.orientation]) pd = center + Point(*self.T_SLOT_D[self.orientation])
a = not self.matrix.is_empty_cell(pa) a = not self.matrix.is_empty_cell(pa)
b = not self.matrix.is_empty_cell(pb) b = not self.matrix.is_empty_cell(pb)
c = not self.matrix.is_empty_cell(pc) c = not self.matrix.is_empty_cell(pc)
d = not self.matrix.is_empty_cell(pd) d = not self.matrix.is_empty_cell(pd)
if (a and b) and (c or d): if (a and b) and (c or d):
if c: if c:
pe = (pa + pc) / 2 pe = (pa + pc) / 2
elif d: elif d:
pe = (pb + pd) / 2 pe = (pb + pd) / 2
if not self.matrix.is_empty_cell(pe): if not self.matrix.is_empty_cell(pe):
self.t_spin = "T-Spin" self.t_spin = "T-Spin"
elif (a or b) and (c and d): elif (a or b) and (c and d):
self.t_spin = "Mini T-Spin" self.t_spin = "Mini T-Spin"
return rotated return rotated
class TetroZ(Tetromino, metaclass=MetaTetro): class TetroZ(Tetromino, metaclass=MetaTetro):
""" """
Tetromino shaped like a capital Z Tetromino shaped like a capital Z
two stacked horizontal dominoes with the top one offset to the left two stacked horizontal dominoes with the top one offset to the left
""" """
COORDS = (0, 0), (L, U), (0, U), (R, 0) COORDS = (0, 0), (L, U), (0, U), (R, 0)
class TetroS(Tetromino, metaclass=MetaTetro): class TetroS(Tetromino, metaclass=MetaTetro):
""" """
Tetromino shaped like a capital S Tetromino shaped like a capital S
two stacked horizontal dominoes with the top one offset to the right two stacked horizontal dominoes with the top one offset to the right
""" """
COORDS = (0, 0), (0, U), (L, 0), (R, U) COORDS = (0, 0), (0, U), (L, 0), (R, U)
class TetroL(Tetromino, metaclass=MetaTetro): class TetroL(Tetromino, metaclass=MetaTetro):
""" """
Tetromino shaped like a capital L Tetromino shaped like a capital L
a row of three minoes with one added above the right side a row of three minoes with one added above the right side
""" """
COORDS = (0, 0), (L, 0), (R, 0), (R, U) COORDS = (0, 0), (L, 0), (R, 0), (R, U)
class TetroJ(Tetromino, metaclass=MetaTetro): class TetroJ(Tetromino, metaclass=MetaTetro):
""" """
Tetromino shaped like a capital J Tetromino shaped like a capital J
a row of three minoes with one added above the left side a row of three minoes with one added above the left side
""" """
COORDS = (0, 0), (L, U), (L, 0), (R, 0) COORDS = (0, 0), (L, U), (L, 0), (R, 0)
class TetroO(Tetromino, metaclass=MetaTetro): class TetroO(Tetromino, metaclass=MetaTetro):
""" """
Square shape Square shape
four minoes in a 2×2 square. four minoes in a 2×2 square.
""" """
COORDS = (0, 0), (L, 0), (0, U), (L, U) COORDS = (0, 0), (L, 0), (0, U), (L, U)
def rotate(self, direction=1): def rotate(self, direction=1):
""" irrelevant """ """ irrelevant """
return False return False
class GhostPiece(Tetromino): class GhostPiece(Tetromino):
""" """
A graphical representation of where the Tetrimino in play will come to rest A graphical representation of where the Tetrimino in play will come to rest
if it is dropped from its current position. if it is dropped from its current position.
""" """
def __new__(cls, piece): def __new__(cls, piece):
return object.__new__(cls) return object.__new__(cls)
def __init__(self, piece): def __init__(self, piece):
self.matrix = piece.matrix self.matrix = piece.matrix
self.minoes = tuple( self.minoes = tuple(
GhostBlock(Point(mino.coord.x, mino.coord.y)) for mino in piece.minoes GhostBlock(Point(mino.coord.x, mino.coord.y)) for mino in piece.minoes
) )
self.hard_drop(show_trail=False, update=False) self.hard_drop(show_trail=False, update=False)