6 Commits
0.1 ... 0.3

Author SHA1 Message Date
5b3e6ec931 fix setup.py 2019-10-03 21:34:55 +02:00
0970e1f6df V0.3 Release
New musics
Piece lock optimize
2019-10-03 21:33:39 +02:00
f48f1fc000 redefine consts in TetrArcade.py 2019-10-03 18:14:07 +02:00
5f137a62ec V0.2 Release
Music
Soft drop autorepeat delay set to fall delay / 20
2019-10-03 01:41:21 +02:00
ec42f17ca7 soft drop autorepeat delay set to fall delay / 20 2019-10-03 01:32:52 +02:00
0d6386d22b music!
change API (pieces are attributes of TetrominoContainers)
2019-10-03 01:26:31 +02:00
17 changed files with 278 additions and 155 deletions

View File

@ -1,26 +1,42 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import sys import sys
import locale
import time
import os
import configparser
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"""
) )
import pyglet
from tetrislogic import TetrisLogic, Color, State import locale
import time
import os
import itertools
import configparser
from tetrislogic import TetrisLogic, Color, State, Coord
# Constants # Constants
# Matrix
NB_LINES = 20
NB_COLS = 10
NB_NEXT = 5
# Delays (seconds)
LOCK_DELAY = 0.5
FALL_DELAY = 1
AUTOREPEAT_DELAY = 0.300
AUTOREPEAT_PERIOD = 0.010
# 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)
# Window # Window
WINDOW_WIDTH = 800 WINDOW_WIDTH = 800
WINDOW_HEIGHT = 600 WINDOW_HEIGHT = 600
@ -39,12 +55,25 @@ GHOST_ALPHA = 30
MATRIX_BG_ALPHA = 100 MATRIX_BG_ALPHA = 100
BAR_ALPHA = 75 BAR_ALPHA = 75
# Mino size
MINO_SIZE = 20
MINO_SPRITE_SIZE = 21
if getattr(sys, 'frozen', False):
# The application is frozen
PROGRAM_DIR = os.path.dirname(sys.executable)
else:
# The application is not frozen
PROGRAM_DIR = os.path.dirname(__file__)
RESOURCES_DIR = os.path.join(PROGRAM_DIR, "resources")
# Sprites # Sprites
WINDOW_BG_PATH = "res/bg.jpg" IMAGES_DIR = os.path.join(RESOURCES_DIR, "images")
MATRIX_BG_PATH = "res/matrix.png" WINDOW_BG_PATH = os.path.join(IMAGES_DIR, "bg.jpg")
HELD_BG_PATH = "res/held.png" MATRIX_BG_PATH = os.path.join(IMAGES_DIR, "matrix.png")
NEXT_BG_PATH = "res/next.png" HELD_BG_PATH = os.path.join(IMAGES_DIR, "held.png")
MINOES_SPRITES_PATH = "res/minoes.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.PRELOCKED = 7
MINOES_COLOR_ID = { MINOES_COLOR_ID = {
Color.BLUE: 0, Color.BLUE: 0,
@ -56,13 +85,24 @@ MINOES_COLOR_ID = {
Color.YELLOW: 6, Color.YELLOW: 6,
Color.PRELOCKED: 7, Color.PRELOCKED: 7,
} }
MINO_SIZE = 20
MINO_SPRITE_SIZE = 21
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()}
# Music
MUSIC_DIR = os.path.join(RESOURCES_DIR, "musics")
MUSICS_PATHS = (entry.path for entry in os.scandir(MUSIC_DIR))
# Text
TEXT_COLOR = arcade.color.BUBBLES
FONT_NAME = os.path.join(RESOURCES_DIR, "fonts/joystix monospace.ttf")
STATS_TEXT_MARGIN = 40
STATS_TEXT_SIZE = 14
STATS_TEXT_WIDTH = 150
HIGHLIGHT_TEXT_COLOR = arcade.color.BUBBLES
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"))
@ -72,17 +112,9 @@ 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, "TetrArcade.ini")
# Text
TEXT_COLOR = arcade.color.BUBBLES
FONT_NAME = "res/joystix monospace.ttf"
STATS_TEXT_MARGIN = 40
STATS_TEXT_SIZE = 14
STATS_TEXT_WIDTH = 150
HIGHLIGHT_TEXT_COLOR = arcade.color.BUBBLES
HIGHLIGHT_TEXT_SIZE = 20
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
@ -94,12 +126,13 @@ class MinoSprite(arcade.Sprite):
def refresh(self, x, y, prelocked=False): def refresh(self, x, y, prelocked=False):
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) 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
@ -107,6 +140,7 @@ class MinoesSprites(arcade.SpriteList):
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
@ -122,6 +156,7 @@ class TetrominoSprites(MinoesSprites):
class MatrixSprites(MinoesSprites): class MatrixSprites(MinoesSprites):
def __init__(self, matrix): def __init__(self, matrix):
super().__init__() super().__init__()
self.matrix = matrix self.matrix = matrix
@ -132,10 +167,26 @@ class MatrixSprites(MinoesSprites):
for x, mino in enumerate(line): for x, mino in enumerate(line):
if mino: if mino:
mino.sprite.refresh(x, y) mino.sprite.refresh(x, y)
self.append(mino.sprite)
def remove_line(self, y):
for mino in self.matrix[y]:
if mino:
self.remove(mino.sprite)
class TetrArcade(TetrisLogic, arcade.Window): 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
def __init__(self): def __init__(self):
locale.setlocale(locale.LC_ALL, "") locale.setlocale(locale.LC_ALL, "")
self.highlight_texts = [] self.highlight_texts = []
@ -166,15 +217,23 @@ class TetrArcade(TetrisLogic, arcade.Window):
arcade.set_background_color(BG_COLOR) arcade.set_background_color(BG_COLOR)
self.set_minimum_size(WINDOW_MIN_WIDTH, WINDOW_MIN_HEIGHT) self.set_minimum_size(WINDOW_MIN_WIDTH, WINDOW_MIN_HEIGHT)
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 = arcade.Sprite(HELD_BG_PATH)
self.held_bg.alpha = BAR_ALPHA self.held.bg.alpha = BAR_ALPHA
self.next_bg = arcade.Sprite(NEXT_BG_PATH) self.next.bg = arcade.Sprite(NEXT_BG_PATH)
self.next_bg.alpha = BAR_ALPHA 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)
if self.play_music:
self.music = pyglet.media.Player()
playlist = itertools.cycle(
pyglet.media.load(path)
for path in MUSICS_PATHS
)
self.music.queue(playlist)
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"] = {
@ -189,6 +248,9 @@ class TetrArcade(TetrisLogic, arcade.Window):
"pause": "ESCAPE", "pause": "ESCAPE",
"fullscreen": "F11", "fullscreen": "F11",
} }
self.conf["MUSIC"] = {
"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):
@ -252,47 +314,77 @@ AGAIN""".format(
self.conf["KEYBOARD"]["start"] self.conf["KEYBOARD"]["start"]
) )
self.play_music = self.conf["MUSIC"].getboolean("play")
def new_game(self): def new_game(self):
self.highlight_texts = [] self.highlight_texts = []
super().new_game() super().new_game()
self.matrix.sprites = MatrixSprites(self.matrix)
if self.play_music:
self.music.seek(0)
self.music.play()
def new_tetromino(self): def new_tetromino(self):
tetromino = super().new_tetromino() tetromino = super().new_tetromino()
tetromino.sprites = TetrominoSprites(tetromino, self) tetromino.sprites = TetrominoSprites(tetromino, self)
return tetromino return tetromino
def new_current(self): def new_matrix_piece(self):
self.matrix.sprites = MatrixSprites(self.matrix) super().new_matrix_piece()
super().new_current() self.matrix.ghost.sprites = TetrominoSprites(self.matrix.ghost, self, GHOST_ALPHA)
self.ghost.sprites = TetrominoSprites(self.ghost, self, GHOST_ALPHA) for tetromino in [self.matrix.piece, self.matrix.ghost] + self.next.pieces:
for tetromino in [self.current, self.ghost] + self.next:
tetromino.sprites.refresh() tetromino.sprites.refresh()
def move(self, movement, prelock=True): def move(self, movement, prelock=True):
moved = super().move(movement, prelock) moved = super().move(movement, prelock)
self.current.sprites.refresh() self.matrix.piece.sprites.refresh()
if moved: if moved:
self.ghost.sprites.refresh() self.matrix.ghost.sprites.refresh()
return moved return moved
def rotate(self, rotation): def rotate(self, rotation):
rotated = super().rotate(rotation) rotated = super().rotate(rotation)
if rotated: if rotated:
for tetromino in (self.current, self.ghost): for tetromino in (self.matrix.piece, self.matrix.ghost):
tetromino.sprites.refresh() tetromino.sprites.refresh()
return rotated return rotated
def swap(self): def swap(self):
super().swap() super().swap()
self.ghost.sprites = TetrominoSprites(self.ghost, self, GHOST_ALPHA) self.matrix.ghost.sprites = TetrominoSprites(self.matrix.ghost, self, GHOST_ALPHA)
for tetromino in [self.held, self.current, self.ghost]: for tetromino in (self.held.piece, self.matrix.piece, self.matrix.ghost):
if tetromino: if tetromino:
tetromino.sprites.refresh() tetromino.sprites.refresh()
def lock(self): def lock(self):
self.current.prelocked = False self.matrix.piece.prelocked = False
self.current.sprites.refresh() self.matrix.piece.sprites.refresh()
super().lock() super().lock()
self.matrix.sprites.refresh()
def enter_the_matrix(self):
super().enter_the_matrix()
for mino in self.matrix.piece:
self.matrix.sprites.append(mino.sprite)
def remove_line(self, y):
self.matrix.sprites.remove_line(y)
super().remove_line(y)
def pause(self):
super().pause()
if self.play_music:
self.music.pause()
def resume(self):
super().resume()
if self.play_music:
self.music.play()
def game_over(self):
super().game_over()
if self.play_music:
self.music.pause()
def on_key_press(self, key, modifiers): def on_key_press(self, key, modifiers):
for key_or_modifier in (key, modifiers): for key_or_modifier in (key, modifiers):
@ -327,12 +419,12 @@ AGAIN""".format(
self.bg.draw() self.bg.draw()
if self.state in (State.PLAYING, State.OVER): if self.state in (State.PLAYING, State.OVER):
self.matrix_bg.draw() self.matrix.bg.draw()
self.held_bg.draw() self.held.bg.draw()
self.next_bg.draw() self.next.bg.draw()
self.matrix.sprites.draw() self.matrix.sprites.draw()
for tetromino in [self.held, self.current, self.ghost] + self.next: 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()
@ -341,8 +433,8 @@ AGAIN""".format(
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,
align="right", align="right",
@ -361,8 +453,8 @@ AGAIN""".format(
): ):
arcade.draw_text( arcade.draw_text(
text=text, text=text,
start_x=self.matrix_bg.left - STATS_TEXT_MARGIN * self.scale, start_x=self.matrix.bg.left - STATS_TEXT_MARGIN * self.scale,
start_y=self.matrix_bg.bottom + 3 * y * font_size, start_y=self.matrix.bg.bottom + 3 * y * font_size,
color=TEXT_COLOR, color=TEXT_COLOR,
font_size=font_size, font_size=font_size,
align="right", align="right",
@ -379,8 +471,8 @@ AGAIN""".format(
if highlight_text: if highlight_text:
arcade.draw_text( arcade.draw_text(
text=highlight_text, text=highlight_text,
start_x=self.matrix_bg.center_x, start_x=self.matrix.bg.center_x,
start_y=self.matrix_bg.center_y, start_y=self.matrix.bg.center_y,
color=HIGHLIGHT_TEXT_COLOR, color=HIGHLIGHT_TEXT_COLOR,
font_size=HIGHLIGHT_TEXT_SIZE * self.scale, font_size=HIGHLIGHT_TEXT_SIZE * self.scale,
align="center", align="center",
@ -405,23 +497,23 @@ AGAIN""".format(
self.bg.center_x = center_x self.bg.center_x = center_x
self.bg.center_y = center_y self.bg.center_y = center_y
self.matrix_bg.scale = self.scale self.matrix.bg.scale = self.scale
self.matrix_bg.center_x = center_x self.matrix.bg.center_x = center_x
self.matrix_bg.center_y = center_y self.matrix.bg.center_y = center_y
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.scale = self.scale
self.held_bg.right = self.matrix_bg.left self.held.bg.right = self.matrix.bg.left
self.held_bg.top = self.matrix_bg.top self.held.bg.top = self.matrix.bg.top
self.next_bg.scale = self.scale self.next.bg.scale = self.scale
self.next_bg.left = self.matrix_bg.right self.next.bg.left = self.matrix.bg.right
self.next_bg.top = self.matrix_bg.top 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, self.current, self.ghost] + self.next: 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)
@ -476,6 +568,8 @@ High score could not be saved:
def on_close(self): def on_close(self):
self.save_high_score() self.save_high_score()
if self.play_music:
self.music.pause()
super().on_close() super().on_close()

View File

Before

Width:  |  Height:  |  Size: 153 KiB

After

Width:  |  Height:  |  Size: 153 KiB

View File

Before

Width:  |  Height:  |  Size: 499 B

After

Width:  |  Height:  |  Size: 499 B

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 389 B

After

Width:  |  Height:  |  Size: 389 B

View File

Before

Width:  |  Height:  |  Size: 475 B

After

Width:  |  Height:  |  Size: 475 B

Binary file not shown.

BIN
resources/musics/2-!!!.mp3 Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -29,13 +29,13 @@ options = {
"build_exe": { "build_exe": {
"packages": ["arcade", "pyglet"], "packages": ["arcade", "pyglet"],
"excludes": excludes, "excludes": excludes,
"include_files": "res", "include_files": "resources",
"silent": True "silent": True
} }
} }
setup( setup(
name = "TetrArcade", name = "TetrArcade",
version = "0.1", version = "0.3",
description = "Tetris clone", description = "Tetris clone",
author = "AdrienMalin", author = "AdrienMalin",
executables = [executable], executables = [executable],

View File

@ -6,10 +6,12 @@ game = TetrArcade()
game.new_game() game.new_game()
game.move_left() game.move_left()
game.move_right() game.move_right()
game.swap()
game.rotate_clockwise() game.rotate_clockwise()
game.rotate_counter() game.rotate_counter()
for i in range(12): for i in range(12):
game.soft_drop() game.soft_drop()
game.matrix.sprites.refresh()
game.on_draw() game.on_draw()
while game.state != State.OVER: while game.state != State.OVER:
game.hard_drop() game.hard_drop()

View File

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from .consts import NB_LINES, NB_COLS, NB_NEXT from .consts import NB_LINES, NB_COLS, NB_NEXT
from .utils import Movement, Rotation, Color from .utils import Movement, Rotation, Color, Coord
from .tetromino import Mino, Tetromino from .tetromino import Mino, Tetromino
from .tetrislogic import TetrisLogic, State, Matrix from .tetrislogic import TetrisLogic, State, Matrix

View File

@ -10,11 +10,10 @@ NB_NEXT = 5
# Delays (seconds) # Delays (seconds)
LOCK_DELAY = 0.5 LOCK_DELAY = 0.5
FALL_DELAY = 1 FALL_DELAY = 1
AUTOREPEAT_DELAY = 0.200 # Official : 0.300 AUTOREPEAT_DELAY = 0.300 # Official : 0.300 s
AUTOREPEAT_PERIOD = 0.010 # Official : 0.010 AUTOREPEAT_PERIOD = 0.010 # Official : 0.010 s
# Piece init coord # Piece init coord
CURRENT_COORD = Coord(4, NB_LINES) MATRIX_PIECE_COORD = Coord(4, NB_LINES)
NEXT_COORDS = [Coord(NB_COLS + 4, NB_LINES - 4 * n - 3) for n in range(NB_NEXT)] NEXT_PIECE_COORDS = [Coord(NB_COLS + 4, NB_LINES - 4 * n - 3) for n in range(NB_NEXT)]
HELD_COORD = Coord(-5, NB_LINES - 3) HELD_PIECE_COORD = Coord(-5, NB_LINES - 3)

View File

@ -3,7 +3,7 @@ import random
import pickle import pickle
from .utils import Coord, Movement, Rotation, T_Spin from .utils import Coord, Movement, Rotation, T_Spin
from .tetromino import Tetromino, T, I from .tetromino import Tetromino, T_Tetrimino, I_Tetrimino
from .consts import ( from .consts import (
NB_LINES, NB_LINES,
NB_COLS, NB_COLS,
@ -12,9 +12,9 @@ from .consts import (
FALL_DELAY, FALL_DELAY,
AUTOREPEAT_DELAY, AUTOREPEAT_DELAY,
AUTOREPEAT_PERIOD, AUTOREPEAT_PERIOD,
CURRENT_COORD, MATRIX_PIECE_COORD,
NEXT_COORDS, NEXT_PIECE_COORDS,
HELD_COORD, HELD_PIECE_COORD,
) )
@ -30,11 +30,33 @@ class State:
OVER = "OVER" OVER = "OVER"
class Matrix(list): class PieceContainer:
def __init__(self):
self.piece = None
class HoldQueue(PieceContainer):
pass
class Matrix(list, PieceContainer):
def __init__(self, *args, **kargs):
list.__init__(self, *args, **kargs)
PieceContainer.__init__(self)
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 < NB_COLS and 0 <= coord.y and not self[coord.y][coord.x]
class NextQueue(PieceContainer):
def __init__(self):
super().__init__()
self.pieces = []
class TetrisLogic: class TetrisLogic:
NB_LINES = NB_LINES NB_LINES = NB_LINES
@ -44,19 +66,18 @@ class TetrisLogic:
FALL_DELAY = FALL_DELAY FALL_DELAY = FALL_DELAY
AUTOREPEAT_DELAY = AUTOREPEAT_DELAY AUTOREPEAT_DELAY = AUTOREPEAT_DELAY
AUTOREPEAT_PERIOD = AUTOREPEAT_PERIOD AUTOREPEAT_PERIOD = AUTOREPEAT_PERIOD
CURRENT_COORD = CURRENT_COORD MATRIX_PIECE_COORD = MATRIX_PIECE_COORD
NEXT_COORDS = NEXT_COORDS NEXT_PIECE_COORDS = NEXT_PIECE_COORDS
HELD_COORD = HELD_COORD HELD_PIECE_COORD = HELD_PIECE_COORD
random_bag = [] random_bag = []
def __init__(self): def __init__(self):
self.load_high_score() self.load_high_score()
self.state = State.STARTING self.state = State.STARTING
self.held = HoldQueue()
self.matrix = Matrix() self.matrix = Matrix()
self.next = [] self.matrix.ghost = None
self.current = None self.next = NextQueue()
self.ghost = None
self.held = None
self.time = 0 self.time = 0
self.autorepeatable_actions = (self.move_left, self.move_right, self.soft_drop) self.autorepeatable_actions = (self.move_left, self.move_right, self.soft_drop)
self.pressed_actions = [] self.pressed_actions = []
@ -88,8 +109,8 @@ class TetrisLogic:
self.matrix.clear() self.matrix.clear()
for y in range(self.NB_LINES + 3): for y in range(self.NB_LINES + 3):
self.append_new_line_to_matrix() self.append_new_line_to_matrix()
self.next = [self.new_tetromino() for n in range(self.NB_NEXT)] self.next.pieces = [self.new_tetromino() for n in range(self.NB_NEXT)]
self.held = None self.held.piece = None
self.state = State.PLAYING self.state = State.PLAYING
self.start(self.update_time, 1) self.start(self.update_time, 1)
@ -113,20 +134,19 @@ class TetrisLogic:
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.show_text("LEVEL\n{:n}".format(self.level))
self.restart(self.fall, self.fall_delay) self.restart(self.fall, self.fall_delay)
self.new_matrix_piece()
self.new_current() def new_matrix_piece(self):
self.matrix.piece = self.next.pieces.pop(0)
def new_current(self): self.matrix.piece.coord = self.MATRIX_PIECE_COORD
self.current = self.next.pop(0) self.matrix.ghost = self.matrix.piece.ghost()
self.current.coord = self.CURRENT_COORD
self.ghost = self.current.ghost()
self.move_ghost() self.move_ghost()
self.next.append(self.new_tetromino()) self.next.pieces.append(self.new_tetromino())
self.next[-1].coord = self.NEXT_COORDS[-1] self.next.pieces[-1].coord = self.NEXT_PIECE_COORDS[-1]
for tetromino, coord in zip(self.next, self.NEXT_COORDS): for tetromino, coord in zip(self.next.pieces, self.NEXT_PIECE_COORDS):
tetromino.coord = coord tetromino.coord = coord
if not self.can_move(self.current.coord, (mino.coord for mino in self.current)): if not self.can_move(self.matrix.piece.coord, (mino.coord for mino in self.matrix.piece)):
self.game_over() self.game_over()
def move_left(self): def move_left(self):
@ -142,11 +162,11 @@ class TetrisLogic:
self.rotate(Rotation.COUNTER) self.rotate(Rotation.COUNTER)
def move_ghost(self): def move_ghost(self):
self.ghost.coord = self.current.coord self.matrix.ghost.coord = self.matrix.piece.coord
for ghost_mino, current_mino in zip(self.ghost, self.current): for ghost_mino, current_mino in zip(self.matrix.ghost, self.matrix.piece):
ghost_mino.coord = current_mino.coord ghost_mino.coord = current_mino.coord
while self.can_move(self.ghost.coord + Movement.DOWN, (mino.coord for mino in self.ghost)): while self.can_move(self.matrix.ghost.coord + Movement.DOWN, (mino.coord for mino in self.matrix.ghost)):
self.ghost.coord += Movement.DOWN self.matrix.ghost.coord += Movement.DOWN
def soft_drop(self): def soft_drop(self):
moved = self.move(Movement.DOWN) moved = self.move(Movement.DOWN)
@ -163,33 +183,33 @@ class TetrisLogic:
self.move(Movement.DOWN) self.move(Movement.DOWN)
def move(self, movement, prelock=True): def move(self, movement, prelock=True):
potential_coord = self.current.coord + movement potential_coord = self.matrix.piece.coord + movement
if self.can_move(potential_coord, (mino.coord for mino in self.current)): if self.can_move(potential_coord, (mino.coord for mino in self.matrix.piece)):
if self.current.prelocked: if self.matrix.piece.prelocked:
self.restart(self.lock, self.lock_delay) self.restart(self.lock, self.lock_delay)
self.current.coord = potential_coord self.matrix.piece.coord = potential_coord
if not movement == Movement.DOWN: if not movement == Movement.DOWN:
self.current.last_rotation_point = None self.matrix.piece.last_rotation_point = None
self.move_ghost() self.move_ghost()
return True return True
else: else:
if prelock and not self.current.prelocked and movement == Movement.DOWN: if prelock and not self.matrix.piece.prelocked and movement == Movement.DOWN:
self.current.prelocked = True self.matrix.piece.prelocked = True
self.start(self.lock, self.lock_delay) self.start(self.lock, self.lock_delay)
return False return False
def rotate(self, rotation): def rotate(self, rotation):
rotated_coords = tuple(Coord(rotation * mino.coord.y, -rotation * mino.coord.x) for mino in self.current) 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.current.SRS[rotation][self.current.orientation], start=1): for rotation_point, liberty_degree in enumerate(self.matrix.piece.SRS[rotation][self.matrix.piece.orientation], start=1):
potential_coord = self.current.coord + liberty_degree potential_coord = self.matrix.piece.coord + liberty_degree
if self.can_move(potential_coord, rotated_coords): if self.can_move(potential_coord, rotated_coords):
if self.current.prelocked: if self.matrix.piece.prelocked:
self.restart(self.lock, self.lock_delay) self.restart(self.lock, self.lock_delay)
self.current.coord = potential_coord self.matrix.piece.coord = potential_coord
for mino, coord in zip(self.current, rotated_coords): for mino, coord in zip(self.matrix.piece, rotated_coords):
mino.coord = coord mino.coord = coord
self.current.orientation = (self.current.orientation + rotation) % 4 self.matrix.piece.orientation = (self.matrix.piece.orientation + rotation) % 4
self.current.last_rotation_point = rotation_point self.matrix.piece.last_rotation_point = rotation_point
self.move_ghost() self.move_ghost()
return True return True
else: else:
@ -204,15 +224,15 @@ class TetrisLogic:
) )
def lock(self): def lock(self):
self.current.prelocked = False self.matrix.piece.prelocked = False
self.stop(self.lock) self.stop(self.lock)
# Piece unlocked # Piece unlocked
if self.can_move(self.current.coord + Movement.DOWN, (mino.coord for mino in self.current)): if self.can_move(self.matrix.piece.coord + Movement.DOWN, (mino.coord for mino in self.matrix.piece)):
return return
# Game over # Game over
if all((mino.coord + self.current.coord).y >= self.NB_LINES for mino in self.current): if all((mino.coord + self.matrix.piece.coord).y >= self.NB_LINES for mino in self.matrix.piece):
self.game_over() self.game_over()
return return
@ -221,12 +241,12 @@ class TetrisLogic:
self.restart(self.repeat_action, self.AUTOREPEAT_DELAY) self.restart(self.repeat_action, self.AUTOREPEAT_DELAY)
# T-Spin # T-Spin
if type(self.current) == T and self.current.last_rotation_point is not None: if type(self.matrix.piece) == T_Tetrimino and self.matrix.piece.last_rotation_point is not None:
a = self.is_t_slot(0) a = self.is_t_slot(0)
b = self.is_t_slot(1) b = self.is_t_slot(1)
c = self.is_t_slot(3) c = self.is_t_slot(3)
d = self.is_t_slot(2) d = self.is_t_slot(2)
if self.current.last_rotation_point == 5 or (a and b and (c or d)): if self.matrix.piece.last_rotation_point == 5 or (a and b and (c or d)):
t_spin = T_Spin.T_SPIN t_spin = T_Spin.T_SPIN
elif c and d and (a or b): elif c and d and (a or b):
t_spin = T_Spin.MINI t_spin = T_Spin.MINI
@ -235,18 +255,14 @@ class TetrisLogic:
else: else:
t_spin = T_Spin.NONE t_spin = T_Spin.NONE
for mino in self.current: self.enter_the_matrix()
coord = mino.coord + self.current.coord
del mino.coord
if coord.y <= self.NB_LINES + 3:
self.matrix[coord.y][coord.x] = mino
# Clear complete lines # Clear complete lines
nb_lines_cleared = 0 nb_lines_cleared = 0
for y, line in reversed(list(enumerate(self.matrix))): for y, line in reversed(list(enumerate(self.matrix))):
if all(mino for mino in line): if all(mino for mino in line):
nb_lines_cleared += 1 nb_lines_cleared += 1
self.matrix.pop(y) self.remove_line(y)
self.append_new_line_to_matrix() self.append_new_line_to_matrix()
if nb_lines_cleared: if nb_lines_cleared:
self.nb_lines_cleared += nb_lines_cleared self.nb_lines_cleared += nb_lines_cleared
@ -281,7 +297,16 @@ class TetrisLogic:
if self.goal <= 0: if self.goal <= 0:
self.new_level() self.new_level()
else: else:
self.new_current() 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): 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) return all(self.matrix.cell_is_free(potential_coord + mino_coord) for mino_coord in minoes_coords)
@ -289,28 +314,27 @@ class TetrisLogic:
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.current.coord + self.T_SLOT_COORDS[(self.current.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): def swap(self):
if self.current.hold_enabled: if self.matrix.piece.hold_enabled:
self.current.hold_enabled = False self.matrix.piece.hold_enabled = False
self.current.prelocked = False self.matrix.piece.prelocked = False
self.stop(self.lock) self.stop(self.lock)
self.current, self.held = self.held, self.current self.matrix.piece, self.held.piece = self.held.piece, self.matrix.piece
if type(self.held) == I: self.held.piece.coord = self.HELD_PIECE_COORD
self.held.coord = self.HELD_COORD + Movement.LEFT if type(self.held.piece) == I_Tetrimino:
else: self.held.piece.coord += Movement.LEFT
self.held.coord = self.HELD_COORD for mino, coord in zip(self.held.piece, self.held.piece.MINOES_COORDS):
for mino, coord in zip(self.held, self.held.MINOES_COORDS):
mino.coord = coord mino.coord = coord
if self.current: if self.matrix.piece:
self.current.coord = self.CURRENT_COORD self.matrix.piece.coord = self.MATRIX_PIECE_COORD
self.ghost = self.current.ghost() self.matrix.ghost = self.matrix.piece.ghost()
self.move_ghost() self.move_ghost()
else: else:
self.new_current() self.new_matrix_piece()
def pause(self): def pause(self):
self.state = State.PAUSED self.state = State.PAUSED
@ -322,7 +346,7 @@ class TetrisLogic:
def resume(self): def resume(self):
self.state = State.PLAYING self.state = State.PLAYING
self.start(self.fall, self.fall_delay) self.start(self.fall, self.fall_delay)
if self.current.prelocked: if self.matrix.piece.prelocked:
self.start(self.lock, self.lock_delay) self.start(self.lock, self.lock_delay)
self.start(self.update_time, 1) self.start(self.update_time, 1)
@ -344,7 +368,11 @@ class TetrisLogic:
if action in self.autorepeatable_actions: if action in self.autorepeatable_actions:
self.auto_repeat = False self.auto_repeat = False
self.pressed_actions.append(action) self.pressed_actions.append(action)
self.restart(self.repeat_action, self.AUTOREPEAT_DELAY) if action == self.soft_drop:
delay = self.fall_delay / 20
else:
delay = self.AUTOREPEAT_DELAY
self.restart(self.repeat_action, delay)
def repeat_action(self): def repeat_action(self):
if self.pressed_actions: if self.pressed_actions:

View File

@ -44,7 +44,7 @@ class Tetromino(list):
return type(self)() return type(self)()
class O(Tetromino, metaclass=MetaTetromino): class O_Tetrimino(Tetromino, metaclass=MetaTetromino):
SRS = { SRS = {
Rotation.CLOCKWISE: (tuple(), tuple(), tuple(), tuple()), Rotation.CLOCKWISE: (tuple(), tuple(), tuple(), tuple()),
@ -57,7 +57,7 @@ class O(Tetromino, metaclass=MetaTetromino):
return False return False
class I(Tetromino, metaclass=MetaTetromino): class I_Tetrimino(Tetromino, metaclass=MetaTetromino):
SRS = { SRS = {
Rotation.CLOCKWISE: ( Rotation.CLOCKWISE: (
@ -77,31 +77,31 @@ class I(Tetromino, metaclass=MetaTetromino):
MINOES_COLOR = Color.CYAN MINOES_COLOR = Color.CYAN
class T(Tetromino, metaclass=MetaTetromino): class T_Tetrimino(Tetromino, 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(Tetromino, metaclass=MetaTetromino): class L_Tetrimino(Tetromino, 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(Tetromino, metaclass=MetaTetromino): class J_Tetrimino(Tetromino, 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(Tetromino, metaclass=MetaTetromino): class S_Tetrimino(Tetromino, 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(Tetromino, metaclass=MetaTetromino): class Z_Tetrimino(Tetromino, 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