252 lines
9.4 KiB
Python
252 lines
9.4 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
import locale
|
|
import time
|
|
|
|
import consts
|
|
from qt5 import QtWidgets, QtGui, QtCore, QtMultimedia
|
|
from settings import qsettings
|
|
from block import Block
|
|
|
|
|
|
|
|
class Stats(QtWidgets.QWidget):
|
|
"""
|
|
Show informations relevant to the game being played is displayed on-screen.
|
|
Looks for patterns made from Locked Down Blocks in the Matrix and calculate score.
|
|
"""
|
|
|
|
ROWS = consts.STATS_ROWS
|
|
COLUMNS = consts.STATS_COLUMNS
|
|
TEXT_COLOR = consts.STATS_TEXT_COLOR
|
|
|
|
temporary_text = QtCore.Signal(str)
|
|
|
|
def __init__(self, frames):
|
|
super().__init__(frames)
|
|
self.frames = frames
|
|
self.setStyleSheet("background-color: transparent")
|
|
|
|
self.load_sfx()
|
|
|
|
self.setSizePolicy(
|
|
QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding
|
|
)
|
|
self.text_options = QtGui.QTextOption(QtCore.Qt.AlignRight)
|
|
self.text_options.setWrapMode(QtGui.QTextOption.WrapAtWordBoundaryOrAnywhere)
|
|
|
|
self.clock = QtCore.QTimer()
|
|
self.clock.timeout.connect(self.tick)
|
|
|
|
self.high_score = int(qsettings.value(self.tr("High score"), 0))
|
|
|
|
def load_sfx(self):
|
|
self.line_clear_sfx = QtMultimedia.QSoundEffect(self)
|
|
url = QtCore.QUrl.fromLocalFile(consts.LINE_CLEAR_SFX_PATH)
|
|
self.line_clear_sfx.setSource(url)
|
|
|
|
self.tetris_sfx = QtMultimedia.QSoundEffect(self)
|
|
url = QtCore.QUrl.fromLocalFile(consts.TETRIS_SFX_PATH)
|
|
self.tetris_sfx.setSource(url)
|
|
|
|
def new_game(self):
|
|
self.level -= 1
|
|
self.goal = 0
|
|
self.complete_lines_total = 0
|
|
self.score_total = 1
|
|
self.t_spin_total = 0
|
|
self.mini_t_spin_total = 0
|
|
self.nb_back_to_back = 0
|
|
self.back_to_back_scores = None
|
|
self.combo = -1
|
|
self.combos_total = 0
|
|
self.max_combo = 0
|
|
self.chronometer = 0
|
|
self.nb_tetro = 0
|
|
self.clock.start(1000)
|
|
self.lines_stats = [0, 0, 0, 0, 0]
|
|
|
|
def new_level(self):
|
|
self.level += 1
|
|
self.goal += 5 * self.level
|
|
return self.level
|
|
|
|
def update_score(self, nb_complete_lines, t_spin):
|
|
"""
|
|
The player scores points by performing Single, Double, Triple,
|
|
and Tetris Line Clears, as well as T-Spins and Mini T-Spins.
|
|
Soft and Hard Drops also award points.
|
|
There is a special bonus for Back-to-Backs,
|
|
which is when two actions such as a Tetris and T-Spin Double take place
|
|
without a Single, Double, or Triple Line Clear occurring between them.
|
|
Scoring for Line Clears, T-Spins, and Mini T-Spins are level dependent,
|
|
while Hard and Soft Drop point values remain constant.
|
|
"""
|
|
self.nb_tetro += 1
|
|
if nb_complete_lines:
|
|
self.complete_lines_total += nb_complete_lines
|
|
self.lines_stats[nb_complete_lines] += 1
|
|
if t_spin == "T-Spin":
|
|
self.t_spin_total += 1
|
|
elif t_spin == "Mini T-Spin":
|
|
self.mini_t_spin_total += 1
|
|
|
|
score = consts.SCORES[nb_complete_lines][t_spin]
|
|
|
|
if score:
|
|
text = " ".join((t_spin, consts.SCORES[nb_complete_lines]["name"]))
|
|
if (t_spin and nb_complete_lines) or nb_complete_lines == 4:
|
|
self.tetris_sfx.play()
|
|
elif t_spin or nb_complete_lines:
|
|
self.line_clear_sfx.play()
|
|
|
|
self.goal -= score
|
|
score = 100 * self.level * score
|
|
self.score_total += score
|
|
|
|
self.temporary_text.emit(text + "\n{:n}".format(score))
|
|
|
|
# ==============================================================================
|
|
# Combo
|
|
# Bonus for complete lines on each consecutive lock downs
|
|
# if nb_complete_lines:
|
|
# ==============================================================================
|
|
if nb_complete_lines:
|
|
self.combo += 1
|
|
if self.combo > 0:
|
|
if nb_complete_lines == 1:
|
|
combo_score = 20 * self.combo * self.level
|
|
else:
|
|
combo_score = 50 * self.combo * self.level
|
|
self.score_total += combo_score
|
|
self.max_combo = max(self.max_combo, self.combo)
|
|
self.combos_total += 1
|
|
self.temporary_text.emit(
|
|
self.tr("COMBO x{:n}\n{:n}").format(self.combo, combo_score)
|
|
)
|
|
else:
|
|
self.combo = -1
|
|
|
|
# ==============================================================================
|
|
# Back-to_back sequence
|
|
# Two major bonus actions, such as two Tetrises, performed without
|
|
# a Single, Double, or Triple Line Clear occurring between them.
|
|
# Bonus for Tetrises, T-Spin Line Clears, and Mini T-Spin Line Clears
|
|
# performed consecutively in a B2B sequence.
|
|
# ==============================================================================
|
|
if (t_spin and nb_complete_lines) or nb_complete_lines == 4:
|
|
if self.back_to_back_scores is not None:
|
|
self.back_to_back_scores.append(score // 2)
|
|
else:
|
|
# The first Line Clear in the Back-to-Back sequence
|
|
# does not receive the Back-to-Back Bonus.
|
|
self.back_to_back_scores = []
|
|
elif nb_complete_lines and not t_spin:
|
|
# A Back-to-Back sequence is only broken by a Single, Double, or Triple Line Clear.
|
|
# Locking down a Tetrimino without clearing a line
|
|
# or holding a Tetrimino does not break the Back-to-Back sequence.
|
|
# T-Spins and Mini T-Spins that do not clear any lines
|
|
# do not receive the Back-to-Back Bonus; instead they are scored as normal.
|
|
# They also cannot start a Back-to-Back sequence, however,
|
|
# they do not break an existing Back-to-Back sequence.
|
|
if self.back_to_back_scores:
|
|
b2b_score = sum(self.back_to_back_scores)
|
|
self.score_total += b2b_score
|
|
self.nb_back_to_back += 1
|
|
self.temporary_text.emit(
|
|
self.tr("BACK TO BACK\n{:n}").format(b2b_score)
|
|
)
|
|
self.back_to_back_scores = None
|
|
|
|
self.high_score = max(self.score_total, self.high_score)
|
|
self.update()
|
|
|
|
def update_drop_score(self, n):
|
|
""" Tetrimino is Soft Dropped for n lines or Hard Dropped for (n/2) lines"""
|
|
self.score_total += n
|
|
self.high_score = max(self.score_total, self.high_score)
|
|
self.update()
|
|
|
|
def tick(self):
|
|
self.chronometer += 1
|
|
self.update()
|
|
|
|
def paintEvent(self, event):
|
|
if not self.frames.playing and not self.frames.matrix.game_over:
|
|
return
|
|
|
|
painter = QtGui.QPainter(self)
|
|
painter.setFont(self.font)
|
|
painter.setPen(self.TEXT_COLOR)
|
|
|
|
painter.drawText(
|
|
QtCore.QRectF(self.rect()), self.text(sep="\n\n"), self.text_options
|
|
)
|
|
|
|
def text(self, full_stats=False, sep="\n"):
|
|
text = (
|
|
self.tr("Score: ")
|
|
+ locale.format("%i", self.score_total, grouping=True, monetary=True)
|
|
+ sep
|
|
+ self.tr("High score: ")
|
|
+ locale.format("%i", self.high_score, grouping=True, monetary=True)
|
|
+ sep
|
|
+ self.tr("Time: {}\n").format(
|
|
time.strftime("%H:%M:%S", time.gmtime(self.chronometer))
|
|
)
|
|
+ sep
|
|
+ self.tr("Level: ")
|
|
+ locale.format("%i", self.level, grouping=True, monetary=True)
|
|
+ sep
|
|
+ self.tr("Goal: ")
|
|
+ locale.format("%i", self.goal, grouping=True, monetary=True)
|
|
+ sep
|
|
+ self.tr("Lines: ")
|
|
+ locale.format(
|
|
"%i", self.complete_lines_total, grouping=True, monetary=True
|
|
)
|
|
+ sep
|
|
+ self.tr("Mini T-Spins: ")
|
|
+ locale.format("%i", self.mini_t_spin_total, grouping=True, monetary=True)
|
|
+ sep
|
|
+ self.tr("T-Spins: ")
|
|
+ locale.format("%i", self.t_spin_total, grouping=True, monetary=True)
|
|
+ sep
|
|
+ self.tr("Back-to-back: ")
|
|
+ locale.format("%i", self.nb_back_to_back, grouping=True, monetary=True)
|
|
+ sep
|
|
+ self.tr("Max combo: ")
|
|
+ locale.format("%i", self.max_combo, grouping=True, monetary=True)
|
|
+ sep
|
|
+ self.tr("Combos: ")
|
|
+ locale.format("%i", self.combos_total, grouping=True, monetary=True)
|
|
)
|
|
if full_stats:
|
|
minutes = self.chronometer / 60
|
|
text += (
|
|
"\n"
|
|
+ sep
|
|
+ self.tr("Lines per minute: {:.1f}").format(
|
|
self.complete_lines_total / minutes
|
|
)
|
|
+ sep
|
|
+ self.tr("Tetrominos locked down: ")
|
|
+ locale.format("%i", self.nb_tetro, grouping=True, monetary=True)
|
|
+ sep
|
|
+ self.tr("Tetrominos per minute: {:.1f}").format(
|
|
self.nb_tetro / minutes
|
|
)
|
|
+ sep
|
|
)
|
|
text += sep.join(
|
|
score_type["name"]
|
|
+ self.tr(": ")
|
|
+ locale.format("%i", nb, grouping=True, monetary=True)
|
|
for score_type, nb in tuple(zip(consts.SCORES, self.lines_stats))[1:]
|
|
)
|
|
return text
|
|
|
|
def resizeEvent(self, event):
|
|
self.font = QtGui.QFont(consts.STATS_FONT_NAME, Block.side / 3.5) |