Undo game_gui splitting
Setting strings must be shared beteen widgets and created after QApplication instanciating for translation
This commit is contained in:
parent
8523ab204c
commit
7c837adf98
@ -12,8 +12,7 @@ Parts of comments issued from 2009 Tetris Design Guideline
|
||||
import sys
|
||||
|
||||
from qt5 import QtWidgets
|
||||
from window import Window
|
||||
|
||||
from game_gui import Window
|
||||
|
||||
|
||||
def play():
|
||||
|
BIN
dist/Tetris2000.exe
vendored
BIN
dist/Tetris2000.exe
vendored
Binary file not shown.
333
frames.py
333
frames.py
@ -1,333 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
import os
|
||||
import itertools
|
||||
|
||||
import consts
|
||||
from qt5 import QtWidgets, QtCore, QtGui, QtMultimedia
|
||||
from settings import s, qsettings, settings
|
||||
from block import Block
|
||||
from tetromino import Tetromino
|
||||
from grids import Grid, HoldQueue, NextQueue
|
||||
from matrix import Matrix
|
||||
from stats import Stats
|
||||
|
||||
|
||||
|
||||
|
||||
class AspectRatioWidget(QtWidgets.QWidget):
|
||||
"""
|
||||
Keeps aspect ratio of child widget on resize
|
||||
https://stackoverflow.com/questions/48043469/how-to-lock-aspect-ratio-while-resizing-the-window
|
||||
"""
|
||||
|
||||
def __init__(self, widget, parent):
|
||||
super().__init__(parent)
|
||||
self.aspect_ratio = widget.size().width() / widget.size().height()
|
||||
self.setLayout(QtWidgets.QBoxLayout(QtWidgets.QBoxLayout.LeftToRight, self))
|
||||
# add spacer, then widget, then spacer
|
||||
self.layout().addItem(QtWidgets.QSpacerItem(0, 0))
|
||||
self.layout().addWidget(widget)
|
||||
self.layout().addItem(QtWidgets.QSpacerItem(0, 0))
|
||||
|
||||
def resizeEvent(self, e):
|
||||
w = e.size().width()
|
||||
h = e.size().height()
|
||||
|
||||
if w / h > self.aspect_ratio: # too wide
|
||||
self.layout().setDirection(QtWidgets.QBoxLayout.LeftToRight)
|
||||
widget_stretch = h * self.aspect_ratio
|
||||
outer_stretch = (w - widget_stretch) / 2 + 0.5
|
||||
else: # too tall
|
||||
self.layout().setDirection(QtWidgets.QBoxLayout.TopToBottom)
|
||||
widget_stretch = w / self.aspect_ratio
|
||||
outer_stretch = (h - widget_stretch) / 2 + 0.5
|
||||
|
||||
self.layout().setStretch(0, outer_stretch)
|
||||
self.layout().setStretch(1, widget_stretch)
|
||||
self.layout().setStretch(2, outer_stretch)
|
||||
|
||||
|
||||
class Frames(QtWidgets.QWidget):
|
||||
"""
|
||||
Display Hold queue, Matrix, Next piece, Next queue and Stats.
|
||||
Manage interactions between them.
|
||||
"""
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent)
|
||||
self.setSizePolicy(
|
||||
QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding
|
||||
)
|
||||
|
||||
self.playing = False
|
||||
self.paused = False
|
||||
self.load_music()
|
||||
|
||||
self.hold_queue = HoldQueue(self)
|
||||
self.matrix = Matrix(self)
|
||||
self.next_piece = Grid(self)
|
||||
self.stats = Stats(self)
|
||||
self.next_queue = NextQueue(self)
|
||||
|
||||
self.matrices = (self.hold_queue, self.matrix, self.next_piece)
|
||||
self.columns = sum(matrix.COLUMNS + 2 for matrix in self.matrices)
|
||||
self.rows = self.matrix.ROWS + consts.GRID_INVISIBLE_ROWS
|
||||
|
||||
w = QtWidgets.QWidget(self)
|
||||
w.setStyleSheet("background-color: transparent")
|
||||
grid = QtWidgets.QGridLayout()
|
||||
for x in range(self.rows):
|
||||
grid.setRowStretch(x, 1)
|
||||
for y in range(self.columns):
|
||||
grid.setColumnStretch(y, 1)
|
||||
grid.setSpacing(0)
|
||||
x, y = 0, 0
|
||||
grid.addWidget(
|
||||
self.hold_queue, y, x, self.hold_queue.ROWS + 1, self.hold_queue.COLUMNS + 2
|
||||
)
|
||||
x += self.hold_queue.COLUMNS + 2
|
||||
grid.addWidget(self.matrix, y, x, self.matrix.ROWS + consts.GRID_INVISIBLE_ROWS, self.matrix.COLUMNS + 2)
|
||||
x += self.matrix.COLUMNS + 3
|
||||
grid.addWidget(
|
||||
self.next_piece, y, x, self.next_piece.ROWS + 1, self.next_piece.COLUMNS + 2
|
||||
)
|
||||
x, y = 0, self.hold_queue.ROWS + 2
|
||||
grid.addWidget(self.stats, y, x, self.stats.ROWS, self.stats.COLUMNS + 1)
|
||||
x += self.stats.COLUMNS + self.matrix.COLUMNS + 5
|
||||
grid.addWidget(
|
||||
self.next_queue, y, x, self.next_queue.ROWS, self.next_queue.COLUMNS + 2
|
||||
)
|
||||
w.setLayout(grid)
|
||||
w.resize(self.columns, self.rows)
|
||||
asw = AspectRatioWidget(w, self)
|
||||
layout = QtWidgets.QGridLayout()
|
||||
layout.addWidget(asw)
|
||||
self.setLayout(layout)
|
||||
|
||||
self.stats.temporary_text.connect(self.matrix.show_temporary_text)
|
||||
self.matrix.drop_signal.connect(self.stats.update_drop_score)
|
||||
self.matrix.lock_signal.connect(self.stats.update_score)
|
||||
|
||||
self.set_background(os.path.join(consts.BG_IMAGE_DIR, consts.START_BG_IMAGE_NAME))
|
||||
|
||||
self.apply_settings()
|
||||
|
||||
def load_music(self):
|
||||
playlist = QtMultimedia.QMediaPlaylist(self)
|
||||
for entry in os.scandir(consts.MUSICS_DIR):
|
||||
path = os.path.join(consts.MUSICS_DIR, entry.name)
|
||||
url = QtCore.QUrl.fromLocalFile(path)
|
||||
music = QtMultimedia.QMediaContent(url)
|
||||
playlist.addMedia(music)
|
||||
playlist.setPlaybackMode(QtMultimedia.QMediaPlaylist.Loop)
|
||||
self.music = QtMultimedia.QMediaPlayer(self)
|
||||
self.music.setAudioRole(QtMultimedia.QAudio.GameRole)
|
||||
self.music.setPlaylist(playlist)
|
||||
self.music.setVolume(settings[s.SOUND][s.MUSIC_VOLUME])
|
||||
|
||||
def apply_settings(self):
|
||||
if self.music.volume() > 5 and self.playing:
|
||||
self.music.play()
|
||||
else:
|
||||
self.music.pause()
|
||||
|
||||
if self.playing:
|
||||
self.hold_enabled = settings[s.OTHER][s.HOLD_ENABLED]
|
||||
self.pause(False)
|
||||
|
||||
self.matrix.keys = {
|
||||
getattr(QtCore.Qt, "Key_" + name): action
|
||||
for action, name in settings[s.KEYBOARD].items()
|
||||
}
|
||||
self.matrix.auto_repeat_timer.start(settings[s.DELAYS][s.AUTO_REPEAT_RATE])
|
||||
self.matrix.spotlight = Matrix.SPOTLIGHT
|
||||
|
||||
for sfx in (
|
||||
self.matrix.rotate_sfx, self.matrix.wall_sfx,
|
||||
self.stats.line_clear_sfx, self.stats.tetris_sfx
|
||||
):
|
||||
sfx.setVolume(settings[s.SOUND][s.SFX_VOLUME])
|
||||
|
||||
def resizeEvent(self, event):
|
||||
Block.side = 0.9 * min(self.width() // self.columns, self.height() // self.rows)
|
||||
self.resize_bg_image()
|
||||
|
||||
def reset_backgrounds(self):
|
||||
backgrounds_paths = (
|
||||
os.path.join(consts.BG_IMAGE_DIR, entry.name)
|
||||
for entry in os.scandir(consts.BG_IMAGE_DIR)
|
||||
)
|
||||
self.backgrounds_cycle = itertools.cycle(backgrounds_paths)
|
||||
|
||||
def set_background(self, path):
|
||||
self.bg_image = QtGui.QImage(path)
|
||||
self.resize_bg_image()
|
||||
|
||||
def resize_bg_image(self):
|
||||
self.resized_bg_image = QtGui.QPixmap.fromImage(self.bg_image)
|
||||
self.resized_bg_image = self.resized_bg_image.scaled(
|
||||
self.size(),
|
||||
QtCore.Qt.KeepAspectRatioByExpanding,
|
||||
QtCore.Qt.SmoothTransformation
|
||||
)
|
||||
self.resized_bg_image = self.resized_bg_image.copy(
|
||||
(self.resized_bg_image.width() - self.width()) // 2,
|
||||
(self.resized_bg_image.height() - self.height()) // 2,
|
||||
self.width(),
|
||||
self.height()
|
||||
)
|
||||
self.update()
|
||||
|
||||
def paintEvent(self, event):
|
||||
painter = QtGui.QPainter(self)
|
||||
painter.drawPixmap(
|
||||
self.rect(),
|
||||
self.resized_bg_image)
|
||||
|
||||
def new_game(self):
|
||||
if self.playing:
|
||||
answer = QtWidgets.QMessageBox.question(
|
||||
self,
|
||||
self.tr("New game"),
|
||||
self.tr("A game is in progress.\n" "Do you want to abord it?"),
|
||||
QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel,
|
||||
QtWidgets.QMessageBox.Cancel,
|
||||
)
|
||||
if answer == QtWidgets.QMessageBox.Cancel:
|
||||
self.pause(False)
|
||||
return
|
||||
self.music.stop()
|
||||
|
||||
self.reset_backgrounds()
|
||||
self.stats.level, ok = QtWidgets.QInputDialog.getInt(
|
||||
self,
|
||||
self.tr("New game"),
|
||||
self.tr("Start level:"),
|
||||
1,
|
||||
1,
|
||||
15,
|
||||
flags=QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint,
|
||||
)
|
||||
if not ok:
|
||||
return
|
||||
|
||||
self.playing = True
|
||||
self.load_music()
|
||||
self.music.play()
|
||||
self.hold_queue.piece = None
|
||||
self.stats.new_game()
|
||||
self.matrix.new_game()
|
||||
self.next_queue.pieces = [Tetromino() for _ in range(5)]
|
||||
self.next_queue.insert_pieces()
|
||||
self.next_piece.insert(Tetromino())
|
||||
self.pause(False)
|
||||
self.new_level()
|
||||
self.new_piece()
|
||||
|
||||
def new_level(self):
|
||||
self.set_background(next(self.backgrounds_cycle))
|
||||
level = self.stats.new_level()
|
||||
self.matrix.new_level(level)
|
||||
|
||||
def new_piece(self):
|
||||
if self.stats.goal <= 0:
|
||||
self.new_level()
|
||||
self.matrix.insert(self.next_piece.piece)
|
||||
self.matrix.lock_wait()
|
||||
self.next_piece.insert(self.next_queue.pieces[0])
|
||||
self.next_queue.new_piece()
|
||||
self.hold_enabled = settings[s.OTHER][s.HOLD_ENABLED]
|
||||
self.update()
|
||||
|
||||
if not self.matrix.piece.move(0, 0):
|
||||
self.game_over()
|
||||
return
|
||||
|
||||
self.matrix.fall_timer.start(self.matrix.speed)
|
||||
|
||||
def pause(self, paused):
|
||||
if not self.playing:
|
||||
return
|
||||
|
||||
if paused:
|
||||
self.paused = True
|
||||
self.update()
|
||||
self.matrix.fall_timer.stop()
|
||||
self.stats.clock.stop()
|
||||
self.matrix.auto_repeat_timer.stop()
|
||||
self.music.pause()
|
||||
else:
|
||||
self.matrix.text = ""
|
||||
self.update()
|
||||
QtCore.QTimer.singleShot(1000, self.resume)
|
||||
|
||||
def resume(self):
|
||||
self.paused = False
|
||||
self.update()
|
||||
self.matrix.fall_timer.start(self.matrix.speed)
|
||||
self.stats.clock.start(1000)
|
||||
self.matrix.auto_repeat_timer.start(settings[s.DELAYS][s.AUTO_REPEAT_RATE])
|
||||
self.music.play()
|
||||
|
||||
def hold(self):
|
||||
"""
|
||||
Using the Hold command places the Tetrimino in play into the Hold Queue.
|
||||
The previously held Tetrimino (if one exists) will then start falling
|
||||
from the top of the Matrix,
|
||||
beginning from its generation position and North Facing orientation.
|
||||
Only one Tetrimino may be held at a time.
|
||||
A Lock Down must take place between Holds.
|
||||
For example, at the beginning, the first Tetrimino is generated and begins to fall.
|
||||
The player decides to hold this Tetrimino.
|
||||
Immediately the Next Tetrimino is generated from the Next Queue and begins to fall.
|
||||
The player must first Lock Down this Tetrimino before holding another Tetrimino.
|
||||
In other words, you may not Hold the same Tetrimino more than once.
|
||||
"""
|
||||
|
||||
if not self.hold_enabled:
|
||||
return
|
||||
|
||||
piece = self.hold_queue.piece
|
||||
if piece:
|
||||
self.hold_queue.insert(self.matrix.piece)
|
||||
self.matrix.insert(piece)
|
||||
self.update()
|
||||
else:
|
||||
self.hold_queue.insert(self.matrix.piece)
|
||||
self.new_piece()
|
||||
self.hold_enabled = False
|
||||
|
||||
def game_over(self):
|
||||
self.matrix.fall_timer.stop()
|
||||
self.stats.clock.stop()
|
||||
self.matrix.auto_repeat_timer.stop()
|
||||
self.music.stop()
|
||||
self.playing = False
|
||||
self.matrix.game_over = True
|
||||
msgbox = QtWidgets.QMessageBox(self)
|
||||
msgbox.setWindowTitle(self.tr("Game over"))
|
||||
msgbox.setIcon(QtWidgets.QMessageBox.Information)
|
||||
if self.stats.score_total == self.stats.high_score:
|
||||
msgbox.setText(
|
||||
self.tr(
|
||||
"Congratulations!\nYou have the high score: {}"
|
||||
).format(
|
||||
|
||||
locale.format("%i", self.stats.high_score, grouping=True, monetary=True)
|
||||
)
|
||||
)
|
||||
qsettings.setValue(self.tr("High score"), self.stats.high_score)
|
||||
else:
|
||||
msgbox.setText(
|
||||
self.tr(
|
||||
"Score: {}\nHigh score: {}"
|
||||
).format(
|
||||
locale.format("%i", self.stats.score_total, grouping=True, monetary=True),
|
||||
locale.format("%i", self.stats.high_score, grouping=True, monetary=True)
|
||||
)
|
||||
)
|
||||
msgbox.setDetailedText(self.stats.text(full_stats=True))
|
||||
msgbox.exec_()
|
1473
game_gui.py
Normal file
1473
game_gui.py
Normal file
File diff suppressed because it is too large
Load Diff
116
grids.py
116
grids.py
@ -1,116 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
import consts
|
||||
from qt5 import QtWidgets, QtGui
|
||||
from block import Block
|
||||
from point import Point
|
||||
from settings import s, settings
|
||||
from tetromino import Tetromino
|
||||
|
||||
|
||||
class Grid(QtWidgets.QWidget):
|
||||
"""
|
||||
Mother class of Hold queue, Matrix, Next piece, and Next queue
|
||||
"""
|
||||
|
||||
ROWS = consts.GRID_DEFAULT_ROWS + consts.GRID_INVISIBLE_ROWS
|
||||
COLUMNS = consts.GRID_DEFAULT_COLUMNS
|
||||
STARTING_POSITION = Point(
|
||||
consts.GRID_DEFAULT_COLUMNS // 2,
|
||||
consts.GRID_DEFAULT_ROWS // 2 + consts.GRID_INVISIBLE_ROWS
|
||||
)
|
||||
GRIDLINE_COLOR = consts.GRID_GRIDLINE_COLOR
|
||||
HARD_DROP_MOVEMENT = consts.GRID_HARD_DROP_MOVEMENT
|
||||
SPOTLIGHT = Point(*consts.GRID_SPOTLIGHT )
|
||||
|
||||
def __init__(self, frames):
|
||||
super().__init__(frames)
|
||||
self.setStyleSheet("background-color: transparent")
|
||||
self.frames = frames
|
||||
self.spotlight = self.SPOTLIGHT
|
||||
self.piece = None
|
||||
|
||||
def insert(self, piece, position=None):
|
||||
"""
|
||||
Add a Tetromino to self
|
||||
Update its coordinates
|
||||
"""
|
||||
piece.insert_into(self, position or self.STARTING_POSITION)
|
||||
self.piece = piece
|
||||
self.update()
|
||||
|
||||
def resizeEvent(self, event):
|
||||
self.bottom = Block.side * self.ROWS
|
||||
self.grid_top = consts.GRID_INVISIBLE_ROWS * Block.side
|
||||
width = Block.side * self.COLUMNS
|
||||
self.left = (self.width() - width) // 2
|
||||
self.right = width + self.left
|
||||
self.top_left_corner = Point(self.left, 0)
|
||||
|
||||
def paintEvent(self, event=None):
|
||||
painter = QtGui.QPainter(self)
|
||||
painter.setRenderHint(QtGui.QPainter.Antialiasing)
|
||||
|
||||
self.paint_grid(painter)
|
||||
|
||||
if (not self.frames.paused or not self.frames.playing) and self.piece:
|
||||
self.paint_piece(painter, self.piece)
|
||||
|
||||
def paint_grid(self, painter):
|
||||
painter.setPen(self.GRIDLINE_COLOR)
|
||||
for x in (self.left + i * Block.side for i in range(self.COLUMNS + 1)):
|
||||
painter.drawLine(x, self.grid_top, x, self.bottom)
|
||||
for y in (j * Block.side for j in range(consts.GRID_INVISIBLE_ROWS, self.ROWS + 1)):
|
||||
painter.drawLine(self.left, y, self.right, y)
|
||||
|
||||
def paint_piece(self, painter, piece):
|
||||
for mino in piece.minoes:
|
||||
mino.paint(painter, self.top_left_corner, self.spotlight)
|
||||
|
||||
|
||||
class HoldQueue(Grid):
|
||||
"""
|
||||
The Hold Queue allows the player to “hold” a falling Tetrimino for as long as they wish.
|
||||
Holding a Tetrimino releases the Tetrimino already in the Hold Queue (if one exists).
|
||||
"""
|
||||
|
||||
def paintEvent(self, event):
|
||||
if not settings[s.OTHER][s.HOLD_ENABLED]:
|
||||
return
|
||||
|
||||
super().paintEvent(event)
|
||||
|
||||
|
||||
class NextQueue(Grid):
|
||||
"""
|
||||
The Next Queue allows the player to see the Next Tetrimino that will be generated
|
||||
and put into play.
|
||||
"""
|
||||
|
||||
ROWS = consts.NEXT_QUEUE_ROWS
|
||||
COLUMNS = consts.NEXT_QUEUE_COLUMNS
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent)
|
||||
self.pieces = []
|
||||
|
||||
def new_piece(self):
|
||||
self.pieces = self.pieces[1:] + [Tetromino()]
|
||||
self.insert_pieces()
|
||||
|
||||
def insert_pieces(self):
|
||||
for y, piece in enumerate(self.pieces):
|
||||
piece.insert_into(self, Point(3, 3 * y + 1))
|
||||
|
||||
def paintEvent(self, event=None):
|
||||
if not settings[s.OTHER][s.SHOW_NEXT_QUEUE]:
|
||||
return
|
||||
|
||||
painter = QtGui.QPainter(self)
|
||||
painter.setRenderHint(QtGui.QPainter.Antialiasing)
|
||||
|
||||
if not self.frames.paused:
|
||||
for piece in self.pieces:
|
||||
self.paint_piece(painter, piece)
|
@ -1,323 +1,323 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS><TS version="2.0">
|
||||
<context>
|
||||
<name>Frames</name>
|
||||
<message>
|
||||
<location filename="../frames.py" line="205"/>
|
||||
<source>New game</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../frames.py" line="192"/>
|
||||
<source>A game is in progress.
|
||||
Do you want to abord it?</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../frames.py" line="205"/>
|
||||
<source>Start level:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../frames.py" line="323"/>
|
||||
<source>High score</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../frames.py" line="312"/>
|
||||
<source>Game over</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../frames.py" line="315"/>
|
||||
<source>Congratulations!
|
||||
You have the high score: {}</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../frames.py" line="325"/>
|
||||
<source>Score: {}
|
||||
High score: {}</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Matrix</name>
|
||||
<message>
|
||||
<location filename="../matrix.py" line="73"/>
|
||||
<source>Level
|
||||
</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../matrix.py" line="356"/>
|
||||
<source>PAUSE
|
||||
|
||||
Press %s
|
||||
to resume</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../matrix.py" line="364"/>
|
||||
<source>GAME
|
||||
OVER</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SettingStrings</name>
|
||||
<message>
|
||||
<location filename="../settings.py" line="20"/>
|
||||
<source>Keyboard settings</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="21"/>
|
||||
<source>Move left</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="22"/>
|
||||
<source>Move right</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="23"/>
|
||||
<source>Rotate clockwise</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="24"/>
|
||||
<source>Rotate counterclockwise</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="25"/>
|
||||
<source>Soft drop</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="26"/>
|
||||
<source>Hard drop</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="27"/>
|
||||
<source>Hold</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="28"/>
|
||||
<source>Pause</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="29"/>
|
||||
<source>Other settings</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="31"/>
|
||||
<source>Delays</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="32"/>
|
||||
<source>Auto-shift delay</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="33"/>
|
||||
<source>Auto-repeat rate</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="35"/>
|
||||
<source>Sound</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="36"/>
|
||||
<source>Music volume</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="37"/>
|
||||
<source>Effects volume</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="39"/>
|
||||
<source>Show ghost piece</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="40"/>
|
||||
<source>Show next queue</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="41"/>
|
||||
<source>Hold enabled</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SettingsDialog</name>
|
||||
<message>
|
||||
<location filename="../settings.py" line="95"/>
|
||||
<source>Settings</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Stats</name>
|
||||
<message>
|
||||
<location filename="../stats.py" line="43"/>
|
||||
<source>High score</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="126"/>
|
||||
<source>COMBO x{:n}
|
||||
{:n}</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="158"/>
|
||||
<source>BACK TO BACK
|
||||
{:n}</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="189"/>
|
||||
<source>Score: </source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="189"/>
|
||||
<source>High score: </source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="189"/>
|
||||
<source>Time: {}
|
||||
</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="189"/>
|
||||
<source>Level: </source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="189"/>
|
||||
<source>Goal: </source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="189"/>
|
||||
<source>Lines: </source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="189"/>
|
||||
<source>Mini T-Spins: </source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="189"/>
|
||||
<source>T-Spins: </source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="189"/>
|
||||
<source>Back-to-back: </source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="189"/>
|
||||
<source>Max combo: </source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="189"/>
|
||||
<source>Combos: </source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="228"/>
|
||||
<source>Lines per minute: {:.1f}</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="228"/>
|
||||
<source>Tetrominos locked down: </source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="228"/>
|
||||
<source>Tetrominos per minute: {:.1f}</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="243"/>
|
||||
<source>: </source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Window</name>
|
||||
<message>
|
||||
<location filename="../window.py" line="93"/>
|
||||
<source>&New game</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../window.py" line="97"/>
|
||||
<source>&Settings</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../window.py" line="101"/>
|
||||
<source>&About</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../window.py" line="139"/>
|
||||
<source>Quit game?</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../window.py" line="139"/>
|
||||
<source>A game is in progress.
|
||||
Do you want to abord it?</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../window.py" line="154"/>
|
||||
<source>High score</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../window.py" line="112"/>
|
||||
<source>Tetris® clone by Adrien Malingrey
|
||||
|
||||
Tetris Game Design by Alekseï Pajitnov
|
||||
Graphism inspired by Tetris Effect
|
||||
Window style sheet: qdarkstyle by Colin Duquesnoy
|
||||
Fonts by Markus Koellmann, Peter Wiegel
|
||||
Images from:
|
||||
OpenGameArt.org by beren77, Duion
|
||||
Pexels.com by Min An, Jaymantri, Felix Mittermeier
|
||||
Pixabay.com by LoganArt
|
||||
Pixnio.com by Adrian Pelletier
|
||||
Unsplash.com by Aron, Patrick Fore, Ilnur Kalimullin, Gabriel Garcia Marengo, Adnanta Raharja
|
||||
StockSnap.io by Nathan Anderson, José Ignacio Pompé
|
||||
Musics from ocremix.org by:
|
||||
CheDDer Nardz, djpretzel, MkVaff, Sir_NutS, R3FORGED, Sir_NutS
|
||||
Sound effects made with voc-one by Simple-Media</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS><TS version="2.0">
|
||||
<context>
|
||||
<name>Frames</name>
|
||||
<message>
|
||||
<location filename="../frames.py" line="205"/>
|
||||
<source>New game</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../frames.py" line="192"/>
|
||||
<source>A game is in progress.
|
||||
Do you want to abord it?</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../frames.py" line="205"/>
|
||||
<source>Start level:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../frames.py" line="323"/>
|
||||
<source>High score</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../frames.py" line="312"/>
|
||||
<source>Game over</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../frames.py" line="315"/>
|
||||
<source>Congratulations!
|
||||
You have the high score: {}</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../frames.py" line="325"/>
|
||||
<source>Score: {}
|
||||
High score: {}</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Matrix</name>
|
||||
<message>
|
||||
<location filename="../matrix.py" line="73"/>
|
||||
<source>Level
|
||||
</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../matrix.py" line="356"/>
|
||||
<source>PAUSE
|
||||
|
||||
Press %s
|
||||
to resume</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../matrix.py" line="364"/>
|
||||
<source>GAME
|
||||
OVER</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SettingStrings</name>
|
||||
<message>
|
||||
<location filename="../settings.py" line="20"/>
|
||||
<source>Keyboard settings</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="21"/>
|
||||
<source>Move left</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="22"/>
|
||||
<source>Move right</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="23"/>
|
||||
<source>Rotate clockwise</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="24"/>
|
||||
<source>Rotate counterclockwise</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="25"/>
|
||||
<source>Soft drop</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="26"/>
|
||||
<source>Hard drop</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="27"/>
|
||||
<source>Hold</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="28"/>
|
||||
<source>Pause</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="29"/>
|
||||
<source>Other settings</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="31"/>
|
||||
<source>Delays</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="32"/>
|
||||
<source>Auto-shift delay</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="33"/>
|
||||
<source>Auto-repeat rate</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="35"/>
|
||||
<source>Sound</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="36"/>
|
||||
<source>Music volume</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="37"/>
|
||||
<source>Effects volume</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="39"/>
|
||||
<source>Show ghost piece</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="40"/>
|
||||
<source>Show next queue</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="41"/>
|
||||
<source>Hold enabled</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SettingsDialog</name>
|
||||
<message>
|
||||
<location filename="../settings.py" line="95"/>
|
||||
<source>Settings</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Stats</name>
|
||||
<message>
|
||||
<location filename="../stats.py" line="43"/>
|
||||
<source>High score</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="126"/>
|
||||
<source>COMBO x{:n}
|
||||
{:n}</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="158"/>
|
||||
<source>BACK TO BACK
|
||||
{:n}</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="189"/>
|
||||
<source>Score: </source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="189"/>
|
||||
<source>High score: </source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="189"/>
|
||||
<source>Time: {}
|
||||
</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="189"/>
|
||||
<source>Level: </source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="189"/>
|
||||
<source>Goal: </source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="189"/>
|
||||
<source>Lines: </source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="189"/>
|
||||
<source>Mini T-Spins: </source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="189"/>
|
||||
<source>T-Spins: </source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="189"/>
|
||||
<source>Back-to-back: </source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="189"/>
|
||||
<source>Max combo: </source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="189"/>
|
||||
<source>Combos: </source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="228"/>
|
||||
<source>Lines per minute: {:.1f}</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="228"/>
|
||||
<source>Tetrominos locked down: </source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="228"/>
|
||||
<source>Tetrominos per minute: {:.1f}</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="243"/>
|
||||
<source>: </source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Window</name>
|
||||
<message>
|
||||
<location filename="../window.py" line="93"/>
|
||||
<source>&New game</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../window.py" line="97"/>
|
||||
<source>&Settings</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../window.py" line="101"/>
|
||||
<source>&About</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../window.py" line="139"/>
|
||||
<source>Quit game?</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../window.py" line="139"/>
|
||||
<source>A game is in progress.
|
||||
Do you want to abord it?</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../window.py" line="154"/>
|
||||
<source>High score</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../window.py" line="112"/>
|
||||
<source>Tetris® clone by Adrien Malingrey
|
||||
|
||||
Tetris Game Design by Alekseï Pajitnov
|
||||
Graphism inspired by Tetris Effect
|
||||
Window style sheet: qdarkstyle by Colin Duquesnoy
|
||||
Fonts by Markus Koellmann, Peter Wiegel
|
||||
Images from:
|
||||
OpenGameArt.org by beren77, Duion
|
||||
Pexels.com by Min An, Jaymantri, Felix Mittermeier
|
||||
Pixabay.com by LoganArt
|
||||
Pixnio.com by Adrian Pelletier
|
||||
Unsplash.com by Aron, Patrick Fore, Ilnur Kalimullin, Gabriel Garcia Marengo, Adnanta Raharja
|
||||
StockSnap.io by Nathan Anderson, José Ignacio Pompé
|
||||
Musics from ocremix.org by:
|
||||
CheDDer Nardz, djpretzel, MkVaff, Sir_NutS, R3FORGED, Sir_NutS
|
||||
Sound effects made with voc-one by Simple-Media</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
|
704
locale/fr.ts
704
locale/fr.ts
@ -1,352 +1,352 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1" language="fr_FR">
|
||||
<context>
|
||||
<name>Frames</name>
|
||||
<message>
|
||||
<location filename="../frames.py" line="205"/>
|
||||
<source>New game</source>
|
||||
<translation>Nouvelle partie</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../frames.py" line="192"/>
|
||||
<source>A game is in progress.
|
||||
Do you want to abord it?</source>
|
||||
<translation>Une partie est en cours.
|
||||
Voulez-vous l'abandonner ?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../frames.py" line="205"/>
|
||||
<source>Start level:</source>
|
||||
<translation>Commencer au niveau :</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../frames.py" line="323"/>
|
||||
<source>High score</source>
|
||||
<translation>Meilleur score</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../frames.py" line="312"/>
|
||||
<source>Game over</source>
|
||||
<translation>Partie terminée</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../frames.py" line="315"/>
|
||||
<source>Congratulations!
|
||||
You have the high score: {}</source>
|
||||
<translation>Bravo !
|
||||
Vous avez atteint le meilleur score : {}</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../frames.py" line="325"/>
|
||||
<source>Score: {}
|
||||
High score: {}</source>
|
||||
<translation>Score : {}
|
||||
Meilleur score : {}</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Matrix</name>
|
||||
<message>
|
||||
<location filename="../matrix.py" line="73"/>
|
||||
<source>Level
|
||||
</source>
|
||||
<translation>Niveau
|
||||
</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../matrix.py" line="356"/>
|
||||
<source>PAUSE
|
||||
|
||||
Press %s
|
||||
to resume</source>
|
||||
<translation>PAUSE
|
||||
|
||||
Appuyez sur
|
||||
%s
|
||||
pour reprendre</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../matrix.py" line="364"/>
|
||||
<source>GAME
|
||||
OVER</source>
|
||||
<translation>PARTIE
|
||||
TERMINÉE</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SettingStrings</name>
|
||||
<message>
|
||||
<location filename="../settings.py" line="20"/>
|
||||
<source>Keyboard settings</source>
|
||||
<translation>Configuration du clavier</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="21"/>
|
||||
<source>Move left</source>
|
||||
<translation>Déplacer à gauche</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="22"/>
|
||||
<source>Move right</source>
|
||||
<translation>Déplacer à droite</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="23"/>
|
||||
<source>Rotate clockwise</source>
|
||||
<translation>Tourner dans le sens horaire</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="24"/>
|
||||
<source>Rotate counterclockwise</source>
|
||||
<translation>Tourner dans le sens anti-horaire</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="25"/>
|
||||
<source>Soft drop</source>
|
||||
<translation>Chute lente</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="26"/>
|
||||
<source>Hard drop</source>
|
||||
<translation>Chute rapide</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="27"/>
|
||||
<source>Hold</source>
|
||||
<translation>Réserve</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="28"/>
|
||||
<source>Pause</source>
|
||||
<translation>Pause</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="29"/>
|
||||
<source>Other settings</source>
|
||||
<translation>Autres paramètres</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="31"/>
|
||||
<source>Delays</source>
|
||||
<translation>Temporisation</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="32"/>
|
||||
<source>Auto-shift delay</source>
|
||||
<translation>Délai avant répétition</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="33"/>
|
||||
<source>Auto-repeat rate</source>
|
||||
<translation>Vitesse de répétition</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="35"/>
|
||||
<source>Sound</source>
|
||||
<translation>Son</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="36"/>
|
||||
<source>Music volume</source>
|
||||
<translation>Volume de la musique</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="37"/>
|
||||
<source>Effects volume</source>
|
||||
<translation>Volume des effets sonores</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="39"/>
|
||||
<source>Show ghost piece</source>
|
||||
<translation>Afficher la pièce fantôme</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="40"/>
|
||||
<source>Show next queue</source>
|
||||
<translation>Afficher les 6 prochaines pièces</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="41"/>
|
||||
<source>Hold enabled</source>
|
||||
<translation>Activer la réserve</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SettingsDialog</name>
|
||||
<message>
|
||||
<location filename="../settings.py" line="95"/>
|
||||
<source>Settings</source>
|
||||
<translation>Préférences</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Stats</name>
|
||||
<message>
|
||||
<location filename="../stats.py" line="43"/>
|
||||
<source>High score</source>
|
||||
<translation>Meilleur score</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="126"/>
|
||||
<source>COMBO x{:n}
|
||||
{:n}</source>
|
||||
<translation>COMBO x{:n}
|
||||
{:n}</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="158"/>
|
||||
<source>BACK TO BACK
|
||||
{:n}</source>
|
||||
<translation>BACK TO BACK
|
||||
{:n}</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="189"/>
|
||||
<source>Time: {}
|
||||
</source>
|
||||
<translation>Temps : {}
|
||||
</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="228"/>
|
||||
<source>Lines per minute: {:.1f}</source>
|
||||
<translation>Lignes par minute : {:.1f}</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="228"/>
|
||||
<source>Tetrominos per minute: {:.1f}</source>
|
||||
<translation>Tétrominos par minute : {:.1f}</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="189"/>
|
||||
<source>Score: </source>
|
||||
<translation>Score : </translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="189"/>
|
||||
<source>High score: </source>
|
||||
<translation>Meilleur score : </translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="189"/>
|
||||
<source>Level: </source>
|
||||
<translation>Niveau : </translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="189"/>
|
||||
<source>Goal: </source>
|
||||
<translation>Objectif : </translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="189"/>
|
||||
<source>Lines: </source>
|
||||
<translation>Lignes : </translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="189"/>
|
||||
<source>Mini T-Spins: </source>
|
||||
<translation>Mini T-Spins : </translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="189"/>
|
||||
<source>T-Spins: </source>
|
||||
<translation>T-Spins : </translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="189"/>
|
||||
<source>Back-to-back: </source>
|
||||
<translation>Back-to-back : </translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="189"/>
|
||||
<source>Max combo: </source>
|
||||
<translation>Combo max : </translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="189"/>
|
||||
<source>Combos: </source>
|
||||
<translation>Combos : </translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="228"/>
|
||||
<source>Tetrominos locked down: </source>
|
||||
<translation>Tétrominos bloqués : </translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="243"/>
|
||||
<source>: </source>
|
||||
<translation> : </translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Window</name>
|
||||
<message>
|
||||
<location filename="../window.py" line="154"/>
|
||||
<source>High score</source>
|
||||
<translation>Meilleur score</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../window.py" line="93"/>
|
||||
<source>&New game</source>
|
||||
<translation>&Nouvelle partie</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../window.py" line="97"/>
|
||||
<source>&Settings</source>
|
||||
<translation>&Préférences</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../window.py" line="101"/>
|
||||
<source>&About</source>
|
||||
<translation>&À propos</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../window.py" line="139"/>
|
||||
<source>A game is in progress.
|
||||
Do you want to abord it?</source>
|
||||
<translation>Une partie est en cours.
|
||||
Voulez-vous l'abandonner ?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../window.py" line="139"/>
|
||||
<source>Quit game?</source>
|
||||
<translation>Quitter la partie ?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../window.py" line="112"/>
|
||||
<source>Tetris® clone by Adrien Malingrey
|
||||
|
||||
Tetris Game Design by Alekseï Pajitnov
|
||||
Graphism inspired by Tetris Effect
|
||||
Window style sheet: qdarkstyle by Colin Duquesnoy
|
||||
Fonts by Markus Koellmann, Peter Wiegel
|
||||
Images from:
|
||||
OpenGameArt.org by beren77, Duion
|
||||
Pexels.com by Min An, Jaymantri, Felix Mittermeier
|
||||
Pixabay.com by LoganArt
|
||||
Pixnio.com by Adrian Pelletier
|
||||
Unsplash.com by Aron, Patrick Fore, Ilnur Kalimullin, Gabriel Garcia Marengo, Adnanta Raharja
|
||||
StockSnap.io by Nathan Anderson, José Ignacio Pompé
|
||||
Musics from ocremix.org by:
|
||||
CheDDer Nardz, djpretzel, MkVaff, Sir_NutS, R3FORGED, Sir_NutS
|
||||
Sound effects made with voc-one by Simple-Media</source>
|
||||
<translation>Clone de Tetris® par Adrien Malingrey
|
||||
|
||||
Conception du jeu : Alekseï Pajitnov
|
||||
Graphismes inspirés de Tetris Effect
|
||||
Style de fenêtre : qdarkstyle par Colin Duquesnoy
|
||||
Polices par Markus Koellmann, Peter Wiegel
|
||||
Images issues de :
|
||||
OpenGameArt.org par beren77, Duion
|
||||
Pexels.com par Min An, Jaymantri, Felix Mittermeier
|
||||
Pixabay.com par LoganArt
|
||||
Pixnio.com par Adrian Pelletier
|
||||
Unsplash.com par Aron, Patrick Fore, Ilnur Kalimullin, Gabriel Garcia Marengo, Adnanta Raharja
|
||||
StockSnap.io par Nathan Anderson, José Ignacio Pompé
|
||||
Musiques issues de ocremix.org par :
|
||||
CheDDer Nardz, djpretzel, MkVaff, Sir_NutS, R3FORGED, Sir_NutS
|
||||
Effets sonores réalisés avec voc-one de Simple-Media</translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1" language="fr_FR">
|
||||
<context>
|
||||
<name>Frames</name>
|
||||
<message>
|
||||
<location filename="../frames.py" line="205"/>
|
||||
<source>New game</source>
|
||||
<translation>Nouvelle partie</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../frames.py" line="192"/>
|
||||
<source>A game is in progress.
|
||||
Do you want to abord it?</source>
|
||||
<translation>Une partie est en cours.
|
||||
Voulez-vous l'abandonner ?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../frames.py" line="205"/>
|
||||
<source>Start level:</source>
|
||||
<translation>Commencer au niveau :</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../frames.py" line="323"/>
|
||||
<source>High score</source>
|
||||
<translation>Meilleur score</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../frames.py" line="312"/>
|
||||
<source>Game over</source>
|
||||
<translation>Partie terminée</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../frames.py" line="315"/>
|
||||
<source>Congratulations!
|
||||
You have the high score: {}</source>
|
||||
<translation>Bravo !
|
||||
Vous avez atteint le meilleur score : {}</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../frames.py" line="325"/>
|
||||
<source>Score: {}
|
||||
High score: {}</source>
|
||||
<translation>Score : {}
|
||||
Meilleur score : {}</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Matrix</name>
|
||||
<message>
|
||||
<location filename="../matrix.py" line="73"/>
|
||||
<source>Level
|
||||
</source>
|
||||
<translation>Niveau
|
||||
</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../matrix.py" line="356"/>
|
||||
<source>PAUSE
|
||||
|
||||
Press %s
|
||||
to resume</source>
|
||||
<translation>PAUSE
|
||||
|
||||
Appuyez sur
|
||||
%s
|
||||
pour reprendre</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../matrix.py" line="364"/>
|
||||
<source>GAME
|
||||
OVER</source>
|
||||
<translation>PARTIE
|
||||
TERMINÉE</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SettingStrings</name>
|
||||
<message>
|
||||
<location filename="../settings.py" line="20"/>
|
||||
<source>Keyboard settings</source>
|
||||
<translation>Configuration du clavier</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="21"/>
|
||||
<source>Move left</source>
|
||||
<translation>Déplacer à gauche</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="22"/>
|
||||
<source>Move right</source>
|
||||
<translation>Déplacer à droite</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="23"/>
|
||||
<source>Rotate clockwise</source>
|
||||
<translation>Tourner dans le sens horaire</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="24"/>
|
||||
<source>Rotate counterclockwise</source>
|
||||
<translation>Tourner dans le sens anti-horaire</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="25"/>
|
||||
<source>Soft drop</source>
|
||||
<translation>Chute lente</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="26"/>
|
||||
<source>Hard drop</source>
|
||||
<translation>Chute rapide</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="27"/>
|
||||
<source>Hold</source>
|
||||
<translation>Réserve</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="28"/>
|
||||
<source>Pause</source>
|
||||
<translation>Pause</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="29"/>
|
||||
<source>Other settings</source>
|
||||
<translation>Autres paramètres</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="31"/>
|
||||
<source>Delays</source>
|
||||
<translation>Temporisation</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="32"/>
|
||||
<source>Auto-shift delay</source>
|
||||
<translation>Délai avant répétition</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="33"/>
|
||||
<source>Auto-repeat rate</source>
|
||||
<translation>Vitesse de répétition</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="35"/>
|
||||
<source>Sound</source>
|
||||
<translation>Son</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="36"/>
|
||||
<source>Music volume</source>
|
||||
<translation>Volume de la musique</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="37"/>
|
||||
<source>Effects volume</source>
|
||||
<translation>Volume des effets sonores</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="39"/>
|
||||
<source>Show ghost piece</source>
|
||||
<translation>Afficher la pièce fantôme</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="40"/>
|
||||
<source>Show next queue</source>
|
||||
<translation>Afficher les 6 prochaines pièces</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings.py" line="41"/>
|
||||
<source>Hold enabled</source>
|
||||
<translation>Activer la réserve</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SettingsDialog</name>
|
||||
<message>
|
||||
<location filename="../settings.py" line="95"/>
|
||||
<source>Settings</source>
|
||||
<translation>Préférences</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Stats</name>
|
||||
<message>
|
||||
<location filename="../stats.py" line="43"/>
|
||||
<source>High score</source>
|
||||
<translation>Meilleur score</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="126"/>
|
||||
<source>COMBO x{:n}
|
||||
{:n}</source>
|
||||
<translation>COMBO x{:n}
|
||||
{:n}</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="158"/>
|
||||
<source>BACK TO BACK
|
||||
{:n}</source>
|
||||
<translation>BACK TO BACK
|
||||
{:n}</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="189"/>
|
||||
<source>Time: {}
|
||||
</source>
|
||||
<translation>Temps : {}
|
||||
</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="228"/>
|
||||
<source>Lines per minute: {:.1f}</source>
|
||||
<translation>Lignes par minute : {:.1f}</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="228"/>
|
||||
<source>Tetrominos per minute: {:.1f}</source>
|
||||
<translation>Tétrominos par minute : {:.1f}</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="189"/>
|
||||
<source>Score: </source>
|
||||
<translation>Score : </translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="189"/>
|
||||
<source>High score: </source>
|
||||
<translation>Meilleur score : </translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="189"/>
|
||||
<source>Level: </source>
|
||||
<translation>Niveau : </translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="189"/>
|
||||
<source>Goal: </source>
|
||||
<translation>Objectif : </translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="189"/>
|
||||
<source>Lines: </source>
|
||||
<translation>Lignes : </translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="189"/>
|
||||
<source>Mini T-Spins: </source>
|
||||
<translation>Mini T-Spins : </translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="189"/>
|
||||
<source>T-Spins: </source>
|
||||
<translation>T-Spins : </translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="189"/>
|
||||
<source>Back-to-back: </source>
|
||||
<translation>Back-to-back : </translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="189"/>
|
||||
<source>Max combo: </source>
|
||||
<translation>Combo max : </translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="189"/>
|
||||
<source>Combos: </source>
|
||||
<translation>Combos : </translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="228"/>
|
||||
<source>Tetrominos locked down: </source>
|
||||
<translation>Tétrominos bloqués : </translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../stats.py" line="243"/>
|
||||
<source>: </source>
|
||||
<translation> : </translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Window</name>
|
||||
<message>
|
||||
<location filename="../window.py" line="154"/>
|
||||
<source>High score</source>
|
||||
<translation>Meilleur score</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../window.py" line="93"/>
|
||||
<source>&New game</source>
|
||||
<translation>&Nouvelle partie</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../window.py" line="97"/>
|
||||
<source>&Settings</source>
|
||||
<translation>&Préférences</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../window.py" line="101"/>
|
||||
<source>&About</source>
|
||||
<translation>&À propos</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../window.py" line="139"/>
|
||||
<source>A game is in progress.
|
||||
Do you want to abord it?</source>
|
||||
<translation>Une partie est en cours.
|
||||
Voulez-vous l'abandonner ?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../window.py" line="139"/>
|
||||
<source>Quit game?</source>
|
||||
<translation>Quitter la partie ?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../window.py" line="112"/>
|
||||
<source>Tetris® clone by Adrien Malingrey
|
||||
|
||||
Tetris Game Design by Alekseï Pajitnov
|
||||
Graphism inspired by Tetris Effect
|
||||
Window style sheet: qdarkstyle by Colin Duquesnoy
|
||||
Fonts by Markus Koellmann, Peter Wiegel
|
||||
Images from:
|
||||
OpenGameArt.org by beren77, Duion
|
||||
Pexels.com by Min An, Jaymantri, Felix Mittermeier
|
||||
Pixabay.com by LoganArt
|
||||
Pixnio.com by Adrian Pelletier
|
||||
Unsplash.com by Aron, Patrick Fore, Ilnur Kalimullin, Gabriel Garcia Marengo, Adnanta Raharja
|
||||
StockSnap.io by Nathan Anderson, José Ignacio Pompé
|
||||
Musics from ocremix.org by:
|
||||
CheDDer Nardz, djpretzel, MkVaff, Sir_NutS, R3FORGED, Sir_NutS
|
||||
Sound effects made with voc-one by Simple-Media</source>
|
||||
<translation>Clone de Tetris® par Adrien Malingrey
|
||||
|
||||
Conception du jeu : Alekseï Pajitnov
|
||||
Graphismes inspirés de Tetris Effect
|
||||
Style de fenêtre : qdarkstyle par Colin Duquesnoy
|
||||
Polices par Markus Koellmann, Peter Wiegel
|
||||
Images issues de :
|
||||
OpenGameArt.org par beren77, Duion
|
||||
Pexels.com par Min An, Jaymantri, Felix Mittermeier
|
||||
Pixabay.com par LoganArt
|
||||
Pixnio.com par Adrian Pelletier
|
||||
Unsplash.com par Aron, Patrick Fore, Ilnur Kalimullin, Gabriel Garcia Marengo, Adnanta Raharja
|
||||
StockSnap.io par Nathan Anderson, José Ignacio Pompé
|
||||
Musiques issues de ocremix.org par :
|
||||
CheDDer Nardz, djpretzel, MkVaff, Sir_NutS, R3FORGED, Sir_NutS
|
||||
Effets sonores réalisés avec voc-one de Simple-Media</translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
|
393
matrix.py
393
matrix.py
@ -1,393 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
import time
|
||||
|
||||
import consts
|
||||
from consts import L, R, CLOCKWISE, COUNTERCLOCKWISE
|
||||
from qt5 import QtGui, QtCore, QtMultimedia
|
||||
from grids import Grid
|
||||
from point import Point
|
||||
from block import Block
|
||||
from tetromino import GhostPiece
|
||||
from settings import s, settings
|
||||
|
||||
|
||||
class Matrix(Grid):
|
||||
"""
|
||||
The rectangular arrangement of cells creating the active game area.
|
||||
Tetriminos fall from the top-middle just above the Skyline (off-screen) to the bottom.
|
||||
"""
|
||||
|
||||
ROWS = consts.MATRIX_ROWS + consts.GRID_INVISIBLE_ROWS
|
||||
COLUMNS = consts.MATRIX_COLUMNS
|
||||
STARTING_POSITION = Point(COLUMNS // 2, consts.GRID_INVISIBLE_ROWS - 1)
|
||||
TEXT_COLOR = consts.MATRIX_TEXT_COLOR
|
||||
|
||||
drop_signal = QtCore.Signal(int)
|
||||
lock_signal = QtCore.Signal(int, str)
|
||||
|
||||
def __init__(self, frames):
|
||||
super().__init__(frames)
|
||||
|
||||
self.load_sfx()
|
||||
|
||||
self.game_over = False
|
||||
self.text = ""
|
||||
self.temporary_texts = []
|
||||
|
||||
self.setFocusPolicy(QtCore.Qt.StrongFocus)
|
||||
|
||||
self.auto_repeat_delay = 0
|
||||
self.auto_repeat_timer = QtCore.QTimer()
|
||||
self.auto_repeat_timer.timeout.connect(self.auto_repeat)
|
||||
self.fall_timer = QtCore.QTimer()
|
||||
self.fall_timer.timeout.connect(self.fall)
|
||||
|
||||
self.cells = []
|
||||
|
||||
def load_sfx(self):
|
||||
self.wall_sfx = QtMultimedia.QSoundEffect(self)
|
||||
url = QtCore.QUrl.fromLocalFile(consts.WALL_SFX_PATH)
|
||||
self.wall_sfx.setSource(url)
|
||||
|
||||
self.rotate_sfx = QtMultimedia.QSoundEffect(self)
|
||||
url = QtCore.QUrl.fromLocalFile(consts.ROTATE_SFX_PATH)
|
||||
self.rotate_sfx.setSource(url)
|
||||
|
||||
self.hard_drop_sfx = QtMultimedia.QSoundEffect(self)
|
||||
url = QtCore.QUrl.fromLocalFile(consts.HARD_DROP_SFX_PATH)
|
||||
self.hard_drop_sfx.setSource(url)
|
||||
|
||||
def new_game(self):
|
||||
self.game_over = False
|
||||
self.lock_delay = consts.LOCK_DELAY
|
||||
self.cells = [self.empty_row() for y in range(self.ROWS)]
|
||||
self.setFocus()
|
||||
self.actions_to_repeat = []
|
||||
self.wall_hit = False
|
||||
|
||||
def new_level(self, level):
|
||||
self.show_temporary_text(self.tr("Level\n") + str(level))
|
||||
self.speed = consts.INITIAL_SPEED * (0.8 - ((level - 1) * 0.007)) ** (level - 1)
|
||||
self.fall_timer.start(self.speed)
|
||||
if level > 15:
|
||||
self.lock_delay *= 0.9
|
||||
|
||||
def empty_row(self):
|
||||
return [None for x in range(self.COLUMNS)]
|
||||
|
||||
def is_empty_cell(self, coord):
|
||||
x, y = coord.x(), coord.y()
|
||||
return (
|
||||
0 <= x < self.COLUMNS
|
||||
and y < self.ROWS
|
||||
and not (0 <= y and self.cells[y][x])
|
||||
)
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
if event.isAutoRepeat():
|
||||
return
|
||||
|
||||
if not self.frames.playing:
|
||||
return
|
||||
|
||||
try:
|
||||
action = self.keys[event.key()]
|
||||
except KeyError:
|
||||
return
|
||||
|
||||
self.do(action)
|
||||
if action in (s.MOVE_LEFT, s.MOVE_RIGHT, s.SOFT_DROP):
|
||||
if action not in self.actions_to_repeat:
|
||||
self.actions_to_repeat.append(action)
|
||||
self.auto_repeat_wait()
|
||||
|
||||
def keyReleaseEvent(self, event):
|
||||
if event.isAutoRepeat():
|
||||
return
|
||||
|
||||
if not self.frames.playing:
|
||||
return
|
||||
|
||||
try:
|
||||
self.actions_to_repeat.remove(self.keys[event.key()])
|
||||
except (KeyError, ValueError):
|
||||
pass
|
||||
else:
|
||||
self.auto_repeat_wait()
|
||||
|
||||
if not self.actions_to_repeat:
|
||||
for mino in self.piece.minoes:
|
||||
mino.fade()
|
||||
self.update()
|
||||
|
||||
def auto_repeat_wait(self):
|
||||
self.auto_repeat_delay = (
|
||||
time.time() + settings[s.DELAYS][s.AUTO_SHIFT_DELAY] / 1000
|
||||
)
|
||||
|
||||
def auto_repeat(self):
|
||||
"""
|
||||
Tapping the move button allows a single cell movement of the Tetrimino
|
||||
in the direction pressed.
|
||||
Holding down the move button triggers an Auto-Repeat movement
|
||||
that allows the player to move a Tetrimino from one side of the Matrix
|
||||
to the other in about 0.5 seconds.
|
||||
This is essential on higher levels when the Fall Speed of a Tetrimino is very fast.
|
||||
There is a slight delay between the time the move button is pressed
|
||||
and the time when Auto-Repeat kicks in : s.AUTO_SHIFT_DELAY.
|
||||
This delay prevents unwanted extra movement of a Tetrimino.
|
||||
Auto-Repeat only affects Left/Right movement.
|
||||
Auto-Repeat continues to the Next Tetrimino (after Lock Down)
|
||||
as long as the move button remains pressed.
|
||||
In addition, when Auto-Repeat begins,
|
||||
and the player then holds the opposite direction button,
|
||||
the Tetrimino then begins moving the opposite direction with the initial delay.
|
||||
When any single button is then released,
|
||||
the Tetrimino should again move in the direction still held,
|
||||
with the Auto-Shift delay applied once more.
|
||||
"""
|
||||
if (
|
||||
not self.frames.playing
|
||||
or self.frames.paused
|
||||
or time.time() < self.auto_repeat_delay
|
||||
):
|
||||
return
|
||||
|
||||
if self.actions_to_repeat:
|
||||
self.do(self.actions_to_repeat[-1])
|
||||
|
||||
def do(self, action):
|
||||
"""The player can move, rotate, Soft Drop, Hard Drop,
|
||||
and Hold the falling Tetrimino (i.e., the Tetrimino in play).
|
||||
"""
|
||||
if action == s.PAUSE:
|
||||
self.frames.pause(not self.frames.paused)
|
||||
|
||||
if not self.frames.playing or self.frames.paused or not self.piece:
|
||||
return
|
||||
|
||||
for mino in self.piece.minoes:
|
||||
mino.shine(0)
|
||||
|
||||
if action == s.MOVE_LEFT:
|
||||
if self.piece.move(L, 0):
|
||||
self.lock_wait()
|
||||
self.wall_hit = False
|
||||
elif not self.wall_hit:
|
||||
self.wall_hit = True
|
||||
self.wall_sfx.play()
|
||||
|
||||
elif action == s.MOVE_RIGHT:
|
||||
if self.piece.move(R, 0):
|
||||
self.rotate_sfx.play()
|
||||
self.lock_wait()
|
||||
self.wall_hit = False
|
||||
elif not self.wall_hit:
|
||||
self.wall_hit = True
|
||||
self.wall_sfx.play()
|
||||
|
||||
elif action == s.ROTATE_CLOCKWISE:
|
||||
if self.piece.rotate(direction=CLOCKWISE):
|
||||
self.rotate_sfx.play()
|
||||
self.lock_wait()
|
||||
self.wall_hit = False
|
||||
elif not self.wall_hit:
|
||||
self.wall_hit = True
|
||||
self.wall_sfx.play()
|
||||
|
||||
elif action == s.ROTATE_COUNTERCLOCKWISE:
|
||||
if self.piece.rotate(direction=COUNTERCLOCKWISE):
|
||||
self.lock_wait()
|
||||
self.wall_hit = False
|
||||
elif not self.wall_hit:
|
||||
self.wall_hit = True
|
||||
self.wall_sfx.play()
|
||||
|
||||
elif action == s.SOFT_DROP:
|
||||
if self.piece.soft_drop():
|
||||
self.drop_signal.emit(1)
|
||||
self.wall_hit = False
|
||||
elif not self.wall_hit:
|
||||
self.wall_hit = True
|
||||
self.wall_sfx.play()
|
||||
|
||||
elif action == s.HARD_DROP:
|
||||
trail = self.piece.hard_drop()
|
||||
self.top_left_corner += Point(0, self.HARD_DROP_MOVEMENT * Block.side)
|
||||
self.drop_signal.emit(2 * trail)
|
||||
QtCore.QTimer.singleShot(consts.ANIMATION_DELAY, self.after_hard_drop)
|
||||
self.hard_drop_sfx.play()
|
||||
self.lock_phase()
|
||||
|
||||
elif action == s.HOLD:
|
||||
self.frames.hold()
|
||||
|
||||
def after_hard_drop(self):
|
||||
""" Reset the animation movement of the Matrix on a hard drop """
|
||||
self.top_left_corner -= Point(0, self.HARD_DROP_MOVEMENT * Block.side)
|
||||
|
||||
def lock_wait(self):
|
||||
self.fall_delay = time.time() + (self.speed + self.lock_delay) / 1000
|
||||
|
||||
def fall(self):
|
||||
"""
|
||||
Once a Tetrimino is generated,
|
||||
it immediately drops one row (if no existing Block is in its path).
|
||||
From here, it begins its descent to the bottom of the Matrix.
|
||||
The Tetrimino will fall at its normal Fall Speed
|
||||
whether or not it is being manipulated by the player.
|
||||
"""
|
||||
if self.piece:
|
||||
if self.piece.move(0, 1):
|
||||
self.lock_wait()
|
||||
else:
|
||||
if time.time() >= self.fall_delay:
|
||||
self.lock_phase()
|
||||
|
||||
def lock_phase(self):
|
||||
"""
|
||||
The player can perform the same actions on a Tetrimino in this phase
|
||||
as he/she can in the Falling Phase,
|
||||
as long as the Tetrimino is not yet Locked Down.
|
||||
A Tetrimino that is Hard Dropped Locks Down immediately.
|
||||
However, if a Tetrimino naturally falls or Soft Drops onto a landing Surface,
|
||||
it is given a delay (self.fall_delay) on a Lock Down Timer
|
||||
before it actually Locks Down.
|
||||
"""
|
||||
|
||||
self.wall_sfx.play()
|
||||
|
||||
# Enter minoes into the matrix
|
||||
for mino in self.piece.minoes:
|
||||
if mino.coord.y() >= 0:
|
||||
self.cells[mino.coord.y()][mino.coord.x()] = mino
|
||||
mino.shine(glowing=2, delay=consts.ANIMATION_DELAY)
|
||||
self.update()
|
||||
|
||||
if all(mino.coord.y() < consts.GRID_INVISIBLE_ROWS for mino in self.piece.minoes):
|
||||
self.frames.game_over()
|
||||
return
|
||||
|
||||
"""
|
||||
In this phase,
|
||||
the engine looks for patterns made from Locked Down Blocks in the Matrix.
|
||||
Once a pattern has been matched,
|
||||
it can trigger any number of Tetris variant-related effects.
|
||||
The classic pattern is the Line Clear pattern.
|
||||
This pattern is matched when one or more rows of 10 horizontally aligned
|
||||
Matrix cells are occupied by Blocks.
|
||||
The matching Blocks are then marked for removal on a hit list.
|
||||
Blocks on the hit list are cleared from the Matrix at a later time
|
||||
in the Eliminate Phase.
|
||||
"""
|
||||
# Dectect complete lines
|
||||
self.complete_lines = []
|
||||
for y, row in enumerate(self.cells):
|
||||
if all(cell for cell in row):
|
||||
self.complete_lines.append(y)
|
||||
for block in row:
|
||||
block.shine()
|
||||
self.spotlight = row[self.COLUMNS // 2].coord
|
||||
self.auto_repeat_timer.stop()
|
||||
self.lock_signal.emit(len(self.complete_lines), self.piece.t_spin)
|
||||
|
||||
if self.complete_lines:
|
||||
self.fall_timer.stop()
|
||||
QtCore.QTimer.singleShot(consts.LINE_CLEAR_DELAY, self.eliminate_phase)
|
||||
else:
|
||||
self.frames.new_piece()
|
||||
|
||||
|
||||
def eliminate_phase(self):
|
||||
"""
|
||||
Any Minos marked for removal, i.e., on the hit list,
|
||||
are cleared from the Matrix in this phase.
|
||||
If this results in one or more complete 10-cell rows in the Matrix
|
||||
becoming unoccupied by Minos,
|
||||
then all Minos above that row(s) collapse,
|
||||
or fall by the number of complete rows cleared from the Matrix.
|
||||
"""
|
||||
for y in self.complete_lines:
|
||||
del self.cells[y]
|
||||
self.cells.insert(0, self.empty_row())
|
||||
|
||||
for y, row in enumerate(self.cells):
|
||||
for x, block in enumerate(row):
|
||||
if block:
|
||||
block.coord.setX(x)
|
||||
block.coord.setY(y)
|
||||
|
||||
self.update()
|
||||
self.auto_repeat_wait()
|
||||
self.auto_repeat_timer.start(settings[s.DELAYS][s.AUTO_REPEAT_RATE])
|
||||
|
||||
self.frames.new_piece()
|
||||
|
||||
def paintEvent(self, event):
|
||||
"""
|
||||
Draws grid, actual piece, blocks in the Matrix and show texts
|
||||
"""
|
||||
painter = QtGui.QPainter(self)
|
||||
painter.setRenderHint(QtGui.QPainter.Antialiasing)
|
||||
|
||||
self.paint_grid(painter)
|
||||
|
||||
if not self.frames.paused or self.game_over:
|
||||
if self.piece:
|
||||
if settings[s.OTHER][s.GHOST]:
|
||||
self.ghost = GhostPiece(self.piece)
|
||||
self.spotlight = self.ghost.minoes[0].coord
|
||||
self.paint_piece(painter, self.ghost)
|
||||
self.paint_piece(painter, self.piece)
|
||||
|
||||
# Blocks in matrix
|
||||
for row in self.cells:
|
||||
for block in row:
|
||||
if block:
|
||||
block.paint(painter, self.top_left_corner, self.spotlight)
|
||||
|
||||
if self.frames.playing and self.frames.paused:
|
||||
painter.setFont(QtGui.QFont("Maassslicer", 0.75 * Block.side))
|
||||
painter.setPen(self.TEXT_COLOR)
|
||||
painter.drawText(
|
||||
self.rect(),
|
||||
QtCore.Qt.AlignCenter | QtCore.Qt.TextWordWrap,
|
||||
self.tr("PAUSE\n\nPress %s\nto resume") % settings[s.KEYBOARD][s.PAUSE],
|
||||
)
|
||||
if self.game_over:
|
||||
painter.setFont(QtGui.QFont("Maassslicer", Block.side))
|
||||
painter.setPen(self.TEXT_COLOR)
|
||||
painter.drawText(
|
||||
self.rect(),
|
||||
QtCore.Qt.AlignCenter | QtCore.Qt.TextWordWrap,
|
||||
self.tr("GAME\nOVER"),
|
||||
)
|
||||
if self.temporary_texts:
|
||||
painter.setFont(self.temporary_text_font)
|
||||
painter.setPen(self.TEXT_COLOR)
|
||||
painter.drawText(
|
||||
self.rect(),
|
||||
QtCore.Qt.AlignHCenter | QtCore.Qt.TextWordWrap,
|
||||
"\n\n\n" + "\n\n".join(self.temporary_texts),
|
||||
)
|
||||
|
||||
def resizeEvent(self, event):
|
||||
super().resizeEvent(event)
|
||||
self.temporary_text_font = QtGui.QFont(consts.MATRIX_FONT_NAME, Block.side)
|
||||
|
||||
def show_temporary_text(self, text):
|
||||
self.temporary_texts.append(text.upper())
|
||||
self.font = self.temporary_text_font
|
||||
self.update()
|
||||
QtCore.QTimer.singleShot(consts.TEMPORARY_TEXT_DURATION, self.delete_text)
|
||||
|
||||
def delete_text(self):
|
||||
del self.temporary_texts[0]
|
||||
self.update()
|
||||
|
||||
def focusOutEvent(self, event):
|
||||
if self.frames.playing:
|
||||
self.frames.pause(True)
|
277
settings.py
277
settings.py
@ -1,277 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
import collections
|
||||
|
||||
import consts
|
||||
from __version__ import __author__, __title__
|
||||
from qt5 import QtWidgets, QtCore
|
||||
|
||||
|
||||
class SettingStrings(QtCore.QObject):
|
||||
"""
|
||||
Setting string for translation
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.KEYBOARD = self.tr("Keyboard settings")
|
||||
self.MOVE_LEFT = self.tr("Move left")
|
||||
self.MOVE_RIGHT = self.tr("Move right")
|
||||
self.ROTATE_CLOCKWISE = self.tr("Rotate clockwise")
|
||||
self.ROTATE_COUNTERCLOCKWISE = self.tr("Rotate counterclockwise")
|
||||
self.SOFT_DROP = self.tr("Soft drop")
|
||||
self.HARD_DROP = self.tr("Hard drop")
|
||||
self.HOLD = self.tr("Hold")
|
||||
self.PAUSE = self.tr("Pause")
|
||||
self.OTHER = self.tr("Other settings")
|
||||
|
||||
self.DELAYS = self.tr("Delays")
|
||||
self.AUTO_SHIFT_DELAY = self.tr("Auto-shift delay")
|
||||
self.AUTO_REPEAT_RATE = self.tr("Auto-repeat rate")
|
||||
|
||||
self.SOUND = self.tr("Sound")
|
||||
self.MUSIC_VOLUME = self.tr("Music volume")
|
||||
self.SFX_VOLUME = self.tr("Effects volume")
|
||||
|
||||
self.GHOST = self.tr("Show ghost piece")
|
||||
self.SHOW_NEXT_QUEUE = self.tr("Show next queue")
|
||||
self.HOLD_ENABLED = self.tr("Hold enabled")
|
||||
|
||||
|
||||
class KeyButton(QtWidgets.QPushButton):
|
||||
""" Button widget capturing key name on focus """
|
||||
|
||||
names = {
|
||||
value: name.replace("Key_", "")
|
||||
for name, value in QtCore.Qt.__dict__.items()
|
||||
if "Key_" in name
|
||||
}
|
||||
|
||||
def __init__(self, *args):
|
||||
super().__init__(*args)
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
key = event.key()
|
||||
self.setText(self.names[key])
|
||||
|
||||
|
||||
class SettingsGroup(QtWidgets.QGroupBox):
|
||||
""" Group box of a type of settings """
|
||||
|
||||
def __init__(self, group, parent, cls):
|
||||
super().__init__(group, parent)
|
||||
layout = QtWidgets.QFormLayout(self)
|
||||
self.widgets = {}
|
||||
for setting, value in settings[group].items():
|
||||
if cls == KeyButton:
|
||||
widget = KeyButton(value)
|
||||
elif cls == QtWidgets.QCheckBox:
|
||||
widget = QtWidgets.QCheckBox(setting)
|
||||
widget.setChecked(value)
|
||||
elif cls == QtWidgets.QSpinBox:
|
||||
widget = QtWidgets.QSpinBox()
|
||||
widget.setRange(0, 1000)
|
||||
widget.setValue(value)
|
||||
widget.setSuffix(" ms")
|
||||
elif cls == QtWidgets.QSlider:
|
||||
widget = QtWidgets.QSlider(QtCore.Qt.Horizontal)
|
||||
widget.setValue(value)
|
||||
if cls == QtWidgets.QCheckBox:
|
||||
layout.addRow(widget)
|
||||
else:
|
||||
layout.addRow(setting, widget)
|
||||
self.widgets[setting] = widget
|
||||
self.setLayout(layout)
|
||||
|
||||
|
||||
class SettingsDialog(QtWidgets.QDialog):
|
||||
""" Show settings dialog """
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle(self.tr("Settings"))
|
||||
self.setModal(True)
|
||||
|
||||
layout = QtWidgets.QGridLayout()
|
||||
|
||||
self.groups = {}
|
||||
self.groups[s.KEYBOARD] = SettingsGroup(s.KEYBOARD, self, KeyButton)
|
||||
self.groups[s.DELAYS] = SettingsGroup(s.DELAYS, self, QtWidgets.QSpinBox)
|
||||
self.groups[s.SOUND] = SettingsGroup(s.SOUND, self, QtWidgets.QSlider)
|
||||
self.groups[s.OTHER] = SettingsGroup(s.OTHER, self, QtWidgets.QCheckBox)
|
||||
|
||||
layout.addWidget(self.groups[s.KEYBOARD], 0, 0, 3, 1)
|
||||
layout.addWidget(self.groups[s.DELAYS], 0, 1)
|
||||
layout.addWidget(self.groups[s.SOUND], 1, 1)
|
||||
layout.addWidget(self.groups[s.OTHER], 2, 1)
|
||||
|
||||
buttons = QtWidgets.QDialogButtonBox(
|
||||
QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel
|
||||
)
|
||||
buttons.accepted.connect(self.ok)
|
||||
buttons.rejected.connect(self.close)
|
||||
layout.addWidget(buttons, 3, 0, 1, 2)
|
||||
|
||||
self.setLayout(layout)
|
||||
|
||||
self.groups[s.SOUND].widgets[s.MUSIC_VOLUME].valueChanged.connect(
|
||||
parent.frames.music.setVolume
|
||||
)
|
||||
self.groups[s.SOUND].widgets[s.MUSIC_VOLUME].sliderPressed.connect(
|
||||
parent.frames.music.play)
|
||||
self.groups[s.SOUND].widgets[s.MUSIC_VOLUME].sliderReleased.connect(
|
||||
parent.frames.music.pause)
|
||||
|
||||
self.groups[s.SOUND].widgets[s.SFX_VOLUME].sliderReleased.connect(
|
||||
parent.frames.stats.line_clear_sfx.play)
|
||||
|
||||
self.show()
|
||||
|
||||
def ok(self):
|
||||
""" Save settings """
|
||||
|
||||
for group, elements in self.groups.items():
|
||||
for setting, widget in elements.widgets.items():
|
||||
if isinstance(widget, KeyButton):
|
||||
value = widget.text()
|
||||
elif isinstance(widget, QtWidgets.QCheckBox):
|
||||
value = widget.isChecked()
|
||||
elif isinstance(widget, QtWidgets.QSpinBox):
|
||||
value = widget.value()
|
||||
elif isinstance(widget, QtWidgets.QSlider):
|
||||
value = widget.value()
|
||||
settings[group][setting] = value
|
||||
qsettings.setValue(group + "/" + setting, value)
|
||||
self.close()
|
||||
|
||||
|
||||
s = SettingStrings()
|
||||
|
||||
qsettings = QtCore.QSettings(__author__, __title__)
|
||||
|
||||
settings = collections.OrderedDict(
|
||||
[
|
||||
(
|
||||
s.KEYBOARD,
|
||||
collections.OrderedDict(
|
||||
[
|
||||
(
|
||||
s.MOVE_LEFT,
|
||||
qsettings.value(s.KEYBOARD + "/" + s.MOVE_LEFT, consts.DEFAULT_MOVE_LEFT_KEY),
|
||||
),
|
||||
(
|
||||
s.MOVE_RIGHT,
|
||||
qsettings.value(
|
||||
s.KEYBOARD + "/" + s.MOVE_RIGHT, consts.DEFAULT_MOVE_RIGHT_KEY
|
||||
),
|
||||
),
|
||||
(
|
||||
s.ROTATE_CLOCKWISE,
|
||||
qsettings.value(
|
||||
s.KEYBOARD + "/" + s.ROTATE_CLOCKWISE, consts.DEFAULT_ROTATE_CLOCKWISE_KEY
|
||||
),
|
||||
),
|
||||
(
|
||||
s.ROTATE_COUNTERCLOCKWISE,
|
||||
qsettings.value(
|
||||
s.KEYBOARD + "/" + s.ROTATE_COUNTERCLOCKWISE,
|
||||
consts.DEFAULT_ROTATE_COUNTERCLOCKWISE_KEY,
|
||||
),
|
||||
),
|
||||
(
|
||||
s.SOFT_DROP,
|
||||
qsettings.value(s.KEYBOARD + "/" + s.SOFT_DROP, consts.DEFAULT_SOFT_DROP_KEY),
|
||||
),
|
||||
(
|
||||
s.HARD_DROP,
|
||||
qsettings.value(
|
||||
s.KEYBOARD + "/" + s.HARD_DROP, consts.DEFAULT_HARD_DROP_KEY
|
||||
),
|
||||
),
|
||||
(
|
||||
s.HOLD,
|
||||
qsettings.value(s.KEYBOARD + "/" + s.HOLD, consts.DEFAULT_HOLD_KEY),
|
||||
),
|
||||
(
|
||||
s.PAUSE,
|
||||
qsettings.value(s.KEYBOARD + "/" + s.PAUSE, consts.DEFAULT_PAUSE_KEY),
|
||||
),
|
||||
]
|
||||
),
|
||||
),
|
||||
(
|
||||
s.DELAYS,
|
||||
collections.OrderedDict(
|
||||
[
|
||||
(
|
||||
s.AUTO_SHIFT_DELAY,
|
||||
int(
|
||||
qsettings.value(
|
||||
s.DELAYS + "/" + s.AUTO_SHIFT_DELAY, consts.DEFAULT_AUTO_SHIFT_DELAY
|
||||
)
|
||||
),
|
||||
),
|
||||
(
|
||||
s.AUTO_REPEAT_RATE,
|
||||
int(
|
||||
qsettings.value(
|
||||
s.DELAYS + "/" + s.AUTO_REPEAT_RATE, consts.DEFAULT_AUTO_REPEAT_RATE
|
||||
)
|
||||
),
|
||||
),
|
||||
]
|
||||
),
|
||||
),
|
||||
(
|
||||
s.SOUND,
|
||||
collections.OrderedDict(
|
||||
[
|
||||
(
|
||||
s.MUSIC_VOLUME,
|
||||
int(
|
||||
qsettings.value(s.SOUND + "/" + s.MUSIC_VOLUME, consts.DEFAUT_MUSIC_VOLUME)
|
||||
),
|
||||
),
|
||||
(
|
||||
s.SFX_VOLUME,
|
||||
int(
|
||||
qsettings.value(
|
||||
s.SOUND + "/" + s.SFX_VOLUME, consts.DEFAULT_SFX_VOLUME
|
||||
)
|
||||
),
|
||||
),
|
||||
]
|
||||
),
|
||||
),
|
||||
(
|
||||
s.OTHER,
|
||||
collections.OrderedDict(
|
||||
[
|
||||
(
|
||||
s.GHOST,
|
||||
bool(qsettings.value(s.OTHER + "/" + s.GHOST, consts.DEFAULT_SHOW_GHOST)),
|
||||
),
|
||||
(
|
||||
s.SHOW_NEXT_QUEUE,
|
||||
bool(
|
||||
qsettings.value(
|
||||
s.OTHER + "/" + s.SHOW_NEXT_QUEUE, consts.DEFAULT_SHOW_NEXT_QUEUE
|
||||
)
|
||||
),
|
||||
),
|
||||
(
|
||||
s.HOLD_ENABLED,
|
||||
bool(
|
||||
qsettings.value(
|
||||
s.OTHER + "/" + s.HOLD_ENABLED, consts.DEFAULT_HOLD_ENABLED
|
||||
)
|
||||
),
|
||||
),
|
||||
]
|
||||
),
|
||||
),
|
||||
]
|
||||
)
|
252
stats.py
252
stats.py
@ -1,252 +0,0 @@
|
||||
#!/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)
|
156
window.py
156
window.py
@ -1,156 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
import locale
|
||||
import ctypes
|
||||
|
||||
import consts
|
||||
from qt5 import QtWidgets, QtCore, QtGui
|
||||
from __version__ import __title__, __author__, __version__
|
||||
from settings import SettingsDialog, qsettings
|
||||
from frames import Frames
|
||||
|
||||
|
||||
class Window(QtWidgets.QMainWindow):
|
||||
""" Main window """
|
||||
|
||||
def __init__(self):
|
||||
splash_screen = QtWidgets.QSplashScreen(
|
||||
QtGui.QPixmap(consts.SPLASH_SCREEN_PATH)
|
||||
)
|
||||
splash_screen.show()
|
||||
|
||||
self.set_locale()
|
||||
|
||||
super().__init__()
|
||||
self.setWindowTitle(__title__.upper())
|
||||
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
|
||||
|
||||
self.setWindowIcon(QtGui.QIcon(consts.ICON_PATH))
|
||||
# Windows' taskbar icon
|
||||
try:
|
||||
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(
|
||||
".".join((__author__, __title__, __version__))
|
||||
)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
# Stylesheet
|
||||
try:
|
||||
import qdarkstyle
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
self.setStyleSheet(qdarkstyle.load_stylesheet_from_environment())
|
||||
|
||||
for font_path in consts.STATS_FONT_PATH, consts.MATRIX_FONT_PATH:
|
||||
QtGui.QFontDatabase.addApplicationFont(font_path)
|
||||
|
||||
self.frames = Frames(self)
|
||||
self.setCentralWidget(self.frames)
|
||||
self.hold_queue = self.frames.hold_queue
|
||||
self.matrix = self.frames.matrix
|
||||
self.stats = self.frames.stats
|
||||
|
||||
self.menu = self.menuBar()
|
||||
|
||||
geometry = qsettings.value("WindowGeometry")
|
||||
if geometry:
|
||||
self.restoreGeometry(geometry)
|
||||
else:
|
||||
self.resize(*consts.DEFAULT_WINDOW_SIZE)
|
||||
self.setWindowState(
|
||||
QtCore.Qt.WindowStates(
|
||||
int(qsettings.value("WindowState", QtCore.Qt.WindowActive))
|
||||
)
|
||||
)
|
||||
|
||||
splash_screen.finish(self);
|
||||
|
||||
def set_locale(self):
|
||||
app = QtWidgets.QApplication.instance()
|
||||
|
||||
# Set appropriate thounsand separator characters
|
||||
locale.setlocale(locale.LC_ALL, "")
|
||||
# Qt
|
||||
language = QtCore.QLocale.system().name()[:2]
|
||||
|
||||
qt_translator = QtCore.QTranslator(app)
|
||||
qt_translation_path = QtCore.QLibraryInfo.location(
|
||||
QtCore.QLibraryInfo.TranslationsPath
|
||||
)
|
||||
if qt_translator.load("qt_" + language, qt_translation_path):
|
||||
app.installTranslator(qt_translator)
|
||||
|
||||
tetris2000_translator = QtCore.QTranslator(app)
|
||||
if tetris2000_translator.load(language, consts.LOCALE_PATH):
|
||||
app.installTranslator(tetris2000_translator)
|
||||
|
||||
def menuBar(self):
|
||||
menu = super().menuBar()
|
||||
|
||||
new_game_action = QtWidgets.QAction(self.tr("&New game"), self)
|
||||
new_game_action.triggered.connect(self.frames.new_game)
|
||||
menu.addAction(new_game_action)
|
||||
|
||||
settings_action = QtWidgets.QAction(self.tr("&Settings"), self)
|
||||
settings_action.triggered.connect(self.show_settings_dialog)
|
||||
menu.addAction(settings_action)
|
||||
|
||||
about_action = QtWidgets.QAction(self.tr("&About"), self)
|
||||
about_action.triggered.connect(self.about)
|
||||
menu.addAction(about_action)
|
||||
return menu
|
||||
|
||||
def show_settings_dialog(self):
|
||||
SettingsDialog(self).exec_()
|
||||
|
||||
self.frames.apply_settings()
|
||||
|
||||
def about(self):
|
||||
QtWidgets.QMessageBox.about(
|
||||
self,
|
||||
__title__,
|
||||
self.tr(
|
||||
"""Tetris® clone by Adrien Malingrey
|
||||
|
||||
Tetris Game Design by Alekseï Pajitnov
|
||||
Graphism inspired by Tetris Effect
|
||||
Window style sheet: qdarkstyle by Colin Duquesnoy
|
||||
Fonts by Markus Koellmann, Peter Wiegel
|
||||
Images from:
|
||||
OpenGameArt.org by beren77, Duion
|
||||
Pexels.com by Min An, Jaymantri, Felix Mittermeier
|
||||
Pixabay.com by LoganArt
|
||||
Pixnio.com by Adrian Pelletier
|
||||
Unsplash.com by Aron, Patrick Fore, Ilnur Kalimullin, Gabriel Garcia Marengo, Adnanta Raharja
|
||||
StockSnap.io by Nathan Anderson, José Ignacio Pompé
|
||||
Musics from ocremix.org by:
|
||||
CheDDer Nardz, djpretzel, MkVaff, Sir_NutS, R3FORGED, Sir_NutS
|
||||
Sound effects made with voc-one by Simple-Media"""
|
||||
),
|
||||
)
|
||||
if self.frames.playing:
|
||||
self.frames.pause(False)
|
||||
|
||||
def closeEvent(self, event):
|
||||
if self.frames.playing:
|
||||
answer = QtWidgets.QMessageBox.question(
|
||||
self,
|
||||
self.tr("Quit game?"),
|
||||
self.tr("A game is in progress.\nDo you want to abord it?"),
|
||||
QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel,
|
||||
QtWidgets.QMessageBox.Cancel,
|
||||
)
|
||||
if answer == QtWidgets.QMessageBox.Cancel:
|
||||
event.ignore()
|
||||
self.frames.pause(False)
|
||||
return
|
||||
|
||||
self.frames.music.stop()
|
||||
|
||||
# Save settings
|
||||
qsettings.setValue(self.tr("High score"), self.stats.high_score)
|
||||
qsettings.setValue("WindowGeometry", self.saveGeometry())
|
||||
qsettings.setValue("WindowState", int(self.windowState()))
|
Loading…
x
Reference in New Issue
Block a user