45 Commits

Author SHA1 Message Date
5b3e6ec931 fix setup.py 2019-10-03 21:34:55 +02:00
0970e1f6df V0.3 Release
New musics
Piece lock optimize
2019-10-03 21:33:39 +02:00
f48f1fc000 redefine consts in TetrArcade.py 2019-10-03 18:14:07 +02:00
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
be66bada11 fix build 2019-10-02 01:53:11 +02:00
02e4aa066d improve build 2019-10-02 01:24:52 +02:00
5b6cfd6512 move ressources 2019-10-02 01:06:58 +02:00
f1ffb1a7c6 build 2019-10-02 01:06:33 +02:00
dda475a584 black 2019-10-02 00:57:19 +02:00
b8e20199af add bg for held and next 2019-10-02 00:01:02 +02:00
4c44089f41 fix prelock alpha on hold 2019-10-01 22:10:20 +02:00
f2bbafeb8b improve lock 2019-10-01 21:46:39 +02:00
54f2554b2a move consts as class variable to permit override 2019-10-01 21:00:46 +02:00
5f10bd782e fix test errors 2019-10-01 20:03:37 +02:00
9aa461fe07 add test 2019-10-01 19:58:39 +02:00
ca0a18a8c5 fix game over text 2019-10-01 19:51:18 +02:00
ecd2915b12 fix no alpha change on lock 2019-10-01 19:48:29 +02:00
ab8afe6fdd Use config file 2019-10-01 18:07:08 +02:00
fa9a323af1 fix alpha prelock on game over 2019-10-01 12:39:44 +02:00
c2ec677f3f move high score pickle to tetrislogic 2019-10-01 12:36:00 +02:00
11c6321f17 move pseudo crypt to tetrislogic 2019-10-01 11:55:08 +02:00
58a7736d53 serialize persistent high score to be less readable 2019-10-01 10:20:05 +02:00
578f33ae64 pseudo crypt high score 2019-10-01 09:00:51 +02:00
00e2adf60c restart prelock 2019-10-01 03:20:54 +02:00
b1cef21f00 fix fullscreen 2019-10-01 03:14:29 +02:00
2c4808312f typo 2019-10-01 03:10:09 +02:00
9db4f4d122 text resize 2019-10-01 03:09:54 +02:00
29 changed files with 632 additions and 423 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
@ -19,3 +21,17 @@ python -m pip install -r requirements.txt
```shell ```shell
python tetrarcade.py python tetrarcade.py
``` ```
## Settings
* Windows: Edit `%appdata%\Tetrarcade\TetrArcade.ini`
* Linux: Edit `~/.local/share/Tetrarcade/TetrArcade.ini`
Use key name from [arcade.key package](http://arcade.academy/arcade.key.html).
## Build
```shell
python -m pip install -r build-requirements.txt
python setup.py bdist
```

View File

@ -1,27 +1,48 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import sys import sys
import locale
import time
import os
try: try:
import arcade import arcade
except ImportError: except ImportError as e:
sys.exit( sys.exit(
"""This game require arcade library. str(e) + """
This game require arcade library.
You can install it with: You can install it with:
python -m pip install --user arcade""" python -m pip install --user arcade"""
) )
import pyglet
import tetrislogic import locale
import time
import os
import itertools
import configparser
from tetrislogic import TetrisLogic, Color, State, Coord
# Constants # Constants
# Matrix
NB_LINES = 20
NB_COLS = 10
NB_NEXT = 5
# Delays (seconds)
LOCK_DELAY = 0.5
FALL_DELAY = 1
AUTOREPEAT_DELAY = 0.300
AUTOREPEAT_PERIOD = 0.010
# Piece init coord
MATRIX_PIECE_COORD = Coord(4, NB_LINES)
NEXT_PIECE_COORDS = [Coord(NB_COLS + 4, NB_LINES - 4 * n - 3) for n in range(NB_NEXT)]
HELD_PIECE_COORD = Coord(-5, NB_LINES - 3)
# Window # Window
WINDOW_WIDTH = 800 WINDOW_WIDTH = 800
WINDOW_HEIGHT = 600 WINDOW_HEIGHT = 600
WINDOW_MIN_WIDTH = 517
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)
@ -32,19 +53,55 @@ NORMAL_ALPHA = 200
PRELOCKED_ALPHA = 100 PRELOCKED_ALPHA = 100
GHOST_ALPHA = 30 GHOST_ALPHA = 30
MATRIX_BG_ALPHA = 100 MATRIX_BG_ALPHA = 100
BAR_ALPHA = 75
# Mino size
MINO_SIZE = 20
MINO_SPRITE_SIZE = 21
if getattr(sys, 'frozen', False):
# The application is frozen
PROGRAM_DIR = os.path.dirname(sys.executable)
else:
# The application is not frozen
PROGRAM_DIR = os.path.dirname(__file__)
RESOURCES_DIR = os.path.join(PROGRAM_DIR, "resources")
# Sprites # Sprites
WINDOW_BG_PATH = "images/bg.jpg" IMAGES_DIR = os.path.join(RESOURCES_DIR, "images")
MATRIX_SPRITE_PATH = "images/matrix.png" WINDOW_BG_PATH = os.path.join(IMAGES_DIR, "bg.jpg")
MINOES_SPRITES_PATHS = { MATRIX_BG_PATH = os.path.join(IMAGES_DIR, "matrix.png")
"orange": "images/orange_mino.png", HELD_BG_PATH = os.path.join(IMAGES_DIR, "held.png")
"blue": "images/blue_mino.png", NEXT_BG_PATH = os.path.join(IMAGES_DIR, "next.png")
"yellow": "images/yellow_mino.png", MINOES_SPRITES_PATH = os.path.join(IMAGES_DIR, "minoes.png")
"cyan": "images/cyan_mino.png", Color.PRELOCKED = 7
"green": "images/green_mino.png", MINOES_COLOR_ID = {
"red": "images/red_mino.png", Color.BLUE: 0,
"magenta": "images/magenta_mino.png" Color.CYAN: 1,
Color.GREEN: 2,
Color.MAGENTA: 3,
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()}
# Music
MUSIC_DIR = os.path.join(RESOURCES_DIR, "musics")
MUSICS_PATHS = (entry.path for entry in os.scandir(MUSIC_DIR))
# Text
TEXT_COLOR = arcade.color.BUBBLES
FONT_NAME = os.path.join(RESOURCES_DIR, "fonts/joystix monospace.ttf")
STATS_TEXT_MARGIN = 40
STATS_TEXT_SIZE = 14
STATS_TEXT_WIDTH = 150
HIGHLIGHT_TEXT_COLOR = arcade.color.BUBBLES
HIGHLIGHT_TEXT_SIZE = 20
# User profile path # User profile path
if sys.platform == "win32": if sys.platform == "win32":
@ -53,68 +110,25 @@ else:
USER_PROFILE_DIR = os.environ.get("XDG_DATA_HOME", os.path.expanduser("~/.local/share")) USER_PROFILE_DIR = os.environ.get("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")
# Text
TEXT_COLOR = arcade.color.BUBBLES
FONT_NAME = "joystix monospace.ttf"
STATS_TEXT_MARGIN = 40
STATS_TEXT_SIZE = 16
STATS_TEXT_HEIGHT = 20.8
HIGHLIGHT_TEXT_COLOR = arcade.color.BUBBLES
HIGHLIGHT_TEXT_SIZE = 20
CONTROL_TEXT = """
CONTROLS
MOVE LEFT ←
MOVE RIGHT →
SOFT DROP ↓
HARD DROP SPACE
ROTATE CLOCKWISE ↑
ROTATE COUNTER Z
HOLD C
FULLSCREEN F11
PAUSE ESC
QUIT ALT+F4
"""
START_TEXT = "TETRARCADE" + CONTROL_TEXT + "PRESS [ENTER] TO START"
PAUSE_TEXT = "PAUSE" + CONTROL_TEXT + "PRESS [ESC] TO RESUME"
STATS_TEXT = """SCORE
HIGH SCORE
LEVEL
GOAL
LINES
TIME
"""
GAME_OVER_TEXT = """GAME
OVER
PRESS
[ENTER]
TO PLAY
AGAIN"""
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):
@ -122,7 +136,7 @@ class MinoesSprites(arcade.SpriteList):
def resize(self, scale): def resize(self, scale):
for sprite in self: for sprite in self:
sprite.scale = scale sprite.scale = scale
self.update() self.refresh()
class TetrominoSprites(MinoesSprites): class TetrominoSprites(MinoesSprites):
@ -130,19 +144,15 @@ class TetrominoSprites(MinoesSprites):
def __init__(self, tetromino, window, alpha=NORMAL_ALPHA): def __init__(self, tetromino, window, alpha=NORMAL_ALPHA):
super().__init__() super().__init__()
self.tetromino = tetromino self.tetromino = tetromino
self.alpha = alpha
for mino in tetromino: for mino in tetromino:
mino.sprite = MinoSprite(mino, window, alpha) mino.sprite = MinoSprite(mino, window, alpha)
self.append(mino.sprite) self.append(mino.sprite)
def update(self): def refresh(self):
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)
super().update()
def set_alpha(self, alpha):
for sprite in self:
sprite.alpha = alpha
class MatrixSprites(MinoesSprites): class MatrixSprites(MinoesSprites):
@ -150,167 +160,236 @@ class MatrixSprites(MinoesSprites):
def __init__(self, matrix): def __init__(self, matrix):
super().__init__() super().__init__()
self.matrix = matrix self.matrix = matrix
self.update() self.refresh()
def update(self): def refresh(self):
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)
super().update() def remove_line(self, y):
for mino in self.matrix[y]:
if mino:
self.remove(mino.sprite)
class TetrArcade(tetrislogic.TetrisLogic, arcade.Window): class TetrArcade(TetrisLogic, arcade.Window):
NB_LINES = NB_LINES
NB_COLS = NB_COLS
NB_NEXT = NB_NEXT
LOCK_DELAY = LOCK_DELAY
FALL_DELAY = FALL_DELAY
AUTOREPEAT_DELAY = AUTOREPEAT_DELAY
AUTOREPEAT_PERIOD = AUTOREPEAT_PERIOD
MATRIX_PIECE_COORD = MATRIX_PIECE_COORD
NEXT_PIECE_COORDS = NEXT_PIECE_COORDS
HELD_PIECE_COORD = HELD_PIECE_COORD
def __init__(self): def __init__(self):
locale.setlocale(locale.LC_ALL, '') locale.setlocale(locale.LC_ALL, "")
self.highlight_texts = [] self.highlight_texts = []
self.tasks = {} self.tasks = {}
self.KEY_MAP = { self.conf = configparser.ConfigParser()
tetrislogic.State.STARTING: { if self.conf.read(CONF_PATH):
arcade.key.ENTER: self.new_game, try:
arcade.key.F11: self.toogle_fullscreen self.load_conf()
}, except:
tetrislogic.State.PLAYING: { self.new_conf()
arcade.key.LEFT: self.move_left, self.load_conf()
arcade.key.NUM_4: self.move_left, else:
arcade.key.RIGHT: self.move_right, self.new_conf()
arcade.key.NUM_6: self.move_right, self.load_conf()
arcade.key.SPACE: self.hard_drop,
arcade.key.NUM_8: self.hard_drop,
arcade.key.DOWN: self.soft_drop,
arcade.key.NUM_2: self.soft_drop,
arcade.key.UP: self.rotate_clockwise,
arcade.key.X: self.rotate_clockwise,
arcade.key.NUM_1: self.rotate_clockwise,
arcade.key.NUM_5: self.rotate_clockwise,
arcade.key.NUM_9: self.rotate_clockwise,
arcade.key.Z: self.rotate_counter,
arcade.key.NUM_3: self.rotate_counter,
arcade.key.NUM_7: self.rotate_counter,
arcade.key.C: self.swap,
arcade.key.MOD_SHIFT: self.swap,
arcade.key.NUM_0: self.swap,
arcade.key.ESCAPE: self.pause,
arcade.key.F1: self.pause,
arcade.key.F11: self.toogle_fullscreen
},
tetrislogic.State.PAUSED: {
arcade.key.ESCAPE: self.resume,
arcade.key.F1: self.resume,
arcade.key.F11: self.toogle_fullscreen
},
tetrislogic.State.OVER: {
arcade.key.ENTER: self.new_game,
arcade.key.F11: self.toogle_fullscreen
}
}
super().__init__() super().__init__()
arcade.Window.__init__( arcade.Window.__init__(
self, self,
width = WINDOW_WIDTH, width=self.init_width,
height = WINDOW_HEIGHT, height=self.init_height,
title = WINDOW_TITLE, title=WINDOW_TITLE,
resizable = True, resizable=True,
antialiasing = False antialiasing=False,
fullscreen=self.init_fullscreen,
) )
arcade.set_background_color(BG_COLOR) arcade.set_background_color(BG_COLOR)
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_SPRITE_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.alpha = BAR_ALPHA
self.next.bg = arcade.Sprite(NEXT_BG_PATH)
self.next.bg.alpha = BAR_ALPHA
self.matrix.sprites = MatrixSprites(self.matrix) self.matrix.sprites = MatrixSprites(self.matrix)
self.stats_text = arcade.create_text( self.on_resize(self.init_width, self.init_height)
text = STATS_TEXT,
color = TEXT_COLOR, if self.play_music:
font_size = STATS_TEXT_SIZE, self.music = pyglet.media.Player()
font_name = FONT_NAME, playlist = itertools.cycle(
anchor_x = 'right' pyglet.media.load(path)
for path in MUSICS_PATHS
)
self.music.queue(playlist)
def new_conf(self):
self.conf["WINDOW"] = {"width": WINDOW_WIDTH, "height": WINDOW_HEIGHT, "fullscreen": False}
self.conf["KEYBOARD"] = {
"start": "ENTER",
"move left": "LEFT",
"move right": "RIGHT",
"soft drop": "DOWN",
"hard drop": "SPACE",
"rotate clockwise": "UP",
"rotate counter": "Z",
"hold": "C",
"pause": "ESCAPE",
"fullscreen": "F11",
}
self.conf["MUSIC"] = {
"play": True
}
self.conf["AUTO-REPEAT"] = {"delay": 0.3, "period": 0.01}
self.load_conf()
if not os.path.exists(USER_PROFILE_DIR):
os.makedirs(USER_PROFILE_DIR)
with open(CONF_PATH, "w") as f:
self.conf.write(f)
def load_conf(self):
self.init_width = int(self.conf["WINDOW"]["width"])
self.init_height = int(self.conf["WINDOW"]["height"])
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 = {
State.STARTING: {
getattr(arcade.key, self.conf["KEYBOARD"]["start"]): self.new_game,
getattr(arcade.key, self.conf["KEYBOARD"]["fullscreen"]): self.toggle_fullscreen,
},
State.PLAYING: {
getattr(arcade.key, self.conf["KEYBOARD"]["move left"]): self.move_left,
getattr(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"]["hard drop"]): self.hard_drop,
getattr(arcade.key, self.conf["KEYBOARD"]["rotate clockwise"]): 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"]["pause"]): self.pause,
getattr(arcade.key, self.conf["KEYBOARD"]["fullscreen"]): self.toggle_fullscreen,
},
State.PAUSED: {
getattr(arcade.key, self.conf["KEYBOARD"]["pause"]): self.resume,
getattr(arcade.key, self.conf["KEYBOARD"]["fullscreen"]): self.toggle_fullscreen,
},
State.OVER: {
getattr(arcade.key, self.conf["KEYBOARD"]["start"]): self.new_game,
getattr(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 = (
"\n\n\nCONTROLS\n\n"
+ "\n".join(
"{:<16s}{:>6s}".format(key, action)
for key, action in tuple(self.conf["KEYBOARD"].items()) + (("QUIT", "ALT+F4"),)
)
+ "\n\n\n"
) )
self.scale = 1 self.start_text = "TETRARCADE" + 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
OVER
def on_hide(self): PRESS
self.pause() [{}]
TO PLAY
AGAIN""".format(
self.conf["KEYBOARD"]["start"]
)
def on_resize(self, width, height): self.play_music = self.conf["MUSIC"].getboolean("play")
super().on_resize(width, height)
center_x = width / 2
center_y = height / 2
self.scale = min(width/WINDOW_WIDTH, height/WINDOW_HEIGHT)
self.bg.scale = max(width/WINDOW_WIDTH, height/WINDOW_HEIGHT)
self.bg.center_x = center_x
self.bg.center_y = center_y
self.matrix_bg.scale = self.scale
self.matrix_bg.center_x = center_x
self.matrix_bg.center_y = center_y
self.matrix_bg.left = int(self.matrix_bg.left)
self.matrix_bg.top = int(self.matrix_bg.top)
self.matrix.sprites.resize(self.scale)
for tetromino in [self.held, self.current, self.ghost] + self.next:
if tetromino:
tetromino.sprites.resize(self.scale)
def toogle_fullscreen(self):
self.fullscreen = not self.fullscreen
def new_game(self): def new_game(self):
self.highlight_texts = [] self.highlight_texts = []
super().new_game() super().new_game()
self.matrix.sprites = MatrixSprites(self.matrix)
if self.play_music:
self.music.seek(0)
self.music.play()
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) super().new_matrix_piece()
super().new_current() self.matrix.ghost.sprites = TetrominoSprites(self.matrix.ghost, self, GHOST_ALPHA)
self.ghost.sprites = TetrominoSprites(self.ghost, self, GHOST_ALPHA) for tetromino in [self.matrix.piece, self.matrix.ghost] + self.next.pieces:
for tetromino in [self.current, self.ghost] + self.next: tetromino.sprites.refresh()
tetromino.sprites.update()
def move(self, movement, prelock=True): def move(self, movement, prelock=True):
moved = super().move(movement, prelock) moved = super().move(movement, prelock)
if self.current.prelocked: self.matrix.piece.sprites.refresh()
self.current.sprites.set_alpha(PRELOCKED_ALPHA)
if moved: if moved:
size = MINO_SIZE * self.scale self.matrix.ghost.sprites.refresh()
change_x = movement.x * size
change_y = movement.y * size
self.current.sprites.move(change_x, change_y)
if movement in (tetrislogic.Movement.LEFT, tetrislogic.Movement.RIGHT):
self.ghost.sprites.update()
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.update() 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.update() tetromino.sprites.refresh()
def lock(self): def lock(self):
self.current.sprites.update() self.matrix.piece.prelocked = False
self.matrix.piece.sprites.refresh()
super().lock() super().lock()
self.matrix.sprites.refresh()
def enter_the_matrix(self):
super().enter_the_matrix()
for mino in self.matrix.piece:
self.matrix.sprites.append(mino.sprite)
def remove_line(self, y):
self.matrix.sprites.remove_line(y)
super().remove_line(y)
def pause(self):
super().pause()
if self.play_music:
self.music.pause()
def resume(self):
super().resume()
if self.play_music:
self.music.play()
def game_over(self):
super().game_over()
if self.play_music:
self.music.pause()
def on_key_press(self, key, modifiers): def on_key_press(self, key, modifiers):
for key_or_modifier in (key, modifiers): for key_or_modifier in (key, modifiers):
try: try:
action = self.KEY_MAP[self.state][key_or_modifier] action = self.key_map[self.state][key_or_modifier]
except KeyError: except KeyError:
pass pass
else: else:
@ -319,7 +398,7 @@ class TetrArcade(tetrislogic.TetrisLogic, arcade.Window):
def on_key_release(self, key, modifiers): def on_key_release(self, key, modifiers):
for key_or_modifier in (key, modifiers): for key_or_modifier in (key, modifiers):
try: try:
action = self.KEY_MAP[self.state][key_or_modifier] action = self.key_map[self.state][key_or_modifier]
except KeyError: except KeyError:
pass pass
else: else:
@ -339,67 +418,110 @@ class TetrArcade(tetrislogic.TetrisLogic, arcade.Window):
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.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()
arcade.render_text(
self.stats_text,
self.matrix_bg.left - STATS_TEXT_MARGIN,
self.matrix_bg.bottom
)
t = time.localtime(self.time) t = time.localtime(self.time)
font_size = STATS_TEXT_SIZE * self.scale
for y, text in enumerate(("TIME", "LINES", "GOAL", "LEVEL", "HIGH SCORE", "SCORE")):
arcade.draw_text(
text=text,
start_x=self.matrix.bg.left - self.scale * (STATS_TEXT_MARGIN + STATS_TEXT_WIDTH),
start_y=self.matrix.bg.bottom + 1.5 * (2 * y + 1) * font_size,
color=TEXT_COLOR,
font_size=font_size,
align="right",
font_name=FONT_NAME,
anchor_x="left",
)
for y, text in enumerate( for y, text in enumerate(
( (
"{:02d}:{:02d}:{:02d}".format(t.tm_hour - 1, t.tm_min, t.tm_sec),
"{:02d}:{:02d}:{:02d}".format(
t.tm_hour-1, t.tm_min, t.tm_sec
),
"{:n}".format(self.nb_lines_cleared), "{:n}".format(self.nb_lines_cleared),
"{:n}".format(self.goal), "{:n}".format(self.goal),
"{:n}".format(self.level), "{:n}".format(self.level),
"{:n}".format(self.high_score), "{:n}".format(self.high_score),
"{:n}".format(self.score) "{:n}".format(self.score),
) )
): ):
arcade.draw_text( arcade.draw_text(
text = text, text=text,
start_x = self.matrix_bg.left - STATS_TEXT_MARGIN, start_x=self.matrix.bg.left - STATS_TEXT_MARGIN * self.scale,
start_y = self.matrix_bg.bottom + 2*y*STATS_TEXT_HEIGHT, start_y=self.matrix.bg.bottom + 3 * y * font_size,
color = TEXT_COLOR, color=TEXT_COLOR,
font_size = STATS_TEXT_SIZE, font_size=font_size,
align = 'right', align="right",
font_name = FONT_NAME, font_name=FONT_NAME,
anchor_x = 'right' anchor_x="right",
) )
highlight_text = { highlight_text = {
tetrislogic.State.STARTING: START_TEXT, State.STARTING: self.start_text,
tetrislogic.State.PLAYING: self.highlight_texts[0] if self.highlight_texts else "", State.PLAYING: self.highlight_texts[0] if self.highlight_texts else "",
tetrislogic.State.PAUSED: PAUSE_TEXT, State.PAUSED: self.pause_text,
tetrislogic.State.OVER: GAME_OVER_TEXT 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",
font_name = FONT_NAME, font_name=FONT_NAME,
anchor_x = 'center', anchor_x="center",
anchor_y = 'center' anchor_y="center",
) )
def on_hide(self):
self.pause()
def toggle_fullscreen(self):
self.set_fullscreen(not self.fullscreen)
def on_resize(self, width, height):
super().on_resize(width, height)
center_x = width / 2
center_y = height / 2
self.scale = min(width / WINDOW_WIDTH, height / WINDOW_HEIGHT)
self.bg.scale = max(width / WINDOW_WIDTH, height / WINDOW_HEIGHT)
self.bg.center_x = center_x
self.bg.center_y = center_y
self.matrix.bg.scale = self.scale
self.matrix.bg.center_x = center_x
self.matrix.bg.center_y = center_y
self.matrix.bg.left = int(self.matrix.bg.left)
self.matrix.bg.top = int(self.matrix.bg.top)
self.held.bg.scale = self.scale
self.held.bg.right = self.matrix.bg.left
self.held.bg.top = self.matrix.bg.top
self.next.bg.scale = self.scale
self.next.bg.left = self.matrix.bg.right
self.next.bg.top = self.matrix.bg.top
self.matrix.sprites.resize(self.scale)
for tetromino in [self.held.piece, self.matrix.piece, self.matrix.ghost] + self.next.pieces:
if tetromino:
tetromino.sprites.resize(self.scale)
def load_high_score(self): def load_high_score(self):
try: try:
with open(HIGH_SCORE_PATH, "r") as f: with open(HIGH_SCORE_PATH, "rb") as f:
self.high_score = int(f.read()) crypted_high_score = f.read()
super().load_high_score(crypted_high_score)
except: except:
self.high_score = 0 self.high_score = 0
@ -407,13 +529,16 @@ class TetrArcade(tetrislogic.TetrisLogic, arcade.Window):
try: try:
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)
with open(HIGH_SCORE_PATH, mode='w') as f: with open(HIGH_SCORE_PATH, mode="wb") as f:
f.write(str(self.high_score)) crypted_high_score = super().save_high_score()
f.write(crypted_high_score)
except Exception as e: except Exception as e:
sys.exit( sys.exit(
"""High score: {:n} """High score: {:n}
High score could not be saved: High score could not be saved:
""".format(self.high_score) """.format(
self.high_score
)
+ str(e) + str(e)
) )
@ -441,11 +566,20 @@ High score could not be saved:
arcade.unschedule(_task) arcade.unschedule(_task)
arcade.schedule(_task, period) arcade.schedule(_task, period)
def on_close(self):
self.save_high_score()
if self.play_music:
self.music.pause()
super().on_close()
def main(): def main():
tetrarcade = TetrArcade() try:
arcade.run() TetrArcade()
tetrarcade.save_high_score() arcade.run()
except Exception as e:
sys.exit(e)
if __name__ == "__main__": if __name__ == "__main__":
main() main()

1
build-requirements.txt Normal file
View File

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

BIN
icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
icon48.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

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

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

Before

Width:  |  Height:  |  Size: 153 KiB

After

Width:  |  Height:  |  Size: 153 KiB

BIN
resources/images/held.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 499 B

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
resources/images/minoes.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 389 B

BIN
resources/images/next.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 475 B

Binary file not shown.

BIN
resources/musics/2-!!!.mp3 Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

43
setup.py Normal file
View File

@ -0,0 +1,43 @@
# -*- coding: utf-8 -*-
import sys
from cx_Freeze import setup, Executable
if sys.platform == "win32":
base = "Win32GUI"
icon = "icon.ico"
else:
base = None
icon = None
excludes = [
"tkinter",
"PyQt4",
"PyQt5",
"PySide",
"PySide2"
]
executable = Executable(
script = "TetrArcade.py",
icon = icon,
base = base,
shortcutName="TetrArcade",
shortcutDir="DesktopFolder"
)
options = {
"build_exe": {
"packages": ["arcade", "pyglet"],
"excludes": excludes,
"include_files": "resources",
"silent": True
}
}
setup(
name = "TetrArcade",
version = "0.3",
description = "Tetris clone",
author = "AdrienMalin",
executables = [executable],
options = options,
)

17
test.py Normal file
View File

@ -0,0 +1,17 @@
# -*- coding: utf-8 -*-
from TetrArcade import TetrArcade, State
game = TetrArcade()
game.new_game()
game.move_left()
game.move_right()
game.swap()
game.rotate_clockwise()
game.rotate_counter()
for i in range(12):
game.soft_drop()
game.matrix.sprites.refresh()
game.on_draw()
while game.state != State.OVER:
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, Coord
from .tetromino import Mino, Tetromino from .tetromino import Mino, Tetromino
from .tetrislogic import TetrisLogic, State, Matrix from .tetrislogic import TetrisLogic, State, Matrix

View File

@ -10,15 +10,10 @@ NB_NEXT = 5
# Delays (seconds) # Delays (seconds)
LOCK_DELAY = 0.5 LOCK_DELAY = 0.5
FALL_DELAY = 1 FALL_DELAY = 1
AUTOREPEAT_DELAY = 0.200 # Official : 0.300 AUTOREPEAT_DELAY = 0.300 # Official : 0.300 s
AUTOREPEAT_PERIOD = 0.010 # Official : 0.010 AUTOREPEAT_PERIOD = 0.010 # Official : 0.010 s
# Piece init coord # Piece init coord
CURRENT_COORD = Coord(4, NB_LINES) MATRIX_PIECE_COORD = Coord(4, NB_LINES)
NEXT_COORDS = [ NEXT_PIECE_COORDS = [Coord(NB_COLS + 4, NB_LINES - 4 * n - 3) for n in range(NB_NEXT)]
Coord(NB_COLS+6, NB_LINES-4*n-3) HELD_PIECE_COORD = Coord(-5, NB_LINES - 3)
for n in range(NB_NEXT)
]
HELD_COORD = Coord(-7, NB_LINES-3)
HELD_I_COORD = Coord(-7, NB_LINES-3)

View File

@ -1,49 +1,83 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import random import random
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_COLS, NB_NEXT, NB_LINES,
LOCK_DELAY, FALL_DELAY, NB_COLS,
AUTOREPEAT_DELAY, AUTOREPEAT_PERIOD, NB_NEXT,
CURRENT_COORD, NEXT_COORDS, HELD_COORD, HELD_I_COORD LOCK_DELAY,
FALL_DELAY,
AUTOREPEAT_DELAY,
AUTOREPEAT_PERIOD,
MATRIX_PIECE_COORD,
NEXT_PIECE_COORDS,
HELD_PIECE_COORD,
) )
LINES_CLEAR_NAME = "LINES_CLEAR_NAME" LINES_CLEAR_NAME = "LINES_CLEAR_NAME"
CRYPT_KEY = 987943759387540938469837689379857347598347598379584857934579343
class State: class State:
STARTING = "STARTING" STARTING = "STARTING"
PLAYING = "PLAYING" PLAYING = "PLAYING"
PAUSED = "PAUSED" PAUSED = "PAUSED"
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 ( return 0 <= coord.x < NB_COLS and 0 <= coord.y and not self[coord.y][coord.x]
0 <= coord.x < NB_COLS
and 0 <= coord.y
and not self[coord.y][coord.x]
)
class TetrisLogic(): class NextQueue(PieceContainer):
def __init__(self):
super().__init__()
self.pieces = []
class TetrisLogic:
NB_LINES = NB_LINES
NB_COLS = NB_COLS
NB_NEXT = NB_NEXT
LOCK_DELAY = LOCK_DELAY
FALL_DELAY = FALL_DELAY
AUTOREPEAT_DELAY = AUTOREPEAT_DELAY
AUTOREPEAT_PERIOD = AUTOREPEAT_PERIOD
MATRIX_PIECE_COORD = MATRIX_PIECE_COORD
NEXT_PIECE_COORDS = NEXT_PIECE_COORDS
HELD_PIECE_COORD = HELD_PIECE_COORD
random_bag = [] random_bag = []
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 = []
@ -69,14 +103,14 @@ class TetrisLogic():
self.pressed_actions = [] self.pressed_actions = []
self.auto_repeat = False self.auto_repeat = False
self.lock_delay = LOCK_DELAY self.lock_delay = self.LOCK_DELAY
self.fall_delay = FALL_DELAY self.fall_delay = self.FALL_DELAY
self.matrix.clear() self.matrix.clear()
for y in range(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(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)
@ -89,34 +123,30 @@ 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(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
self.goal += 5 * self.level self.goal += 5 * self.level
if self.level <= 20: if self.level <= 20:
self.fall_delay = pow(0.8 - ((self.level-1)*0.007), self.level-1) self.fall_delay = pow(0.8 - ((self.level - 1) * 0.007), self.level - 1)
if self.level > 15: if self.level > 15:
self.lock_delay = 0.5 * pow(0.9, self.level-15) self.lock_delay = 0.5 * pow(0.9, self.level - 15)
self.show_text("LEVEL\n{:n}".format(self.level)) self.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.MATRIX_PIECE_COORD
self.current = self.next.pop(0) self.matrix.ghost = self.matrix.piece.ghost()
self.current.coord = 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 = NEXT_COORDS[-1] self.next.pieces[-1].coord = self.NEXT_PIECE_COORDS[-1]
for tetromino, coord in zip (self.next, NEXT_COORDS): for tetromino, coord in zip(self.next.pieces, self.NEXT_PIECE_COORDS):
tetromino.coord = coord tetromino.coord = coord
if not self.can_move( if not self.can_move(self.matrix.piece.coord, (mino.coord for mino in self.matrix.piece)):
self.current.coord,
(mino.coord for mino in self.current)
):
self.game_over() self.game_over()
def move_left(self): def move_left(self):
@ -132,14 +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, self.matrix.ghost.coord += Movement.DOWN
(mino.coord for mino in self.ghost)
):
self.ghost.coord += Movement.DOWN
def soft_drop(self): def soft_drop(self):
moved = self.move(Movement.DOWN) moved = self.move(Movement.DOWN)
@ -156,47 +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( if self.can_move(potential_coord, (mino.coord for mino in self.matrix.piece)):
potential_coord, if self.matrix.piece.prelocked:
(mino.coord for mino in self.current)
):
if self.current.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 ( if prelock and not self.matrix.piece.prelocked and movement == Movement.DOWN:
prelock and not self.current.prelocked self.matrix.piece.prelocked = True
and movement == Movement.DOWN
):
self.current.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.matrix.piece.orientation = (self.matrix.piece.orientation + rotation) % 4
(self.current.orientation + rotation) % 4 self.matrix.piece.last_rotation_point = rotation_point
)
self.current.last_rotation_point = rotation_point
self.move_ghost() self.move_ghost()
return True return True
else: else:
@ -207,44 +220,33 @@ class TetrisLogic():
{LINES_CLEAR_NAME: "SINGLE", T_Spin.NONE: 1, T_Spin.MINI: 2, T_Spin.T_SPIN: 8}, {LINES_CLEAR_NAME: "SINGLE", T_Spin.NONE: 1, T_Spin.MINI: 2, T_Spin.T_SPIN: 8},
{LINES_CLEAR_NAME: "DOUBLE", T_Spin.NONE: 3, T_Spin.T_SPIN: 12}, {LINES_CLEAR_NAME: "DOUBLE", T_Spin.NONE: 3, T_Spin.T_SPIN: 12},
{LINES_CLEAR_NAME: "TRIPLE", T_Spin.NONE: 5, T_Spin.T_SPIN: 16}, {LINES_CLEAR_NAME: "TRIPLE", T_Spin.NONE: 5, T_Spin.T_SPIN: 16},
{LINES_CLEAR_NAME: "TETRIS", T_Spin.NONE: 8} {LINES_CLEAR_NAME: "TETRIS", T_Spin.NONE: 8},
) )
def lock(self): def lock(self):
self.matrix.piece.prelocked = False
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
# Start lock
self.current.prelocked = False
self.stop(self.lock)
if self.pressed_actions:
self.auto_repeat = False
self.restart(self.repeat_action, AUTOREPEAT_DELAY)
# 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 >= NB_LINES
for mino in self.current
):
self.game_over() self.game_over()
return return
if self.pressed_actions:
self.auto_repeat = False
self.restart(self.repeat_action, self.AUTOREPEAT_DELAY)
# T-Spin # T-Spin
if ( if type(self.matrix.piece) == T_Tetrimino and self.matrix.piece.last_rotation_point is not None:
type(self.current) == T
and self.current.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 ( if self.matrix.piece.last_rotation_point == 5 or (a and b and (c or d)):
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
@ -253,18 +255,14 @@ class TetrisLogic():
else: else:
t_spin = T_Spin.NONE t_spin = T_Spin.NONE
for mino in self.current: self.enter_the_matrix()
coord = mino.coord + self.current.coord
del(mino.coord)
if coord.y <= NB_LINES+3:
self.matrix[coord.y][coord.x] = mino
# Clear complete lines # Clear complete lines
nb_lines_cleared = 0 nb_lines_cleared = 0
for y, line in reversed(list(enumerate(self.matrix))): for y, line in reversed(list(enumerate(self.matrix))):
if all(mino for mino in line): if all(mino for mino in line):
nb_lines_cleared += 1 nb_lines_cleared += 1
self.matrix.pop(y) self.remove_line(y)
self.append_new_line_to_matrix() self.append_new_line_to_matrix()
if nb_lines_cleared: if nb_lines_cleared:
self.nb_lines_cleared += nb_lines_cleared self.nb_lines_cleared += nb_lines_cleared
@ -290,7 +288,7 @@ class TetrisLogic():
self.show_text("\n".join(lock_strings)) self.show_text("\n".join(lock_strings))
if self.combo >= 1: if self.combo >= 1:
ds = (20 if nb_lines_cleared==1 else 50) * self.combo * self.level ds = (20 if nb_lines_cleared == 1 else 50) * self.combo * self.level
lock_score += ds lock_score += ds
self.show_text("COMBO x{:n}\n{:n}".format(self.combo, ds)) self.show_text("COMBO x{:n}\n{:n}".format(self.combo, ds))
@ -299,46 +297,44 @@ 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 enter_the_matrix(self):
for mino in self.matrix.piece:
coord = mino.coord + self.matrix.piece.coord
if coord.y <= self.NB_LINES + 3:
self.matrix[coord.y][coord.x] = mino
def remove_line(self, y):
self.matrix.pop(y)
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 = ( T_SLOT_COORDS = (Coord(-1, 1), Coord(1, 1), Coord(-1, 1), Coord(-1, -1))
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 = self.current.coord + self.T_SLOT_COORDS[ t_slot_coord = self.matrix.piece.coord + self.T_SLOT_COORDS[(self.matrix.piece.orientation + n) % 4]
(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_PIECE_COORD
self.held.coord = HELD_I_COORD if type(self.held.piece) == I_Tetrimino:
else: self.held.piece.coord += Movement.LEFT
self.held.coord = 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 = CURRENT_COORD self.matrix.piece.coord = self.MATRIX_PIECE_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
@ -350,7 +346,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)
@ -372,14 +368,18 @@ 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, 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:
self.pressed_actions[-1]() self.pressed_actions[-1]()
if not self.auto_repeat: if not self.auto_repeat:
self.auto_repeat = True self.auto_repeat = True
self.restart(self.repeat_action, AUTOREPEAT_PERIOD) self.restart(self.repeat_action, self.AUTOREPEAT_PERIOD)
else: else:
self.auto_repeat = False self.auto_repeat = False
self.stop(self.repeat_action) self.stop(self.repeat_action)
@ -395,19 +395,21 @@ class TetrisLogic():
print(text) print(text)
raise Warning("TetrisLogic.show_text not implemented.") raise Warning("TetrisLogic.show_text not implemented.")
def load_high_score(self): def load_high_score(self, crypted_high_score=None):
self.high_score = 0 if crypted_high_score:
raise Warning( crypted_high_score = int(pickle.loads(crypted_high_score))
"""TetrisLogic.load_high_score not implemented. self.high_score = crypted_high_score ^ CRYPT_KEY
else:
raise Warning(
"""TetrisLogic.load_high_score not implemented.
High score is set to 0""" High score is set to 0"""
) )
self.high_score = 0
def save_high_score(self): def save_high_score(self):
print("High score: {:n}".format(self.high_score)) crypted_high_score = self.high_score ^ CRYPT_KEY
raise Warning( crypted_high_score = pickle.dumps(crypted_high_score)
"""TetrisLogic.save_high_score not implemented. return crypted_high_score
High score is not saved"""
)
def start(task, period): def start(task, period):
raise Warning("TetrisLogic.start is not implemented.") raise Warning("TetrisLogic.start is not implemented.")
@ -418,4 +420,3 @@ High score is not saved"""
def restart(self, task, period): def restart(self, task, period):
self.stop(task) self.stop(task)
self.start(task, period) self.start(task, period)

View File

@ -1,15 +1,14 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from .utils import Coord, Rotation from .utils import Coord, Rotation, Color
class Mino: class Mino:
def __init__(self, color, coord): def __init__(self, color, coord):
self.color = color self.color = color
self.coord = coord self.coord = coord
class MetaTetromino(type): class MetaTetromino(type):
def __init__(cls, name, bases, dct): def __init__(cls, name, bases, dct):
super().__init__(name, bases, dct) super().__init__(name, bases, dct)
Tetromino.shapes.append(cls) Tetromino.shapes.append(cls)
@ -35,10 +34,7 @@ class Tetromino(list):
} }
def __init__(self): def __init__(self):
super().__init__( super().__init__(Mino(self.MINOES_COLOR, coord) for coord in self.MINOES_COORDS)
Mino(self.MINOES_COLOR, coord)
for coord in self.MINOES_COORDS
)
self.orientation = 0 self.orientation = 0
self.last_rotation_point = None self.last_rotation_point = None
self.hold_enabled = True self.hold_enabled = True
@ -47,20 +43,21 @@ class Tetromino(list):
def ghost(self): def ghost(self):
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: (
@ -77,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

@ -1,34 +1,39 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
class Coord: class Coord:
def __init__(self, x, y): def __init__(self, x, y):
self.x = x self.x = x
self.y = y self.y = y
def __add__(self, other): def __add__(self, other):
return Coord(self.x+other.x, self.y+other.y) return Coord(self.x + other.x, self.y + other.y)
class Movement: class Movement:
LEFT = Coord(-1, 0) LEFT = Coord(-1, 0)
RIGHT = Coord( 1, 0) RIGHT = Coord(1, 0)
DOWN = Coord( 0, -1) DOWN = Coord(0, -1)
class Rotation: class Rotation:
CLOCKWISE = 1 CLOCKWISE = 1
COUNTER = -1 COUNTER = -1
class T_Spin: class T_Spin:
NONE = "" NONE = ""
MINI = "MINI\nT-SPIN" MINI = "MINI\nT-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