19 Commits

Author SHA1 Message Date
5f137a62ec V0.2 Release
Music
Soft drop autorepeat delay set to fall delay / 20
2019-10-03 01:41:21 +02:00
ec42f17ca7 soft drop autorepeat delay set to fall delay / 20 2019-10-03 01:32:52 +02:00
0d6386d22b music!
change API (pieces are attributes of TetrominoContainers)
2019-10-03 01:26:31 +02:00
abaeb3be9a v0.1 release 2019-10-02 23:14:41 +02:00
dff4ae487a setup.py 2019-10-02 17:19:11 +02:00
5c830fd828 update setup.py info 2019-10-02 16:44:25 +02:00
a46c07af8b add start menu shortcut for windows installer 2019-10-02 16:39:37 +02:00
338371f443 fix resize error since texture use 2019-10-02 16:37:39 +02:00
a95463438e Add screenshot 2019-10-02 16:22:16 +02:00
06fe72d8db Merge branch 'master' of https://git.malingrey.fr/adrien/TetrArcade 2019-10-02 15:44:02 +02:00
4a69c12349 no python2 2019-10-02 14:51:56 +02:00
2bd75be892 Arcade require Python 3.6+ 2019-10-02 13:07:30 +02:00
3015a36984 case insensitive keyboard conf 2019-10-02 12:36:03 +02:00
7fc342e061 V0.2-dev 2019-10-02 12:35:35 +02:00
0e68ea4e51 build-requirements.txt 2019-10-02 12:16:41 +02:00
b95478ea8d use texture, import from, black 2019-10-02 11:54:50 +02:00
df8257c4da held mino sprite 2019-10-02 09:00:35 +02:00
0b3dd847d3 exclude Qt from build 2019-10-02 02:29:00 +02:00
2a7900586c Autorepeat in conf 2019-10-02 02:28:49 +02:00
18 changed files with 311 additions and 255 deletions

View File

