change tetrislogic api

This commit is contained in:
Adrien MALINGREY 2019-10-04 17:48:47 +02:00
parent 9a7aead918
commit f025ad5fd8
5 changed files with 220 additions and 168 deletions

View File

@ -176,17 +176,6 @@ 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
def __init__(self):
locale.setlocale(locale.LC_ALL, "")
self.highlight_texts = []
@ -203,7 +192,7 @@ class TetrArcade(TetrisLogic, arcade.Window):
self.new_conf()
self.load_conf()
super().__init__()
super().__init__(NB_LINES, NB_COLS, NB_NEXT)
arcade.Window.__init__(
self,
width=self.init_width,
@ -234,8 +223,8 @@ class TetrArcade(TetrisLogic, arcade.Window):
for path in MUSICS_PATHS
)
self.music.queue(playlist)
except Exception as e:
Warning("Can't play music :" + str(e))
except:
Warning("Can't play music.")
self.play_music = False
def new_conf(self):
@ -320,38 +309,37 @@ AGAIN""".format(
self.play_music = self.conf["MUSIC"].getboolean("play")
def new_game(self):
def on_new_game(self):
self.highlight_texts = []
super().new_game()
self.matrix.sprites = MatrixSprites(self.matrix)
if self.play_music:
self.music.seek(0)
self.music.play()
def new_tetromino(self):
tetromino = super().new_tetromino()
tetromino.sprites = TetrominoSprites(tetromino, self)
return tetromino
def on_new_piece(self, piece):
piece.sprites = TetrominoSprites(piece, self)
def new_matrix_piece(self):
super().new_matrix_piece()
def on_new_level(self, level):
self.show_text("LEVEL\n{:n}".format(level))
def on_generation_phase(self, 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 move(self, movement, prelock=True):
moved = super().move(movement, prelock)
def on_falling_phase(self):
self.matrix.piece.sprites.refresh()
if moved:
self.matrix.ghost.sprites.refresh()
return moved
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_moved(self, moved):
self.matrix.piece.sprites.refresh()
self.matrix.ghost.sprites.refresh()
def on_rotated(self, direction):
for tetromino in (self.matrix.piece, self.matrix.ghost):
tetromino.sprites.refresh()
def on_lock_phase(self):
self.matrix.piece.sprites.refresh()
def swap(self):
super().swap()
@ -360,20 +348,15 @@ AGAIN""".format(
if tetromino:
tetromino.sprites.refresh()
def lock(self):
self.matrix.piece.prelocked = False
self.matrix.piece.sprites.refresh()
super().lock()
self.matrix.sprites.refresh()
def enter_the_matrix(self):
super().enter_the_matrix()
for mino in self.matrix.piece:
def on_locked(self, piece):
piece.sprites.refresh()
for mino in piece:
self.matrix.sprites.append(mino.sprite)
def remove_line(self, y):
self.matrix.sprites.remove_line(y)
super().remove_line(y)
self.matrix.sprites.refresh()
def pause(self):
super().pause()
@ -432,7 +415,7 @@ AGAIN""".format(
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")):
arcade.draw_text(
@ -448,11 +431,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.lines_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(

10
test.py
View File

@ -1,10 +1,18 @@
# -*- coding: utf-8 -*-
from TetrArcade import TetrArcade, State
from TetrArcade import TetrArcade, State, MinoSprite
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.rotate_clockwise()

View File

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

View File

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

View File

@ -5,9 +5,9 @@ import pickle
from .utils import Coord, Movement, Rotation, T_Spin
from .tetromino import Tetromino, T_Tetrimino, I_Tetrimino
from .consts import (
NB_LINES,
NB_COLS,
NB_NEXT,
LINES,
COLLUMNS,
NEXT_PIECES,
LOCK_DELAY,
FALL_DELAY,
AUTOREPEAT_DELAY,
@ -42,88 +42,58 @@ class HoldQueue(PieceContainer):
class Matrix(list, PieceContainer):
def __init__(self, *args, **kargs):
list.__init__(self, *args, **kargs)
def __init__(self, lines, collumns):
list.__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):
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):
def __init__(self):
def __init__(self, nb_pieces):
super().__init__()
self.nb_pieces = nb_pieces
self.pieces = []
class TetrisLogic:
class Stats:
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, lines=NB_LINES, collumns=NB_COLS, next_pieces=NB_NEXT):
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):
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, level=1):
self.level = level - 1
self.score = 0
self.nb_lines_cleared = 0
self.goal = 0
def __init__(self):
self._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.lines_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 +102,102 @@ 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
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.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.append(self.new_piece())
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.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)):
self.game_over()
def on_generation_phase(self, piece):
pass
def on_falling_phase(self):
pass
def move_left(self):
self.move(Movement.LEFT)
@ -171,12 +220,12 @@ class TetrisLogic:
def soft_drop(self):
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.stats.score += 2
self.lock()
def fall(self):
@ -186,35 +235,47 @@ class TetrisLogic:
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.restart(self.lock, self.stats.lock_delay)
self.matrix.piece.coord = potential_coord
if not movement == Movement.DOWN:
self.matrix.piece.last_rotation_point = None
self.move_ghost()
self.on_moved(movement)
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)
self.start(self.lock, self.stats.lock_delay)
self.on_lock_phase()
return False
def on_moved(self, movement):
pass
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.restart(self.lock, self.stats.lock_delay)
self.matrix.piece.coord = potential_coord
for mino, coord in zip(self.matrix.piece, rotated_coords):
mino.coord = coord
self.matrix.piece.orientation = (self.matrix.piece.orientation + rotation) % 4
self.matrix.piece.last_rotation_point = rotation_point
self.move_ghost()
self.on_rotated(rotation)
return True
else:
return False
def on_rotated(self, direction):
pass
def on_lock_phase(self):
pass
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},
@ -232,13 +293,12 @@ class TetrisLogic:
return
# 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()
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:
@ -255,17 +315,21 @@ class TetrisLogic:
else:
t_spin = T_Spin.NONE
self.enter_the_matrix()
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)
# Clear complete lines
nb_lines_cleared = 0
lines_cleared = 0
for y, line in reversed(list(enumerate(self.matrix))):
if all(mino for mino in line):
nb_lines_cleared += 1
lines_cleared += 1
self.remove_line(y)
self.append_new_line_to_matrix()
if nb_lines_cleared:
self.nb_lines_cleared += nb_lines_cleared
self.matrix.append_new_line()
if lines_cleared:
self.stats.lines_cleared += lines_cleared
# Scoring
lock_strings = []
@ -273,37 +337,37 @@ class TetrisLogic:
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
if lines_cleared:
lock_strings.append(self.SCORES[lines_cleared][LINES_CLEAR_NAME])
self.stats.combo += 1
else:
self.combo = -1
self.stats.combo = -1
if nb_lines_cleared or t_spin:
ds = self.SCORES[nb_lines_cleared][t_spin]
self.goal -= ds
ds *= 100 * self.level
if lines_cleared or t_spin:
ds = self.SCORES[lines_cleared][t_spin]
self.stats.goal -= ds
ds *= 100 * self.stats.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
if self.stats.combo >= 1:
ds = (20 if lines_cleared == 1 else 50) * self.stats.combo * self.stats.level
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()
else:
self.new_matrix_piece()
self.generation_phase()
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
if self.auto_repeat:
self.restart(self.repeat_action, self.AUTOREPEAT_DELAY)
def on_locked(piece):
pass
def remove_line(self, y):
self.matrix.pop(y)
@ -334,7 +398,7 @@ class TetrisLogic:
self.matrix.ghost = self.matrix.piece.ghost()
self.move_ghost()
else:
self.new_matrix_piece()
self.generation_phase()
def pause(self):
self.state = State.PAUSED
@ -345,10 +409,10 @@ class TetrisLogic:
def resume(self):
self.state = State.PLAYING
self.start(self.fall, self.fall_delay)
self.start(self.fall, self.stats.fall_delay)
if self.matrix.piece.prelocked:
self.start(self.lock, self.lock_delay)
self.start(self.update_time, 1)
self.start(self.lock, self.stats.lock_delay)
self.start(self.stats.update_time, 1)
def game_over(self):
self.state = State.OVER
@ -358,10 +422,7 @@ class TetrisLogic:
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.stop(self.stats.update_time)
def do_action(self, action):
action()
@ -369,7 +430,7 @@ class TetrisLogic:
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)
@ -398,16 +459,16 @@ 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