diff --git a/block.py b/block.py index e988273..5bfe767 100644 --- a/block.py +++ b/block.py @@ -1,12 +1,13 @@ from colors import Colors import pygame from position import Position +from constants import CELL_SIZE class Block: def __init__(self, id): self.id = id self.cells = {} - self.cell_size = 30 + self.cell_size = CELL_SIZE self.row_offset = 0 self.column_offset = 0 self.rotation_state = 0 @@ -37,6 +38,10 @@ def undo_rotation(self): def draw(self, screen, offset_x, offset_y): tiles = self.get_cell_positions() for tile in tiles: - tile_rect = pygame.Rect(offset_x + tile.column * self.cell_size, - offset_y + tile.row * self.cell_size, self.cell_size -1, self.cell_size -1) + tile_rect = pygame.Rect( + offset_x + tile.column * self.cell_size, + offset_y + tile.row * self.cell_size, + self.cell_size - 1, + self.cell_size - 1 + ) pygame.draw.rect(screen, self.colors[self.id], tile_rect) diff --git a/blocks.py b/blocks.py index e039030..3673d33 100644 --- a/blocks.py +++ b/blocks.py @@ -3,7 +3,7 @@ class LBlock(Block): def __init__(self): - super().__init__(id = 1) + super().__init__(id=1) self.cells = { 0: [Position(0, 2), Position(1, 0), Position(1, 1), Position(1, 2)], 1: [Position(0, 1), Position(1, 1), Position(2, 1), Position(2, 2)], @@ -13,64 +13,64 @@ def __init__(self): self.move(0, 3) class JBlock(Block): - def __init__(self): - super().__init__(id = 2) - self.cells = { - 0: [Position(0, 0), Position(1, 0), Position(1, 1), Position(1, 2)], - 1: [Position(0, 1), Position(0, 2), Position(1, 1), Position(2, 1)], - 2: [Position(1, 0), Position(1, 1), Position(1, 2), Position(2, 2)], - 3: [Position(0, 1), Position(1, 1), Position(2, 0), Position(2, 1)] - } - self.move(0, 3) + def __init__(self): + super().__init__(id=2) + self.cells = { + 0: [Position(0, 0), Position(1, 0), Position(1, 1), Position(1, 2)], + 1: [Position(0, 1), Position(0, 2), Position(1, 1), Position(2, 1)], + 2: [Position(1, 0), Position(1, 1), Position(1, 2), Position(2, 2)], + 3: [Position(0, 1), Position(1, 1), Position(2, 0), Position(2, 1)] + } + self.move(0, 3) class IBlock(Block): - def __init__(self): - super().__init__(id = 3) - self.cells = { - 0: [Position(1, 0), Position(1, 1), Position(1, 2), Position(1, 3)], - 1: [Position(0, 2), Position(1, 2), Position(2, 2), Position(3, 2)], - 2: [Position(2, 0), Position(2, 1), Position(2, 2), Position(2, 3)], - 3: [Position(0, 1), Position(1, 1), Position(2, 1), Position(3, 1)] - } - self.move(-1, 3) + def __init__(self): + super().__init__(id=3) + self.cells = { + 0: [Position(1, 0), Position(1, 1), Position(1, 2), Position(1, 3)], + 1: [Position(0, 2), Position(1, 2), Position(2, 2), Position(3, 2)], + 2: [Position(2, 0), Position(2, 1), Position(2, 2), Position(2, 3)], + 3: [Position(0, 1), Position(1, 1), Position(2, 1), Position(3, 1)] + } + self.move(-1, 3) class OBlock(Block): - def __init__(self): - super().__init__(id = 4) - self.cells = { - 0: [Position(0, 0), Position(0, 1), Position(1, 0), Position(1, 1)] - } - self.move(0, 4) + def __init__(self): + super().__init__(id=4) + self.cells = { + 0: [Position(0, 0), Position(0, 1), Position(1, 0), Position(1, 1)] + } + self.move(0, 4) class SBlock(Block): - def __init__(self): - super().__init__(id = 5) - self.cells = { - 0: [Position(0, 1), Position(0, 2), Position(1, 0), Position(1, 1)], - 1: [Position(0, 1), Position(1, 1), Position(1, 2), Position(2, 2)], - 2: [Position(1, 1), Position(1, 2), Position(2, 0), Position(2, 1)], - 3: [Position(0, 0), Position(1, 0), Position(1, 1), Position(2, 1)] - } - self.move(0, 3) + def __init__(self): + super().__init__(id=5) + self.cells = { + 0: [Position(0, 1), Position(0, 2), Position(1, 0), Position(1, 1)], + 1: [Position(0, 1), Position(1, 1), Position(1, 2), Position(2, 2)], + 2: [Position(1, 1), Position(1, 2), Position(2, 0), Position(2, 1)], + 3: [Position(0, 0), Position(1, 0), Position(1, 1), Position(2, 1)] + } + self.move(0, 3) class TBlock(Block): - def __init__(self): - super().__init__(id = 6) - self.cells = { - 0: [Position(0, 1), Position(1, 0), Position(1, 1), Position(1, 2)], - 1: [Position(0, 1), Position(1, 1), Position(1, 2), Position(2, 1)], - 2: [Position(1, 0), Position(1, 1), Position(1, 2), Position(2, 1)], - 3: [Position(0, 1), Position(1, 0), Position(1, 1), Position(2, 1)] - } - self.move(0, 3) + def __init__(self): + super().__init__(id=6) + self.cells = { + 0: [Position(0, 1), Position(1, 0), Position(1, 1), Position(1, 2)], + 1: [Position(0, 1), Position(1, 1), Position(1, 2), Position(2, 1)], + 2: [Position(1, 0), Position(1, 1), Position(1, 2), Position(2, 1)], + 3: [Position(0, 1), Position(1, 0), Position(1, 1), Position(2, 1)] + } + self.move(0, 3) class ZBlock(Block): - def __init__(self): - super().__init__(id = 7) - self.cells = { - 0: [Position(0, 0), Position(0, 1), Position(1, 1), Position(1, 2)], - 1: [Position(0, 2), Position(1, 1), Position(1, 2), Position(2, 1)], - 2: [Position(1, 0), Position(1, 1), Position(2, 1), Position(2, 2)], - 3: [Position(0, 1), Position(1, 0), Position(1, 1), Position(2, 0)] - } - self.move(0, 3) \ No newline at end of file + def __init__(self): + super().__init__(id=7) + self.cells = { + 0: [Position(0, 0), Position(0, 1), Position(1, 1), Position(1, 2)], + 1: [Position(0, 2), Position(1, 1), Position(1, 2), Position(2, 1)], + 2: [Position(1, 0), Position(1, 1), Position(2, 1), Position(2, 2)], + 3: [Position(0, 1), Position(1, 0), Position(1, 1), Position(2, 0)] + } + self.move(0, 3) diff --git a/constants.py b/constants.py new file mode 100644 index 0000000..f837652 --- /dev/null +++ b/constants.py @@ -0,0 +1,26 @@ +CELL_SIZE = 30 +NUM_ROWS = 20 +NUM_COLS = 10 +SCREEN_WIDTH = 500 +SCREEN_HEIGHT = 620 +GAME_SPEED_MS = 200 +MIN_SPEED_MS = 100 +SPEED_STEP = 10 +SPEED_SCORE_INTERVAL = 500 +GRID_OFFSET_X = 11 +GRID_OFFSET_Y = 11 +SCORE_RECT = (320, 55, 170, 60) +NEXT_RECT = (320, 215, 170, 180) +HIGHSCORE_RECT = (320, 400, 170, 60) +SCORE_LABEL_POS = (365, 20) +NEXT_LABEL_POS = (375, 180) +HIGHSCORE_LABEL_POS = (340, 370) +NEXT_BLOCK_DEFAULT_X = 270 +NEXT_BLOCK_DEFAULT_Y = 270 +NEXT_BLOCK_IBLOCK_X = 255 +NEXT_BLOCK_IBLOCK_Y = 290 +NEXT_BLOCK_OBLOCK_X = 255 +NEXT_BLOCK_OBLOCK_Y = 280 +IBLOCK_ID = 3 +OBLOCK_ID = 4 +HIGHSCORE_FILE = "highscore.txt" diff --git a/game.py b/game.py index a610a73..11849f5 100644 --- a/game.py +++ b/game.py @@ -2,6 +2,10 @@ from blocks import * import random import pygame +from constants import ( + GAME_SPEED_MS, MIN_SPEED_MS, SPEED_STEP, SPEED_SCORE_INTERVAL, + GRID_OFFSET_X, GRID_OFFSET_Y, HIGHSCORE_FILE +) class Game: def __init__(self): @@ -10,12 +14,36 @@ def __init__(self): self.current_block = self.get_random_block() self.next_block = self.get_random_block() self.game_over = False + self.paused = False self.score = 0 - self.rotate_sound = pygame.mixer.Sound("Sounds/rotate.ogg") - self.clear_sound = pygame.mixer.Sound("Sounds/clear.ogg") + self.high_score = self.load_high_score() + self.rotate_sound = None + self.clear_sound = None + try: + self.rotate_sound = pygame.mixer.Sound("Sounds/rotate.ogg") + self.clear_sound = pygame.mixer.Sound("Sounds/clear.ogg") + pygame.mixer.music.load("Sounds/music.ogg") + pygame.mixer.music.play(-1) + except: + pass - pygame.mixer.music.load("Sounds/music.ogg") - pygame.mixer.music.play(-1) + def load_high_score(self): + try: + with open(HIGHSCORE_FILE, "r") as f: + return int(f.read()) + except: + return 0 + + def save_high_score(self): + try: + with open(HIGHSCORE_FILE, "w") as f: + f.write(str(self.high_score)) + except: + pass + + def get_speed(self): + reduction = (self.score // SPEED_SCORE_INTERVAL) * SPEED_STEP + return max(MIN_SPEED_MS, GAME_SPEED_MS - reduction) def update_score(self, lines_cleared, move_down_points): if lines_cleared == 1: @@ -25,6 +53,9 @@ def update_score(self, lines_cleared, move_down_points): elif lines_cleared == 3: self.score += 500 self.score += move_down_points + if self.score > self.high_score: + self.high_score = self.score + self.save_high_score() def get_random_block(self): if len(self.blocks) == 0: @@ -35,17 +66,17 @@ def get_random_block(self): def move_left(self): self.current_block.move(0, -1) - if self.block_inside() == False or self.block_fits() == False: + if not self.block_inside() or not self.block_fits(): self.current_block.move(0, 1) def move_right(self): self.current_block.move(0, 1) - if self.block_inside() == False or self.block_fits() == False: + if not self.block_inside() or not self.block_fits(): self.current_block.move(0, -1) def move_down(self): self.current_block.move(1, 0) - if self.block_inside() == False or self.block_fits() == False: + if not self.block_inside() or not self.block_fits(): self.current_block.move(-1, 0) self.lock_block() @@ -57,9 +88,10 @@ def lock_block(self): self.next_block = self.get_random_block() rows_cleared = self.grid.clear_full_rows() if rows_cleared > 0: - self.clear_sound.play() + if self.clear_sound: + self.clear_sound.play() self.update_score(rows_cleared, 0) - if self.block_fits() == False: + if not self.block_fits(): self.game_over = True def reset(self): @@ -72,31 +104,25 @@ def reset(self): def block_fits(self): tiles = self.current_block.get_cell_positions() for tile in tiles: - if self.grid.is_empty(tile.row, tile.column) == False: + if not self.grid.is_empty(tile.row, tile.column): return False return True def rotate(self): self.current_block.rotate() - if self.block_inside() == False or self.block_fits() == False: + if not self.block_inside() or not self.block_fits(): self.current_block.undo_rotation() else: - self.rotate_sound.play() + if self.rotate_sound: + self.rotate_sound.play() def block_inside(self): tiles = self.current_block.get_cell_positions() for tile in tiles: - if self.grid.is_inside(tile.row, tile.column) == False: + if not self.grid.is_inside(tile.row, tile.column): return False return True def draw(self, screen): self.grid.draw(screen) - self.current_block.draw(screen, 11, 11) - - if self.next_block.id == 3: - self.next_block.draw(screen, 255, 290) - elif self.next_block.id == 4: - self.next_block.draw(screen, 255, 280) - else: - self.next_block.draw(screen, 270, 270) \ No newline at end of file + self.current_block.draw(screen, GRID_OFFSET_X, GRID_OFFSET_Y) diff --git a/grid.py b/grid.py index 7b146b9..dea432d 100644 --- a/grid.py +++ b/grid.py @@ -1,29 +1,26 @@ import pygame from colors import Colors +from constants import NUM_ROWS, NUM_COLS, CELL_SIZE, GRID_OFFSET_X, GRID_OFFSET_Y class Grid: def __init__(self): - self.num_rows = 20 - self.num_cols = 10 - self.cell_size = 30 + self.num_rows = NUM_ROWS + self.num_cols = NUM_COLS + self.cell_size = CELL_SIZE self.grid = [[0 for j in range(self.num_cols)] for i in range(self.num_rows)] self.colors = Colors.get_cell_colors() def print_grid(self): for row in range(self.num_rows): for column in range(self.num_cols): - print(self.grid[row][column], end = " ") + print(self.grid[row][column], end=" ") print() def is_inside(self, row, column): - if row >= 0 and row < self.num_rows and column >= 0 and column < self.num_cols: - return True - return False + return 0 <= row < self.num_rows and 0 <= column < self.num_cols def is_empty(self, row, column): - if self.grid[row][column] == 0: - return True - return False + return self.grid[row][column] == 0 def is_row_full(self, row): for column in range(self.num_cols): @@ -37,12 +34,12 @@ def clear_row(self, row): def move_row_down(self, row, num_rows): for column in range(self.num_cols): - self.grid[row+num_rows][column] = self.grid[row][column] + self.grid[row + num_rows][column] = self.grid[row][column] self.grid[row][column] = 0 def clear_full_rows(self): completed = 0 - for row in range(self.num_rows-1, 0, -1): + for row in range(self.num_rows - 1, 0, -1): if self.is_row_full(row): self.clear_row(row) completed += 1 @@ -59,6 +56,10 @@ def draw(self, screen): for row in range(self.num_rows): for column in range(self.num_cols): cell_value = self.grid[row][column] - cell_rect = pygame.Rect(column*self.cell_size + 11, row*self.cell_size + 11, - self.cell_size -1, self.cell_size -1) + cell_rect = pygame.Rect( + column * self.cell_size + GRID_OFFSET_X, + row * self.cell_size + GRID_OFFSET_Y, + self.cell_size - 1, + self.cell_size - 1 + ) pygame.draw.rect(screen, self.colors[cell_value], cell_rect) diff --git a/main.py b/main.py index 144ef7f..b6054b6 100644 --- a/main.py +++ b/main.py @@ -1,6 +1,15 @@ -import pygame,sys +import pygame, sys from game import Game from colors import Colors +from constants import ( + SCREEN_WIDTH, SCREEN_HEIGHT, GAME_SPEED_MS, + SCORE_RECT, NEXT_RECT, HIGHSCORE_RECT, + SCORE_LABEL_POS, NEXT_LABEL_POS, HIGHSCORE_LABEL_POS, + NEXT_BLOCK_DEFAULT_X, NEXT_BLOCK_DEFAULT_Y, + NEXT_BLOCK_IBLOCK_X, NEXT_BLOCK_IBLOCK_Y, + NEXT_BLOCK_OBLOCK_X, NEXT_BLOCK_OBLOCK_Y, + IBLOCK_ID, OBLOCK_ID +) pygame.init() @@ -8,11 +17,13 @@ score_surface = title_font.render("Score", True, Colors.white) next_surface = title_font.render("Next", True, Colors.white) game_over_surface = title_font.render("GAME OVER", True, Colors.white) +highscore_surface = title_font.render("High Score", True, Colors.white) -score_rect = pygame.Rect(320, 55, 170, 60) -next_rect = pygame.Rect(320, 215, 170, 180) +score_rect = pygame.Rect(*SCORE_RECT) +next_rect = pygame.Rect(*NEXT_RECT) +highscore_rect = pygame.Rect(*HIGHSCORE_RECT) -screen = pygame.display.set_mode((500, 620)) +screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) pygame.display.set_caption("Python Tetris") clock = pygame.time.Clock() @@ -20,7 +31,7 @@ game = Game() GAME_UPDATE = pygame.USEREVENT -pygame.time.set_timer(GAME_UPDATE, 200) +pygame.time.set_timer(GAME_UPDATE, GAME_SPEED_MS) while True: for event in pygame.event.get(): @@ -28,36 +39,53 @@ pygame.quit() sys.exit() if event.type == pygame.KEYDOWN: - if game.game_over == True: + if game.game_over: game.game_over = False game.reset() - if event.key == pygame.K_LEFT and game.game_over == False: + if event.key == pygame.K_p and not game.game_over: + game.paused = not game.paused + if event.key == pygame.K_LEFT and not game.game_over and not game.paused: game.move_left() - if event.key == pygame.K_RIGHT and game.game_over == False: + if event.key == pygame.K_RIGHT and not game.game_over and not game.paused: game.move_right() - if event.key == pygame.K_DOWN and game.game_over == False: + if event.key == pygame.K_DOWN and not game.game_over and not game.paused: game.move_down() game.update_score(0, 1) - if event.key == pygame.K_UP and game.game_over == False: + if event.key == pygame.K_UP and not game.game_over and not game.paused: game.rotate() - if event.type == GAME_UPDATE and game.game_over == False: + if event.type == GAME_UPDATE and not game.game_over and not game.paused: game.move_down() + pygame.time.set_timer(GAME_UPDATE, game.get_speed()) - #Drawing score_value_surface = title_font.render(str(game.score), True, Colors.white) + highscore_value_surface = title_font.render(str(game.high_score), True, Colors.white) screen.fill(Colors.dark_blue) - screen.blit(score_surface, (365, 20, 50, 50)) - screen.blit(next_surface, (375, 180, 50, 50)) + screen.blit(score_surface, (*SCORE_LABEL_POS, 50, 50)) + screen.blit(next_surface, (*NEXT_LABEL_POS, 50, 50)) + screen.blit(highscore_surface, (*HIGHSCORE_LABEL_POS, 50, 50)) - if game.game_over == True: + if game.game_over: screen.blit(game_over_surface, (320, 450, 50, 50)) pygame.draw.rect(screen, Colors.light_blue, score_rect, 0, 10) - screen.blit(score_value_surface, score_value_surface.get_rect(centerx = score_rect.centerx, - centery = score_rect.centery)) + screen.blit(score_value_surface, score_value_surface.get_rect( + centerx=score_rect.centerx, centery=score_rect.centery)) + pygame.draw.rect(screen, Colors.light_blue, next_rect, 0, 10) + + pygame.draw.rect(screen, Colors.light_blue, highscore_rect, 0, 10) + screen.blit(highscore_value_surface, highscore_value_surface.get_rect( + centerx=highscore_rect.centerx, centery=highscore_rect.centery)) + game.draw(screen) + if game.next_block.id == IBLOCK_ID: + game.next_block.draw(screen, NEXT_BLOCK_IBLOCK_X, NEXT_BLOCK_IBLOCK_Y) + elif game.next_block.id == OBLOCK_ID: + game.next_block.draw(screen, NEXT_BLOCK_OBLOCK_X, NEXT_BLOCK_OBLOCK_Y) + else: + game.next_block.draw(screen, NEXT_BLOCK_DEFAULT_X, NEXT_BLOCK_DEFAULT_Y) + pygame.display.update() - clock.tick(60) \ No newline at end of file + clock.tick(60)