@ -2,9 +2,11 @@
Tetris clone made with Python and Arcade graphic library Tetris clone made with Python and Arcade graphic library
![Screenshot](https://malingrey.fr/images/fMd4EseZ/VtwJIMyQ.png)
## Requirements ## Requirements
* [Python](https://www.python.org/) * [Python](https://www.python.org/) 3.6 or upper
## Install ## Install
@ -30,6 +32,6 @@ Use key name from [arcade.key package](http://arcade.academy/arcade.key.html).
## Build ## Build
```shell ```shell
python -m pip install cx-freeze python -m pip install -r build-requirements.txt
python setup.py build python setup.py bdist
``` ```

View File

@ -4,21 +4,20 @@ import locale
import time import time
import os import os
try: import configparser
import configparser
except ImportError:
import ConfigParser as configparser
try: try:
import arcade import arcade
except ImportError as e: except ImportError as e:
sys.exit(str(e) + """ sys.exit(
str(e)
+ """
This game require arcade library. This game require arcade library.
You can install it with: You can install it with:
python -m pip install --user arcade""" python -m pip install --user arcade"""
) )
import tetrislogic from tetrislogic import TetrisLogic, Color, State
# Constants # Constants
@ -28,7 +27,6 @@ WINDOW_HEIGHT = 600
WINDOW_MIN_WIDTH = 517 WINDOW_MIN_WIDTH = 517
WINDOW_MIN_HEIGHT = 388 WINDOW_MIN_HEIGHT = 388
WINDOW_TITLE = "TETRARCADE" WINDOW_TITLE = "TETRARCADE"
MINO_SIZE = 20
BG_COLOR = (7, 11, 21) BG_COLOR = (7, 11, 21)
# Delays (seconds) # Delays (seconds)
@ -41,54 +39,78 @@ GHOST_ALPHA = 30
MATRIX_BG_ALPHA = 100 MATRIX_BG_ALPHA = 100
BAR_ALPHA = 75 BAR_ALPHA = 75
# Mino size
MINO_SIZE = 20
MINO_SPRITE_SIZE = 21
if getattr(sys, 'frozen', False):
# The application is frozen
DATA_DIR = os.path.dirname(sys.executable)
else:
# The application is not frozen
# Change this bit to match where you store your data files:
DATA_DIR = os.path.dirname(__file__)
DATA_DIR = os.path.join(DATA_DIR, "res")
# Sprites # Sprites
WINDOW_BG_PATH = "res/bg.jpg" WINDOW_BG_PATH = os.path.join(DATA_DIR, "bg.jpg")
MATRIX_BG_PATH = "res/matrix.png" MATRIX_BG_PATH = os.path.join(DATA_DIR, "matrix.png")
HELD_BG_PATH = "res/held.png" HELD_BG_PATH = os.path.join(DATA_DIR, "held.png")
NEXT_BG_PATH = "res/next.png" NEXT_BG_PATH = os.path.join(DATA_DIR, "next.png")
MINOES_SPRITES_PATHS = { MINOES_SPRITES_PATH = os.path.join(DATA_DIR, "minoes.png")
"orange": "res/orange_mino.png", Color.PRELOCKED = 7
"blue": "res/blue_mino.png", MINOES_COLOR_ID = {
"yellow": "res/yellow_mino.png", Color.BLUE: 0,
"cyan": "res/cyan_mino.png", Color.CYAN: 1,
"green": "res/green_mino.png", Color.GREEN: 2,
"red": "res/red_mino.png", Color.MAGENTA: 3,
"magenta": "res/magenta_mino.png", Color.ORANGE: 4,
Color.RED: 5,
Color.YELLOW: 6,
Color.PRELOCKED: 7,
} }
TEXTURES = arcade.load_textures(
MINOES_SPRITES_PATH, ((i * MINO_SPRITE_SIZE, 0, MINO_SPRITE_SIZE, MINO_SPRITE_SIZE) for i in range(8))
)
TEXTURES = {color: TEXTURES[i] for color, i in MINOES_COLOR_ID.items()}
# User profile path # User profile path
if sys.platform == "win32": if sys.platform == "win32":
USER_PROFILE_DIR = os.environ.get( USER_PROFILE_DIR = os.environ.get("appdata", os.path.expanduser("~\Appdata\Roaming"))
"appdata", os.path.expanduser("~\Appdata\Roaming")
)
else: else:
USER_PROFILE_DIR = os.environ.get( USER_PROFILE_DIR = os.environ.get("XDG_DATA_HOME", os.path.expanduser("~/.local/share"))
"XDG_DATA_HOME", os.path.expanduser("~/.local/share")
)
USER_PROFILE_DIR = os.path.join(USER_PROFILE_DIR, "TetrArcade") USER_PROFILE_DIR = os.path.join(USER_PROFILE_DIR, "TetrArcade")
HIGH_SCORE_PATH = os.path.join(USER_PROFILE_DIR, ".high_score") HIGH_SCORE_PATH = os.path.join(USER_PROFILE_DIR, ".high_score")
CONF_PATH = os.path.join(USER_PROFILE_DIR, "TetrArcade.ini") CONF_PATH = os.path.join(USER_PROFILE_DIR, "TetrArcade.ini")
# Text # Text
TEXT_COLOR = arcade.color.BUBBLES TEXT_COLOR = arcade.color.BUBBLES
FONT_NAME = "res/joystix monospace.ttf" FONT_NAME = os.path.join(DATA_DIR, "joystix monospace.ttf")
STATS_TEXT_MARGIN = 40 STATS_TEXT_MARGIN = 40
STATS_TEXT_SIZE = 14 STATS_TEXT_SIZE = 14
STATS_TEXT_WIDTH = 150 STATS_TEXT_WIDTH = 150
HIGHLIGHT_TEXT_COLOR = arcade.color.BUBBLES HIGHLIGHT_TEXT_COLOR = arcade.color.BUBBLES
HIGHLIGHT_TEXT_SIZE = 20 HIGHLIGHT_TEXT_SIZE = 20
# Music
MUSIC_PATH = os.path.join(DATA_DIR, "Tetris - Song A.mp3")
class MinoSprite(arcade.Sprite): class MinoSprite(arcade.Sprite):
def __init__(self, mino, window, alpha): def __init__(self, mino, window, alpha):
super().__init__(MINOES_SPRITES_PATHS[mino.color], window.scale) super().__init__()
self.alpha = alpha self.alpha = alpha
self.window = window self.window = window
self.append_texture(TEXTURES[mino.color])
self.append_texture(TEXTURES[Color.PRELOCKED])
self.set_texture(0)
def set_position(self, x, y): def refresh(self, x, y, prelocked=False):
self.scale = self.window.scale
size = MINO_SIZE * self.scale size = MINO_SIZE * self.scale
self.left = self.window.matrix_bg.left + x * size self.left = self.window.matrix.bg.left + x * size
self.bottom = self.window.matrix_bg.bottom + y * size self.bottom = self.window.matrix.bg.bottom + y * size
self.set_texture(prelocked)
class MinoesSprites(arcade.SpriteList): class MinoesSprites(arcade.SpriteList):
@ -108,14 +130,9 @@ class TetrominoSprites(MinoesSprites):
self.append(mino.sprite) self.append(mino.sprite)
def refresh(self): def refresh(self):
if self.tetromino.prelocked:
alpha = PRELOCKED_ALPHA
else:
alpha = self.alpha
for mino in self.tetromino: for mino in self.tetromino:
coord = mino.coord + self.tetromino.coord coord = mino.coord + self.tetromino.coord
mino.sprite.set_position(coord.x, coord.y) mino.sprite.refresh(coord.x, coord.y, self.tetromino.prelocked)
mino.sprite.alpha = alpha
class MatrixSprites(MinoesSprites): class MatrixSprites(MinoesSprites):
@ -128,11 +145,11 @@ class MatrixSprites(MinoesSprites):
for y, line in enumerate(self.matrix): for y, line in enumerate(self.matrix):
for x, mino in enumerate(line): for x, mino in enumerate(line):
if mino: if mino:
mino.sprite.set_position(x, y) mino.sprite.refresh(x, y)
self.append(mino.sprite) self.append(mino.sprite)
class TetrArcade(tetrislogic.TetrisLogic, arcade.Window): class TetrArcade(TetrisLogic, arcade.Window):
def __init__(self): def __init__(self):
locale.setlocale(locale.LC_ALL, "") locale.setlocale(locale.LC_ALL, "")
self.highlight_texts = [] self.highlight_texts = []
@ -163,21 +180,20 @@ class TetrArcade(tetrislogic.TetrisLogic, arcade.Window):
arcade.set_background_color(BG_COLOR) arcade.set_background_color(BG_COLOR)
self.set_minimum_size(WINDOW_MIN_WIDTH, WINDOW_MIN_HEIGHT) self.set_minimum_size(WINDOW_MIN_WIDTH, WINDOW_MIN_HEIGHT)
self.bg = arcade.Sprite(WINDOW_BG_PATH) self.bg = arcade.Sprite(WINDOW_BG_PATH)
self.matrix_bg = arcade.Sprite(MATRIX_BG_PATH) self.matrix.bg = arcade.Sprite(MATRIX_BG_PATH)
self.matrix_bg.alpha = MATRIX_BG_ALPHA self.matrix.bg.alpha = MATRIX_BG_ALPHA
self.held_bg = arcade.Sprite(HELD_BG_PATH) self.held.bg = arcade.Sprite(HELD_BG_PATH)
self.held_bg.alpha = BAR_ALPHA self.held.bg.alpha = BAR_ALPHA
self.next_bg = arcade.Sprite(NEXT_BG_PATH) self.next.bg = arcade.Sprite(NEXT_BG_PATH)
self.next_bg.alpha = BAR_ALPHA self.next.bg.alpha = BAR_ALPHA
self.matrix.sprites = MatrixSprites(self.matrix) self.matrix.sprites = MatrixSprites(self.matrix)
self.on_resize(self.init_width, self.init_height) self.on_resize(self.init_width, self.init_height)
if self.play_music:
self.music = arcade.Sound(MUSIC_PATH)
self.music_player = None
def new_conf(self): def new_conf(self):
self.conf["WINDOW"] = { self.conf["WINDOW"] = {"width": WINDOW_WIDTH, "height": WINDOW_HEIGHT, "fullscreen": False}
"width": WINDOW_WIDTH,
"height": WINDOW_HEIGHT,
"fullscreen": False,
}
self.conf["KEYBOARD"] = { self.conf["KEYBOARD"] = {
"start": "ENTER", "start": "ENTER",
"move left": "LEFT", "move left": "LEFT",
@ -190,6 +206,10 @@ class TetrArcade(tetrislogic.TetrisLogic, arcade.Window):
"pause": "ESCAPE", "pause": "ESCAPE",
"fullscreen": "F11", "fullscreen": "F11",
} }
self.conf["MUSIC"] = {
"play": True
}
self.conf["AUTO-REPEAT"] = {"delay": 0.3, "period": 0.01}
self.load_conf() self.load_conf()
if not os.path.exists(USER_PROFILE_DIR): if not os.path.exists(USER_PROFILE_DIR):
os.makedirs(USER_PROFILE_DIR) os.makedirs(USER_PROFILE_DIR)
@ -201,65 +221,47 @@ class TetrArcade(tetrislogic.TetrisLogic, arcade.Window):
self.init_height = int(self.conf["WINDOW"]["height"]) self.init_height = int(self.conf["WINDOW"]["height"])
self.init_fullscreen = self.conf["WINDOW"].getboolean("fullscreen") self.init_fullscreen = self.conf["WINDOW"].getboolean("fullscreen")
for action, key in self.conf["KEYBOARD"].items():
self.conf["KEYBOARD"][action] = key.upper()
self.key_map = { self.key_map = {
tetrislogic.State.STARTING: { State.STARTING: {
getattr(arcade.key, self.conf["KEYBOARD"]["start"]): self.new_game, getattr(arcade.key, self.conf["KEYBOARD"]["start"]): self.new_game,
getattr( getattr(arcade.key, self.conf["KEYBOARD"]["fullscreen"]): self.toggle_fullscreen,
arcade.key, self.conf["KEYBOARD"]["fullscreen"]
): self.toggle_fullscreen,
}, },
tetrislogic.State.PLAYING: { State.PLAYING: {
getattr(arcade.key, self.conf["KEYBOARD"]["move left"]): self.move_left, getattr(arcade.key, self.conf["KEYBOARD"]["move left"]): self.move_left,
getattr( getattr(arcade.key, self.conf["KEYBOARD"]["move right"]): self.move_right,
arcade.key, self.conf["KEYBOARD"]["move right"]
): self.move_right,
getattr(arcade.key, self.conf["KEYBOARD"]["soft drop"]): self.soft_drop, getattr(arcade.key, self.conf["KEYBOARD"]["soft drop"]): self.soft_drop,
getattr(arcade.key, self.conf["KEYBOARD"]["hard drop"]): self.hard_drop, getattr(arcade.key, self.conf["KEYBOARD"]["hard drop"]): self.hard_drop,
getattr( getattr(arcade.key, self.conf["KEYBOARD"]["rotate clockwise"]): self.rotate_clockwise,
arcade.key, self.conf["KEYBOARD"]["rotate clockwise"] getattr(arcade.key, self.conf["KEYBOARD"]["rotate counter"]): self.rotate_counter,
): self.rotate_clockwise,
getattr(
arcade.key, self.conf["KEYBOARD"]["rotate counter"]
): self.rotate_counter,
getattr(arcade.key, self.conf["KEYBOARD"]["hold"]): self.swap, getattr(arcade.key, self.conf["KEYBOARD"]["hold"]): self.swap,
getattr(arcade.key, self.conf["KEYBOARD"]["pause"]): self.pause, getattr(arcade.key, self.conf["KEYBOARD"]["pause"]): self.pause,
getattr( getattr(arcade.key, self.conf["KEYBOARD"]["fullscreen"]): self.toggle_fullscreen,
arcade.key, self.conf["KEYBOARD"]["fullscreen"]
): self.toggle_fullscreen,
}, },
tetrislogic.State.PAUSED: { State.PAUSED: {
getattr(arcade.key, self.conf["KEYBOARD"]["pause"]): self.resume, getattr(arcade.key, self.conf["KEYBOARD"]["pause"]): self.resume,
getattr( getattr(arcade.key, self.conf["KEYBOARD"]["fullscreen"]): self.toggle_fullscreen,
arcade.key, self.conf["KEYBOARD"]["fullscreen"]
): self.toggle_fullscreen,
}, },
tetrislogic.State.OVER: { State.OVER: {
getattr(arcade.key, self.conf["KEYBOARD"]["start"]): self.new_game, getattr(arcade.key, self.conf["KEYBOARD"]["start"]): self.new_game,
getattr( getattr(arcade.key, self.conf["KEYBOARD"]["fullscreen"]): self.toggle_fullscreen,
arcade.key, self.conf["KEYBOARD"]["fullscreen"]
): self.toggle_fullscreen,
}, },
} }
self.AUTOREPEAT_DELAY = float(self.conf["AUTO-REPEAT"]["delay"])
self.AUTOREPEAT_PERIOD = float(self.conf["AUTO-REPEAT"]["period"])
controls_text = ( controls_text = (
"\n\n\nCONTROLS\n\n" "\n\n\nCONTROLS\n\n"
+ "\n".join( + "\n".join(
"{:<16s}{:>6s}".format(key, action) "{:<16s}{:>6s}".format(key, action)
for key, action in tuple(self.conf["KEYBOARD"].items()) for key, action in tuple(self.conf["KEYBOARD"].items()) + (("QUIT", "ALT+F4"),)
+ (("QUIT", "ALT+F4"),)
) )
+ "\n\n\n" + "\n\n\n"
) )
self.start_text = ( self.start_text = "TETRARCADE" + controls_text + "PRESS [{}] TO START".format(self.conf["KEYBOARD"]["start"])
"TETRARCADE" self.pause_text = "PAUSE" + controls_text + "PRESS [{}] TO RESUME".format(self.conf["KEYBOARD"]["pause"])
+ controls_text
+ "PRESS [{}] TO START".format(self.conf["KEYBOARD"]["start"])
)
self.pause_text = (
"PAUSE"
+ controls_text
+ "PRESS [{}] TO RESUME".format(self.conf["KEYBOARD"]["pause"])
)
self.game_over_text = """GAME self.game_over_text = """GAME
OVER OVER
@ -270,48 +272,72 @@ AGAIN""".format(
self.conf["KEYBOARD"]["start"] self.conf["KEYBOARD"]["start"]
) )
self.play_music = self.conf["MUSIC"].getboolean("play")
def new_game(self): def new_game(self):
self.highlight_texts = [] self.highlight_texts = []
super().new_game() super().new_game()
if self.play_music:
if self.music_player:
self.music_player.seek(0)
self.music_player.play()
else:
self.music_player = self.music.player.play()
self.music_player.loop = True
def new_tetromino(self): def new_tetromino(self):
tetromino = super().new_tetromino() tetromino = super().new_tetromino()
tetromino.sprites = TetrominoSprites(tetromino, self) tetromino.sprites = TetrominoSprites(tetromino, self)
return tetromino return tetromino
def new_current(self): def new_matrix_piece(self):
self.matrix.sprites = MatrixSprites(self.matrix) self.matrix.sprites = MatrixSprites(self.matrix)
super().new_current() super().new_matrix_piece()
self.ghost.sprites = TetrominoSprites(self.ghost, self, GHOST_ALPHA) self.matrix.ghost.sprites = TetrominoSprites(self.matrix.ghost, self, GHOST_ALPHA)
for tetromino in [self.current, self.ghost] + self.next: for tetromino in [self.matrix.piece, self.matrix.ghost] + self.next.pieces:
tetromino.sprites.refresh() tetromino.sprites.refresh()
def move(self, movement, prelock=True): def move(self, movement, prelock=True):
moved = super().move(movement, prelock) moved = super().move(movement, prelock)
self.current.sprites.refresh() self.matrix.piece.sprites.refresh()
if moved: if moved:
self.ghost.sprites.refresh() self.matrix.ghost.sprites.refresh()
return moved return moved
def rotate(self, rotation): def rotate(self, rotation):
rotated = super().rotate(rotation) rotated = super().rotate(rotation)
if rotated: if rotated:
for tetromino in (self.current, self.ghost): for tetromino in (self.matrix.piece, self.matrix.ghost):
tetromino.sprites.refresh() tetromino.sprites.refresh()
return rotated return rotated
def swap(self): def swap(self):
super().swap() super().swap()
self.ghost.sprites = TetrominoSprites(self.ghost, self, GHOST_ALPHA) self.matrix.ghost.sprites = TetrominoSprites(self.matrix.ghost, self, GHOST_ALPHA)
for tetromino in [self.held, self.current, self.ghost]: for tetromino in (self.held.piece, self.matrix.piece, self.matrix.ghost):
if tetromino: if tetromino:
tetromino.sprites.refresh() tetromino.sprites.refresh()
def lock(self): def lock(self):
self.current.prelocked = False self.matrix.piece.prelocked = False
self.current.sprites.refresh() self.matrix.piece.sprites.refresh()
super().lock() super().lock()
def pause(self):
super().pause()
if self.play_music:
self.music_player.pause()
def resume(self):
super().resume()
if self.play_music:
self.music_player.play()
def game_over(self):
super().game_over()
if self.play_music:
self.music_player.pause()
def on_key_press(self, key, modifiers): def on_key_press(self, key, modifiers):
for key_or_modifier in (key, modifiers): for key_or_modifier in (key, modifiers):
try: try:
@ -344,26 +370,23 @@ AGAIN""".format(
arcade.start_render() arcade.start_render()
self.bg.draw() self.bg.draw()
if self.state in (tetrislogic.State.PLAYING, tetrislogic.State.OVER): if self.state in (State.PLAYING, State.OVER):
self.matrix_bg.draw() self.matrix.bg.draw()
self.held_bg.draw() self.held.bg.draw()
self.next_bg.draw() self.next.bg.draw()
self.matrix.sprites.draw() self.matrix.sprites.draw()
for tetromino in [self.held, self.current, self.ghost] + self.next: for tetromino in [self.held.piece, self.matrix.piece, self.matrix.ghost] + self.next.pieces:
if tetromino: if tetromino:
tetromino.sprites.draw() tetromino.sprites.draw()
t = time.localtime(self.time) t = time.localtime(self.time)
font_size = STATS_TEXT_SIZE * self.scale font_size = STATS_TEXT_SIZE * self.scale
for y, text in enumerate( for y, text in enumerate(("TIME", "LINES", "GOAL", "LEVEL", "HIGH SCORE", "SCORE")):
("TIME", "LINES", "GOAL", "LEVEL", "HIGH SCORE", "SCORE")
):
arcade.draw_text( arcade.draw_text(
text=text, text=text,
start_x=self.matrix_bg.left start_x=self.matrix.bg.left - self.scale * (STATS_TEXT_MARGIN + STATS_TEXT_WIDTH),
- self.scale * (STATS_TEXT_MARGIN + STATS_TEXT_WIDTH), start_y=self.matrix.bg.bottom + 1.5 * (2 * y + 1) * font_size,
start_y=self.matrix_bg.bottom + 1.5 * (2 * y + 1) * font_size,
color=TEXT_COLOR, color=TEXT_COLOR,
font_size=font_size, font_size=font_size,
align="right", align="right",
@ -382,8 +405,8 @@ AGAIN""".format(
): ):
arcade.draw_text( arcade.draw_text(
text=text, text=text,
start_x=self.matrix_bg.left - STATS_TEXT_MARGIN * self.scale, start_x=self.matrix.bg.left - STATS_TEXT_MARGIN * self.scale,
start_y=self.matrix_bg.bottom + 3 * y * font_size, start_y=self.matrix.bg.bottom + 3 * y * font_size,
color=TEXT_COLOR, color=TEXT_COLOR,
font_size=font_size, font_size=font_size,
align="right", align="right",
@ -392,18 +415,16 @@ AGAIN""".format(
) )
highlight_text = { highlight_text = {
tetrislogic.State.STARTING: self.start_text, State.STARTING: self.start_text,
tetrislogic.State.PLAYING: self.highlight_texts[0] State.PLAYING: self.highlight_texts[0] if self.highlight_texts else "",
if self.highlight_texts State.PAUSED: self.pause_text,
else "", State.OVER: self.game_over_text,
tetrislogic.State.PAUSED: self.pause_text,
tetrislogic.State.OVER: self.game_over_text,
}.get(self.state, "") }.get(self.state, "")
if highlight_text: if highlight_text:
arcade.draw_text( arcade.draw_text(
text=highlight_text, text=highlight_text,
start_x=self.matrix_bg.center_x, start_x=self.matrix.bg.center_x,
start_y=self.matrix_bg.center_y, start_y=self.matrix.bg.center_y,
color=HIGHLIGHT_TEXT_COLOR, color=HIGHLIGHT_TEXT_COLOR,
font_size=HIGHLIGHT_TEXT_SIZE * self.scale, font_size=HIGHLIGHT_TEXT_SIZE * self.scale,
align="center", align="center",
@ -428,23 +449,23 @@ AGAIN""".format(
self.bg.center_x = center_x self.bg.center_x = center_x
self.bg.center_y = center_y self.bg.center_y = center_y
self.matrix_bg.scale = self.scale self.matrix.bg.scale = self.scale
self.matrix_bg.center_x = center_x self.matrix.bg.center_x = center_x
self.matrix_bg.center_y = center_y self.matrix.bg.center_y = center_y
self.matrix_bg.left = int(self.matrix_bg.left) self.matrix.bg.left = int(self.matrix.bg.left)
self.matrix_bg.top = int(self.matrix_bg.top) self.matrix.bg.top = int(self.matrix.bg.top)
self.held_bg.scale = self.scale self.held.bg.scale = self.scale
self.held_bg.right = self.matrix_bg.left self.held.bg.right = self.matrix.bg.left
self.held_bg.top = self.matrix_bg.top self.held.bg.top = self.matrix.bg.top
self.next_bg.scale = self.scale self.next.bg.scale = self.scale
self.next_bg.left = self.matrix_bg.right self.next.bg.left = self.matrix.bg.right
self.next_bg.top = self.matrix_bg.top self.next.bg.top = self.matrix.bg.top
self.matrix.sprites.resize(self.scale) self.matrix.sprites.resize(self.scale)
for tetromino in [self.held, self.current, self.ghost] + self.next: for tetromino in [self.held.piece, self.matrix.piece, self.matrix.ghost] + self.next.pieces:
if tetromino: if tetromino:
tetromino.sprites.resize(self.scale) tetromino.sprites.resize(self.scale)
@ -499,12 +520,17 @@ High score could not be saved:
def on_close(self): def on_close(self):
self.save_high_score() self.save_high_score()
if self.play_music:
self.music_player.pause()
super().on_close() super().on_close()
def main(): def main():
try:
TetrArcade() TetrArcade()
arcade.run() arcade.run()
except Exception as e:
sys.exit(e)
if __name__ == "__main__": if __name__ == "__main__":

1
build-requirements.txt Normal file
View File

@ -0,0 +1 @@
arcade cx-freeze

BIN
res/Tetris - Song A.mp3 Normal file

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 167 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 B

BIN
res/minoes.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 389 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 143 B

View File

@ -9,22 +9,35 @@ else:
base = None base = None
icon = None icon = None
setup( excludes = [
name="TetrArcade", "tkinter",
version="0.1", "PyQt4",
description="Tetris clone", "PyQt5",
author="adrienmalin", "PySide",
executables=[Executable( "PySide2"
script="TetrArcade.py", ]
icon=icon,
base=base executable = Executable(
)], script = "TetrArcade.py",
options={ icon = icon,
base = base,
shortcutName="TetrArcade",
shortcutDir="DesktopFolder"
)
options = {
"build_exe": { "build_exe": {
"packages": ["arcade", "pyglet"], "packages": ["arcade", "pyglet"],
"excludes": ["tkinter"], "excludes": excludes,
"include_files": "res", "include_files": "res",
"silent": True "silent": True
} }
}, }
setup(
name = "TetrArcade",
version = "0.2",
description = "Tetris clone",
author = "AdrienMalin",
executables = [executable],
options = options,
) )

View File

@ -1,15 +1,16 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from TetrArcade import TetrArcade, tetrislogic from TetrArcade import TetrArcade, State
game = TetrArcade() game = TetrArcade()
game.new_game() game.new_game()
game.move_left() game.move_left()
game.move_right() game.move_right()
game.swap()
game.rotate_clockwise() game.rotate_clockwise()
game.rotate_counter() game.rotate_counter()
for i in range(12): for i in range(12):
game.soft_drop() game.soft_drop()
game.on_draw() game.on_draw()
while game.state != tetrislogic.State.OVER: while game.state != State.OVER:
game.hard_drop() game.hard_drop()

View File

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

View File

@ -2,8 +2,8 @@
import random import random
import pickle import pickle
from .utils import Coord, Movement, Rotation, T_Spin, Line from .utils import Coord, Movement, Rotation, T_Spin
from .tetromino import Tetromino, T, I from .tetromino import Tetromino, T_Tetrimino, I_Tetrimino
from .consts import ( from .consts import (
NB_LINES, NB_LINES,
NB_COLS, NB_COLS,
@ -30,11 +30,33 @@ class State:
OVER = "OVER" OVER = "OVER"
class Matrix(list): class PieceContainer:
def __init__(self):
self.piece = None
class HoldQueue(PieceContainer):
pass
class Matrix(list, PieceContainer):
def __init__(self, *args, **kargs):
list.__init__(self, *args, **kargs)
PieceContainer.__init__(self)
def cell_is_free(self, coord): def cell_is_free(self, coord):
return 0 <= coord.x < NB_COLS and 0 <= coord.y and not self[coord.y][coord.x] return 0 <= coord.x < NB_COLS and 0 <= coord.y and not self[coord.y][coord.x]
class NextQueue(PieceContainer):
def __init__(self):
super().__init__()
self.pieces = []
class TetrisLogic: class TetrisLogic:
NB_LINES = NB_LINES NB_LINES = NB_LINES
@ -52,11 +74,10 @@ class TetrisLogic:
def __init__(self): def __init__(self):
self.load_high_score() self.load_high_score()
self.state = State.STARTING self.state = State.STARTING
self.held = HoldQueue()
self.matrix = Matrix() self.matrix = Matrix()
self.next = [] self.matrix.ghost = None
self.current = None self.next = NextQueue()
self.ghost = None
self.held = None
self.time = 0 self.time = 0
self.autorepeatable_actions = (self.move_left, self.move_right, self.soft_drop) self.autorepeatable_actions = (self.move_left, self.move_right, self.soft_drop)
self.pressed_actions = [] self.pressed_actions = []
@ -88,8 +109,8 @@ class TetrisLogic:
self.matrix.clear() self.matrix.clear()
for y in range(self.NB_LINES + 3): for y in range(self.NB_LINES + 3):
self.append_new_line_to_matrix() self.append_new_line_to_matrix()
self.next = [self.new_tetromino() for n in range(self.NB_NEXT)] self.next.pieces = [self.new_tetromino() for n in range(self.NB_NEXT)]
self.held = None self.held.piece = None
self.state = State.PLAYING self.state = State.PLAYING
self.start(self.update_time, 1) self.start(self.update_time, 1)
@ -102,7 +123,7 @@ class TetrisLogic:
return self.random_bag.pop()() return self.random_bag.pop()()
def append_new_line_to_matrix(self): def append_new_line_to_matrix(self):
self.matrix.append(Line(None for x in range(self.NB_COLS))) self.matrix.append([None for x in range(self.NB_COLS)])
def new_level(self): def new_level(self):
self.level += 1 self.level += 1
@ -113,20 +134,19 @@ class TetrisLogic:
self.lock_delay = 0.5 * pow(0.9, self.level - 15) self.lock_delay = 0.5 * pow(0.9, self.level - 15)
self.show_text("LEVEL\n{:n}".format(self.level)) self.show_text("LEVEL\n{:n}".format(self.level))
self.restart(self.fall, self.fall_delay) self.restart(self.fall, self.fall_delay)
self.new_matrix_piece()
self.new_current() def new_matrix_piece(self):
self.matrix.piece = self.next.pieces.pop(0)
def new_current(self): self.matrix.piece.coord = self.CURRENT_COORD
self.current = self.next.pop(0) self.matrix.ghost = self.matrix.piece.ghost()
self.current.coord = self.CURRENT_COORD
self.ghost = self.current.ghost()
self.move_ghost() self.move_ghost()
self.next.append(self.new_tetromino()) self.next.pieces.append(self.new_tetromino())
self.next[-1].coord = self.NEXT_COORDS[-1] self.next.pieces[-1].coord = self.NEXT_COORDS[-1]
for tetromino, coord in zip(self.next, self.NEXT_COORDS): for tetromino, coord in zip(self.next.pieces, self.NEXT_COORDS):
tetromino.coord = coord tetromino.coord = coord
if not self.can_move(self.current.coord, (mino.coord for mino in self.current)): if not self.can_move(self.matrix.piece.coord, (mino.coord for mino in self.matrix.piece)):
self.game_over() self.game_over()
def move_left(self): def move_left(self):
@ -142,13 +162,11 @@ class TetrisLogic:
self.rotate(Rotation.COUNTER) self.rotate(Rotation.COUNTER)
def move_ghost(self): def move_ghost(self):
self.ghost.coord = self.current.coord self.matrix.ghost.coord = self.matrix.piece.coord
for ghost_mino, current_mino in zip(self.ghost, self.current): for ghost_mino, current_mino in zip(self.matrix.ghost, self.matrix.piece):
ghost_mino.coord = current_mino.coord ghost_mino.coord = current_mino.coord
while self.can_move( while self.can_move(self.matrix.ghost.coord + Movement.DOWN, (mino.coord for mino in self.matrix.ghost)):
self.ghost.coord + Movement.DOWN, (mino.coord for mino in self.ghost) self.matrix.ghost.coord += Movement.DOWN
):
self.ghost.coord += Movement.DOWN
def soft_drop(self): def soft_drop(self):
moved = self.move(Movement.DOWN) moved = self.move(Movement.DOWN)
@ -165,38 +183,33 @@ class TetrisLogic:
self.move(Movement.DOWN) self.move(Movement.DOWN)
def move(self, movement, prelock=True): def move(self, movement, prelock=True):
potential_coord = self.current.coord + movement potential_coord = self.matrix.piece.coord + movement
if self.can_move(potential_coord, (mino.coord for mino in self.current)): if self.can_move(potential_coord, (mino.coord for mino in self.matrix.piece)):
if self.current.prelocked: if self.matrix.piece.prelocked:
self.restart(self.lock, self.lock_delay) self.restart(self.lock, self.lock_delay)
self.current.coord = potential_coord self.matrix.piece.coord = potential_coord
if not movement == Movement.DOWN: if not movement == Movement.DOWN:
self.current.last_rotation_point = None self.matrix.piece.last_rotation_point = None
self.move_ghost() self.move_ghost()
return True return True
else: else:
if prelock and not self.current.prelocked and movement == Movement.DOWN: if prelock and not self.matrix.piece.prelocked and movement == Movement.DOWN:
self.current.prelocked = True self.matrix.piece.prelocked = True
self.start(self.lock, self.lock_delay) self.start(self.lock, self.lock_delay)
return False return False
def rotate(self, rotation): def rotate(self, rotation):
rotated_coords = tuple( rotated_coords = tuple(Coord(rotation * mino.coord.y, -rotation * mino.coord.x) for mino in self.matrix.piece)
Coord(rotation * mino.coord.y, -rotation * mino.coord.x) for rotation_point, liberty_degree in enumerate(self.matrix.piece.SRS[rotation][self.matrix.piece.orientation], start=1):
for mino in self.current potential_coord = self.matrix.piece.coord + liberty_degree
)
for rotation_point, liberty_degree in enumerate(
self.current.SRS[rotation][self.current.orientation], start=1
):
potential_coord = self.current.coord + liberty_degree
if self.can_move(potential_coord, rotated_coords): if self.can_move(potential_coord, rotated_coords):
if self.current.prelocked: if self.matrix.piece.prelocked:
self.restart(self.lock, self.lock_delay) self.restart(self.lock, self.lock_delay)
self.current.coord = potential_coord self.matrix.piece.coord = potential_coord
for mino, coord in zip(self.current, rotated_coords): for mino, coord in zip(self.matrix.piece, rotated_coords):
mino.coord = coord mino.coord = coord
self.current.orientation = (self.current.orientation + rotation) % 4 self.matrix.piece.orientation = (self.matrix.piece.orientation + rotation) % 4
self.current.last_rotation_point = rotation_point self.matrix.piece.last_rotation_point = rotation_point
self.move_ghost() self.move_ghost()
return True return True
else: else:
@ -211,20 +224,15 @@ class TetrisLogic:
) )
def lock(self): def lock(self):
self.current.prelocked = False self.matrix.piece.prelocked = False
self.stop(self.lock) self.stop(self.lock)
# Piece unlocked # Piece unlocked
if self.can_move( if self.can_move(self.matrix.piece.coord + Movement.DOWN, (mino.coord for mino in self.matrix.piece)):
self.current.coord + Movement.DOWN, (mino.coord for mino in self.current)
):
return return
# Game over # Game over
if all( if all((mino.coord + self.matrix.piece.coord).y >= self.NB_LINES for mino in self.matrix.piece):
(mino.coord + self.current.coord).y >= self.NB_LINES
for mino in self.current
):
self.game_over() self.game_over()
return return
@ -233,12 +241,12 @@ class TetrisLogic:
self.restart(self.repeat_action, self.AUTOREPEAT_DELAY) self.restart(self.repeat_action, self.AUTOREPEAT_DELAY)
# T-Spin # T-Spin
if type(self.current) == T and self.current.last_rotation_point is not None: if type(self.matrix.piece) == T_Tetrimino and self.matrix.piece.last_rotation_point is not None:
a = self.is_t_slot(0) a = self.is_t_slot(0)
b = self.is_t_slot(1) b = self.is_t_slot(1)
c = self.is_t_slot(3) c = self.is_t_slot(3)
d = self.is_t_slot(2) d = self.is_t_slot(2)
if self.current.last_rotation_point == 5 or (a and b and (c or d)): if self.matrix.piece.last_rotation_point == 5 or (a and b and (c or d)):
t_spin = T_Spin.T_SPIN t_spin = T_Spin.T_SPIN
elif c and d and (a or b): elif c and d and (a or b):
t_spin = T_Spin.MINI t_spin = T_Spin.MINI
@ -247,8 +255,8 @@ class TetrisLogic:
else: else:
t_spin = T_Spin.NONE t_spin = T_Spin.NONE
for mino in self.current: for mino in self.matrix.piece:
coord = mino.coord + self.current.coord coord = mino.coord + self.matrix.piece.coord
del mino.coord del mino.coord
if coord.y <= self.NB_LINES + 3: if coord.y <= self.NB_LINES + 3:
self.matrix[coord.y][coord.x] = mino self.matrix[coord.y][coord.x] = mino
@ -293,41 +301,35 @@ class TetrisLogic:
if self.goal <= 0: if self.goal <= 0:
self.new_level() self.new_level()
else: else:
self.new_current() self.new_matrix_piece()
def can_move(self, potential_coord, minoes_coords): def can_move(self, potential_coord, minoes_coords):
return all( return all(self.matrix.cell_is_free(potential_coord + mino_coord) for mino_coord in minoes_coords)
self.matrix.cell_is_free(potential_coord + mino_coord)
for mino_coord in minoes_coords
)
T_SLOT_COORDS = (Coord(-1, 1), Coord(1, 1), Coord(-1, 1), Coord(-1, -1)) T_SLOT_COORDS = (Coord(-1, 1), Coord(1, 1), Coord(-1, 1), Coord(-1, -1))
def is_t_slot(self, n): def is_t_slot(self, n):
t_slot_coord = ( t_slot_coord = self.matrix.piece.coord + self.T_SLOT_COORDS[(self.matrix.piece.orientation + n) % 4]
self.current.coord + self.T_SLOT_COORDS[(self.current.orientation + n) % 4]
)
return not self.matrix.cell_is_free(t_slot_coord) return not self.matrix.cell_is_free(t_slot_coord)
def swap(self): def swap(self):
if self.current.hold_enabled: if self.matrix.piece.hold_enabled:
self.current.hold_enabled = False self.matrix.piece.hold_enabled = False
self.current.prelocked = False self.matrix.piece.prelocked = False
self.stop(self.lock) self.stop(self.lock)
self.current, self.held = self.held, self.current self.matrix.piece, self.held.piece = self.held.piece, self.matrix.piece
if type(self.held) == I: self.held.piece.coord = self.HELD_COORD
self.held.coord = self.HELD_COORD + Movement.LEFT if type(self.held.piece) == I_Tetrimino:
else: self.held.piece.coord += Movement.LEFT
self.held.coord = self.HELD_COORD for mino, coord in zip(self.held.piece, self.held.piece.MINOES_COORDS):
for mino, coord in zip(self.held, self.held.MINOES_COORDS):
mino.coord = coord mino.coord = coord
if self.current: if self.matrix.piece:
self.current.coord = self.CURRENT_COORD self.matrix.piece.coord = self.CURRENT_COORD
self.ghost = self.current.ghost() self.matrix.ghost = self.matrix.piece.ghost()
self.move_ghost() self.move_ghost()
else: else:
self.new_current() self.new_matrix_piece()
def pause(self): def pause(self):
self.state = State.PAUSED self.state = State.PAUSED
@ -339,7 +341,7 @@ class TetrisLogic:
def resume(self): def resume(self):
self.state = State.PLAYING self.state = State.PLAYING
self.start(self.fall, self.fall_delay) self.start(self.fall, self.fall_delay)
if self.current.prelocked: if self.matrix.piece.prelocked:
self.start(self.lock, self.lock_delay) self.start(self.lock, self.lock_delay)
self.start(self.update_time, 1) self.start(self.update_time, 1)
@ -361,7 +363,11 @@ class TetrisLogic:
if action in self.autorepeatable_actions: if action in self.autorepeatable_actions:
self.auto_repeat = False self.auto_repeat = False
self.pressed_actions.append(action) self.pressed_actions.append(action)
self.restart(self.repeat_action, self.AUTOREPEAT_DELAY) if action == self.soft_drop:
delay = self.fall_delay / 20
else:
delay = self.AUTOREPEAT_DELAY
self.restart(self.repeat_action, delay)
def repeat_action(self): def repeat_action(self):
if self.pressed_actions: if self.pressed_actions:

View File

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from .utils import Coord, Rotation from .utils import Coord, Rotation, Color
class Mino: class Mino:
@ -44,20 +44,20 @@ class Tetromino(list):
return type(self)() return type(self)()
class O(Tetromino, metaclass=MetaTetromino): class O_Tetrimino(Tetromino, metaclass=MetaTetromino):
SRS = { SRS = {
Rotation.CLOCKWISE: (tuple(), tuple(), tuple(), tuple()), Rotation.CLOCKWISE: (tuple(), tuple(), tuple(), tuple()),
Rotation.COUNTER: (tuple(), tuple(), tuple(), tuple()), Rotation.COUNTER: (tuple(), tuple(), tuple(), tuple()),
} }
MINOES_COORDS = (Coord(0, 0), Coord(1, 0), Coord(0, 1), Coord(1, 1)) MINOES_COORDS = (Coord(0, 0), Coord(1, 0), Coord(0, 1), Coord(1, 1))
MINOES_COLOR = "yellow" MINOES_COLOR = Color.YELLOW
def rotate(self, direction): def rotate(self, direction):
return False return False
class I(Tetromino, metaclass=MetaTetromino): class I_Tetrimino(Tetromino, metaclass=MetaTetromino):
SRS = { SRS = {
Rotation.CLOCKWISE: ( Rotation.CLOCKWISE: (
@ -74,34 +74,34 @@ class I(Tetromino, metaclass=MetaTetromino):
), ),
} }
MINOES_COORDS = (Coord(-1, 0), Coord(0, 0), Coord(1, 0), Coord(2, 0)) MINOES_COORDS = (Coord(-1, 0), Coord(0, 0), Coord(1, 0), Coord(2, 0))
MINOES_COLOR = "cyan" MINOES_COLOR = Color.CYAN
class T(Tetromino, metaclass=MetaTetromino): class T_Tetrimino(Tetromino, metaclass=MetaTetromino):
MINOES_COORDS = (Coord(-1, 0), Coord(0, 0), Coord(0, 1), Coord(1, 0)) MINOES_COORDS = (Coord(-1, 0), Coord(0, 0), Coord(0, 1), Coord(1, 0))
MINOES_COLOR = "magenta" MINOES_COLOR = Color.MAGENTA
class L(Tetromino, metaclass=MetaTetromino): class L_Tetrimino(Tetromino, metaclass=MetaTetromino):
MINOES_COORDS = (Coord(-1, 0), Coord(0, 0), Coord(1, 0), Coord(1, 1)) MINOES_COORDS = (Coord(-1, 0), Coord(0, 0), Coord(1, 0), Coord(1, 1))
MINOES_COLOR = "orange" MINOES_COLOR = Color.ORANGE
class J(Tetromino, metaclass=MetaTetromino): class J_Tetrimino(Tetromino, metaclass=MetaTetromino):
MINOES_COORDS = (Coord(-1, 1), Coord(-1, 0), Coord(0, 0), Coord(1, 0)) MINOES_COORDS = (Coord(-1, 1), Coord(-1, 0), Coord(0, 0), Coord(1, 0))
MINOES_COLOR = "blue" MINOES_COLOR = Color.BLUE
class S(Tetromino, metaclass=MetaTetromino): class S_Tetrimino(Tetromino, metaclass=MetaTetromino):
MINOES_COORDS = (Coord(-1, 0), Coord(0, 0), Coord(0, 1), Coord(1, 1)) MINOES_COORDS = (Coord(-1, 0), Coord(0, 0), Coord(0, 1), Coord(1, 1))
MINOES_COLOR = "green" MINOES_COLOR = Color.GREEN
class Z(Tetromino, metaclass=MetaTetromino): class Z_Tetrimino(Tetromino, metaclass=MetaTetromino):
MINOES_COORDS = (Coord(-1, 1), Coord(0, 1), Coord(0, 0), Coord(1, 0)) MINOES_COORDS = (Coord(-1, 1), Coord(0, 1), Coord(0, 0), Coord(1, 0))
MINOES_COLOR = "red" MINOES_COLOR = Color.RED

View File

@ -28,5 +28,12 @@ class T_Spin:
T_SPIN = "T-SPIN" T_SPIN = "T-SPIN"
class Line(list): class Color:
pass
BLUE = 0
CYAN = 1
GREEN = 2
MAGENTA = 3
ORANGE = 4
RED = 5
YELLOW = 6