11 Commits

Author SHA1 Message Date
f013a061b2 fix line remove 2019-10-04 18:03:35 +02:00
f025ad5fd8 change tetrislogic api 2019-10-04 17:48:47 +02:00
9a7aead918 fix (at last?) music codec warning 2019-10-04 16:15:54 +02:00
b173b6ff73 Warn on music codec not found 2019-10-04 16:07:40 +02:00
ddf7ea0f4e Warn on music codec not found 2019-10-04 15:54:06 +02:00
d308618556 music warning 2019-10-04 12:51:11 +02:00
9c77096bfb Don't play music if codec not found 2019-10-04 12:24:49 +02:00
093264c351 enable level choice 2019-10-04 01:23:47 +02:00
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
16 changed files with 325 additions and 211 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
@ -45,19 +61,19 @@ MINO_SPRITE_SIZE = 21
if getattr(sys, 'frozen', False): if getattr(sys, 'frozen', False):
# The application is frozen # The application is frozen
DATA_DIR = os.path.dirname(sys.executable) PROGRAM_DIR = os.path.dirname(sys.executable)
else: else:
# The application is not frozen # The application is not frozen
# Change this bit to match where you store your data files: PROGRAM_DIR = os.path.dirname(__file__)
DATA_DIR = os.path.dirname(__file__) RESOURCES_DIR = os.path.join(PROGRAM_DIR, "resources")
DATA_DIR = os.path.join(DATA_DIR, "res")
# Sprites # Sprites
WINDOW_BG_PATH = os.path.join(DATA_DIR, "bg.jpg") IMAGES_DIR = os.path.join(RESOURCES_DIR, "images")
MATRIX_BG_PATH = os.path.join(DATA_DIR, "matrix.png") WINDOW_BG_PATH = os.path.join(IMAGES_DIR, "bg.jpg")
HELD_BG_PATH = os.path.join(DATA_DIR, "held.png") MATRIX_BG_PATH = os.path.join(IMAGES_DIR, "matrix.png")
NEXT_BG_PATH = os.path.join(DATA_DIR, "next.png") HELD_BG_PATH = os.path.join(IMAGES_DIR, "held.png")
MINOES_SPRITES_PATH = os.path.join(DATA_DIR, "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,
@ -74,6 +90,19 @@ TEXTURES = arcade.load_textures(
) )
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"))
@ -83,20 +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 = os.path.join(DATA_DIR, "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
# Music
MUSIC_PATH = os.path.join(DATA_DIR, "Tetris - Song A.mp3")
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
@ -114,6 +132,7 @@ class MinoSprite(arcade.Sprite):
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
@ -121,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
@ -136,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
@ -146,10 +167,15 @@ 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):
def __init__(self): def __init__(self):
locale.setlocale(locale.LC_ALL, "") locale.setlocale(locale.LC_ALL, "")
self.highlight_texts = [] self.highlight_texts = []
@ -166,7 +192,7 @@ class TetrArcade(TetrisLogic, arcade.Window):
self.new_conf() self.new_conf()
self.load_conf() self.load_conf()
super().__init__() super().__init__(NB_LINES, NB_COLS, NB_NEXT)
arcade.Window.__init__( arcade.Window.__init__(
self, self,
width=self.init_width, width=self.init_width,
@ -188,9 +214,18 @@ class TetrArcade(TetrisLogic, arcade.Window):
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: if self.play_music:
self.music = arcade.Sound(MUSIC_PATH) try:
self.music_player = None self.music = pyglet.media.Player()
playlist = itertools.cycle(
pyglet.media.load(path)
for path in MUSICS_PATHS
)
self.music.queue(playlist)
except:
Warning("Can't play music.")
self.play_music = False
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}
@ -274,42 +309,47 @@ AGAIN""".format(
self.play_music = self.conf["MUSIC"].getboolean("play") self.play_music = self.conf["MUSIC"].getboolean("play")
def new_game(self): def on_new_game(self):
self.highlight_texts = [] self.highlight_texts = []
super().new_game()
if self.play_music:
if self.music_player:
self.music_player.seek(0)
self.music_player.play()
else:
self.music_player = self.music.player.play()
self.music_player.loop = True
def new_tetromino(self):
tetromino = super().new_tetromino()
tetromino.sprites = TetrominoSprites(tetromino, self)
return tetromino
def new_matrix_piece(self):
self.matrix.sprites = MatrixSprites(self.matrix) self.matrix.sprites = MatrixSprites(self.matrix)
super().new_matrix_piece() if self.play_music:
self.music.seek(0)
self.music.play()
def on_new_piece(self, piece):
piece.sprites = TetrominoSprites(piece, self)
def on_new_level(self, level):
self.show_text("LEVEL\n{:n}".format(level))
def on_generation_phase(self, piece):
self.matrix.sprites.refresh()
self.matrix.ghost.sprites = TetrominoSprites(self.matrix.ghost, self, GHOST_ALPHA) self.matrix.ghost.sprites = TetrominoSprites(self.matrix.ghost, self, GHOST_ALPHA)
for tetromino in [self.matrix.piece, self.matrix.ghost] + self.next.pieces: for tetromino in [self.matrix.piece, self.matrix.ghost] + self.next.pieces:
tetromino.sprites.refresh() tetromino.sprites.refresh()
def move(self, movement, prelock=True): def on_falling_phase(self):
moved = super().move(movement, prelock)
self.matrix.piece.sprites.refresh() self.matrix.piece.sprites.refresh()
if moved:
self.matrix.ghost.sprites.refresh()
return moved
def rotate(self, rotation): def on_moved(self, moved):
rotated = super().rotate(rotation) self.matrix.piece.sprites.refresh()
if rotated: self.matrix.ghost.sprites.refresh()
for tetromino in (self.matrix.piece, self.matrix.ghost):
tetromino.sprites.refresh() def on_rotated(self, direction):
return rotated for tetromino in (self.matrix.piece, self.matrix.ghost):
tetromino.sprites.refresh()
def on_lock_phase(self):
self.matrix.piece.sprites.refresh()
self.matrix.sprites.refresh()
def on_locked(self, piece):
piece.sprites.refresh()
for mino in piece:
self.matrix.sprites.append(mino.sprite)
def on_line_remove(self, y):
self.matrix.sprites.remove_line(y)
def swap(self): def swap(self):
super().swap() super().swap()
@ -318,25 +358,20 @@ AGAIN""".format(
if tetromino: if tetromino:
tetromino.sprites.refresh() tetromino.sprites.refresh()
def lock(self):
self.matrix.piece.prelocked = False
self.matrix.piece.sprites.refresh()
super().lock()
def pause(self): def pause(self):
super().pause() super().pause()
if self.play_music: if self.play_music:
self.music_player.pause() self.music.pause()
def resume(self): def resume(self):
super().resume() super().resume()
if self.play_music: if self.play_music:
self.music_player.play() self.music.play()
def game_over(self): def game_over(self):
super().game_over() super().game_over()
if self.play_music: if self.play_music:
self.music_player.pause() self.music.pause()
def on_key_press(self, key, modifiers): def on_key_press(self, key, modifiers):
for key_or_modifier in (key, modifiers): for key_or_modifier in (key, modifiers):
@ -380,7 +415,7 @@ AGAIN""".format(
if tetromino: if tetromino:
tetromino.sprites.draw() tetromino.sprites.draw()
t = time.localtime(self.time) t = time.localtime(self.stats.time)
font_size = STATS_TEXT_SIZE * self.scale font_size = STATS_TEXT_SIZE * self.scale
for y, text in enumerate(("TIME", "LINES", "GOAL", "LEVEL", "HIGH SCORE", "SCORE")): for y, text in enumerate(("TIME", "LINES", "GOAL", "LEVEL", "HIGH SCORE", "SCORE")):
arcade.draw_text( arcade.draw_text(
@ -396,11 +431,11 @@ AGAIN""".format(
for y, text in enumerate( for y, text in enumerate(
( (
"{:02d}:{:02d}:{:02d}".format(t.tm_hour - 1, t.tm_min, t.tm_sec), "{:02d}:{:02d}:{:02d}".format(t.tm_hour - 1, t.tm_min, t.tm_sec),
"{:n}".format(self.nb_lines_cleared), "{:n}".format(self.stats.lines_cleared),
"{:n}".format(self.goal), "{:n}".format(self.stats.goal),
"{:n}".format(self.level), "{:n}".format(self.stats.level),
"{:n}".format(self.high_score), "{:n}".format(self.stats.high_score),
"{:n}".format(self.score), "{:n}".format(self.stats.score),
) )
): ):
arcade.draw_text( arcade.draw_text(
@ -521,7 +556,7 @@ 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: if self.play_music:
self.music_player.pause() 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

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.2", version = "0.3",
description = "Tetris clone", description = "Tetris clone",
author = "AdrienMalin", author = "AdrienMalin",
executables = [executable], executables = [executable],

11
test.py
View File

@ -1,16 +1,25 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from TetrArcade import TetrArcade, State from TetrArcade import TetrArcade, State, MinoSprite
from tetrislogic import Mino, Color, Coord
game = TetrArcade() game = TetrArcade()
game.new_game() game.new_game()
for x in range(game.matrix.collumns):
mino = Mino(Color.ORANGE, Coord(x, 0))
mino.sprite = MinoSprite(mino, game, 200)
game.matrix[0][x] = mino
game.matrix.sprites.append(mino.sprite)
game.move_left() game.move_left()
game.pause()
game.resume()
game.move_right() game.move_right()
game.swap() game.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 LINES, COLLUMNS, NEXT_PIECES
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

@ -3,18 +3,17 @@ from .utils import Coord
# Matrix # Matrix
NB_LINES = 20 LINES = 20
NB_COLS = 10 COLLUMNS = 10
NB_NEXT = 5 NEXT_PIECES = 5
# Delays (seconds) # Delays (seconds)
LOCK_DELAY = 0.5 LOCK_DELAY = 0.5
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, LINES)
NEXT_COORDS = [Coord(NB_COLS + 4, NB_LINES - 4 * n - 3) for n in range(NB_NEXT)] NEXT_PIECE_COORDS = [Coord(COLLUMNS + 4, LINES - 4 * n - 3) for n in range(NEXT_PIECES)]
HELD_COORD = Coord(-5, NB_LINES - 3) HELD_PIECE_COORD = Coord(-5, LINES - 3)

View File

@ -5,16 +5,16 @@ import pickle
from .utils import Coord, Movement, Rotation, T_Spin from .utils import Coord, Movement, Rotation, T_Spin
from .tetromino import Tetromino, T_Tetrimino, I_Tetrimino from .tetromino import Tetromino, T_Tetrimino, I_Tetrimino
from .consts import ( from .consts import (
NB_LINES, LINES,
NB_COLS, COLLUMNS,
NB_NEXT, NEXT_PIECES,
LOCK_DELAY, LOCK_DELAY,
FALL_DELAY, FALL_DELAY,
AUTOREPEAT_DELAY, AUTOREPEAT_DELAY,
AUTOREPEAT_PERIOD, AUTOREPEAT_PERIOD,
CURRENT_COORD, MATRIX_PIECE_COORD,
NEXT_COORDS, NEXT_PIECE_COORDS,
HELD_COORD, HELD_PIECE_COORD,
) )
@ -42,88 +42,58 @@ class HoldQueue(PieceContainer):
class Matrix(list, PieceContainer): class Matrix(list, PieceContainer):
def __init__(self, *args, **kargs): def __init__(self, lines, collumns):
list.__init__(self, *args, **kargs) list.__init__(self)
PieceContainer.__init__(self) PieceContainer.__init__(self)
self.lines = lines
self.collumns = collumns
def reset(self):
self.clear()
for y in range(self.lines + 3):
self.append_new_line()
def append_new_line(self):
self.append([None for x in range(self.collumns)])
def cell_is_free(self, coord): def cell_is_free(self, coord):
return 0 <= coord.x < NB_COLS and 0 <= coord.y and not self[coord.y][coord.x] return 0 <= coord.x < self.collumns and 0 <= coord.y and not self[coord.y][coord.x]
class NextQueue(PieceContainer): class NextQueue(PieceContainer):
def __init__(self): def __init__(self, nb_pieces):
super().__init__() super().__init__()
self.nb_pieces = nb_pieces
self.pieces = [] self.pieces = []
class TetrisLogic: class Stats:
NB_LINES = NB_LINES def _get_score(self):
NB_COLS = NB_COLS
NB_NEXT = NB_NEXT
LOCK_DELAY = LOCK_DELAY
FALL_DELAY = FALL_DELAY
AUTOREPEAT_DELAY = AUTOREPEAT_DELAY
AUTOREPEAT_PERIOD = AUTOREPEAT_PERIOD
CURRENT_COORD = CURRENT_COORD
NEXT_COORDS = NEXT_COORDS
HELD_COORD = HELD_COORD
random_bag = []
def __init__(self):
self.load_high_score()
self.state = State.STARTING
self.held = HoldQueue()
self.matrix = Matrix()
self.matrix.ghost = None
self.next = NextQueue()
self.time = 0
self.autorepeatable_actions = (self.move_left, self.move_right, self.soft_drop)
self.pressed_actions = []
self._score = 0
def get_score(self):
return self._score return self._score
def set_score(self, new_score): def _set_score(self, new_score):
self._score = new_score self._score = new_score
if self._score > self.high_score: if self._score > self.high_score:
self.high_score = self._score self.high_score = self._score
score = property(get_score, set_score) score = property(_get_score, _set_score)
def new_game(self): def __init__(self):
self.level = 0 self._score = 0
self.score = 0
self.nb_lines_cleared = 0
self.goal = 0
self.time = 0 self.time = 0
self.pressed_actions = [] def new_game(self, level):
self.auto_repeat = False self.level = level - 1
self.score = 0
self.lines_cleared = 0
self.goal = 0
self.time = 0
self.combo = -1
self.lock_delay = self.LOCK_DELAY self.lock_delay = LOCK_DELAY
self.fall_delay = self.FALL_DELAY self.fall_delay = FALL_DELAY
self.matrix.clear()
for y in range(self.NB_LINES + 3):
self.append_new_line_to_matrix()
self.next.pieces = [self.new_tetromino() for n in range(self.NB_NEXT)]
self.held.piece = None
self.state = State.PLAYING
self.start(self.update_time, 1)
self.new_level()
def new_tetromino(self):
if not self.random_bag:
self.random_bag = list(Tetromino.shapes)
random.shuffle(self.random_bag)
return self.random_bag.pop()()
def append_new_line_to_matrix(self):
self.matrix.append([None for x in range(self.NB_COLS)])
def new_level(self): def new_level(self):
self.level += 1 self.level += 1
@ -132,23 +102,102 @@ class TetrisLogic:
self.fall_delay = pow(0.8 - ((self.level - 1) * 0.007), self.level - 1) self.fall_delay = pow(0.8 - ((self.level - 1) * 0.007), self.level - 1)
if self.level > 15: if self.level > 15:
self.lock_delay = 0.5 * pow(0.9, self.level - 15) self.lock_delay = 0.5 * pow(0.9, self.level - 15)
self.show_text("LEVEL\n{:n}".format(self.level))
self.restart(self.fall, self.fall_delay)
self.new_matrix_piece()
def new_matrix_piece(self): def update_time(self):
self.time += 1
class TetrisLogic:
LINES = LINES
COLLUMNS = COLLUMNS
NEXT_PIECES = NEXT_PIECES
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,
lines=LINES,
collumns=COLLUMNS,
next_pieces=NEXT_PIECES,
):
self.stats = Stats()
self.load_high_score()
self.state = State.STARTING
self.held = HoldQueue()
self.matrix = Matrix(lines, collumns)
self.matrix.ghost = None
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.auto_repeat = False
self.matrix.reset()
self.next.pieces = [self.new_piece() for n in range(self.next.nb_pieces)]
self.held.piece = None
self.state = State.PLAYING
self.start(self.stats.update_time, 1)
self.on_new_game()
self.new_level()
def on_new_game(self):
pass
def new_piece(self):
if not self.random_bag:
self.random_bag = list(Tetromino.shapes)
random.shuffle(self.random_bag)
piece = self.random_bag.pop()()
self.on_new_piece(piece)
return piece
def on_new_piece(self, piece):
pass
def new_level(self):
self.stats.new_level()
self.restart(self.fall, self.stats.fall_delay)
self.on_new_level(self.stats.level)
self.generation_phase()
def on_new_level(self, level):
pass
def generation_phase(self):
self.matrix.piece = self.next.pieces.pop(0) self.matrix.piece = self.next.pieces.pop(0)
self.matrix.piece.coord = self.CURRENT_COORD self.matrix.piece.coord = self.MATRIX_PIECE_COORD
self.matrix.ghost = self.matrix.piece.ghost() self.matrix.ghost = self.matrix.piece.ghost()
self.move_ghost() self.move_ghost()
self.next.pieces.append(self.new_tetromino()) self.next.pieces.append(self.new_piece())
self.next.pieces[-1].coord = self.NEXT_COORDS[-1] self.next.pieces[-1].coord = self.NEXT_PIECE_COORDS[-1]
for tetromino, coord in zip(self.next.pieces, self.NEXT_COORDS): for tetromino, coord in zip(self.next.pieces, self.NEXT_PIECE_COORDS):
tetromino.coord = coord tetromino.coord = coord
self.on_generation_phase(self.matrix.piece)
self.on_falling_phase()
if not self.can_move(self.matrix.piece.coord, (mino.coord for mino in self.matrix.piece)): if not self.can_move(self.matrix.piece.coord, (mino.coord for mino in self.matrix.piece)):
self.game_over() self.game_over()
def on_generation_phase(self, piece):
pass
def on_falling_phase(self):
pass
def move_left(self): def move_left(self):
self.move(Movement.LEFT) self.move(Movement.LEFT)
@ -171,12 +220,12 @@ class TetrisLogic:
def soft_drop(self): def soft_drop(self):
moved = self.move(Movement.DOWN) moved = self.move(Movement.DOWN)
if moved: if moved:
self.score += 1 self.stats.score += 1
return moved return moved
def hard_drop(self): def hard_drop(self):
while self.move(Movement.DOWN, prelock=False): while self.move(Movement.DOWN, prelock=False):
self.score += 2 self.stats.score += 2
self.lock() self.lock()
def fall(self): def fall(self):
@ -186,35 +235,47 @@ class TetrisLogic:
potential_coord = self.matrix.piece.coord + movement potential_coord = self.matrix.piece.coord + movement
if self.can_move(potential_coord, (mino.coord for mino in self.matrix.piece)): if self.can_move(potential_coord, (mino.coord for mino in self.matrix.piece)):
if self.matrix.piece.prelocked: if self.matrix.piece.prelocked:
self.restart(self.lock, self.lock_delay) self.restart(self.lock, self.stats.lock_delay)
self.matrix.piece.coord = potential_coord self.matrix.piece.coord = potential_coord
if not movement == Movement.DOWN: if not movement == Movement.DOWN:
self.matrix.piece.last_rotation_point = None self.matrix.piece.last_rotation_point = None
self.move_ghost() self.move_ghost()
self.on_moved(movement)
return True return True
else: else:
if prelock and not self.matrix.piece.prelocked and movement == Movement.DOWN: if prelock and not self.matrix.piece.prelocked and movement == Movement.DOWN:
self.matrix.piece.prelocked = True self.matrix.piece.prelocked = True
self.start(self.lock, self.lock_delay) self.start(self.lock, self.stats.lock_delay)
self.on_lock_phase()
return False return False
def on_moved(self, movement):
pass
def rotate(self, rotation): def rotate(self, rotation):
rotated_coords = tuple(Coord(rotation * mino.coord.y, -rotation * mino.coord.x) for mino in self.matrix.piece) 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): 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 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.matrix.piece.prelocked: if self.matrix.piece.prelocked:
self.restart(self.lock, self.lock_delay) self.restart(self.lock, self.stats.lock_delay)
self.matrix.piece.coord = potential_coord self.matrix.piece.coord = potential_coord
for mino, coord in zip(self.matrix.piece, rotated_coords): for mino, coord in zip(self.matrix.piece, rotated_coords):
mino.coord = coord mino.coord = coord
self.matrix.piece.orientation = (self.matrix.piece.orientation + rotation) % 4 self.matrix.piece.orientation = (self.matrix.piece.orientation + rotation) % 4
self.matrix.piece.last_rotation_point = rotation_point self.matrix.piece.last_rotation_point = rotation_point
self.move_ghost() self.move_ghost()
self.on_rotated(rotation)
return True return True
else: else:
return False return False
def on_rotated(self, direction):
pass
def on_lock_phase(self):
pass
SCORES = ( SCORES = (
{LINES_CLEAR_NAME: "", T_Spin.NONE: 0, T_Spin.MINI: 1, T_Spin.T_SPIN: 4}, {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: "SINGLE", T_Spin.NONE: 1, T_Spin.MINI: 2, T_Spin.T_SPIN: 8},
@ -232,14 +293,23 @@ class TetrisLogic:
return return
# Game over # Game over
if all((mino.coord + self.matrix.piece.coord).y >= self.NB_LINES for mino in self.matrix.piece): if all((mino.coord + self.matrix.piece.coord).y >= self.matrix.lines for mino in self.matrix.piece):
self.game_over() self.game_over()
return return
if self.pressed_actions: if self.pressed_actions:
self.auto_repeat = False self.auto_repeat = False
self.restart(self.repeat_action, self.AUTOREPEAT_DELAY) self.stop(self.repeat_action)
for mino in self.matrix.piece:
coord = mino.coord + self.matrix.piece.coord
if coord.y <= self.matrix.lines + 3:
self.matrix[coord.y][coord.x] = mino
self.on_locked(self.matrix.piece)
self.pattern_phase()
def pattern_phase(self):
# T-Spin # T-Spin
if type(self.matrix.piece) == T_Tetrimino and self.matrix.piece.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)
@ -255,21 +325,16 @@ class TetrisLogic:
else: else:
t_spin = T_Spin.NONE t_spin = T_Spin.NONE
for mino in self.matrix.piece:
coord = mino.coord + self.matrix.piece.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 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 lines_cleared += 1
self.on_line_remove(y)
self.matrix.pop(y) self.matrix.pop(y)
self.append_new_line_to_matrix() self.matrix.append_new_line()
if nb_lines_cleared: if lines_cleared:
self.nb_lines_cleared += nb_lines_cleared self.stats.lines_cleared += lines_cleared
# Scoring # Scoring
lock_strings = [] lock_strings = []
@ -277,31 +342,40 @@ class TetrisLogic:
if t_spin: if t_spin:
lock_strings.append(t_spin) lock_strings.append(t_spin)
if nb_lines_cleared: if lines_cleared:
lock_strings.append(self.SCORES[nb_lines_cleared][LINES_CLEAR_NAME]) lock_strings.append(self.SCORES[lines_cleared][LINES_CLEAR_NAME])
self.combo += 1 self.stats.combo += 1
else: else:
self.combo = -1 self.stats.combo = -1
if nb_lines_cleared or t_spin: if lines_cleared or t_spin:
ds = self.SCORES[nb_lines_cleared][t_spin] ds = self.SCORES[lines_cleared][t_spin]
self.goal -= ds self.stats.goal -= ds
ds *= 100 * self.level ds *= 100 * self.stats.level
lock_score += ds lock_score += ds
lock_strings.append(str(ds)) lock_strings.append(str(ds))
self.show_text("\n".join(lock_strings)) self.show_text("\n".join(lock_strings))
if self.combo >= 1: if self.stats.combo >= 1:
ds = (20 if nb_lines_cleared == 1 else 50) * self.combo * self.level ds = (20 if lines_cleared == 1 else 50) * self.stats.combo * self.stats.level
lock_score += ds lock_score += ds
self.show_text("COMBO x{:n}\n{:n}".format(self.combo, ds)) self.show_text("COMBO x{:n}\n{:n}".format(self.stats.combo, ds))
self.score += lock_score self.stats.score += lock_score
if self.goal <= 0: if self.stats.goal <= 0:
self.new_level() self.new_level()
else: else:
self.new_matrix_piece() self.generation_phase()
if self.pressed_actions:
self.start(self.repeat_action, self.AUTOREPEAT_DELAY)
def on_locked(piece):
pass
def on_line_remove(self, y):
pass
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)
@ -318,18 +392,18 @@ class TetrisLogic:
self.matrix.piece.prelocked = False self.matrix.piece.prelocked = False
self.stop(self.lock) self.stop(self.lock)
self.matrix.piece, self.held.piece = self.held.piece, self.matrix.piece self.matrix.piece, self.held.piece = self.held.piece, self.matrix.piece
self.held.piece.coord = self.HELD_COORD self.held.piece.coord = self.HELD_PIECE_COORD
if type(self.held.piece) == I_Tetrimino: if type(self.held.piece) == I_Tetrimino:
self.held.piece.coord += Movement.LEFT self.held.piece.coord += Movement.LEFT
for mino, coord in zip(self.held.piece, self.held.piece.MINOES_COORDS): for mino, coord in zip(self.held.piece, self.held.piece.MINOES_COORDS):
mino.coord = coord mino.coord = coord
if self.matrix.piece: if self.matrix.piece:
self.matrix.piece.coord = self.CURRENT_COORD self.matrix.piece.coord = self.MATRIX_PIECE_COORD
self.matrix.ghost = self.matrix.piece.ghost() self.matrix.ghost = self.matrix.piece.ghost()
self.move_ghost() self.move_ghost()
else: else:
self.new_matrix_piece() self.generation_phase()
def pause(self): def pause(self):
self.state = State.PAUSED self.state = State.PAUSED
@ -340,10 +414,10 @@ 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.stats.fall_delay)
if self.matrix.piece.prelocked: if self.matrix.piece.prelocked:
self.start(self.lock, self.lock_delay) self.start(self.lock, self.stats.lock_delay)
self.start(self.update_time, 1) self.start(self.stats.update_time, 1)
def game_over(self): def game_over(self):
self.state = State.OVER self.state = State.OVER
@ -353,10 +427,7 @@ class TetrisLogic:
def stop_all(self): def stop_all(self):
self.stop(self.fall) self.stop(self.fall)
self.stop(self.lock) self.stop(self.lock)
self.stop(self.update_time) self.stop(self.stats.update_time)
def update_time(self):
self.time += 1
def do_action(self, action): def do_action(self, action):
action() action()
@ -364,7 +435,7 @@ class TetrisLogic:
self.auto_repeat = False self.auto_repeat = False
self.pressed_actions.append(action) self.pressed_actions.append(action)
if action == self.soft_drop: if action == self.soft_drop:
delay = self.fall_delay / 20 delay = self.stats.fall_delay / 20
else: else:
delay = self.AUTOREPEAT_DELAY delay = self.AUTOREPEAT_DELAY
self.restart(self.repeat_action, delay) self.restart(self.repeat_action, delay)
@ -393,16 +464,16 @@ class TetrisLogic:
def load_high_score(self, crypted_high_score=None): def load_high_score(self, crypted_high_score=None):
if crypted_high_score: if crypted_high_score:
crypted_high_score = int(pickle.loads(crypted_high_score)) crypted_high_score = int(pickle.loads(crypted_high_score))
self.high_score = crypted_high_score ^ CRYPT_KEY self.stats.high_score = crypted_high_score ^ CRYPT_KEY
else: else:
raise Warning( raise Warning(
"""TetrisLogic.load_high_score not implemented. """TetrisLogic.load_high_score not implemented.
High score is set to 0""" High score is set to 0"""
) )
self.high_score = 0 self.stats.high_score = 0
def save_high_score(self): def save_high_score(self):
crypted_high_score = self.high_score ^ CRYPT_KEY crypted_high_score = self.stats.high_score ^ CRYPT_KEY
crypted_high_score = pickle.dumps(crypted_high_score) crypted_high_score = pickle.dumps(crypted_high_score)
return crypted_high_score return crypted_high_score