Compare commits
32 Commits
0.3
...
a062ba1ac1
| Author | SHA1 | Date | |
|---|---|---|---|
| a062ba1ac1 | |||
| 0d7470fd51 | |||
| fe93336bb9 | |||
| 578b126b3e | |||
| 6135e24eac | |||
| 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
|
||||
|
||||
* [Python](https://www.python.org/) 3.6 or upper
|
||||
* [Python](https://www.python.org/) 3.6 or later
|
||||
|
||||
## Install
|
||||
|
||||
|
||||
+252
-171
@@ -1,10 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import sys
|
||||
import random
|
||||
|
||||
try:
|
||||
import arcade
|
||||
except ImportError as e:
|
||||
sys.exit(
|
||||
str(e) + """
|
||||
str(e)
|
||||
+ """
|
||||
This game require arcade library.
|
||||
You can install it with:
|
||||
python -m pip install --user arcade"""
|
||||
@@ -17,25 +20,26 @@ import os
|
||||
import itertools
|
||||
import configparser
|
||||
|
||||
from tetrislogic import TetrisLogic, Color, State, Coord
|
||||
from tetrislogic import TetrisLogic, Color, Coord, I_Tetrimino, Movement, AbstractScheduler
|
||||
|
||||
|
||||
# Constants
|
||||
# Matrix
|
||||
NB_LINES = 20
|
||||
NB_COLS = 10
|
||||
NB_NEXT = 5
|
||||
ROWS = 20
|
||||
COLLUMNS = 10
|
||||
NEXT_PIECES = 6
|
||||
|
||||
# Delays (seconds)
|
||||
LOCK_DELAY = 0.5
|
||||
FALL_DELAY = 1
|
||||
AUTOREPEAT_DELAY = 0.300
|
||||
AUTOREPEAT_PERIOD = 0.010
|
||||
PARTICULE_ACCELERATION = 1.1
|
||||
|
||||
# Piece init coord
|
||||
MATRIX_PIECE_COORD = Coord(4, NB_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)
|
||||
MATRIX_PIECE_COORD = Coord(4, ROWS)
|
||||
NEXT_PIECES_COORDS = [Coord(COLLUMNS + 4, ROWS - 4 * n) for n in range(NEXT_PIECES)]
|
||||
HELD_PIECE_COORD = Coord(-5, ROWS)
|
||||
|
||||
# Window
|
||||
WINDOW_WIDTH = 800
|
||||
@@ -49,7 +53,7 @@ BG_COLOR = (7, 11, 21)
|
||||
HIGHLIGHT_TEXT_DISPLAY_DELAY = 0.7
|
||||
|
||||
# Transparency (0=invisible, 255=opaque)
|
||||
NORMAL_ALPHA = 200
|
||||
NORMAL_ALPHA = 255
|
||||
PRELOCKED_ALPHA = 100
|
||||
GHOST_ALPHA = 30
|
||||
MATRIX_BG_ALPHA = 100
|
||||
@@ -59,7 +63,7 @@ BAR_ALPHA = 75
|
||||
MINO_SIZE = 20
|
||||
MINO_SPRITE_SIZE = 21
|
||||
|
||||
if getattr(sys, 'frozen', False):
|
||||
if getattr(sys, "frozen", False):
|
||||
# The application is frozen
|
||||
PROGRAM_DIR = os.path.dirname(sys.executable)
|
||||
else:
|
||||
@@ -71,10 +75,8 @@ RESOURCES_DIR = os.path.join(PROGRAM_DIR, "resources")
|
||||
IMAGES_DIR = os.path.join(RESOURCES_DIR, "images")
|
||||
WINDOW_BG_PATH = os.path.join(IMAGES_DIR, "bg.jpg")
|
||||
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")
|
||||
Color.PRELOCKED = 7
|
||||
Color.LOCKED = 7
|
||||
MINOES_COLOR_ID = {
|
||||
Color.BLUE: 0,
|
||||
Color.CYAN: 1,
|
||||
@@ -83,10 +85,11 @@ MINOES_COLOR_ID = {
|
||||
Color.ORANGE: 4,
|
||||
Color.RED: 5,
|
||||
Color.YELLOW: 6,
|
||||
Color.PRELOCKED: 7,
|
||||
Color.LOCKED: 7,
|
||||
}
|
||||
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()}
|
||||
|
||||
@@ -105,42 +108,85 @@ HIGHLIGHT_TEXT_SIZE = 20
|
||||
|
||||
# User profile path
|
||||
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:
|
||||
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")
|
||||
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 Scheduler(AbstractScheduler):
|
||||
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):
|
||||
|
||||
def __init__(self, mino, window, alpha):
|
||||
super().__init__()
|
||||
self.alpha = alpha
|
||||
self.window = window
|
||||
self.append_texture(TEXTURES[mino.color])
|
||||
self.append_texture(TEXTURES[Color.PRELOCKED])
|
||||
self.append_texture(TEXTURES[Color.LOCKED])
|
||||
self.set_texture(0)
|
||||
|
||||
def refresh(self, x, y, prelocked=False):
|
||||
def update(self, x, y):
|
||||
self.scale = self.window.scale
|
||||
size = MINO_SIZE * self.scale
|
||||
self.left = self.window.matrix.bg.left + x * size
|
||||
self.bottom = self.window.matrix.bg.bottom + y * size
|
||||
self.set_texture(prelocked)
|
||||
|
||||
|
||||
class MinoesSprites(arcade.SpriteList):
|
||||
|
||||
def resize(self, scale):
|
||||
for sprite in self:
|
||||
sprite.scale = scale
|
||||
self.refresh()
|
||||
self.update()
|
||||
|
||||
|
||||
class TetrominoSprites(MinoesSprites):
|
||||
|
||||
def __init__(self, tetromino, window, alpha=NORMAL_ALPHA):
|
||||
super().__init__()
|
||||
self.tetromino = tetromino
|
||||
@@ -149,26 +195,30 @@ class TetrominoSprites(MinoesSprites):
|
||||
mino.sprite = MinoSprite(mino, window, alpha)
|
||||
self.append(mino.sprite)
|
||||
|
||||
def refresh(self):
|
||||
def update(self):
|
||||
for mino in self.tetromino:
|
||||
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):
|
||||
|
||||
def __init__(self, matrix):
|
||||
super().__init__()
|
||||
self.matrix = matrix
|
||||
self.refresh()
|
||||
self.update()
|
||||
|
||||
def refresh(self):
|
||||
for y, line in enumerate(self.matrix):
|
||||
for x, mino in enumerate(line):
|
||||
def update(self):
|
||||
for y, row in enumerate(self.matrix):
|
||||
for x, mino in enumerate(row):
|
||||
if mino:
|
||||
mino.sprite.refresh(x, y)
|
||||
mino.sprite.update(x, y)
|
||||
|
||||
def remove_line(self, y):
|
||||
def remove_row(self, y):
|
||||
for mino in self.matrix[y]:
|
||||
if mino:
|
||||
self.remove(mino.sprite)
|
||||
@@ -176,21 +226,11 @@ class MatrixSprites(MinoesSprites):
|
||||
|
||||
class TetrArcade(TetrisLogic, arcade.Window):
|
||||
|
||||
NB_LINES = NB_LINES
|
||||
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
|
||||
timer = Scheduler()
|
||||
|
||||
def __init__(self):
|
||||
locale.setlocale(locale.LC_ALL, "")
|
||||
self.highlight_texts = []
|
||||
self.tasks = {}
|
||||
|
||||
self.conf = configparser.ConfigParser()
|
||||
if self.conf.read(CONF_PATH):
|
||||
@@ -203,7 +243,7 @@ class TetrArcade(TetrisLogic, arcade.Window):
|
||||
self.new_conf()
|
||||
self.load_conf()
|
||||
|
||||
super().__init__()
|
||||
super().__init__(ROWS, COLLUMNS, NEXT_PIECES)
|
||||
arcade.Window.__init__(
|
||||
self,
|
||||
width=self.init_width,
|
||||
@@ -219,23 +259,31 @@ class TetrArcade(TetrisLogic, arcade.Window):
|
||||
self.bg = arcade.Sprite(WINDOW_BG_PATH)
|
||||
self.matrix.bg = arcade.Sprite(MATRIX_BG_PATH)
|
||||
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.on_resize(self.init_width, self.init_height)
|
||||
self.exploding_minoes = [None for y in range(ROWS)]
|
||||
|
||||
if self.play_music:
|
||||
try:
|
||||
self.music = pyglet.media.Player()
|
||||
playlist = itertools.cycle(
|
||||
pyglet.media.load(path)
|
||||
for path in MUSICS_PATHS
|
||||
pyglet.media.load(path) for path in MUSICS_PATHS
|
||||
)
|
||||
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):
|
||||
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"] = {
|
||||
"start": "ENTER",
|
||||
"move left": "LEFT",
|
||||
@@ -248,9 +296,7 @@ class TetrArcade(TetrisLogic, arcade.Window):
|
||||
"pause": "ESCAPE",
|
||||
"fullscreen": "F11",
|
||||
}
|
||||
self.conf["MUSIC"] = {
|
||||
"play": True
|
||||
}
|
||||
self.conf["MUSIC"] = {"play": True}
|
||||
self.conf["AUTO-REPEAT"] = {"delay": 0.3, "period": 0.01}
|
||||
self.load_conf()
|
||||
if not os.path.exists(USER_PROFILE_DIR):
|
||||
@@ -268,26 +314,40 @@ class TetrArcade(TetrisLogic, arcade.Window):
|
||||
self.key_map = {
|
||||
State.STARTING: {
|
||||
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: {
|
||||
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"]["hard drop"]): self.hard_drop,
|
||||
getattr(arcade.key, self.conf["KEYBOARD"]["rotate clockwise"]): self.rotate_clockwise,
|
||||
getattr(arcade.key, self.conf["KEYBOARD"]["rotate counter"]): self.rotate_counter,
|
||||
getattr(arcade.key, self.conf["KEYBOARD"]["hold"]): self.swap,
|
||||
getattr(
|
||||
arcade.key, self.conf["KEYBOARD"]["rotate clockwise"]
|
||||
): 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"]["fullscreen"]): self.toggle_fullscreen,
|
||||
getattr(
|
||||
arcade.key, self.conf["KEYBOARD"]["fullscreen"]
|
||||
): self.toggle_fullscreen,
|
||||
},
|
||||
State.PAUSED: {
|
||||
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: {
|
||||
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".join(
|
||||
"{:<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"
|
||||
)
|
||||
self.start_text = "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.start_text = (
|
||||
"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
|
||||
OVER
|
||||
|
||||
@@ -316,124 +385,148 @@ AGAIN""".format(
|
||||
|
||||
self.play_music = self.conf["MUSIC"].getboolean("play")
|
||||
|
||||
def new_game(self):
|
||||
def on_new_game(self, next_pieces):
|
||||
self.highlight_texts = []
|
||||
super().new_game()
|
||||
|
||||
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.play()
|
||||
|
||||
def new_tetromino(self):
|
||||
tetromino = super().new_tetromino()
|
||||
tetromino.sprites = TetrominoSprites(tetromino, self)
|
||||
return tetromino
|
||||
self.state = State.PLAYING
|
||||
|
||||
def new_matrix_piece(self):
|
||||
super().new_matrix_piece()
|
||||
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 on_new_level(self, level):
|
||||
self.show_text("LEVEL\n{:n}".format(level))
|
||||
|
||||
def move(self, movement, prelock=True):
|
||||
moved = super().move(movement, prelock)
|
||||
self.matrix.piece.sprites.refresh()
|
||||
if moved:
|
||||
self.matrix.ghost.sprites.refresh()
|
||||
return moved
|
||||
def on_generation_phase(self, matrix, falling_piece, ghost_piece, next_pieces):
|
||||
matrix.sprites.update()
|
||||
falling_piece.sprites = TetrominoSprites(falling_piece, self)
|
||||
ghost_piece.sprites = TetrominoSprites(ghost_piece, self, GHOST_ALPHA)
|
||||
next_pieces[-1].sprites = TetrominoSprites(next_pieces[-1], self)
|
||||
for piece, coord in zip(next_pieces, NEXT_PIECES_COORDS):
|
||||
piece.coord = coord
|
||||
|
||||
def rotate(self, rotation):
|
||||
rotated = super().rotate(rotation)
|
||||
if rotated:
|
||||
for tetromino in (self.matrix.piece, self.matrix.ghost):
|
||||
tetromino.sprites.refresh()
|
||||
return rotated
|
||||
def on_falling_phase(self, falling_piece):
|
||||
falling_piece.sprites.set_texture(Texture.NORMAL)
|
||||
|
||||
def swap(self):
|
||||
super().swap()
|
||||
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 on_locked(self, falling_piece):
|
||||
falling_piece.sprites.set_texture(Texture.LOCKED)
|
||||
|
||||
def lock(self):
|
||||
self.matrix.piece.prelocked = False
|
||||
self.matrix.piece.sprites.refresh()
|
||||
super().lock()
|
||||
self.matrix.sprites.refresh()
|
||||
def on_locks_down(self, matrix, falling_piece):
|
||||
falling_piece.sprites.set_texture(Texture.NORMAL)
|
||||
for mino in falling_piece:
|
||||
matrix.sprites.append(mino.sprite)
|
||||
|
||||
def enter_the_matrix(self):
|
||||
super().enter_the_matrix()
|
||||
for mino in self.matrix.piece:
|
||||
self.matrix.sprites.append(mino.sprite)
|
||||
def on_animate_phase(self, matrix, rows_to_remove):
|
||||
for y in rows_to_remove:
|
||||
row_textures = tuple(TEXTURES[mino.color] for mino in matrix[y])
|
||||
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(row_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):
|
||||
self.matrix.sprites.remove_line(y)
|
||||
super().remove_line(y)
|
||||
def speed_up_particule(self, particule):
|
||||
particule.change_x *= PARTICULE_ACCELERATION
|
||||
particule.change_y *= PARTICULE_ACCELERATION
|
||||
|
||||
def pause(self):
|
||||
super().pause()
|
||||
if self.play_music:
|
||||
def on_eliminate_phase(self, matrix, rows_to_remove):
|
||||
for y in rows_to_remove:
|
||||
matrix.sprites.remove_row(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()
|
||||
|
||||
def resume(self):
|
||||
super().resume()
|
||||
if self.play_music:
|
||||
if self.music:
|
||||
self.music.play()
|
||||
self.state = State.PLAYING
|
||||
|
||||
def game_over(self):
|
||||
super().game_over()
|
||||
if self.play_music:
|
||||
def on_game_over(self):
|
||||
self.state = State.OVER
|
||||
if self.music:
|
||||
self.music.pause()
|
||||
|
||||
def on_key_press(self, key, modifiers):
|
||||
for key_or_modifier in (key, modifiers):
|
||||
try:
|
||||
action = self.key_map[self.state][key_or_modifier]
|
||||
action = self.key_map[self.state][key]
|
||||
except KeyError:
|
||||
pass
|
||||
return
|
||||
else:
|
||||
self.do_action(action)
|
||||
|
||||
def on_key_release(self, key, modifiers):
|
||||
for key_or_modifier in (key, modifiers):
|
||||
try:
|
||||
action = self.key_map[self.state][key_or_modifier]
|
||||
action = self.key_map[self.state][key]
|
||||
except KeyError:
|
||||
pass
|
||||
return
|
||||
else:
|
||||
self.remove_action(action)
|
||||
|
||||
def show_text(self, 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):
|
||||
if self.highlight_texts:
|
||||
self.highlight_texts.pop(0)
|
||||
else:
|
||||
self.stop(self.del_highlight_text)
|
||||
self.timer.cancel(self.del_highlight_text)
|
||||
|
||||
def on_draw(self):
|
||||
arcade.start_render()
|
||||
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.held.bg.draw()
|
||||
self.next.bg.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:
|
||||
tetromino.sprites.draw()
|
||||
|
||||
t = time.localtime(self.time)
|
||||
t = time.localtime(self.stats.time)
|
||||
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", "ROWS", "GOAL", "LEVEL", "HIGH SCORE", "SCORE")
|
||||
):
|
||||
arcade.draw_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,
|
||||
color=TEXT_COLOR,
|
||||
font_size=font_size,
|
||||
@@ -444,11 +537,11 @@ AGAIN""".format(
|
||||
for y, text in enumerate(
|
||||
(
|
||||
"{:02d}:{:02d}:{:02d}".format(t.tm_hour - 1, t.tm_min, t.tm_sec),
|
||||
"{:n}".format(self.nb_lines_cleared),
|
||||
"{:n}".format(self.goal),
|
||||
"{:n}".format(self.level),
|
||||
"{:n}".format(self.high_score),
|
||||
"{:n}".format(self.score),
|
||||
"{:n}".format(self.stats.rows_cleared),
|
||||
"{:n}".format(self.stats.goal),
|
||||
"{:n}".format(self.stats.level),
|
||||
"{:n}".format(self.stats.high_score),
|
||||
"{:n}".format(self.stats.score),
|
||||
)
|
||||
):
|
||||
arcade.draw_text(
|
||||
@@ -462,6 +555,10 @@ AGAIN""".format(
|
||||
anchor_x="right",
|
||||
)
|
||||
|
||||
for exploding_minoes in self.exploding_minoes:
|
||||
if exploding_minoes:
|
||||
exploding_minoes.draw()
|
||||
|
||||
highlight_text = {
|
||||
State.STARTING: self.start_text,
|
||||
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.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)
|
||||
|
||||
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:
|
||||
tetromino.sprites.resize(self.scale)
|
||||
|
||||
@@ -523,7 +616,7 @@ AGAIN""".format(
|
||||
crypted_high_score = f.read()
|
||||
super().load_high_score(crypted_high_score)
|
||||
except:
|
||||
self.high_score = 0
|
||||
self.stats.high_score = 0
|
||||
|
||||
def save_high_score(self):
|
||||
try:
|
||||
@@ -542,33 +635,21 @@ High score could not be saved:
|
||||
+ str(e)
|
||||
)
|
||||
|
||||
def start(self, task, period):
|
||||
_task = lambda _: task()
|
||||
self.tasks[task] = _task
|
||||
arcade.schedule(_task, period)
|
||||
|
||||
def stop(self, task):
|
||||
try:
|
||||
_task = self.tasks[task]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
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 update(self, delta_time):
|
||||
for piece in [
|
||||
self.held.piece,
|
||||
self.matrix.piece,
|
||||
self.matrix.ghost,
|
||||
] + self.next.pieces:
|
||||
if piece:
|
||||
piece.sprites.update()
|
||||
for exploding_minoes in self.exploding_minoes:
|
||||
if exploding_minoes:
|
||||
exploding_minoes.update()
|
||||
|
||||
def on_close(self):
|
||||
self.save_high_score()
|
||||
if self.play_music:
|
||||
if self.music:
|
||||
self.music.pause()
|
||||
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
|
||||
icon = None
|
||||
|
||||
excludes = [
|
||||
"tkinter",
|
||||
"PyQt4",
|
||||
"PyQt5",
|
||||
"PySide",
|
||||
"PySide2"
|
||||
]
|
||||
excludes = ["tkinter", "PyQt4", "PyQt5", "PySide", "PySide2"]
|
||||
|
||||
executable = Executable(
|
||||
script="TetrArcade.py",
|
||||
icon=icon,
|
||||
base=base,
|
||||
shortcutName="TetrArcade",
|
||||
shortcutDir="DesktopFolder"
|
||||
shortcutDir="DesktopFolder",
|
||||
)
|
||||
|
||||
options = {
|
||||
@@ -30,12 +24,12 @@ options = {
|
||||
"packages": ["arcade", "pyglet"],
|
||||
"excludes": excludes,
|
||||
"include_files": "resources",
|
||||
"silent": True
|
||||
"silent": True,
|
||||
}
|
||||
}
|
||||
setup(
|
||||
name="TetrArcade",
|
||||
version = "0.3",
|
||||
version="0.5",
|
||||
description="Tetris clone",
|
||||
author="AdrienMalin",
|
||||
executables=[executable],
|
||||
|
||||
@@ -1,17 +1,36 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from TetrArcade import TetrArcade, State
|
||||
from TetrArcade import TetrArcade, MinoSprite, State
|
||||
from tetrislogic import Mino, Color, Coord
|
||||
|
||||
game = TetrArcade()
|
||||
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.pause()
|
||||
game.resume()
|
||||
game.move_right()
|
||||
game.swap()
|
||||
game.hold()
|
||||
game.update(0)
|
||||
game.on_draw()
|
||||
game.rotate_clockwise()
|
||||
game.hold()
|
||||
game.update(0)
|
||||
game.on_draw()
|
||||
game.rotate_counter()
|
||||
for i in range(12):
|
||||
for i in range(22):
|
||||
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()
|
||||
while game.state != State.OVER:
|
||||
game.hard_drop()
|
||||
game.on_draw()
|
||||
|
||||
+14
-4
@@ -1,5 +1,15 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from .consts import NB_LINES, NB_COLS, NB_NEXT
|
||||
from .utils import Movement, Rotation, Color, Coord
|
||||
from .tetromino import Mino, Tetromino
|
||||
from .tetrislogic import TetrisLogic, State, Matrix
|
||||
from .consts import ROWS, COLLUMNS, NEXT_PIECES
|
||||
from .utils import Movement, Spin, Color, Coord
|
||||
from .tetromino import (
|
||||
Mino,
|
||||
Tetromino,
|
||||
I_Tetrimino,
|
||||
J_Tetrimino,
|
||||
L_Tetrimino,
|
||||
O_Tetrimino,
|
||||
S_Tetrimino,
|
||||
T_Tetrimino,
|
||||
Z_Tetrimino,
|
||||
)
|
||||
from .tetrislogic import TetrisLogic, Matrix, AbstractScheduler
|
||||
|
||||
+15
-7
@@ -1,11 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from .utils import Coord
|
||||
from .utils import Coord, T_Spin
|
||||
|
||||
|
||||
# Matrix
|
||||
NB_LINES = 20
|
||||
NB_COLS = 10
|
||||
NB_NEXT = 5
|
||||
ROWS = 20
|
||||
COLLUMNS = 10
|
||||
NEXT_PIECES = 5
|
||||
|
||||
# Delays (seconds)
|
||||
LOCK_DELAY = 0.5
|
||||
@@ -14,6 +14,14 @@ AUTOREPEAT_DELAY = 0.300 # Official : 0.300 s
|
||||
AUTOREPEAT_PERIOD = 0.010 # Official : 0.010 s
|
||||
|
||||
# Piece init coord
|
||||
MATRIX_PIECE_COORD = Coord(4, NB_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)
|
||||
MATRIX_PIECE_COORD = Coord(4, ROWS)
|
||||
|
||||
# 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},
|
||||
)
|
||||
|
||||
+436
-285
@@ -1,129 +1,126 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import random
|
||||
import pickle
|
||||
|
||||
from .utils import Coord, Movement, Rotation, T_Spin
|
||||
from .tetromino import Tetromino, T_Tetrimino, I_Tetrimino
|
||||
from .utils import Coord, Movement, Spin, T_Spin, T_Slot
|
||||
from .tetromino import Tetromino, T_Tetrimino
|
||||
from .consts import (
|
||||
NB_LINES,
|
||||
NB_COLS,
|
||||
NB_NEXT,
|
||||
ROWS,
|
||||
COLLUMNS,
|
||||
NEXT_PIECES,
|
||||
LOCK_DELAY,
|
||||
FALL_DELAY,
|
||||
AUTOREPEAT_DELAY,
|
||||
AUTOREPEAT_PERIOD,
|
||||
MATRIX_PIECE_COORD,
|
||||
NEXT_PIECE_COORDS,
|
||||
HELD_PIECE_COORD,
|
||||
SCORES,
|
||||
LINES_CLEAR_NAME,
|
||||
)
|
||||
|
||||
|
||||
LINES_CLEAR_NAME = "LINES_CLEAR_NAME"
|
||||
CRYPT_KEY = 987943759387540938469837689379857347598347598379584857934579343
|
||||
|
||||
|
||||
class State:
|
||||
class AbstractScheduler:
|
||||
"""Scheduler to implement"""
|
||||
def postpone(task, delay):
|
||||
"""Schedule callable once after delay in seconds"""
|
||||
raise Warning("AbstractTimer.postpone is not implemented.")
|
||||
|
||||
STARTING = "STARTING"
|
||||
PLAYING = "PLAYING"
|
||||
PAUSED = "PAUSED"
|
||||
OVER = "OVER"
|
||||
def cancel(self, task):
|
||||
"""Unschedule task or pass if task is not scheduled"""
|
||||
raise Warning("AbstractTimer.stop is not implemented.")
|
||||
|
||||
def reset(self, task, delay):
|
||||
"""Cancel schedule and reschedule task after delay in seconds"""
|
||||
self.timer.cancel(task)
|
||||
self.timer.postpone(task, delay)
|
||||
|
||||
|
||||
class PieceContainer:
|
||||
|
||||
"""Object with piece attribute: None or Tetromino"""
|
||||
def __init__(self):
|
||||
self.piece = None
|
||||
|
||||
|
||||
class HoldQueue(PieceContainer):
|
||||
"""The storage place where players can Hold any falling tetrimino for use later.
|
||||
When called for, the held tetrimino swaps places with the currently falling tetrimino,
|
||||
and begins falling again at the generation point."""
|
||||
pass
|
||||
|
||||
|
||||
class Matrix(list, PieceContainer):
|
||||
|
||||
def __init__(self, *args, **kargs):
|
||||
list.__init__(self, *args, **kargs)
|
||||
"""The rectangular arrangement of cells creating the active game area,
|
||||
usually 10 columns wide by 20 rows high.
|
||||
Tetriminos fall from the top-middle just above the Skyline (off-screen) to the bottom."""
|
||||
def __init__(self, rows, collumns):
|
||||
list.__init__(self)
|
||||
PieceContainer.__init__(self)
|
||||
self.rows = rows
|
||||
self.collumns = collumns
|
||||
self.ghost = None
|
||||
|
||||
def reset(self):
|
||||
self.clear()
|
||||
for y in range(self.rows + 3):
|
||||
self.append_new_row()
|
||||
|
||||
def append_new_row(self):
|
||||
self.append([None for x in range(self.collumns)])
|
||||
|
||||
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):
|
||||
|
||||
def __init__(self):
|
||||
"""Displays the next tetrimino(s) to be placed (generated) just above the Matrix.
|
||||
If hardware permits, the next six tetriminos should be shown."""
|
||||
def __init__(self, number):
|
||||
super().__init__()
|
||||
self.number = number
|
||||
self.pieces = []
|
||||
|
||||
|
||||
class TetrisLogic:
|
||||
|
||||
NB_LINES = NB_LINES
|
||||
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):
|
||||
class Stats:
|
||||
"""Game statistics"""
|
||||
def _get_score(self):
|
||||
return self._score
|
||||
|
||||
def set_score(self, new_score):
|
||||
def _set_score(self, new_score):
|
||||
self._score = new_score
|
||||
if self._score > self.high_score:
|
||||
self.high_score = self._score
|
||||
|
||||
score = property(get_score, set_score)
|
||||
score = property(_get_score, _set_score)
|
||||
|
||||
def new_game(self):
|
||||
self.level = 0
|
||||
self.score = 0
|
||||
self.nb_lines_cleared = 0
|
||||
self.goal = 0
|
||||
def __init__(self):
|
||||
self._score = 0
|
||||
self.high_score = 0
|
||||
self.time = 0
|
||||
|
||||
self.pressed_actions = []
|
||||
self.auto_repeat = False
|
||||
def new_game(self, level):
|
||||
self.level = level - 1
|
||||
self.score = 0
|
||||
self.rows_cleared = 0
|
||||
self.goal = 0
|
||||
self.time = 0
|
||||
self.combo = -1
|
||||
|
||||
self.lock_delay = self.LOCK_DELAY
|
||||
self.fall_delay = self.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)])
|
||||
self.lock_delay = LOCK_DELAY
|
||||
self.fall_delay = FALL_DELAY
|
||||
|
||||
def new_level(self):
|
||||
self.level += 1
|
||||
@@ -132,23 +129,287 @@ class TetrisLogic:
|
||||
self.fall_delay = pow(0.8 - ((self.level - 1) * 0.007), self.level - 1)
|
||||
if 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.time += 1
|
||||
|
||||
def locks_down(self, t_spin, rows_cleared):
|
||||
pattern_name = []
|
||||
pattern_score = 0
|
||||
combo_score = 0
|
||||
|
||||
if t_spin:
|
||||
pattern_name.append(t_spin)
|
||||
if rows_cleared:
|
||||
pattern_name.append(SCORES[rows_cleared][LINES_CLEAR_NAME])
|
||||
self.combo += 1
|
||||
else:
|
||||
self.combo = -1
|
||||
|
||||
if rows_cleared or t_spin:
|
||||
pattern_score = SCORES[rows_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 rows_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:
|
||||
"""Tetris game logic intended to implement with GUI"""
|
||||
# These class attributes can be redefined on inheritance
|
||||
AUTOREPEAT_DELAY = AUTOREPEAT_DELAY
|
||||
AUTOREPEAT_PERIOD = AUTOREPEAT_PERIOD
|
||||
MATRIX_PIECE_COORD = MATRIX_PIECE_COORD
|
||||
|
||||
timer = AbstractScheduler()
|
||||
|
||||
def __init__(self, rows=ROWS, collumns=COLLUMNS, next_pieces=NEXT_PIECES):
|
||||
self.stats = Stats()
|
||||
self.load_high_score()
|
||||
self.held = HoldQueue()
|
||||
self.matrix = Matrix(rows, 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.ghost = self.matrix.piece.ghost()
|
||||
self.move_ghost()
|
||||
self.next.pieces.append(self.new_tetromino())
|
||||
self.next.pieces[-1].coord = self.NEXT_PIECE_COORDS[-1]
|
||||
for tetromino, coord in zip(self.next.pieces, self.NEXT_PIECE_COORDS):
|
||||
tetromino.coord = coord
|
||||
self.refresh_ghost()
|
||||
# if self.pressed_actions:
|
||||
# self.timer.postpone(self.repeat_action, self.AUTOREPEAT_DELAY)
|
||||
|
||||
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()
|
||||
|
||||
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):
|
||||
"""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.
|
||||
Each Mino of a tetrimino “snaps” to the appropriate cell position at the completion of a move,
|
||||
although intermediate tetrimino movement appears smooth.
|
||||
Only right, left, and downward movement are allowed.
|
||||
Movement into occupied cells and Matrix walls and floors is not allowed."""
|
||||
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:
|
||||
|
||||
"""Classic Lock down rules apply.
|
||||
Like Infinite Placement, the Lock down timer starts counting down from 0.5 seconds once the
|
||||
tetrimino in play lands on a Surface. the y-coordinate of the tetrimino must decrease (i.e., the
|
||||
tetrimino falls further down in the Matrix) in order for the timer to be reset."""
|
||||
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):
|
||||
"""Tetriminos can rotate clockwise and counterclockwise using the Super Rotation System. this
|
||||
system allows tetrimino rotation in situations that the original Classic Rotation System did not
|
||||
allow, such as rotating against walls.
|
||||
each time a rotation button is pressed, 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. there is no Auto-Repeat for rotation itself."""
|
||||
rotated_coords = tuple(mino.coord @ spin 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):
|
||||
"""A tetrimino that is Hard dropped Locks down immediately.
|
||||
However, if a tetrimino naturally falls or Soft drops onto a Surface,
|
||||
it is given 0.5 seconds (less after level 20) on a Lock down timer
|
||||
before it actually Locks down."""
|
||||
self.timer.cancel(self.lock_phase)
|
||||
|
||||
# Game over
|
||||
if all(
|
||||
(mino.coord + self.matrix.piece.coord).y >= self.matrix.rows
|
||||
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.rows + 3:
|
||||
self.matrix[coord.y][coord.x] = mino
|
||||
|
||||
self.on_locks_down(self.matrix, self.matrix.piece)
|
||||
|
||||
# Pattern phase
|
||||
|
||||
# T-Spin
|
||||
"""A t-Spin or Mini t-Spin is a special rotation of the t-tetrimino into a t-Slot, and when
|
||||
accomplished, awards a scoring or line bonus in most variants. A t-Slot is defined as any Block
|
||||
formation such that when the t-tetrimino is spun in it, any three of the four cells diagonally
|
||||
adjacent to the center of the t-tetrimino are occupied by existing Blocks. In order to be
|
||||
considered a t-Spin or Mini t-Spin, the t-tetrimino must spin clockwise or counterclockwise first
|
||||
(it cannot merely be moved or dropped into a t-Slot). In addition to a scoring or other bonus,
|
||||
t-Spins and Mini t-Spins can also continue a Back-to-Back sequence."""
|
||||
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):
|
||||
"""A rotation is considered a t-Spin if any of the following conditions are met:
|
||||
• Sides A and B + (C or d) are touching a Surface when the tetrimino Locks down.
|
||||
• the t-tetrimino fills a t-Slot completely with no holes.
|
||||
• Rotation Point 5 is used to rotate the tetrimino into the t-Slot.
|
||||
Any further rotation will be considered a t-Spin, not a Mini t-Spin."""
|
||||
t_spin = T_Spin.T_SPIN
|
||||
elif c and d and (a or b):
|
||||
"""A rotation is considered a Mini t-Spin if either of the following conditions are met:
|
||||
• Sides C and d + (A or B) are touching a Surface when the tetrimino Locks down.
|
||||
• the t-tetrimino creates holes in a t-Slot. However, if Rotation Point 5 was used to rotate
|
||||
the tetrimino into the t-Slot, the rotation is considered a t-Spin. """
|
||||
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 rows
|
||||
self.rows_to_remove = []
|
||||
for y, row in reversed(list(enumerate(self.matrix))):
|
||||
if all(mino for mino in row):
|
||||
self.rows_to_remove.append(y)
|
||||
rows_cleared = len(self.rows_to_remove)
|
||||
if rows_cleared:
|
||||
self.stats.rows_cleared += rows_cleared
|
||||
|
||||
# Animate phase
|
||||
|
||||
self.on_animate_phase(self.matrix, self.rows_to_remove)
|
||||
|
||||
# Eliminate phase
|
||||
self.on_eliminate_phase(self.matrix, self.rows_to_remove)
|
||||
for y in self.rows_to_remove:
|
||||
self.matrix.pop(y)
|
||||
self.matrix.append_new_row()
|
||||
|
||||
# Completion phase
|
||||
|
||||
pattern_name, pattern_score, nb_combo, combo_score = self.stats.locks_down(
|
||||
t_spin, rows_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, rows_to_remove):
|
||||
pass
|
||||
|
||||
def on_eliminate_phase(self, matrix, rows_to_remove):
|
||||
pass
|
||||
|
||||
def on_completion_phase(self, pattern_name, pattern_score, nb_combo, combo_score):
|
||||
pass
|
||||
|
||||
# Actions
|
||||
|
||||
def move_left(self):
|
||||
self.move(Movement.LEFT)
|
||||
|
||||
@@ -156,233 +417,133 @@ class TetrisLogic:
|
||||
self.move(Movement.RIGHT)
|
||||
|
||||
def rotate_clockwise(self):
|
||||
self.rotate(Rotation.CLOCKWISE)
|
||||
self.rotate(Spin.CLOCKWISE)
|
||||
|
||||
def rotate_counter(self):
|
||||
self.rotate(Rotation.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
|
||||
self.rotate(Spin.COUNTER)
|
||||
|
||||
def soft_drop(self):
|
||||
"""when the Soft drop command is pressed, the tetrimino in play drops at a rate 20 times faster
|
||||
than the normal fall Speed, measured in seconds per line. the tetrimino resumes its normal
|
||||
fall Speed once the Soft drop button is released. for example, if the normal fall Speed is 0.5
|
||||
seconds per line, then the Soft drop speed is (0.5 / 20) = 0.025 seconds per line.
|
||||
note that if the player Soft drops a tetrimino until it lands on a Surface, Lock down does not
|
||||
occur until the Lock down timer hits zero.
|
||||
Press and hold the Soft drop button to continue the downward movement. Soft drop continues
|
||||
to the next tetrimino (after Lock down) as long as the button remains pressed."""
|
||||
moved = self.move(Movement.DOWN)
|
||||
if moved:
|
||||
self.score += 1
|
||||
self.stats.score += 1
|
||||
return moved
|
||||
|
||||
def hard_drop(self):
|
||||
while self.move(Movement.DOWN, prelock=False):
|
||||
self.score += 2
|
||||
self.lock()
|
||||
"""The Hard drop command instantly drops the tetrimino
|
||||
and locks it down on the Surface directly below it.
|
||||
There is no Auto-Repeat for a Hard drop."""
|
||||
self.timer.cancel(self.lock_phase)
|
||||
self.timer.cancel(self.locks_down)
|
||||
while self.move(Movement.DOWN, lock=False):
|
||||
self.stats.score += 2
|
||||
self.locks_down()
|
||||
|
||||
def fall(self):
|
||||
self.move(Movement.DOWN)
|
||||
def hold(self):
|
||||
"""Using the Hold command places the tetrimino in play into the Hold Queue.
|
||||
The previously held tetrimino (if one exists) will then start falling from the top of the Matrix,
|
||||
beginning from its generation position and north facing orientation.
|
||||
Only one tetrimino may be held at a time.
|
||||
A Lock down must take place between Holds.
|
||||
Ror example, at the beginning, the first tetrimino is generated and begins to fall.
|
||||
The player decides to hold this tetrimino.
|
||||
Immediately the next tetrimino is generated from the next Queue and begins to fall.
|
||||
The player must first Lock down this tetrimino before holding another tetrimino.
|
||||
In other words, you may not Hold the same tetrimino more than once."""
|
||||
if not self.matrix.piece.hold_enabled:
|
||||
return
|
||||
|
||||
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
|
||||
self.matrix.piece.hold_enabled = False
|
||||
self.timer.cancel(self.lock_phase)
|
||||
self.matrix.piece, self.held.piece = self.held.piece, self.matrix.piece
|
||||
|
||||
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):
|
||||
for mino, coord in zip(self.held.piece, self.held.piece.MINOES_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},
|
||||
)
|
||||
self.on_hold(self.held.piece)
|
||||
self.generation_phase(self.matrix.piece)
|
||||
|
||||
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
|
||||
|
||||
# Game over
|
||||
if all((mino.coord + self.matrix.piece.coord).y >= self.NB_LINES for mino in self.matrix.piece):
|
||||
self.game_over()
|
||||
return
|
||||
|
||||
if self.pressed_actions:
|
||||
self.auto_repeat = False
|
||||
self.restart(self.repeat_action, self.AUTOREPEAT_DELAY)
|
||||
|
||||
# T-Spin
|
||||
if type(self.matrix.piece) == T_Tetrimino and self.matrix.piece.last_rotation_point is not None:
|
||||
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()
|
||||
|
||||
# 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)
|
||||
def on_hold(self, held_piece):
|
||||
pass
|
||||
|
||||
T_SLOT_COORDS = (Coord(-1, 1), Coord(1, 1), Coord(-1, 1), Coord(-1, -1))
|
||||
|
||||
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)
|
||||
|
||||
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):
|
||||
self.state = State.PAUSED
|
||||
self.stop_all()
|
||||
self.pressed_actions = []
|
||||
self.auto_repeat = False
|
||||
self.stop(self.repeat_action)
|
||||
self.timer.cancel(self.repeat_action)
|
||||
self.on_pause()
|
||||
|
||||
def on_pause(self):
|
||||
pass
|
||||
|
||||
def resume(self):
|
||||
self.state = State.PLAYING
|
||||
self.start(self.fall, self.fall_delay)
|
||||
if self.matrix.piece.prelocked:
|
||||
self.start(self.lock, self.lock_delay)
|
||||
self.start(self.update_time, 1)
|
||||
self.timer.postpone(self.lock_phase, self.stats.fall_delay)
|
||||
if self.matrix.piece.locked:
|
||||
self.timer.postpone(self.locks_down, self.stats.lock_delay)
|
||||
self.timer.postpone(self.stats.update_time, 1)
|
||||
self.on_resume()
|
||||
|
||||
def on_resume(self):
|
||||
pass
|
||||
|
||||
def game_over(self):
|
||||
self.state = State.OVER
|
||||
self.stop_all()
|
||||
self.save_high_score()
|
||||
self.on_game_over()
|
||||
|
||||
def on_game_over(self):
|
||||
pass
|
||||
|
||||
def stop_all(self):
|
||||
self.stop(self.fall)
|
||||
self.stop(self.lock)
|
||||
self.stop(self.update_time)
|
||||
|
||||
def update_time(self):
|
||||
self.time += 1
|
||||
self.timer.cancel(self.lock_phase)
|
||||
self.timer.cancel(self.locks_down)
|
||||
self.timer.cancel(self.stats.update_time)
|
||||
|
||||
def do_action(self, action):
|
||||
action()
|
||||
if action in self.autorepeatable_actions:
|
||||
self.auto_repeat = False
|
||||
self.pressed_actions.append(action)
|
||||
if action == self.soft_drop:
|
||||
delay = self.fall_delay / 20
|
||||
delay = self.stats.fall_delay / 20
|
||||
else:
|
||||
delay = self.AUTOREPEAT_DELAY
|
||||
self.restart(self.repeat_action, delay)
|
||||
self.timer.reset(self.repeat_action, delay)
|
||||
|
||||
def repeat_action(self):
|
||||
if self.pressed_actions:
|
||||
"""tapping the move button allows a single cell movement of the tetrimino in the direction
|
||||
pressed. Holding down the move button triggers an Auto-Repeat movement that allows the
|
||||
player to move a tetrimino from one side of the Matrix to the other in about 0.5 seconds. this is
|
||||
essential on higher levels when the fall Speed of a tetrimino is very fast.
|
||||
there must be a slight delay between the time the move button is pressed and the time when
|
||||
Auto-Repeat kicks in, roughly 0.3 seconds. this delay prevents unwanted extra movement of a
|
||||
tetrimino. Auto-Repeat only affects Left/Right movement. Auto-Repeat continues to the next
|
||||
tetrimino (after Lock down) as long as the move button remains pressed.
|
||||
In addition, when Auto-Repeat begins, and the player then holds the opposite direction button,
|
||||
the tetrimino must then begin moving the opposite direction with the initial delay. this mainly
|
||||
applies to devices with movement buttons—such as a keyboard or mobile phone—where more
|
||||
than one direction button is able to be pressed simultaneously. when any single button is then
|
||||
released, the tetrimino should again move in the direction still held, with the Auto-Repeat delay
|
||||
of roughly 0.3 seconds applied once more."""
|
||||
if not self.pressed_actions:
|
||||
return
|
||||
|
||||
self.pressed_actions[-1]()
|
||||
if not self.auto_repeat:
|
||||
self.auto_repeat = True
|
||||
self.restart(self.repeat_action, self.AUTOREPEAT_PERIOD)
|
||||
else:
|
||||
self.auto_repeat = False
|
||||
self.stop(self.repeat_action)
|
||||
self.timer.postpone(self.repeat_action, self.AUTOREPEAT_PERIOD)
|
||||
|
||||
def remove_action(self, action):
|
||||
if action in self.autorepeatable_actions:
|
||||
@@ -398,25 +559,15 @@ class TetrisLogic:
|
||||
def load_high_score(self, crypted_high_score=None):
|
||||
if 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:
|
||||
raise Warning(
|
||||
"""TetrisLogic.load_high_score not implemented.
|
||||
High score is set to 0"""
|
||||
)
|
||||
self.high_score = 0
|
||||
self.stats.high_score = 0
|
||||
|
||||
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)
|
||||
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 -*-
|
||||
from .utils import Coord, Rotation, Color
|
||||
import random
|
||||
|
||||
from .utils import Coord, Spin, Color
|
||||
|
||||
|
||||
class Mino:
|
||||
@@ -14,18 +16,29 @@ class MetaTetromino(type):
|
||||
Tetromino.shapes.append(cls)
|
||||
|
||||
|
||||
class Tetromino(list):
|
||||
class Tetromino:
|
||||
|
||||
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
|
||||
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)),
|
||||
),
|
||||
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)),
|
||||
@@ -36,19 +49,19 @@ class Tetromino(list):
|
||||
def __init__(self):
|
||||
super().__init__(Mino(self.MINOES_COLOR, coord) for coord in self.MINOES_COORDS)
|
||||
self.orientation = 0
|
||||
self.last_rotation_point = None
|
||||
self.rotated_last = False
|
||||
self.rotation_point_5_used = False
|
||||
self.hold_enabled = True
|
||||
self.prelocked = False
|
||||
|
||||
def ghost(self):
|
||||
return type(self)()
|
||||
|
||||
|
||||
class O_Tetrimino(Tetromino, metaclass=MetaTetromino):
|
||||
class O_Tetrimino(TetrominoBase, metaclass=MetaTetromino):
|
||||
|
||||
SRS = {
|
||||
Rotation.CLOCKWISE: (tuple(), tuple(), tuple(), tuple()),
|
||||
Rotation.COUNTER: (tuple(), tuple(), tuple(), tuple()),
|
||||
Spin.CLOCKWISE: (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_COLOR = Color.YELLOW
|
||||
@@ -57,16 +70,16 @@ class O_Tetrimino(Tetromino, metaclass=MetaTetromino):
|
||||
return False
|
||||
|
||||
|
||||
class I_Tetrimino(Tetromino, metaclass=MetaTetromino):
|
||||
class I_Tetrimino(TetrominoBase, metaclass=MetaTetromino):
|
||||
|
||||
SRS = {
|
||||
Rotation.CLOCKWISE: (
|
||||
Spin.CLOCKWISE: (
|
||||
(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(-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)),
|
||||
),
|
||||
Rotation.COUNTER: (
|
||||
Spin.COUNTER: (
|
||||
(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(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
|
||||
|
||||
|
||||
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_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_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_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_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_COLOR = Color.RED
|
||||
|
||||
+12
-1
@@ -7,6 +7,9 @@ class Coord:
|
||||
def __add__(self, other):
|
||||
return Coord(self.x + other.x, self.y + other.y)
|
||||
|
||||
def __matmul__(self, spin):
|
||||
return Coord(spin * self.y, -spin * self.x)
|
||||
|
||||
|
||||
class Movement:
|
||||
|
||||
@@ -15,7 +18,7 @@ class Movement:
|
||||
DOWN = Coord(0, -1)
|
||||
|
||||
|
||||
class Rotation:
|
||||
class Spin:
|
||||
|
||||
CLOCKWISE = 1
|
||||
COUNTER = -1
|
||||
@@ -28,6 +31,14 @@ class T_Spin:
|
||||
T_SPIN = "T-SPIN"
|
||||
|
||||
|
||||
class T_Slot:
|
||||
|
||||
A = 0
|
||||
B = 1
|
||||
C = 3
|
||||
D = 2
|
||||
|
||||
|
||||
class Color:
|
||||
|
||||
BLUE = 0
|
||||
|
||||
Reference in New Issue
Block a user