Compare commits
27 Commits
0.3
..
ee7e6fcdb9
| Author | SHA1 | Date | |
|---|---|---|---|
| ee7e6fcdb9 | |||
| e7f3146e9a | |||
| deba1a2daf | |||
| c5c21c5017 | |||
| 28a8ea0953 | |||
| 5367e77149 | |||
| af005f72ca | |||
| 363a89a590 | |||
| f9c1fe4688 | |||
| a0a414db14 | |||
| 4522ac1d4b | |||
| e3e05e87d7 | |||
| 82f2b74e68 | |||
| 504ebf8e51 | |||
| 4452eb821c | |||
| e041a8118a | |||
| 0db5dd4d0d | |||
| fe69557bc6 | |||
| 32bf60313c | |||
| f013a061b2 | |||
| f025ad5fd8 | |||
| 9a7aead918 | |||
| b173b6ff73 | |||
| ddf7ea0f4e | |||
| d308618556 | |||
| 9c77096bfb | |||
| 093264c351 |
@@ -6,7 +6,7 @@ Tetris clone made with Python and Arcade graphic library
|
|||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
* [Python](https://www.python.org/) 3.6 or upper
|
* [Python](https://www.python.org/) 3.6 or later
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
|
|||||||
+261
-180
@@ -1,10 +1,13 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import sys
|
import sys
|
||||||
|
import random
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import arcade
|
import arcade
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
sys.exit(
|
sys.exit(
|
||||||
str(e) + """
|
str(e)
|
||||||
|
+ """
|
||||||
This game require arcade library.
|
This game require arcade library.
|
||||||
You can install it with:
|
You can install it with:
|
||||||
python -m pip install --user arcade"""
|
python -m pip install --user arcade"""
|
||||||
@@ -17,25 +20,26 @@ import os
|
|||||||
import itertools
|
import itertools
|
||||||
import configparser
|
import configparser
|
||||||
|
|
||||||
from tetrislogic import TetrisLogic, Color, State, Coord
|
from tetrislogic import TetrisLogic, Color, Coord, I_Tetrimino, Movement, AbstractTimer
|
||||||
|
|
||||||
|
|
||||||
# Constants
|
# Constants
|
||||||
# Matrix
|
# Matrix
|
||||||
NB_LINES = 20
|
LINES = 20
|
||||||
NB_COLS = 10
|
COLLUMNS = 10
|
||||||
NB_NEXT = 5
|
NEXT_PIECES = 6
|
||||||
|
|
||||||
# Delays (seconds)
|
# Delays (seconds)
|
||||||
LOCK_DELAY = 0.5
|
LOCK_DELAY = 0.5
|
||||||
FALL_DELAY = 1
|
FALL_DELAY = 1
|
||||||
AUTOREPEAT_DELAY = 0.300
|
AUTOREPEAT_DELAY = 0.300
|
||||||
AUTOREPEAT_PERIOD = 0.010
|
AUTOREPEAT_PERIOD = 0.010
|
||||||
|
PARTICULE_ACCELERATION = 1.1
|
||||||
|
|
||||||
# Piece init coord
|
# Piece init coord
|
||||||
MATRIX_PIECE_COORD = Coord(4, NB_LINES)
|
MATRIX_PIECE_COORD = Coord(4, LINES)
|
||||||
NEXT_PIECE_COORDS = [Coord(NB_COLS + 4, NB_LINES - 4 * n - 3) for n in range(NB_NEXT)]
|
NEXT_PIECES_COORDS = [Coord(COLLUMNS + 4, LINES - 4 * n) for n in range(NEXT_PIECES)]
|
||||||
HELD_PIECE_COORD = Coord(-5, NB_LINES - 3)
|
HELD_PIECE_COORD = Coord(-5, LINES)
|
||||||
|
|
||||||
# Window
|
# Window
|
||||||
WINDOW_WIDTH = 800
|
WINDOW_WIDTH = 800
|
||||||
@@ -49,7 +53,7 @@ BG_COLOR = (7, 11, 21)
|
|||||||
HIGHLIGHT_TEXT_DISPLAY_DELAY = 0.7
|
HIGHLIGHT_TEXT_DISPLAY_DELAY = 0.7
|
||||||
|
|
||||||
# Transparency (0=invisible, 255=opaque)
|
# Transparency (0=invisible, 255=opaque)
|
||||||
NORMAL_ALPHA = 200
|
NORMAL_ALPHA = 255
|
||||||
PRELOCKED_ALPHA = 100
|
PRELOCKED_ALPHA = 100
|
||||||
GHOST_ALPHA = 30
|
GHOST_ALPHA = 30
|
||||||
MATRIX_BG_ALPHA = 100
|
MATRIX_BG_ALPHA = 100
|
||||||
@@ -59,7 +63,7 @@ BAR_ALPHA = 75
|
|||||||
MINO_SIZE = 20
|
MINO_SIZE = 20
|
||||||
MINO_SPRITE_SIZE = 21
|
MINO_SPRITE_SIZE = 21
|
||||||
|
|
||||||
if getattr(sys, 'frozen', False):
|
if getattr(sys, "frozen", False):
|
||||||
# The application is frozen
|
# The application is frozen
|
||||||
PROGRAM_DIR = os.path.dirname(sys.executable)
|
PROGRAM_DIR = os.path.dirname(sys.executable)
|
||||||
else:
|
else:
|
||||||
@@ -71,10 +75,8 @@ RESOURCES_DIR = os.path.join(PROGRAM_DIR, "resources")
|
|||||||
IMAGES_DIR = os.path.join(RESOURCES_DIR, "images")
|
IMAGES_DIR = os.path.join(RESOURCES_DIR, "images")
|
||||||
WINDOW_BG_PATH = os.path.join(IMAGES_DIR, "bg.jpg")
|
WINDOW_BG_PATH = os.path.join(IMAGES_DIR, "bg.jpg")
|
||||||
MATRIX_BG_PATH = os.path.join(IMAGES_DIR, "matrix.png")
|
MATRIX_BG_PATH = os.path.join(IMAGES_DIR, "matrix.png")
|
||||||
HELD_BG_PATH = os.path.join(IMAGES_DIR, "held.png")
|
|
||||||
NEXT_BG_PATH = os.path.join(IMAGES_DIR, "next.png")
|
|
||||||
MINOES_SPRITES_PATH = os.path.join(IMAGES_DIR, "minoes.png")
|
MINOES_SPRITES_PATH = os.path.join(IMAGES_DIR, "minoes.png")
|
||||||
Color.PRELOCKED = 7
|
Color.LOCKED = 7
|
||||||
MINOES_COLOR_ID = {
|
MINOES_COLOR_ID = {
|
||||||
Color.BLUE: 0,
|
Color.BLUE: 0,
|
||||||
Color.CYAN: 1,
|
Color.CYAN: 1,
|
||||||
@@ -83,10 +85,11 @@ MINOES_COLOR_ID = {
|
|||||||
Color.ORANGE: 4,
|
Color.ORANGE: 4,
|
||||||
Color.RED: 5,
|
Color.RED: 5,
|
||||||
Color.YELLOW: 6,
|
Color.YELLOW: 6,
|
||||||
Color.PRELOCKED: 7,
|
Color.LOCKED: 7,
|
||||||
}
|
}
|
||||||
TEXTURES = arcade.load_textures(
|
TEXTURES = arcade.load_textures(
|
||||||
MINOES_SPRITES_PATH, ((i * MINO_SPRITE_SIZE, 0, MINO_SPRITE_SIZE, MINO_SPRITE_SIZE) for i in range(8))
|
MINOES_SPRITES_PATH,
|
||||||
|
((i * MINO_SPRITE_SIZE, 0, MINO_SPRITE_SIZE, MINO_SPRITE_SIZE) for i in range(8)),
|
||||||
)
|
)
|
||||||
TEXTURES = {color: TEXTURES[i] for color, i in MINOES_COLOR_ID.items()}
|
TEXTURES = {color: TEXTURES[i] for color, i in MINOES_COLOR_ID.items()}
|
||||||
|
|
||||||
@@ -105,42 +108,85 @@ HIGHLIGHT_TEXT_SIZE = 20
|
|||||||
|
|
||||||
# User profile path
|
# User profile path
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
USER_PROFILE_DIR = os.environ.get("appdata", os.path.expanduser("~\Appdata\Roaming"))
|
USER_PROFILE_DIR = os.environ.get(
|
||||||
|
"appdata", os.path.expanduser("~\Appdata\Roaming")
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
USER_PROFILE_DIR = os.environ.get("XDG_DATA_HOME", os.path.expanduser("~/.local/share"))
|
USER_PROFILE_DIR = os.environ.get(
|
||||||
|
"XDG_DATA_HOME", os.path.expanduser("~/.local/share")
|
||||||
|
)
|
||||||
USER_PROFILE_DIR = os.path.join(USER_PROFILE_DIR, "TetrArcade")
|
USER_PROFILE_DIR = os.path.join(USER_PROFILE_DIR, "TetrArcade")
|
||||||
HIGH_SCORE_PATH = os.path.join(USER_PROFILE_DIR, ".high_score")
|
HIGH_SCORE_PATH = os.path.join(USER_PROFILE_DIR, ".high_score")
|
||||||
CONF_PATH = os.path.join(USER_PROFILE_DIR, "TetrArcade.ini")
|
CONF_PATH = os.path.join(USER_PROFILE_DIR, "config.ini")
|
||||||
|
|
||||||
|
|
||||||
|
class Texture:
|
||||||
|
|
||||||
|
NORMAL = 0
|
||||||
|
LOCKED = 1
|
||||||
|
|
||||||
|
|
||||||
|
class State:
|
||||||
|
|
||||||
|
STARTING = 0
|
||||||
|
PLAYING = 1
|
||||||
|
PAUSED = 2
|
||||||
|
OVER = 3
|
||||||
|
|
||||||
|
|
||||||
|
class Timer(AbstractTimer):
|
||||||
|
def __init__(self):
|
||||||
|
self.tasks = {}
|
||||||
|
|
||||||
|
def postpone(self, task, delay):
|
||||||
|
_task = lambda _: task()
|
||||||
|
self.tasks[task] = _task
|
||||||
|
pyglet.clock.schedule_once(_task, delay)
|
||||||
|
|
||||||
|
def cancel(self, task):
|
||||||
|
try:
|
||||||
|
_task = self.tasks[task]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
arcade.unschedule(_task)
|
||||||
|
del self.tasks[task]
|
||||||
|
|
||||||
|
def reset(self, task, delay):
|
||||||
|
try:
|
||||||
|
_task = self.tasks[task]
|
||||||
|
except KeyError:
|
||||||
|
_task = lambda _: task()
|
||||||
|
self.tasks[task] = _task
|
||||||
|
else:
|
||||||
|
arcade.unschedule(_task)
|
||||||
|
pyglet.clock.schedule_once(_task, delay)
|
||||||
|
|
||||||
|
|
||||||
class MinoSprite(arcade.Sprite):
|
class MinoSprite(arcade.Sprite):
|
||||||
|
|
||||||
def __init__(self, mino, window, alpha):
|
def __init__(self, mino, window, alpha):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.alpha = alpha
|
self.alpha = alpha
|
||||||
self.window = window
|
self.window = window
|
||||||
self.append_texture(TEXTURES[mino.color])
|
self.append_texture(TEXTURES[mino.color])
|
||||||
self.append_texture(TEXTURES[Color.PRELOCKED])
|
self.append_texture(TEXTURES[Color.LOCKED])
|
||||||
self.set_texture(0)
|
self.set_texture(0)
|
||||||
|
|
||||||
def refresh(self, x, y, prelocked=False):
|
def update(self, x, y):
|
||||||
self.scale = self.window.scale
|
self.scale = self.window.scale
|
||||||
size = MINO_SIZE * self.scale
|
size = MINO_SIZE * self.scale
|
||||||
self.left = self.window.matrix.bg.left + x * size
|
self.left = self.window.matrix.bg.left + x * size
|
||||||
self.bottom = self.window.matrix.bg.bottom + y * size
|
self.bottom = self.window.matrix.bg.bottom + y * size
|
||||||
self.set_texture(prelocked)
|
|
||||||
|
|
||||||
|
|
||||||
class MinoesSprites(arcade.SpriteList):
|
class MinoesSprites(arcade.SpriteList):
|
||||||
|
|
||||||
def resize(self, scale):
|
def resize(self, scale):
|
||||||
for sprite in self:
|
for sprite in self:
|
||||||
sprite.scale = scale
|
sprite.scale = scale
|
||||||
self.refresh()
|
self.update()
|
||||||
|
|
||||||
|
|
||||||
class TetrominoSprites(MinoesSprites):
|
class TetrominoSprites(MinoesSprites):
|
||||||
|
|
||||||
def __init__(self, tetromino, window, alpha=NORMAL_ALPHA):
|
def __init__(self, tetromino, window, alpha=NORMAL_ALPHA):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.tetromino = tetromino
|
self.tetromino = tetromino
|
||||||
@@ -149,24 +195,28 @@ class TetrominoSprites(MinoesSprites):
|
|||||||
mino.sprite = MinoSprite(mino, window, alpha)
|
mino.sprite = MinoSprite(mino, window, alpha)
|
||||||
self.append(mino.sprite)
|
self.append(mino.sprite)
|
||||||
|
|
||||||
def refresh(self):
|
def update(self):
|
||||||
for mino in self.tetromino:
|
for mino in self.tetromino:
|
||||||
coord = mino.coord + self.tetromino.coord
|
coord = mino.coord + self.tetromino.coord
|
||||||
mino.sprite.refresh(coord.x, coord.y, self.tetromino.prelocked)
|
mino.sprite.update(coord.x, coord.y)
|
||||||
|
|
||||||
|
def set_texture(self, texture):
|
||||||
|
for mino in self.tetromino:
|
||||||
|
mino.sprite.set_texture(texture)
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
|
||||||
class MatrixSprites(MinoesSprites):
|
class MatrixSprites(MinoesSprites):
|
||||||
|
|
||||||
def __init__(self, matrix):
|
def __init__(self, matrix):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.matrix = matrix
|
self.matrix = matrix
|
||||||
self.refresh()
|
self.update()
|
||||||
|
|
||||||
def refresh(self):
|
def update(self):
|
||||||
for y, line in enumerate(self.matrix):
|
for y, line in enumerate(self.matrix):
|
||||||
for x, mino in enumerate(line):
|
for x, mino in enumerate(line):
|
||||||
if mino:
|
if mino:
|
||||||
mino.sprite.refresh(x, y)
|
mino.sprite.update(x, y)
|
||||||
|
|
||||||
def remove_line(self, y):
|
def remove_line(self, y):
|
||||||
for mino in self.matrix[y]:
|
for mino in self.matrix[y]:
|
||||||
@@ -176,21 +226,11 @@ class MatrixSprites(MinoesSprites):
|
|||||||
|
|
||||||
class TetrArcade(TetrisLogic, arcade.Window):
|
class TetrArcade(TetrisLogic, arcade.Window):
|
||||||
|
|
||||||
NB_LINES = NB_LINES
|
timer = Timer()
|
||||||
NB_COLS = NB_COLS
|
|
||||||
NB_NEXT = NB_NEXT
|
|
||||||
LOCK_DELAY = LOCK_DELAY
|
|
||||||
FALL_DELAY = FALL_DELAY
|
|
||||||
AUTOREPEAT_DELAY = AUTOREPEAT_DELAY
|
|
||||||
AUTOREPEAT_PERIOD = AUTOREPEAT_PERIOD
|
|
||||||
MATRIX_PIECE_COORD = MATRIX_PIECE_COORD
|
|
||||||
NEXT_PIECE_COORDS = NEXT_PIECE_COORDS
|
|
||||||
HELD_PIECE_COORD = HELD_PIECE_COORD
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
locale.setlocale(locale.LC_ALL, "")
|
locale.setlocale(locale.LC_ALL, "")
|
||||||
self.highlight_texts = []
|
self.highlight_texts = []
|
||||||
self.tasks = {}
|
|
||||||
|
|
||||||
self.conf = configparser.ConfigParser()
|
self.conf = configparser.ConfigParser()
|
||||||
if self.conf.read(CONF_PATH):
|
if self.conf.read(CONF_PATH):
|
||||||
@@ -203,7 +243,7 @@ class TetrArcade(TetrisLogic, arcade.Window):
|
|||||||
self.new_conf()
|
self.new_conf()
|
||||||
self.load_conf()
|
self.load_conf()
|
||||||
|
|
||||||
super().__init__()
|
super().__init__(LINES, COLLUMNS, NEXT_PIECES)
|
||||||
arcade.Window.__init__(
|
arcade.Window.__init__(
|
||||||
self,
|
self,
|
||||||
width=self.init_width,
|
width=self.init_width,
|
||||||
@@ -219,23 +259,31 @@ class TetrArcade(TetrisLogic, arcade.Window):
|
|||||||
self.bg = arcade.Sprite(WINDOW_BG_PATH)
|
self.bg = arcade.Sprite(WINDOW_BG_PATH)
|
||||||
self.matrix.bg = arcade.Sprite(MATRIX_BG_PATH)
|
self.matrix.bg = arcade.Sprite(MATRIX_BG_PATH)
|
||||||
self.matrix.bg.alpha = MATRIX_BG_ALPHA
|
self.matrix.bg.alpha = MATRIX_BG_ALPHA
|
||||||
self.held.bg = arcade.Sprite(HELD_BG_PATH)
|
|
||||||
self.held.bg.alpha = BAR_ALPHA
|
|
||||||
self.next.bg = arcade.Sprite(NEXT_BG_PATH)
|
|
||||||
self.next.bg.alpha = BAR_ALPHA
|
|
||||||
self.matrix.sprites = MatrixSprites(self.matrix)
|
self.matrix.sprites = MatrixSprites(self.matrix)
|
||||||
self.on_resize(self.init_width, self.init_height)
|
self.on_resize(self.init_width, self.init_height)
|
||||||
|
self.exploding_minoes = [None for y in range(LINES)]
|
||||||
|
|
||||||
if self.play_music:
|
if self.play_music:
|
||||||
self.music = pyglet.media.Player()
|
try:
|
||||||
playlist = itertools.cycle(
|
self.music = pyglet.media.Player()
|
||||||
pyglet.media.load(path)
|
playlist = itertools.cycle(
|
||||||
for path in MUSICS_PATHS
|
pyglet.media.load(path) for path in MUSICS_PATHS
|
||||||
)
|
)
|
||||||
self.music.queue(playlist)
|
self.music.queue(playlist)
|
||||||
|
except:
|
||||||
|
Warning("Can't play music.")
|
||||||
|
self.music = None
|
||||||
|
else:
|
||||||
|
self.music = None
|
||||||
|
|
||||||
|
self.state = State.STARTING
|
||||||
|
|
||||||
def new_conf(self):
|
def new_conf(self):
|
||||||
self.conf["WINDOW"] = {"width": WINDOW_WIDTH, "height": WINDOW_HEIGHT, "fullscreen": False}
|
self.conf["WINDOW"] = {
|
||||||
|
"width": WINDOW_WIDTH,
|
||||||
|
"height": WINDOW_HEIGHT,
|
||||||
|
"fullscreen": False,
|
||||||
|
}
|
||||||
self.conf["KEYBOARD"] = {
|
self.conf["KEYBOARD"] = {
|
||||||
"start": "ENTER",
|
"start": "ENTER",
|
||||||
"move left": "LEFT",
|
"move left": "LEFT",
|
||||||
@@ -248,9 +296,7 @@ class TetrArcade(TetrisLogic, arcade.Window):
|
|||||||
"pause": "ESCAPE",
|
"pause": "ESCAPE",
|
||||||
"fullscreen": "F11",
|
"fullscreen": "F11",
|
||||||
}
|
}
|
||||||
self.conf["MUSIC"] = {
|
self.conf["MUSIC"] = {"play": True}
|
||||||
"play": True
|
|
||||||
}
|
|
||||||
self.conf["AUTO-REPEAT"] = {"delay": 0.3, "period": 0.01}
|
self.conf["AUTO-REPEAT"] = {"delay": 0.3, "period": 0.01}
|
||||||
self.load_conf()
|
self.load_conf()
|
||||||
if not os.path.exists(USER_PROFILE_DIR):
|
if not os.path.exists(USER_PROFILE_DIR):
|
||||||
@@ -268,26 +314,40 @@ class TetrArcade(TetrisLogic, arcade.Window):
|
|||||||
self.key_map = {
|
self.key_map = {
|
||||||
State.STARTING: {
|
State.STARTING: {
|
||||||
getattr(arcade.key, self.conf["KEYBOARD"]["start"]): self.new_game,
|
getattr(arcade.key, self.conf["KEYBOARD"]["start"]): self.new_game,
|
||||||
getattr(arcade.key, self.conf["KEYBOARD"]["fullscreen"]): self.toggle_fullscreen,
|
getattr(
|
||||||
|
arcade.key, self.conf["KEYBOARD"]["fullscreen"]
|
||||||
|
): self.toggle_fullscreen,
|
||||||
},
|
},
|
||||||
State.PLAYING: {
|
State.PLAYING: {
|
||||||
getattr(arcade.key, self.conf["KEYBOARD"]["move left"]): self.move_left,
|
getattr(arcade.key, self.conf["KEYBOARD"]["move left"]): self.move_left,
|
||||||
getattr(arcade.key, self.conf["KEYBOARD"]["move right"]): self.move_right,
|
getattr(
|
||||||
|
arcade.key, self.conf["KEYBOARD"]["move right"]
|
||||||
|
): self.move_right,
|
||||||
getattr(arcade.key, self.conf["KEYBOARD"]["soft drop"]): self.soft_drop,
|
getattr(arcade.key, self.conf["KEYBOARD"]["soft drop"]): self.soft_drop,
|
||||||
getattr(arcade.key, self.conf["KEYBOARD"]["hard drop"]): self.hard_drop,
|
getattr(arcade.key, self.conf["KEYBOARD"]["hard drop"]): self.hard_drop,
|
||||||
getattr(arcade.key, self.conf["KEYBOARD"]["rotate clockwise"]): self.rotate_clockwise,
|
getattr(
|
||||||
getattr(arcade.key, self.conf["KEYBOARD"]["rotate counter"]): self.rotate_counter,
|
arcade.key, self.conf["KEYBOARD"]["rotate clockwise"]
|
||||||
getattr(arcade.key, self.conf["KEYBOARD"]["hold"]): self.swap,
|
): self.rotate_clockwise,
|
||||||
|
getattr(
|
||||||
|
arcade.key, self.conf["KEYBOARD"]["rotate counter"]
|
||||||
|
): self.rotate_counter,
|
||||||
|
getattr(arcade.key, self.conf["KEYBOARD"]["hold"]): self.hold,
|
||||||
getattr(arcade.key, self.conf["KEYBOARD"]["pause"]): self.pause,
|
getattr(arcade.key, self.conf["KEYBOARD"]["pause"]): self.pause,
|
||||||
getattr(arcade.key, self.conf["KEYBOARD"]["fullscreen"]): self.toggle_fullscreen,
|
getattr(
|
||||||
|
arcade.key, self.conf["KEYBOARD"]["fullscreen"]
|
||||||
|
): self.toggle_fullscreen,
|
||||||
},
|
},
|
||||||
State.PAUSED: {
|
State.PAUSED: {
|
||||||
getattr(arcade.key, self.conf["KEYBOARD"]["pause"]): self.resume,
|
getattr(arcade.key, self.conf["KEYBOARD"]["pause"]): self.resume,
|
||||||
getattr(arcade.key, self.conf["KEYBOARD"]["fullscreen"]): self.toggle_fullscreen,
|
getattr(
|
||||||
|
arcade.key, self.conf["KEYBOARD"]["fullscreen"]
|
||||||
|
): self.toggle_fullscreen,
|
||||||
},
|
},
|
||||||
State.OVER: {
|
State.OVER: {
|
||||||
getattr(arcade.key, self.conf["KEYBOARD"]["start"]): self.new_game,
|
getattr(arcade.key, self.conf["KEYBOARD"]["start"]): self.new_game,
|
||||||
getattr(arcade.key, self.conf["KEYBOARD"]["fullscreen"]): self.toggle_fullscreen,
|
getattr(
|
||||||
|
arcade.key, self.conf["KEYBOARD"]["fullscreen"]
|
||||||
|
): self.toggle_fullscreen,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,12 +358,21 @@ class TetrArcade(TetrisLogic, arcade.Window):
|
|||||||
"\n\n\nCONTROLS\n\n"
|
"\n\n\nCONTROLS\n\n"
|
||||||
+ "\n".join(
|
+ "\n".join(
|
||||||
"{:<16s}{:>6s}".format(key, action)
|
"{:<16s}{:>6s}".format(key, action)
|
||||||
for key, action in tuple(self.conf["KEYBOARD"].items()) + (("QUIT", "ALT+F4"),)
|
for key, action in tuple(self.conf["KEYBOARD"].items())
|
||||||
|
+ (("QUIT", "ALT+F4"),)
|
||||||
)
|
)
|
||||||
+ "\n\n\n"
|
+ "\n\n\n"
|
||||||
)
|
)
|
||||||
self.start_text = "TETRARCADE" + controls_text + "PRESS [{}] TO START".format(self.conf["KEYBOARD"]["start"])
|
self.start_text = (
|
||||||
self.pause_text = "PAUSE" + controls_text + "PRESS [{}] TO RESUME".format(self.conf["KEYBOARD"]["pause"])
|
"TETRARCADE"
|
||||||
|
+ controls_text
|
||||||
|
+ "PRESS [{}] TO START".format(self.conf["KEYBOARD"]["start"])
|
||||||
|
)
|
||||||
|
self.pause_text = (
|
||||||
|
"PAUSE"
|
||||||
|
+ controls_text
|
||||||
|
+ "PRESS [{}] TO RESUME".format(self.conf["KEYBOARD"]["pause"])
|
||||||
|
)
|
||||||
self.game_over_text = """GAME
|
self.game_over_text = """GAME
|
||||||
OVER
|
OVER
|
||||||
|
|
||||||
@@ -316,124 +385,148 @@ AGAIN""".format(
|
|||||||
|
|
||||||
self.play_music = self.conf["MUSIC"].getboolean("play")
|
self.play_music = self.conf["MUSIC"].getboolean("play")
|
||||||
|
|
||||||
def new_game(self):
|
def on_new_game(self, next_pieces):
|
||||||
self.highlight_texts = []
|
self.highlight_texts = []
|
||||||
super().new_game()
|
|
||||||
self.matrix.sprites = MatrixSprites(self.matrix)
|
self.matrix.sprites = MatrixSprites(self.matrix)
|
||||||
if self.play_music:
|
for piece in next_pieces:
|
||||||
|
piece.sprites = TetrominoSprites(piece, self)
|
||||||
|
|
||||||
|
if self.music:
|
||||||
self.music.seek(0)
|
self.music.seek(0)
|
||||||
self.music.play()
|
self.music.play()
|
||||||
|
|
||||||
def new_tetromino(self):
|
self.state = State.PLAYING
|
||||||
tetromino = super().new_tetromino()
|
|
||||||
tetromino.sprites = TetrominoSprites(tetromino, self)
|
|
||||||
return tetromino
|
|
||||||
|
|
||||||
def new_matrix_piece(self):
|
def on_new_level(self, level):
|
||||||
super().new_matrix_piece()
|
self.show_text("LEVEL\n{:n}".format(level))
|
||||||
self.matrix.ghost.sprites = TetrominoSprites(self.matrix.ghost, self, GHOST_ALPHA)
|
|
||||||
for tetromino in [self.matrix.piece, self.matrix.ghost] + self.next.pieces:
|
|
||||||
tetromino.sprites.refresh()
|
|
||||||
|
|
||||||
def move(self, movement, prelock=True):
|
def on_generation_phase(self, matrix, falling_piece, ghost_piece, next_pieces):
|
||||||
moved = super().move(movement, prelock)
|
matrix.sprites.update()
|
||||||
self.matrix.piece.sprites.refresh()
|
falling_piece.sprites = TetrominoSprites(falling_piece, self)
|
||||||
if moved:
|
ghost_piece.sprites = TetrominoSprites(ghost_piece, self, GHOST_ALPHA)
|
||||||
self.matrix.ghost.sprites.refresh()
|
next_pieces[-1].sprites = TetrominoSprites(next_pieces[-1], self)
|
||||||
return moved
|
for piece, coord in zip(next_pieces, NEXT_PIECES_COORDS):
|
||||||
|
piece.coord = coord
|
||||||
|
|
||||||
def rotate(self, rotation):
|
def on_falling_phase(self, falling_piece):
|
||||||
rotated = super().rotate(rotation)
|
falling_piece.sprites.set_texture(Texture.NORMAL)
|
||||||
if rotated:
|
|
||||||
for tetromino in (self.matrix.piece, self.matrix.ghost):
|
|
||||||
tetromino.sprites.refresh()
|
|
||||||
return rotated
|
|
||||||
|
|
||||||
def swap(self):
|
def on_locked(self, falling_piece):
|
||||||
super().swap()
|
falling_piece.sprites.set_texture(Texture.LOCKED)
|
||||||
self.matrix.ghost.sprites = TetrominoSprites(self.matrix.ghost, self, GHOST_ALPHA)
|
|
||||||
for tetromino in (self.held.piece, self.matrix.piece, self.matrix.ghost):
|
|
||||||
if tetromino:
|
|
||||||
tetromino.sprites.refresh()
|
|
||||||
|
|
||||||
def lock(self):
|
def on_locks_down(self, matrix, falling_piece):
|
||||||
self.matrix.piece.prelocked = False
|
falling_piece.sprites.set_texture(Texture.NORMAL)
|
||||||
self.matrix.piece.sprites.refresh()
|
for mino in falling_piece:
|
||||||
super().lock()
|
matrix.sprites.append(mino.sprite)
|
||||||
self.matrix.sprites.refresh()
|
|
||||||
|
|
||||||
def enter_the_matrix(self):
|
def on_animate_phase(self, matrix, lines_to_remove):
|
||||||
super().enter_the_matrix()
|
for y in lines_to_remove:
|
||||||
for mino in self.matrix.piece:
|
line_textures = tuple(TEXTURES[mino.color] for mino in matrix[y])
|
||||||
self.matrix.sprites.append(mino.sprite)
|
self.exploding_minoes[y] = arcade.Emitter(
|
||||||
|
center_xy=(matrix.bg.left, matrix.bg.bottom + (y + 0.5) * MINO_SIZE),
|
||||||
|
emit_controller=arcade.EmitBurst(COLLUMNS),
|
||||||
|
particle_factory=lambda emitter: arcade.LifetimeParticle(
|
||||||
|
filename_or_texture=random.choice(line_textures),
|
||||||
|
change_xy=arcade.rand_in_rect(
|
||||||
|
(-COLLUMNS * MINO_SIZE, -4 * MINO_SIZE),
|
||||||
|
2 * COLLUMNS * MINO_SIZE,
|
||||||
|
5 * MINO_SIZE,
|
||||||
|
),
|
||||||
|
lifetime=0.2,
|
||||||
|
center_xy=arcade.rand_on_line((0, 0), (matrix.bg.width, 0)),
|
||||||
|
scale=self.scale,
|
||||||
|
alpha=NORMAL_ALPHA,
|
||||||
|
change_angle=2,
|
||||||
|
mutation_callback=self.speed_up_particule,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
def remove_line(self, y):
|
def speed_up_particule(self, particule):
|
||||||
self.matrix.sprites.remove_line(y)
|
particule.change_x *= PARTICULE_ACCELERATION
|
||||||
super().remove_line(y)
|
particule.change_y *= PARTICULE_ACCELERATION
|
||||||
|
|
||||||
def pause(self):
|
def on_eliminate_phase(self, matrix, lines_to_remove):
|
||||||
super().pause()
|
for y in lines_to_remove:
|
||||||
if self.play_music:
|
matrix.sprites.remove_line(y)
|
||||||
|
|
||||||
|
def on_completion_phase(self, pattern_name, pattern_score, nb_combo, combo_score):
|
||||||
|
if pattern_score:
|
||||||
|
self.show_text("{:s}\n{:n}".format(pattern_name, pattern_score))
|
||||||
|
if combo_score:
|
||||||
|
self.show_text("COMBO x{:n}\n{:n}".format(nb_combo, combo_score))
|
||||||
|
|
||||||
|
def on_hold(self, held_piece):
|
||||||
|
held_piece.coord = HELD_PIECE_COORD
|
||||||
|
if type(held_piece) == I_Tetrimino:
|
||||||
|
held_piece.coord += Movement.LEFT
|
||||||
|
|
||||||
|
def on_pause(self):
|
||||||
|
self.state = State.PAUSED
|
||||||
|
if self.music:
|
||||||
self.music.pause()
|
self.music.pause()
|
||||||
|
|
||||||
def resume(self):
|
def resume(self):
|
||||||
super().resume()
|
if self.music:
|
||||||
if self.play_music:
|
|
||||||
self.music.play()
|
self.music.play()
|
||||||
|
self.state = State.PLAYING
|
||||||
|
|
||||||
def game_over(self):
|
def on_game_over(self):
|
||||||
super().game_over()
|
self.state = State.OVER
|
||||||
if self.play_music:
|
if self.music:
|
||||||
self.music.pause()
|
self.music.pause()
|
||||||
|
|
||||||
def on_key_press(self, key, modifiers):
|
def on_key_press(self, key, modifiers):
|
||||||
for key_or_modifier in (key, modifiers):
|
try:
|
||||||
try:
|
action = self.key_map[self.state][key]
|
||||||
action = self.key_map[self.state][key_or_modifier]
|
except KeyError:
|
||||||
except KeyError:
|
return
|
||||||
pass
|
else:
|
||||||
else:
|
self.do_action(action)
|
||||||
self.do_action(action)
|
|
||||||
|
|
||||||
def on_key_release(self, key, modifiers):
|
def on_key_release(self, key, modifiers):
|
||||||
for key_or_modifier in (key, modifiers):
|
try:
|
||||||
try:
|
action = self.key_map[self.state][key]
|
||||||
action = self.key_map[self.state][key_or_modifier]
|
except KeyError:
|
||||||
except KeyError:
|
return
|
||||||
pass
|
else:
|
||||||
else:
|
self.remove_action(action)
|
||||||
self.remove_action(action)
|
|
||||||
|
|
||||||
def show_text(self, text):
|
def show_text(self, text):
|
||||||
self.highlight_texts.append(text)
|
self.highlight_texts.append(text)
|
||||||
self.restart(self.del_highlight_text, HIGHLIGHT_TEXT_DISPLAY_DELAY)
|
self.timer.postpone(self.del_highlight_text, HIGHLIGHT_TEXT_DISPLAY_DELAY)
|
||||||
|
|
||||||
def del_highlight_text(self):
|
def del_highlight_text(self):
|
||||||
if self.highlight_texts:
|
if self.highlight_texts:
|
||||||
self.highlight_texts.pop(0)
|
self.highlight_texts.pop(0)
|
||||||
else:
|
else:
|
||||||
self.stop(self.del_highlight_text)
|
self.timer.cancel(self.del_highlight_text)
|
||||||
|
|
||||||
def on_draw(self):
|
def on_draw(self):
|
||||||
arcade.start_render()
|
arcade.start_render()
|
||||||
self.bg.draw()
|
self.bg.draw()
|
||||||
|
|
||||||
if self.state in (State.PLAYING, State.OVER):
|
if self.state not in (State.STARTING, State.PAUSED):
|
||||||
self.matrix.bg.draw()
|
self.matrix.bg.draw()
|
||||||
self.held.bg.draw()
|
|
||||||
self.next.bg.draw()
|
|
||||||
self.matrix.sprites.draw()
|
self.matrix.sprites.draw()
|
||||||
|
|
||||||
for tetromino in [self.held.piece, self.matrix.piece, self.matrix.ghost] + self.next.pieces:
|
for tetromino in [
|
||||||
|
self.held.piece,
|
||||||
|
self.matrix.piece,
|
||||||
|
self.matrix.ghost,
|
||||||
|
] + self.next.pieces:
|
||||||
if tetromino:
|
if tetromino:
|
||||||
tetromino.sprites.draw()
|
tetromino.sprites.draw()
|
||||||
|
|
||||||
t = time.localtime(self.time)
|
t = time.localtime(self.stats.time)
|
||||||
font_size = STATS_TEXT_SIZE * self.scale
|
font_size = STATS_TEXT_SIZE * self.scale
|
||||||
for y, text in enumerate(("TIME", "LINES", "GOAL", "LEVEL", "HIGH SCORE", "SCORE")):
|
for y, text in enumerate(
|
||||||
|
("TIME", "LINES", "GOAL", "LEVEL", "HIGH SCORE", "SCORE")
|
||||||
|
):
|
||||||
arcade.draw_text(
|
arcade.draw_text(
|
||||||
text=text,
|
text=text,
|
||||||
start_x=self.matrix.bg.left - self.scale * (STATS_TEXT_MARGIN + STATS_TEXT_WIDTH),
|
start_x=self.matrix.bg.left
|
||||||
|
- self.scale * (STATS_TEXT_MARGIN + STATS_TEXT_WIDTH),
|
||||||
start_y=self.matrix.bg.bottom + 1.5 * (2 * y + 1) * font_size,
|
start_y=self.matrix.bg.bottom + 1.5 * (2 * y + 1) * font_size,
|
||||||
color=TEXT_COLOR,
|
color=TEXT_COLOR,
|
||||||
font_size=font_size,
|
font_size=font_size,
|
||||||
@@ -444,11 +537,11 @@ AGAIN""".format(
|
|||||||
for y, text in enumerate(
|
for y, text in enumerate(
|
||||||
(
|
(
|
||||||
"{:02d}:{:02d}:{:02d}".format(t.tm_hour - 1, t.tm_min, t.tm_sec),
|
"{:02d}:{:02d}:{:02d}".format(t.tm_hour - 1, t.tm_min, t.tm_sec),
|
||||||
"{:n}".format(self.nb_lines_cleared),
|
"{:n}".format(self.stats.lines_cleared),
|
||||||
"{:n}".format(self.goal),
|
"{:n}".format(self.stats.goal),
|
||||||
"{:n}".format(self.level),
|
"{:n}".format(self.stats.level),
|
||||||
"{:n}".format(self.high_score),
|
"{:n}".format(self.stats.high_score),
|
||||||
"{:n}".format(self.score),
|
"{:n}".format(self.stats.score),
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
arcade.draw_text(
|
arcade.draw_text(
|
||||||
@@ -462,6 +555,10 @@ AGAIN""".format(
|
|||||||
anchor_x="right",
|
anchor_x="right",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
for exploding_minoes in self.exploding_minoes:
|
||||||
|
if exploding_minoes:
|
||||||
|
exploding_minoes.draw()
|
||||||
|
|
||||||
highlight_text = {
|
highlight_text = {
|
||||||
State.STARTING: self.start_text,
|
State.STARTING: self.start_text,
|
||||||
State.PLAYING: self.highlight_texts[0] if self.highlight_texts else "",
|
State.PLAYING: self.highlight_texts[0] if self.highlight_texts else "",
|
||||||
@@ -503,17 +600,13 @@ AGAIN""".format(
|
|||||||
self.matrix.bg.left = int(self.matrix.bg.left)
|
self.matrix.bg.left = int(self.matrix.bg.left)
|
||||||
self.matrix.bg.top = int(self.matrix.bg.top)
|
self.matrix.bg.top = int(self.matrix.bg.top)
|
||||||
|
|
||||||
self.held.bg.scale = self.scale
|
|
||||||
self.held.bg.right = self.matrix.bg.left
|
|
||||||
self.held.bg.top = self.matrix.bg.top
|
|
||||||
|
|
||||||
self.next.bg.scale = self.scale
|
|
||||||
self.next.bg.left = self.matrix.bg.right
|
|
||||||
self.next.bg.top = self.matrix.bg.top
|
|
||||||
|
|
||||||
self.matrix.sprites.resize(self.scale)
|
self.matrix.sprites.resize(self.scale)
|
||||||
|
|
||||||
for tetromino in [self.held.piece, self.matrix.piece, self.matrix.ghost] + self.next.pieces:
|
for tetromino in [
|
||||||
|
self.held.piece,
|
||||||
|
self.matrix.piece,
|
||||||
|
self.matrix.ghost,
|
||||||
|
] + self.next.pieces:
|
||||||
if tetromino:
|
if tetromino:
|
||||||
tetromino.sprites.resize(self.scale)
|
tetromino.sprites.resize(self.scale)
|
||||||
|
|
||||||
@@ -523,7 +616,7 @@ AGAIN""".format(
|
|||||||
crypted_high_score = f.read()
|
crypted_high_score = f.read()
|
||||||
super().load_high_score(crypted_high_score)
|
super().load_high_score(crypted_high_score)
|
||||||
except:
|
except:
|
||||||
self.high_score = 0
|
self.stats.high_score = 0
|
||||||
|
|
||||||
def save_high_score(self):
|
def save_high_score(self):
|
||||||
try:
|
try:
|
||||||
@@ -542,33 +635,21 @@ High score could not be saved:
|
|||||||
+ str(e)
|
+ str(e)
|
||||||
)
|
)
|
||||||
|
|
||||||
def start(self, task, period):
|
def update(self, delta_time):
|
||||||
_task = lambda _: task()
|
for piece in [
|
||||||
self.tasks[task] = _task
|
self.held.piece,
|
||||||
arcade.schedule(_task, period)
|
self.matrix.piece,
|
||||||
|
self.matrix.ghost,
|
||||||
def stop(self, task):
|
] + self.next.pieces:
|
||||||
try:
|
if piece:
|
||||||
_task = self.tasks[task]
|
piece.sprites.update()
|
||||||
except KeyError:
|
for exploding_minoes in self.exploding_minoes:
|
||||||
pass
|
if exploding_minoes:
|
||||||
else:
|
exploding_minoes.update()
|
||||||
arcade.unschedule(_task)
|
|
||||||
del self.tasks[task]
|
|
||||||
|
|
||||||
def restart(self, task, period):
|
|
||||||
try:
|
|
||||||
_task = self.tasks[task]
|
|
||||||
except KeyError:
|
|
||||||
_task = lambda _: task()
|
|
||||||
self.tasks[task] = _task
|
|
||||||
else:
|
|
||||||
arcade.unschedule(_task)
|
|
||||||
arcade.schedule(_task, period)
|
|
||||||
|
|
||||||
def on_close(self):
|
def on_close(self):
|
||||||
self.save_high_score()
|
self.save_high_score()
|
||||||
if self.play_music:
|
if self.music:
|
||||||
self.music.pause()
|
self.music.pause()
|
||||||
super().on_close()
|
super().on_close()
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
arcade cx-freeze
|
arcade
|
||||||
|
cx-freeze
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 499 B |
Binary file not shown.
|
Before Width: | Height: | Size: 475 B |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -9,20 +9,14 @@ else:
|
|||||||
base = None
|
base = None
|
||||||
icon = None
|
icon = None
|
||||||
|
|
||||||
excludes = [
|
excludes = ["tkinter", "PyQt4", "PyQt5", "PySide", "PySide2"]
|
||||||
"tkinter",
|
|
||||||
"PyQt4",
|
|
||||||
"PyQt5",
|
|
||||||
"PySide",
|
|
||||||
"PySide2"
|
|
||||||
]
|
|
||||||
|
|
||||||
executable = Executable(
|
executable = Executable(
|
||||||
script = "TetrArcade.py",
|
script="TetrArcade.py",
|
||||||
icon = icon,
|
icon=icon,
|
||||||
base = base,
|
base=base,
|
||||||
shortcutName="TetrArcade",
|
shortcutName="TetrArcade",
|
||||||
shortcutDir="DesktopFolder"
|
shortcutDir="DesktopFolder",
|
||||||
)
|
)
|
||||||
|
|
||||||
options = {
|
options = {
|
||||||
@@ -30,14 +24,14 @@ options = {
|
|||||||
"packages": ["arcade", "pyglet"],
|
"packages": ["arcade", "pyglet"],
|
||||||
"excludes": excludes,
|
"excludes": excludes,
|
||||||
"include_files": "resources",
|
"include_files": "resources",
|
||||||
"silent": True
|
"silent": True,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setup(
|
setup(
|
||||||
name = "TetrArcade",
|
name="TetrArcade",
|
||||||
version = "0.3",
|
version="0.5",
|
||||||
description = "Tetris clone",
|
description="Tetris clone",
|
||||||
author = "AdrienMalin",
|
author="AdrienMalin",
|
||||||
executables = [executable],
|
executables=[executable],
|
||||||
options = options,
|
options=options,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,17 +1,36 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from TetrArcade import TetrArcade, State
|
from TetrArcade import TetrArcade, MinoSprite, State
|
||||||
|
from tetrislogic import Mino, Color, Coord
|
||||||
|
|
||||||
game = TetrArcade()
|
game = TetrArcade()
|
||||||
game.new_game()
|
game.new_game()
|
||||||
|
for x in range(game.matrix.collumns):
|
||||||
|
mino = Mino(Color.ORANGE, Coord(x, 0))
|
||||||
|
mino.sprite = MinoSprite(mino, game, 200)
|
||||||
|
game.matrix[0][x] = mino
|
||||||
|
game.matrix.sprites.append(mino.sprite)
|
||||||
game.move_left()
|
game.move_left()
|
||||||
|
game.pause()
|
||||||
|
game.resume()
|
||||||
game.move_right()
|
game.move_right()
|
||||||
game.swap()
|
game.hold()
|
||||||
|
game.update(0)
|
||||||
|
game.on_draw()
|
||||||
game.rotate_clockwise()
|
game.rotate_clockwise()
|
||||||
|
game.hold()
|
||||||
|
game.update(0)
|
||||||
|
game.on_draw()
|
||||||
game.rotate_counter()
|
game.rotate_counter()
|
||||||
for i in range(12):
|
for i in range(22):
|
||||||
game.soft_drop()
|
game.soft_drop()
|
||||||
game.matrix.sprites.refresh()
|
game.on_draw()
|
||||||
|
game.lock_phase()
|
||||||
|
game.hold()
|
||||||
|
game.update(0)
|
||||||
|
game.on_draw()
|
||||||
|
game.matrix.sprites.update()
|
||||||
game.on_draw()
|
game.on_draw()
|
||||||
while game.state != State.OVER:
|
while game.state != State.OVER:
|
||||||
game.hard_drop()
|
game.hard_drop()
|
||||||
|
game.on_draw()
|
||||||
|
|||||||
+14
-4
@@ -1,5 +1,15 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from .consts import NB_LINES, NB_COLS, NB_NEXT
|
from .consts import LINES, COLLUMNS, NEXT_PIECES
|
||||||
from .utils import Movement, Rotation, Color, Coord
|
from .utils import Movement, Spin, Color, Coord
|
||||||
from .tetromino import Mino, Tetromino
|
from .tetromino import (
|
||||||
from .tetrislogic import TetrisLogic, State, Matrix
|
Mino,
|
||||||
|
Tetromino,
|
||||||
|
I_Tetrimino,
|
||||||
|
J_Tetrimino,
|
||||||
|
L_Tetrimino,
|
||||||
|
O_Tetrimino,
|
||||||
|
S_Tetrimino,
|
||||||
|
T_Tetrimino,
|
||||||
|
Z_Tetrimino,
|
||||||
|
)
|
||||||
|
from .tetrislogic import TetrisLogic, Matrix, AbstractTimer
|
||||||
|
|||||||
+15
-7
@@ -1,11 +1,11 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from .utils import Coord
|
from .utils import Coord, T_Spin
|
||||||
|
|
||||||
|
|
||||||
# Matrix
|
# Matrix
|
||||||
NB_LINES = 20
|
LINES = 20
|
||||||
NB_COLS = 10
|
COLLUMNS = 10
|
||||||
NB_NEXT = 5
|
NEXT_PIECES = 5
|
||||||
|
|
||||||
# Delays (seconds)
|
# Delays (seconds)
|
||||||
LOCK_DELAY = 0.5
|
LOCK_DELAY = 0.5
|
||||||
@@ -14,6 +14,14 @@ AUTOREPEAT_DELAY = 0.300 # Official : 0.300 s
|
|||||||
AUTOREPEAT_PERIOD = 0.010 # Official : 0.010 s
|
AUTOREPEAT_PERIOD = 0.010 # Official : 0.010 s
|
||||||
|
|
||||||
# Piece init coord
|
# Piece init coord
|
||||||
MATRIX_PIECE_COORD = Coord(4, NB_LINES)
|
MATRIX_PIECE_COORD = Coord(4, LINES)
|
||||||
NEXT_PIECE_COORDS = [Coord(NB_COLS + 4, NB_LINES - 4 * n - 3) for n in range(NB_NEXT)]
|
|
||||||
HELD_PIECE_COORD = Coord(-5, NB_LINES - 3)
|
# Scores
|
||||||
|
LINES_CLEAR_NAME = "LINES_CLEAR_NAME"
|
||||||
|
SCORES = (
|
||||||
|
{LINES_CLEAR_NAME: "", T_Spin.NONE: 0, T_Spin.MINI: 1, T_Spin.T_SPIN: 4},
|
||||||
|
{LINES_CLEAR_NAME: "SINGLE", T_Spin.NONE: 1, T_Spin.MINI: 2, T_Spin.T_SPIN: 8},
|
||||||
|
{LINES_CLEAR_NAME: "DOUBLE", T_Spin.NONE: 3, T_Spin.T_SPIN: 12},
|
||||||
|
{LINES_CLEAR_NAME: "TRIPLE", T_Spin.NONE: 5, T_Spin.T_SPIN: 16},
|
||||||
|
{LINES_CLEAR_NAME: "TETRIS", T_Spin.NONE: 8},
|
||||||
|
)
|
||||||
+359
-286
@@ -1,37 +1,38 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import random
|
|
||||||
import pickle
|
import pickle
|
||||||
|
|
||||||
from .utils import Coord, Movement, Rotation, T_Spin
|
from .utils import Coord, Movement, Spin, T_Spin, T_Slot
|
||||||
from .tetromino import Tetromino, T_Tetrimino, I_Tetrimino
|
from .tetromino import Tetromino, T_Tetrimino
|
||||||
from .consts import (
|
from .consts import (
|
||||||
NB_LINES,
|
LINES,
|
||||||
NB_COLS,
|
COLLUMNS,
|
||||||
NB_NEXT,
|
NEXT_PIECES,
|
||||||
LOCK_DELAY,
|
LOCK_DELAY,
|
||||||
FALL_DELAY,
|
FALL_DELAY,
|
||||||
AUTOREPEAT_DELAY,
|
AUTOREPEAT_DELAY,
|
||||||
AUTOREPEAT_PERIOD,
|
AUTOREPEAT_PERIOD,
|
||||||
MATRIX_PIECE_COORD,
|
MATRIX_PIECE_COORD,
|
||||||
NEXT_PIECE_COORDS,
|
SCORES,
|
||||||
HELD_PIECE_COORD,
|
LINES_CLEAR_NAME
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
LINES_CLEAR_NAME = "LINES_CLEAR_NAME"
|
|
||||||
CRYPT_KEY = 987943759387540938469837689379857347598347598379584857934579343
|
CRYPT_KEY = 987943759387540938469837689379857347598347598379584857934579343
|
||||||
|
|
||||||
|
|
||||||
class State:
|
class AbstractTimer:
|
||||||
|
def postpone(task, delay):
|
||||||
|
raise Warning("AbstractTimer.postpone is not implemented.")
|
||||||
|
|
||||||
STARTING = "STARTING"
|
def cancel(self, task):
|
||||||
PLAYING = "PLAYING"
|
raise Warning("AbstractTimer.stop is not implemented.")
|
||||||
PAUSED = "PAUSED"
|
|
||||||
OVER = "OVER"
|
def reset(self, task, period):
|
||||||
|
self.timer.cancel(task)
|
||||||
|
self.timer.postpone(task, period)
|
||||||
|
|
||||||
|
|
||||||
class PieceContainer:
|
class PieceContainer:
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.piece = None
|
self.piece = None
|
||||||
|
|
||||||
@@ -41,89 +42,72 @@ class HoldQueue(PieceContainer):
|
|||||||
|
|
||||||
|
|
||||||
class Matrix(list, PieceContainer):
|
class Matrix(list, PieceContainer):
|
||||||
|
def __init__(self, lines, collumns):
|
||||||
def __init__(self, *args, **kargs):
|
list.__init__(self)
|
||||||
list.__init__(self, *args, **kargs)
|
|
||||||
PieceContainer.__init__(self)
|
PieceContainer.__init__(self)
|
||||||
|
self.lines = lines
|
||||||
|
self.collumns = collumns
|
||||||
|
self.ghost = None
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
self.clear()
|
||||||
|
for y in range(self.lines + 3):
|
||||||
|
self.append_new_line()
|
||||||
|
|
||||||
|
def append_new_line(self):
|
||||||
|
self.append([None for x in range(self.collumns)])
|
||||||
|
|
||||||
def cell_is_free(self, coord):
|
def cell_is_free(self, coord):
|
||||||
return 0 <= coord.x < NB_COLS and 0 <= coord.y and not self[coord.y][coord.x]
|
return (
|
||||||
|
0 <= coord.x < self.collumns and 0 <= coord.y and not self[coord.y][coord.x]
|
||||||
|
)
|
||||||
|
|
||||||
|
def space_to_move(self, potential_coord, minoes_coord):
|
||||||
|
return all(
|
||||||
|
self.cell_is_free(potential_coord + mino_coord)
|
||||||
|
for mino_coord in minoes_coord
|
||||||
|
)
|
||||||
|
|
||||||
|
def space_to_fall(self):
|
||||||
|
return self.space_to_move(
|
||||||
|
self.piece.coord + Movement.DOWN, (mino.coord for mino in self.piece)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class NextQueue(PieceContainer):
|
class NextQueue(PieceContainer):
|
||||||
|
def __init__(self, nb_pieces):
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
self.nb_pieces = nb_pieces
|
||||||
self.pieces = []
|
self.pieces = []
|
||||||
|
|
||||||
|
|
||||||
class TetrisLogic:
|
class Stats:
|
||||||
|
|
||||||
NB_LINES = NB_LINES
|
def _get_score(self):
|
||||||
NB_COLS = NB_COLS
|
|
||||||
NB_NEXT = NB_NEXT
|
|
||||||
LOCK_DELAY = LOCK_DELAY
|
|
||||||
FALL_DELAY = FALL_DELAY
|
|
||||||
AUTOREPEAT_DELAY = AUTOREPEAT_DELAY
|
|
||||||
AUTOREPEAT_PERIOD = AUTOREPEAT_PERIOD
|
|
||||||
MATRIX_PIECE_COORD = MATRIX_PIECE_COORD
|
|
||||||
NEXT_PIECE_COORDS = NEXT_PIECE_COORDS
|
|
||||||
HELD_PIECE_COORD = HELD_PIECE_COORD
|
|
||||||
random_bag = []
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.load_high_score()
|
|
||||||
self.state = State.STARTING
|
|
||||||
self.held = HoldQueue()
|
|
||||||
self.matrix = Matrix()
|
|
||||||
self.matrix.ghost = None
|
|
||||||
self.next = NextQueue()
|
|
||||||
self.time = 0
|
|
||||||
self.autorepeatable_actions = (self.move_left, self.move_right, self.soft_drop)
|
|
||||||
self.pressed_actions = []
|
|
||||||
self._score = 0
|
|
||||||
|
|
||||||
def get_score(self):
|
|
||||||
return self._score
|
return self._score
|
||||||
|
|
||||||
def set_score(self, new_score):
|
def _set_score(self, new_score):
|
||||||
self._score = new_score
|
self._score = new_score
|
||||||
if self._score > self.high_score:
|
if self._score > self.high_score:
|
||||||
self.high_score = self._score
|
self.high_score = self._score
|
||||||
|
|
||||||
score = property(get_score, set_score)
|
score = property(_get_score, _set_score)
|
||||||
|
|
||||||
def new_game(self):
|
def __init__(self):
|
||||||
self.level = 0
|
self._score = 0
|
||||||
self.score = 0
|
self.high_score = 0
|
||||||
self.nb_lines_cleared = 0
|
|
||||||
self.goal = 0
|
|
||||||
self.time = 0
|
self.time = 0
|
||||||
|
|
||||||
self.pressed_actions = []
|
def new_game(self, level):
|
||||||
self.auto_repeat = False
|
self.level = level - 1
|
||||||
|
self.score = 0
|
||||||
|
self.lines_cleared = 0
|
||||||
|
self.goal = 0
|
||||||
|
self.time = 0
|
||||||
|
self.combo = -1
|
||||||
|
|
||||||
self.lock_delay = self.LOCK_DELAY
|
self.lock_delay = LOCK_DELAY
|
||||||
self.fall_delay = self.FALL_DELAY
|
self.fall_delay = FALL_DELAY
|
||||||
|
|
||||||
self.matrix.clear()
|
|
||||||
for y in range(self.NB_LINES + 3):
|
|
||||||
self.append_new_line_to_matrix()
|
|
||||||
self.next.pieces = [self.new_tetromino() for n in range(self.NB_NEXT)]
|
|
||||||
self.held.piece = None
|
|
||||||
self.state = State.PLAYING
|
|
||||||
self.start(self.update_time, 1)
|
|
||||||
|
|
||||||
self.new_level()
|
|
||||||
|
|
||||||
def new_tetromino(self):
|
|
||||||
if not self.random_bag:
|
|
||||||
self.random_bag = list(Tetromino.shapes)
|
|
||||||
random.shuffle(self.random_bag)
|
|
||||||
return self.random_bag.pop()()
|
|
||||||
|
|
||||||
def append_new_line_to_matrix(self):
|
|
||||||
self.matrix.append([None for x in range(self.NB_COLS)])
|
|
||||||
|
|
||||||
def new_level(self):
|
def new_level(self):
|
||||||
self.level += 1
|
self.level += 1
|
||||||
@@ -132,23 +116,257 @@ class TetrisLogic:
|
|||||||
self.fall_delay = pow(0.8 - ((self.level - 1) * 0.007), self.level - 1)
|
self.fall_delay = pow(0.8 - ((self.level - 1) * 0.007), self.level - 1)
|
||||||
if self.level > 15:
|
if self.level > 15:
|
||||||
self.lock_delay = 0.5 * pow(0.9, self.level - 15)
|
self.lock_delay = 0.5 * pow(0.9, self.level - 15)
|
||||||
self.show_text("LEVEL\n{:n}".format(self.level))
|
|
||||||
self.restart(self.fall, self.fall_delay)
|
|
||||||
self.new_matrix_piece()
|
|
||||||
|
|
||||||
def new_matrix_piece(self):
|
def update_time(self):
|
||||||
self.matrix.piece = self.next.pieces.pop(0)
|
self.time += 1
|
||||||
|
|
||||||
|
def locks_down(self, t_spin, lines_cleared):
|
||||||
|
pattern_name = []
|
||||||
|
pattern_score = 0
|
||||||
|
combo_score = 0
|
||||||
|
|
||||||
|
if t_spin:
|
||||||
|
pattern_name.append(t_spin)
|
||||||
|
if lines_cleared:
|
||||||
|
pattern_name.append(SCORES[lines_cleared][LINES_CLEAR_NAME])
|
||||||
|
self.combo += 1
|
||||||
|
else:
|
||||||
|
self.combo = -1
|
||||||
|
|
||||||
|
if lines_cleared or t_spin:
|
||||||
|
pattern_score = SCORES[lines_cleared][t_spin]
|
||||||
|
self.goal -= pattern_score
|
||||||
|
pattern_score *= 100 * self.level
|
||||||
|
pattern_name = "\n".join(pattern_name)
|
||||||
|
|
||||||
|
if self.combo >= 1:
|
||||||
|
combo_score = (20 if lines_cleared == 1 else 50) * self.combo * self.level
|
||||||
|
|
||||||
|
self.score += pattern_score + combo_score
|
||||||
|
|
||||||
|
return pattern_name, pattern_score, self.combo, combo_score
|
||||||
|
|
||||||
|
|
||||||
|
class TetrisLogic:
|
||||||
|
|
||||||
|
# These class attributes can be redefined on inheritance
|
||||||
|
AUTOREPEAT_DELAY = AUTOREPEAT_DELAY
|
||||||
|
AUTOREPEAT_PERIOD = AUTOREPEAT_PERIOD
|
||||||
|
MATRIX_PIECE_COORD = MATRIX_PIECE_COORD
|
||||||
|
|
||||||
|
timer = AbstractTimer()
|
||||||
|
|
||||||
|
def __init__(self, lines=LINES, collumns=COLLUMNS, next_pieces=NEXT_PIECES):
|
||||||
|
self.stats = Stats()
|
||||||
|
self.load_high_score()
|
||||||
|
self.held = HoldQueue()
|
||||||
|
self.matrix = Matrix(lines, collumns)
|
||||||
|
self.next = NextQueue(next_pieces)
|
||||||
|
self.autorepeatable_actions = (self.move_left, self.move_right, self.soft_drop)
|
||||||
|
self.pressed_actions = []
|
||||||
|
|
||||||
|
def new_game(self, level=1):
|
||||||
|
self.stats.new_game(level)
|
||||||
|
|
||||||
|
self.pressed_actions = []
|
||||||
|
|
||||||
|
self.matrix.reset()
|
||||||
|
self.next.pieces = [Tetromino() for n in range(self.next.nb_pieces)]
|
||||||
|
self.held.piece = None
|
||||||
|
self.timer.postpone(self.stats.update_time, 1)
|
||||||
|
|
||||||
|
self.on_new_game(self.next.pieces)
|
||||||
|
self.new_level()
|
||||||
|
|
||||||
|
def on_new_game(self, next_pieces):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def new_level(self):
|
||||||
|
self.stats.new_level()
|
||||||
|
self.on_new_level(self.stats.level)
|
||||||
|
self.generation_phase()
|
||||||
|
|
||||||
|
def on_new_level(self, level):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Tetris Engine
|
||||||
|
|
||||||
|
def generation_phase(self, held_piece=None):
|
||||||
|
if not held_piece:
|
||||||
|
self.matrix.piece = self.next.pieces.pop(0)
|
||||||
|
self.next.pieces.append(Tetromino())
|
||||||
self.matrix.piece.coord = self.MATRIX_PIECE_COORD
|
self.matrix.piece.coord = self.MATRIX_PIECE_COORD
|
||||||
self.matrix.ghost = self.matrix.piece.ghost()
|
self.matrix.ghost = self.matrix.piece.ghost()
|
||||||
self.move_ghost()
|
self.refresh_ghost()
|
||||||
self.next.pieces.append(self.new_tetromino())
|
# if self.pressed_actions:
|
||||||
self.next.pieces[-1].coord = self.NEXT_PIECE_COORDS[-1]
|
# self.timer.postpone(self.repeat_action, self.AUTOREPEAT_DELAY)
|
||||||
for tetromino, coord in zip(self.next.pieces, self.NEXT_PIECE_COORDS):
|
|
||||||
tetromino.coord = coord
|
|
||||||
|
|
||||||
if not self.can_move(self.matrix.piece.coord, (mino.coord for mino in self.matrix.piece)):
|
self.on_generation_phase(
|
||||||
|
self.matrix, self.matrix.piece, self.matrix.ghost, self.next.pieces
|
||||||
|
)
|
||||||
|
if self.move(Movement.DOWN):
|
||||||
|
self.falling_phase()
|
||||||
|
else:
|
||||||
self.game_over()
|
self.game_over()
|
||||||
|
|
||||||
|
def refresh_ghost(self):
|
||||||
|
self.matrix.ghost.coord = self.matrix.piece.coord
|
||||||
|
for ghost_mino, current_mino in zip(self.matrix.ghost, self.matrix.piece):
|
||||||
|
ghost_mino.coord = current_mino.coord
|
||||||
|
while self.matrix.space_to_move(
|
||||||
|
self.matrix.ghost.coord + Movement.DOWN,
|
||||||
|
(mino.coord for mino in self.matrix.ghost),
|
||||||
|
):
|
||||||
|
self.matrix.ghost.coord += Movement.DOWN
|
||||||
|
|
||||||
|
def on_generation_phase(self, matrix, falling_piece, ghost_piece, next_pieces):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def falling_phase(self):
|
||||||
|
self.timer.cancel(self.lock_phase)
|
||||||
|
self.timer.cancel(self.locks_down)
|
||||||
|
self.matrix.piece.locked = False
|
||||||
|
self.timer.postpone(self.lock_phase, self.stats.fall_delay)
|
||||||
|
self.on_falling_phase(self.matrix.piece)
|
||||||
|
|
||||||
|
def on_falling_phase(self, falling_piece):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def lock_phase(self):
|
||||||
|
self.move(Movement.DOWN)
|
||||||
|
|
||||||
|
def on_locked(self, falling_piece):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def move(self, movement, rotated_coords=None, lock=True):
|
||||||
|
potential_coord = self.matrix.piece.coord + movement
|
||||||
|
potential_minoes_coords = rotated_coords or (
|
||||||
|
mino.coord for mino in self.matrix.piece
|
||||||
|
)
|
||||||
|
if self.matrix.space_to_move(potential_coord, potential_minoes_coords):
|
||||||
|
self.matrix.piece.coord = potential_coord
|
||||||
|
if rotated_coords:
|
||||||
|
for mino, coord in zip(self.matrix.piece, rotated_coords):
|
||||||
|
mino.coord = coord
|
||||||
|
self.refresh_ghost()
|
||||||
|
if movement != Movement.DOWN:
|
||||||
|
self.matrix.piece.rotated_last = False
|
||||||
|
if self.matrix.space_to_fall():
|
||||||
|
self.falling_phase()
|
||||||
|
else:
|
||||||
|
self.matrix.piece.locked = True
|
||||||
|
self.on_locked(self.matrix.piece)
|
||||||
|
self.timer.reset(self.locks_down, self.stats.lock_delay)
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def rotate(self, spin):
|
||||||
|
rotated_coords = tuple(
|
||||||
|
Coord(spin * mino.coord.y, -spin * mino.coord.x)
|
||||||
|
for mino in self.matrix.piece
|
||||||
|
)
|
||||||
|
for rotation_point, liberty_degree in enumerate(
|
||||||
|
self.matrix.piece.SRS[spin][self.matrix.piece.orientation], start=1
|
||||||
|
):
|
||||||
|
if self.move(liberty_degree, rotated_coords, lock=False):
|
||||||
|
self.matrix.piece.orientation = (
|
||||||
|
self.matrix.piece.orientation + spin
|
||||||
|
) % 4
|
||||||
|
self.matrix.piece.rotated_last = True
|
||||||
|
if rotation_point == 5:
|
||||||
|
self.matrix.piece.rotation_point_5_used = True
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def locks_down(self):
|
||||||
|
# self.timer.cancel(self.repeat_action)
|
||||||
|
self.timer.cancel(self.lock_phase)
|
||||||
|
|
||||||
|
# Game over
|
||||||
|
if all(
|
||||||
|
(mino.coord + self.matrix.piece.coord).y >= self.matrix.lines
|
||||||
|
for mino in self.matrix.piece
|
||||||
|
):
|
||||||
|
self.game_over()
|
||||||
|
return
|
||||||
|
|
||||||
|
for mino in self.matrix.piece:
|
||||||
|
coord = mino.coord + self.matrix.piece.coord
|
||||||
|
if coord.y <= self.matrix.lines + 3:
|
||||||
|
self.matrix[coord.y][coord.x] = mino
|
||||||
|
|
||||||
|
self.on_locks_down(self.matrix, self.matrix.piece)
|
||||||
|
|
||||||
|
# Pattern phase
|
||||||
|
|
||||||
|
# T-Spin
|
||||||
|
if (
|
||||||
|
type(self.matrix.piece) == T_Tetrimino
|
||||||
|
and self.matrix.piece.rotated_last
|
||||||
|
):
|
||||||
|
a = self.is_t_slot(T_Slot.A)
|
||||||
|
b = self.is_t_slot(T_Slot.B)
|
||||||
|
c = self.is_t_slot(T_Slot.C)
|
||||||
|
d = self.is_t_slot(T_Slot.D)
|
||||||
|
if a and b and (c or d):
|
||||||
|
t_spin = T_Spin.T_SPIN
|
||||||
|
elif c and d and (a or b):
|
||||||
|
if self.matrix.piece.rotation_point_5_used:
|
||||||
|
t_spin = T_Spin.T_SPIN
|
||||||
|
else:
|
||||||
|
t_spin = T_Spin.MINI
|
||||||
|
else:
|
||||||
|
t_spin = T_Spin.NONE
|
||||||
|
else:
|
||||||
|
t_spin = T_Spin.NONE
|
||||||
|
|
||||||
|
# Clear complete lines
|
||||||
|
self.lines_to_remove = []
|
||||||
|
for y, line in reversed(list(enumerate(self.matrix))):
|
||||||
|
if all(mino for mino in line):
|
||||||
|
self.lines_to_remove.append(y)
|
||||||
|
lines_cleared = len(self.lines_to_remove)
|
||||||
|
if lines_cleared:
|
||||||
|
self.stats.lines_cleared += lines_cleared
|
||||||
|
|
||||||
|
# Animate phase
|
||||||
|
|
||||||
|
self.on_animate_phase(self.matrix, self.lines_to_remove)
|
||||||
|
|
||||||
|
# Eliminate phase
|
||||||
|
self.on_eliminate_phase(self.matrix, self.lines_to_remove)
|
||||||
|
for y in self.lines_to_remove:
|
||||||
|
self.matrix.pop(y)
|
||||||
|
self.matrix.append_new_line()
|
||||||
|
|
||||||
|
# Completion phase
|
||||||
|
|
||||||
|
pattern_name, pattern_score, nb_combo, combo_score = self.stats.locks_down(
|
||||||
|
t_spin, lines_cleared
|
||||||
|
)
|
||||||
|
self.on_completion_phase(pattern_name, pattern_score, nb_combo, combo_score)
|
||||||
|
|
||||||
|
if self.stats.goal <= 0:
|
||||||
|
self.new_level()
|
||||||
|
else:
|
||||||
|
self.generation_phase()
|
||||||
|
|
||||||
|
def on_locks_down(self, matrix, falling_piece):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def on_animate_phase(self, matrix, lines_to_remove):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def on_eliminate_phase(self, matrix, lines_to_remove):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def on_completion_phase(self, pattern_name, pattern_score, nb_combo, combo_score):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Actions
|
||||||
|
|
||||||
def move_left(self):
|
def move_left(self):
|
||||||
self.move(Movement.LEFT)
|
self.move(Movement.LEFT)
|
||||||
|
|
||||||
@@ -156,233 +374,98 @@ class TetrisLogic:
|
|||||||
self.move(Movement.RIGHT)
|
self.move(Movement.RIGHT)
|
||||||
|
|
||||||
def rotate_clockwise(self):
|
def rotate_clockwise(self):
|
||||||
self.rotate(Rotation.CLOCKWISE)
|
self.rotate(Spin.CLOCKWISE)
|
||||||
|
|
||||||
def rotate_counter(self):
|
def rotate_counter(self):
|
||||||
self.rotate(Rotation.COUNTER)
|
self.rotate(Spin.COUNTER)
|
||||||
|
|
||||||
def move_ghost(self):
|
|
||||||
self.matrix.ghost.coord = self.matrix.piece.coord
|
|
||||||
for ghost_mino, current_mino in zip(self.matrix.ghost, self.matrix.piece):
|
|
||||||
ghost_mino.coord = current_mino.coord
|
|
||||||
while self.can_move(self.matrix.ghost.coord + Movement.DOWN, (mino.coord for mino in self.matrix.ghost)):
|
|
||||||
self.matrix.ghost.coord += Movement.DOWN
|
|
||||||
|
|
||||||
def soft_drop(self):
|
def soft_drop(self):
|
||||||
moved = self.move(Movement.DOWN)
|
moved = self.move(Movement.DOWN)
|
||||||
if moved:
|
if moved:
|
||||||
self.score += 1
|
self.stats.score += 1
|
||||||
return moved
|
return moved
|
||||||
|
|
||||||
def hard_drop(self):
|
def hard_drop(self):
|
||||||
while self.move(Movement.DOWN, prelock=False):
|
self.timer.cancel(self.lock_phase)
|
||||||
self.score += 2
|
self.timer.cancel(self.locks_down)
|
||||||
self.lock()
|
while self.move(Movement.DOWN, lock=False):
|
||||||
|
self.stats.score += 2
|
||||||
|
self.locks_down()
|
||||||
|
|
||||||
def fall(self):
|
def hold(self):
|
||||||
self.move(Movement.DOWN)
|
if not self.matrix.piece.hold_enabled:
|
||||||
|
|
||||||
def move(self, movement, prelock=True):
|
|
||||||
potential_coord = self.matrix.piece.coord + movement
|
|
||||||
if self.can_move(potential_coord, (mino.coord for mino in self.matrix.piece)):
|
|
||||||
if self.matrix.piece.prelocked:
|
|
||||||
self.restart(self.lock, self.lock_delay)
|
|
||||||
self.matrix.piece.coord = potential_coord
|
|
||||||
if not movement == Movement.DOWN:
|
|
||||||
self.matrix.piece.last_rotation_point = None
|
|
||||||
self.move_ghost()
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
if prelock and not self.matrix.piece.prelocked and movement == Movement.DOWN:
|
|
||||||
self.matrix.piece.prelocked = True
|
|
||||||
self.start(self.lock, self.lock_delay)
|
|
||||||
return False
|
|
||||||
|
|
||||||
def rotate(self, rotation):
|
|
||||||
rotated_coords = tuple(Coord(rotation * mino.coord.y, -rotation * mino.coord.x) for mino in self.matrix.piece)
|
|
||||||
for rotation_point, liberty_degree in enumerate(self.matrix.piece.SRS[rotation][self.matrix.piece.orientation], start=1):
|
|
||||||
potential_coord = self.matrix.piece.coord + liberty_degree
|
|
||||||
if self.can_move(potential_coord, rotated_coords):
|
|
||||||
if self.matrix.piece.prelocked:
|
|
||||||
self.restart(self.lock, self.lock_delay)
|
|
||||||
self.matrix.piece.coord = potential_coord
|
|
||||||
for mino, coord in zip(self.matrix.piece, rotated_coords):
|
|
||||||
mino.coord = coord
|
|
||||||
self.matrix.piece.orientation = (self.matrix.piece.orientation + rotation) % 4
|
|
||||||
self.matrix.piece.last_rotation_point = rotation_point
|
|
||||||
self.move_ghost()
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
SCORES = (
|
|
||||||
{LINES_CLEAR_NAME: "", T_Spin.NONE: 0, T_Spin.MINI: 1, T_Spin.T_SPIN: 4},
|
|
||||||
{LINES_CLEAR_NAME: "SINGLE", T_Spin.NONE: 1, T_Spin.MINI: 2, T_Spin.T_SPIN: 8},
|
|
||||||
{LINES_CLEAR_NAME: "DOUBLE", T_Spin.NONE: 3, T_Spin.T_SPIN: 12},
|
|
||||||
{LINES_CLEAR_NAME: "TRIPLE", T_Spin.NONE: 5, T_Spin.T_SPIN: 16},
|
|
||||||
{LINES_CLEAR_NAME: "TETRIS", T_Spin.NONE: 8},
|
|
||||||
)
|
|
||||||
|
|
||||||
def lock(self):
|
|
||||||
self.matrix.piece.prelocked = False
|
|
||||||
self.stop(self.lock)
|
|
||||||
|
|
||||||
# Piece unlocked
|
|
||||||
if self.can_move(self.matrix.piece.coord + Movement.DOWN, (mino.coord for mino in self.matrix.piece)):
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# Game over
|
self.matrix.piece.hold_enabled = False
|
||||||
if all((mino.coord + self.matrix.piece.coord).y >= self.NB_LINES for mino in self.matrix.piece):
|
self.timer.cancel(self.lock_phase)
|
||||||
self.game_over()
|
self.matrix.piece, self.held.piece = self.held.piece, self.matrix.piece
|
||||||
return
|
|
||||||
|
|
||||||
if self.pressed_actions:
|
for mino, coord in zip(self.held.piece, self.held.piece.MINOES_COORDS):
|
||||||
self.auto_repeat = False
|
mino.coord = coord
|
||||||
self.restart(self.repeat_action, self.AUTOREPEAT_DELAY)
|
|
||||||
|
|
||||||
# T-Spin
|
self.on_hold(self.held.piece)
|
||||||
if type(self.matrix.piece) == T_Tetrimino and self.matrix.piece.last_rotation_point is not None:
|
self.generation_phase(self.matrix.piece)
|
||||||
a = self.is_t_slot(0)
|
|
||||||
b = self.is_t_slot(1)
|
|
||||||
c = self.is_t_slot(3)
|
|
||||||
d = self.is_t_slot(2)
|
|
||||||
if self.matrix.piece.last_rotation_point == 5 or (a and b and (c or d)):
|
|
||||||
t_spin = T_Spin.T_SPIN
|
|
||||||
elif c and d and (a or b):
|
|
||||||
t_spin = T_Spin.MINI
|
|
||||||
else:
|
|
||||||
t_spin = T_Spin.NONE
|
|
||||||
else:
|
|
||||||
t_spin = T_Spin.NONE
|
|
||||||
|
|
||||||
self.enter_the_matrix()
|
def on_hold(self, held_piece):
|
||||||
|
pass
|
||||||
# Clear complete lines
|
|
||||||
nb_lines_cleared = 0
|
|
||||||
for y, line in reversed(list(enumerate(self.matrix))):
|
|
||||||
if all(mino for mino in line):
|
|
||||||
nb_lines_cleared += 1
|
|
||||||
self.remove_line(y)
|
|
||||||
self.append_new_line_to_matrix()
|
|
||||||
if nb_lines_cleared:
|
|
||||||
self.nb_lines_cleared += nb_lines_cleared
|
|
||||||
|
|
||||||
# Scoring
|
|
||||||
lock_strings = []
|
|
||||||
lock_score = 0
|
|
||||||
|
|
||||||
if t_spin:
|
|
||||||
lock_strings.append(t_spin)
|
|
||||||
if nb_lines_cleared:
|
|
||||||
lock_strings.append(self.SCORES[nb_lines_cleared][LINES_CLEAR_NAME])
|
|
||||||
self.combo += 1
|
|
||||||
else:
|
|
||||||
self.combo = -1
|
|
||||||
|
|
||||||
if nb_lines_cleared or t_spin:
|
|
||||||
ds = self.SCORES[nb_lines_cleared][t_spin]
|
|
||||||
self.goal -= ds
|
|
||||||
ds *= 100 * self.level
|
|
||||||
lock_score += ds
|
|
||||||
lock_strings.append(str(ds))
|
|
||||||
self.show_text("\n".join(lock_strings))
|
|
||||||
|
|
||||||
if self.combo >= 1:
|
|
||||||
ds = (20 if nb_lines_cleared == 1 else 50) * self.combo * self.level
|
|
||||||
lock_score += ds
|
|
||||||
self.show_text("COMBO x{:n}\n{:n}".format(self.combo, ds))
|
|
||||||
|
|
||||||
self.score += lock_score
|
|
||||||
|
|
||||||
if self.goal <= 0:
|
|
||||||
self.new_level()
|
|
||||||
else:
|
|
||||||
self.new_matrix_piece()
|
|
||||||
|
|
||||||
def enter_the_matrix(self):
|
|
||||||
for mino in self.matrix.piece:
|
|
||||||
coord = mino.coord + self.matrix.piece.coord
|
|
||||||
if coord.y <= self.NB_LINES + 3:
|
|
||||||
self.matrix[coord.y][coord.x] = mino
|
|
||||||
|
|
||||||
def remove_line(self, y):
|
|
||||||
self.matrix.pop(y)
|
|
||||||
|
|
||||||
def can_move(self, potential_coord, minoes_coords):
|
|
||||||
return all(self.matrix.cell_is_free(potential_coord + mino_coord) for mino_coord in minoes_coords)
|
|
||||||
|
|
||||||
T_SLOT_COORDS = (Coord(-1, 1), Coord(1, 1), Coord(-1, 1), Coord(-1, -1))
|
T_SLOT_COORDS = (Coord(-1, 1), Coord(1, 1), Coord(-1, 1), Coord(-1, -1))
|
||||||
|
|
||||||
def is_t_slot(self, n):
|
def is_t_slot(self, n):
|
||||||
t_slot_coord = self.matrix.piece.coord + self.T_SLOT_COORDS[(self.matrix.piece.orientation + n) % 4]
|
t_slot_coord = (
|
||||||
|
self.matrix.piece.coord
|
||||||
|
+ self.T_SLOT_COORDS[(self.matrix.piece.orientation + n) % 4]
|
||||||
|
)
|
||||||
return not self.matrix.cell_is_free(t_slot_coord)
|
return not self.matrix.cell_is_free(t_slot_coord)
|
||||||
|
|
||||||
def swap(self):
|
|
||||||
if self.matrix.piece.hold_enabled:
|
|
||||||
self.matrix.piece.hold_enabled = False
|
|
||||||
self.matrix.piece.prelocked = False
|
|
||||||
self.stop(self.lock)
|
|
||||||
self.matrix.piece, self.held.piece = self.held.piece, self.matrix.piece
|
|
||||||
self.held.piece.coord = self.HELD_PIECE_COORD
|
|
||||||
if type(self.held.piece) == I_Tetrimino:
|
|
||||||
self.held.piece.coord += Movement.LEFT
|
|
||||||
for mino, coord in zip(self.held.piece, self.held.piece.MINOES_COORDS):
|
|
||||||
mino.coord = coord
|
|
||||||
|
|
||||||
if self.matrix.piece:
|
|
||||||
self.matrix.piece.coord = self.MATRIX_PIECE_COORD
|
|
||||||
self.matrix.ghost = self.matrix.piece.ghost()
|
|
||||||
self.move_ghost()
|
|
||||||
else:
|
|
||||||
self.new_matrix_piece()
|
|
||||||
|
|
||||||
def pause(self):
|
def pause(self):
|
||||||
self.state = State.PAUSED
|
|
||||||
self.stop_all()
|
self.stop_all()
|
||||||
self.pressed_actions = []
|
self.pressed_actions = []
|
||||||
self.auto_repeat = False
|
self.timer.cancel(self.repeat_action)
|
||||||
self.stop(self.repeat_action)
|
self.on_pause()
|
||||||
|
|
||||||
|
def on_pause(self):
|
||||||
|
pass
|
||||||
|
|
||||||
def resume(self):
|
def resume(self):
|
||||||
self.state = State.PLAYING
|
self.timer.postpone(self.lock_phase, self.stats.fall_delay)
|
||||||
self.start(self.fall, self.fall_delay)
|
if self.matrix.piece.locked:
|
||||||
if self.matrix.piece.prelocked:
|
self.timer.postpone(self.locks_down, self.stats.lock_delay)
|
||||||
self.start(self.lock, self.lock_delay)
|
self.timer.postpone(self.stats.update_time, 1)
|
||||||
self.start(self.update_time, 1)
|
self.on_resume()
|
||||||
|
|
||||||
|
def on_resume(self):
|
||||||
|
pass
|
||||||
|
|
||||||
def game_over(self):
|
def game_over(self):
|
||||||
self.state = State.OVER
|
|
||||||
self.stop_all()
|
self.stop_all()
|
||||||
self.save_high_score()
|
self.save_high_score()
|
||||||
|
self.on_game_over()
|
||||||
|
|
||||||
|
def on_game_over(self):
|
||||||
|
pass
|
||||||
|
|
||||||
def stop_all(self):
|
def stop_all(self):
|
||||||
self.stop(self.fall)
|
self.timer.cancel(self.lock_phase)
|
||||||
self.stop(self.lock)
|
self.timer.cancel(self.locks_down)
|
||||||
self.stop(self.update_time)
|
self.timer.cancel(self.stats.update_time)
|
||||||
|
|
||||||
def update_time(self):
|
|
||||||
self.time += 1
|
|
||||||
|
|
||||||
def do_action(self, action):
|
def do_action(self, action):
|
||||||
action()
|
action()
|
||||||
if action in self.autorepeatable_actions:
|
if action in self.autorepeatable_actions:
|
||||||
self.auto_repeat = False
|
|
||||||
self.pressed_actions.append(action)
|
self.pressed_actions.append(action)
|
||||||
if action == self.soft_drop:
|
if action == self.soft_drop:
|
||||||
delay = self.fall_delay / 20
|
delay = self.stats.fall_delay / 20
|
||||||
else:
|
else:
|
||||||
delay = self.AUTOREPEAT_DELAY
|
delay = self.AUTOREPEAT_DELAY
|
||||||
self.restart(self.repeat_action, delay)
|
self.timer.reset(self.repeat_action, delay)
|
||||||
|
|
||||||
def repeat_action(self):
|
def repeat_action(self):
|
||||||
if self.pressed_actions:
|
if not self.pressed_actions:
|
||||||
self.pressed_actions[-1]()
|
return
|
||||||
if not self.auto_repeat:
|
|
||||||
self.auto_repeat = True
|
self.pressed_actions[-1]()
|
||||||
self.restart(self.repeat_action, self.AUTOREPEAT_PERIOD)
|
self.timer.postpone(self.repeat_action, self.AUTOREPEAT_PERIOD)
|
||||||
else:
|
|
||||||
self.auto_repeat = False
|
|
||||||
self.stop(self.repeat_action)
|
|
||||||
|
|
||||||
def remove_action(self, action):
|
def remove_action(self, action):
|
||||||
if action in self.autorepeatable_actions:
|
if action in self.autorepeatable_actions:
|
||||||
@@ -398,25 +481,15 @@ class TetrisLogic:
|
|||||||
def load_high_score(self, crypted_high_score=None):
|
def load_high_score(self, crypted_high_score=None):
|
||||||
if crypted_high_score:
|
if crypted_high_score:
|
||||||
crypted_high_score = int(pickle.loads(crypted_high_score))
|
crypted_high_score = int(pickle.loads(crypted_high_score))
|
||||||
self.high_score = crypted_high_score ^ CRYPT_KEY
|
self.stats.high_score = crypted_high_score ^ CRYPT_KEY
|
||||||
else:
|
else:
|
||||||
raise Warning(
|
raise Warning(
|
||||||
"""TetrisLogic.load_high_score not implemented.
|
"""TetrisLogic.load_high_score not implemented.
|
||||||
High score is set to 0"""
|
High score is set to 0"""
|
||||||
)
|
)
|
||||||
self.high_score = 0
|
self.stats.high_score = 0
|
||||||
|
|
||||||
def save_high_score(self):
|
def save_high_score(self):
|
||||||
crypted_high_score = self.high_score ^ CRYPT_KEY
|
crypted_high_score = self.stats.high_score ^ CRYPT_KEY
|
||||||
crypted_high_score = pickle.dumps(crypted_high_score)
|
crypted_high_score = pickle.dumps(crypted_high_score)
|
||||||
return crypted_high_score
|
return crypted_high_score
|
||||||
|
|
||||||
def start(task, period):
|
|
||||||
raise Warning("TetrisLogic.start is not implemented.")
|
|
||||||
|
|
||||||
def stop(self, task):
|
|
||||||
raise Warning("TetrisLogic.stop is not implemented.")
|
|
||||||
|
|
||||||
def restart(self, task, period):
|
|
||||||
self.stop(task)
|
|
||||||
self.start(task, period)
|
|
||||||
|
|||||||
+30
-17
@@ -1,5 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from .utils import Coord, Rotation, Color
|
import random
|
||||||
|
|
||||||
|
from .utils import Coord, Spin, Color
|
||||||
|
|
||||||
|
|
||||||
class Mino:
|
class Mino:
|
||||||
@@ -14,18 +16,29 @@ class MetaTetromino(type):
|
|||||||
Tetromino.shapes.append(cls)
|
Tetromino.shapes.append(cls)
|
||||||
|
|
||||||
|
|
||||||
class Tetromino(list):
|
class Tetromino:
|
||||||
|
|
||||||
shapes = []
|
shapes = []
|
||||||
|
random_bag = []
|
||||||
|
|
||||||
|
def __new__(cls):
|
||||||
|
if not cls.random_bag:
|
||||||
|
cls.random_bag = list(cls.shapes)
|
||||||
|
random.shuffle(cls.random_bag)
|
||||||
|
return cls.random_bag.pop()()
|
||||||
|
|
||||||
|
|
||||||
|
class TetrominoBase(list):
|
||||||
|
|
||||||
# Super rotation system
|
# Super rotation system
|
||||||
SRS = {
|
SRS = {
|
||||||
Rotation.CLOCKWISE: (
|
Spin.CLOCKWISE: (
|
||||||
(Coord(0, 0), Coord(-1, 0), Coord(-1, 1), Coord(0, -2), Coord(-1, -2)),
|
(Coord(0, 0), Coord(-1, 0), Coord(-1, 1), Coord(0, -2), Coord(-1, -2)),
|
||||||
(Coord(0, 0), Coord(1, 0), Coord(1, -1), Coord(0, 2), Coord(1, 2)),
|
(Coord(0, 0), Coord(1, 0), Coord(1, -1), Coord(0, 2), Coord(1, 2)),
|
||||||
(Coord(0, 0), Coord(1, 0), Coord(1, 1), Coord(0, -2), Coord(1, -2)),
|
(Coord(0, 0), Coord(1, 0), Coord(1, 1), Coord(0, -2), Coord(1, -2)),
|
||||||
(Coord(0, 0), Coord(-1, 0), Coord(-1, -1), Coord(0, -2), Coord(-1, 2)),
|
(Coord(0, 0), Coord(-1, 0), Coord(-1, -1), Coord(0, -2), Coord(-1, 2)),
|
||||||
),
|
),
|
||||||
Rotation.COUNTER: (
|
Spin.COUNTER: (
|
||||||
(Coord(0, 0), Coord(1, 0), Coord(1, 1), Coord(0, -2), Coord(1, -2)),
|
(Coord(0, 0), Coord(1, 0), Coord(1, 1), Coord(0, -2), Coord(1, -2)),
|
||||||
(Coord(0, 0), Coord(1, 0), Coord(1, -1), Coord(0, 2), Coord(1, 2)),
|
(Coord(0, 0), Coord(1, 0), Coord(1, -1), Coord(0, 2), Coord(1, 2)),
|
||||||
(Coord(0, 0), Coord(-1, 0), Coord(-1, 1), Coord(0, -2), Coord(-1, -2)),
|
(Coord(0, 0), Coord(-1, 0), Coord(-1, 1), Coord(0, -2), Coord(-1, -2)),
|
||||||
@@ -36,19 +49,19 @@ class Tetromino(list):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__(Mino(self.MINOES_COLOR, coord) for coord in self.MINOES_COORDS)
|
super().__init__(Mino(self.MINOES_COLOR, coord) for coord in self.MINOES_COORDS)
|
||||||
self.orientation = 0
|
self.orientation = 0
|
||||||
self.last_rotation_point = None
|
self.rotated_last = False
|
||||||
|
self.rotation_point_5_used = False
|
||||||
self.hold_enabled = True
|
self.hold_enabled = True
|
||||||
self.prelocked = False
|
|
||||||
|
|
||||||
def ghost(self):
|
def ghost(self):
|
||||||
return type(self)()
|
return type(self)()
|
||||||
|
|
||||||
|
|
||||||
class O_Tetrimino(Tetromino, metaclass=MetaTetromino):
|
class O_Tetrimino(TetrominoBase, metaclass=MetaTetromino):
|
||||||
|
|
||||||
SRS = {
|
SRS = {
|
||||||
Rotation.CLOCKWISE: (tuple(), tuple(), tuple(), tuple()),
|
Spin.CLOCKWISE: (tuple(), tuple(), tuple(), tuple()),
|
||||||
Rotation.COUNTER: (tuple(), tuple(), tuple(), tuple()),
|
Spin.COUNTER: (tuple(), tuple(), tuple(), tuple()),
|
||||||
}
|
}
|
||||||
MINOES_COORDS = (Coord(0, 0), Coord(1, 0), Coord(0, 1), Coord(1, 1))
|
MINOES_COORDS = (Coord(0, 0), Coord(1, 0), Coord(0, 1), Coord(1, 1))
|
||||||
MINOES_COLOR = Color.YELLOW
|
MINOES_COLOR = Color.YELLOW
|
||||||
@@ -57,16 +70,16 @@ class O_Tetrimino(Tetromino, metaclass=MetaTetromino):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
class I_Tetrimino(Tetromino, metaclass=MetaTetromino):
|
class I_Tetrimino(TetrominoBase, metaclass=MetaTetromino):
|
||||||
|
|
||||||
SRS = {
|
SRS = {
|
||||||
Rotation.CLOCKWISE: (
|
Spin.CLOCKWISE: (
|
||||||
(Coord(1, 0), Coord(-1, 0), Coord(2, 0), Coord(-1, -1), Coord(2, 2)),
|
(Coord(1, 0), Coord(-1, 0), Coord(2, 0), Coord(-1, -1), Coord(2, 2)),
|
||||||
(Coord(0, -1), Coord(-1, -1), Coord(2, -1), Coord(-1, 1), Coord(2, -2)),
|
(Coord(0, -1), Coord(-1, -1), Coord(2, -1), Coord(-1, 1), Coord(2, -2)),
|
||||||
(Coord(-1, 0), Coord(1, 0), Coord(-2, 0), Coord(1, 1), Coord(-2, -2)),
|
(Coord(-1, 0), Coord(1, 0), Coord(-2, 0), Coord(1, 1), Coord(-2, -2)),
|
||||||
(Coord(0, -1), Coord(1, 1), Coord(-2, 1), Coord(1, -1), Coord(-2, 2)),
|
(Coord(0, -1), Coord(1, 1), Coord(-2, 1), Coord(1, -1), Coord(-2, 2)),
|
||||||
),
|
),
|
||||||
Rotation.COUNTER: (
|
Spin.COUNTER: (
|
||||||
(Coord(0, -1), Coord(-1, -1), Coord(2, -1), Coord(-1, 1), Coord(2, -2)),
|
(Coord(0, -1), Coord(-1, -1), Coord(2, -1), Coord(-1, 1), Coord(2, -2)),
|
||||||
(Coord(-1, 0), Coord(1, 0), Coord(-2, 0), Coord(1, 1), Coord(-2, -2)),
|
(Coord(-1, 0), Coord(1, 0), Coord(-2, 0), Coord(1, 1), Coord(-2, -2)),
|
||||||
(Coord(0, 1), Coord(1, 1), Coord(-2, 1), Coord(1, -1), Coord(-2, 2)),
|
(Coord(0, 1), Coord(1, 1), Coord(-2, 1), Coord(1, -1), Coord(-2, 2)),
|
||||||
@@ -77,31 +90,31 @@ class I_Tetrimino(Tetromino, metaclass=MetaTetromino):
|
|||||||
MINOES_COLOR = Color.CYAN
|
MINOES_COLOR = Color.CYAN
|
||||||
|
|
||||||
|
|
||||||
class T_Tetrimino(Tetromino, metaclass=MetaTetromino):
|
class T_Tetrimino(TetrominoBase, metaclass=MetaTetromino):
|
||||||
|
|
||||||
MINOES_COORDS = (Coord(-1, 0), Coord(0, 0), Coord(0, 1), Coord(1, 0))
|
MINOES_COORDS = (Coord(-1, 0), Coord(0, 0), Coord(0, 1), Coord(1, 0))
|
||||||
MINOES_COLOR = Color.MAGENTA
|
MINOES_COLOR = Color.MAGENTA
|
||||||
|
|
||||||
|
|
||||||
class L_Tetrimino(Tetromino, metaclass=MetaTetromino):
|
class L_Tetrimino(TetrominoBase, metaclass=MetaTetromino):
|
||||||
|
|
||||||
MINOES_COORDS = (Coord(-1, 0), Coord(0, 0), Coord(1, 0), Coord(1, 1))
|
MINOES_COORDS = (Coord(-1, 0), Coord(0, 0), Coord(1, 0), Coord(1, 1))
|
||||||
MINOES_COLOR = Color.ORANGE
|
MINOES_COLOR = Color.ORANGE
|
||||||
|
|
||||||
|
|
||||||
class J_Tetrimino(Tetromino, metaclass=MetaTetromino):
|
class J_Tetrimino(TetrominoBase, metaclass=MetaTetromino):
|
||||||
|
|
||||||
MINOES_COORDS = (Coord(-1, 1), Coord(-1, 0), Coord(0, 0), Coord(1, 0))
|
MINOES_COORDS = (Coord(-1, 1), Coord(-1, 0), Coord(0, 0), Coord(1, 0))
|
||||||
MINOES_COLOR = Color.BLUE
|
MINOES_COLOR = Color.BLUE
|
||||||
|
|
||||||
|
|
||||||
class S_Tetrimino(Tetromino, metaclass=MetaTetromino):
|
class S_Tetrimino(TetrominoBase, metaclass=MetaTetromino):
|
||||||
|
|
||||||
MINOES_COORDS = (Coord(-1, 0), Coord(0, 0), Coord(0, 1), Coord(1, 1))
|
MINOES_COORDS = (Coord(-1, 0), Coord(0, 0), Coord(0, 1), Coord(1, 1))
|
||||||
MINOES_COLOR = Color.GREEN
|
MINOES_COLOR = Color.GREEN
|
||||||
|
|
||||||
|
|
||||||
class Z_Tetrimino(Tetromino, metaclass=MetaTetromino):
|
class Z_Tetrimino(TetrominoBase, metaclass=MetaTetromino):
|
||||||
|
|
||||||
MINOES_COORDS = (Coord(-1, 1), Coord(0, 1), Coord(0, 0), Coord(1, 0))
|
MINOES_COORDS = (Coord(-1, 1), Coord(0, 1), Coord(0, 0), Coord(1, 0))
|
||||||
MINOES_COLOR = Color.RED
|
MINOES_COLOR = Color.RED
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ class Movement:
|
|||||||
DOWN = Coord(0, -1)
|
DOWN = Coord(0, -1)
|
||||||
|
|
||||||
|
|
||||||
class Rotation:
|
class Spin:
|
||||||
|
|
||||||
CLOCKWISE = 1
|
CLOCKWISE = 1
|
||||||
COUNTER = -1
|
COUNTER = -1
|
||||||
@@ -28,6 +28,14 @@ class T_Spin:
|
|||||||
T_SPIN = "T-SPIN"
|
T_SPIN = "T-SPIN"
|
||||||
|
|
||||||
|
|
||||||
|
class T_Slot:
|
||||||
|
|
||||||
|
A = 0
|
||||||
|
B = 1
|
||||||
|
C = 3
|
||||||
|
D = 2
|
||||||
|
|
||||||
|
|
||||||
class Color:
|
class Color:
|
||||||
|
|
||||||
BLUE = 0
|
BLUE = 0
|
||||||
|
|||||||
Reference in New Issue
Block a user