From 28d7f6d291aa4508053b8f40230ce0f8768e3fc1 Mon Sep 17 00:00:00 2001 From: dan63047 Date: Sat, 22 May 2021 19:39:54 +0300 Subject: [PATCH] init --- .idea/.gitignore | 3 + .../inspectionProfiles/profiles_settings.xml | 6 + .idea/misc.xml | 4 + .idea/modules.xml | 8 + .idea/pygame-tetris.iml | 10 + PressStart2P-vaV7.ttf | Bin 0 -> 82480 bytes test.py | 68 ++ tetris-main.py | 1081 +++++++++++++++++ 8 files changed, 1180 insertions(+) create mode 100755 .idea/.gitignore create mode 100755 .idea/inspectionProfiles/profiles_settings.xml create mode 100755 .idea/misc.xml create mode 100755 .idea/modules.xml create mode 100755 .idea/pygame-tetris.iml create mode 100755 PressStart2P-vaV7.ttf create mode 100755 test.py create mode 100755 tetris-main.py diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100755 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100755 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100755 index 0000000..d56657a --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100755 index 0000000..acacf83 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/pygame-tetris.iml b/.idea/pygame-tetris.iml new file mode 100755 index 0000000..7eb1baf --- /dev/null +++ b/.idea/pygame-tetris.iml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/PressStart2P-vaV7.ttf b/PressStart2P-vaV7.ttf new file mode 100755 index 0000000000000000000000000000000000000000..98044e935e515b6b7c5eb562ea880a7b7dc5179c GIT binary patch literal 82480 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 literal 0 HcmV?d00001 diff --git a/test.py b/test.py new file mode 100755 index 0000000..cf68d3d --- /dev/null +++ b/test.py @@ -0,0 +1,68 @@ +def speed_and_lines_for_levels(level): + if level == 0: + return 48, 10 + elif level == 1: + return 43, 20 + elif level == 2: + return 38, 30 + elif level == 3: + return 33, 40 + elif level == 4: + return 28, 50 + elif level == 5: + return 23, 60 + elif level == 6: + return 18, 70 + elif level == 7: + return 13, 80 + elif level == 8: + return 8, 90 + elif level == 9: + return 6, 100 + elif 10 <= level <= 12: + return 5, 100 + elif level == 11: + return 5, 100 + elif level == 12: + return 5, 100 + elif level == 13: + return 43, 100 + elif level == 14: + return 43, 100 + elif level == 15: + return 43, 100 + elif level == 16: + return 43, 110 + elif level == 17: + return 43, 120 + elif level == 18: + return 43, 130 + elif level == 19: + return 43, 140 + elif level == 20: + return 43, 150 + elif level == 21: + return 43, 160 + elif level == 22: + return 43, 170 + elif level == 23: + return 43, 180 + elif level == 24: + return 43, 190 + elif level == 25: + return 43, 200 + elif level == 26: + return 43, 200 + elif level == 27: + return 43, 200 + elif level == 28: + return 1, 200 + else: + return 1, 200 + + +print(speed_and_lines_for_levels(9)) +print(speed_and_lines_for_levels(10)) +print(speed_and_lines_for_levels(11)) +print(speed_and_lines_for_levels(12)) +print(speed_and_lines_for_levels(13)) diff --git a/tetris-main.py b/tetris-main.py new file mode 100755 index 0000000..987ab19 --- /dev/null +++ b/tetris-main.py @@ -0,0 +1,1081 @@ +from typing import List + +import pygame, random, datetime +from string import Template + +BLOCK_SIZE = 30 +FIELD_SIZE_X = 10 +FIELD_SIZE_Y = 20 +GUIDELINES = ["NES like", "Current"] + + +class Block: + def __init__(self, color): + self.color_str = str(color) + self.color = pygame.Color(color) + + def __str__(self): + return f"Block {self.color_str}" + + def __repr__(self): + return f"Block {self.color_str}" + + +TETROMINOS = [ + [ + [ + [None, None, Block((240, 160, 0)), None], + [Block((240, 160, 0)), Block((240, 160, 0)), Block((240, 160, 0)), None], + [None, None, None, None], + [None, None, None, None] + ], + [ + [None, Block((240, 160, 0)), None, None], + [None, Block((240, 160, 0)), None, None], + [None, Block((240, 160, 0)), Block((240, 160, 0)), None], + [None, None, None, None] + ], + [ + [None, None, None, None], + [Block((240, 160, 0)), Block((240, 160, 0)), Block((240, 160, 0)), None], + [Block((240, 160, 0)), None, None, None], + [None, None, None, None] + ], + [ + [Block((240, 160, 0)), Block((240, 160, 0)), None, None], + [None, Block((240, 160, 0)), None, None], + [None, Block((240, 160, 0)), None, None], + [None, None, None, None] + ] + + ], # 0, L + [ + + [ + [Block((0, 0, 240)), None, None, None], + [Block((0, 0, 240)), Block((0, 0, 240)), Block((0, 0, 240)), None], + [None, None, None, None], + [None, None, None, None] + ], + [ + [None, Block((0, 0, 240)), Block((0, 0, 240)), None], + [None, Block((0, 0, 240)), None, None], + [None, Block((0, 0, 240)), None, None], + [None, None, None, None] + ], + [ + [None, None, None, None], + [Block((0, 0, 240)), Block((0, 0, 240)), Block((0, 0, 240)), None], + [None, None, Block((0, 0, 240)), None], + [None, None, None, None] + ], + [ + [None, Block((0, 0, 240)), None, None], + [None, Block((0, 0, 240)), None, None], + [Block((0, 0, 240)), Block((0, 0, 240)), None, None], + [None, None, None, None] + ] + ], # 1, J + [ + [ + [None, Block((0, 240, 0)), Block((0, 240, 0)), None], + [Block((0, 240, 0)), Block((0, 240, 0)), None, None], + [None, None, None, None], + [None, None, None, None] + ], + [ + [None, Block((0, 240, 0)), None, None], + [None, Block((0, 240, 0)), Block((0, 240, 0)), None], + [None, None, Block((0, 240, 0)), None], + [None, None, None, None] + ], + [ + [None, None, None, None], + [None, Block((0, 240, 0)), Block((0, 240, 0)), None], + [Block((0, 240, 0)), Block((0, 240, 0)), None, None], + [None, None, None, None] + ], + [ + [Block((0, 240, 0)), None, None, None], + [Block((0, 240, 0)), Block((0, 240, 0)), None, None], + [None, Block((0, 240, 0)), None, None], + [None, None, None, None] + ] + ], # 2, S + [ + [ + [Block((240, 0, 0)), Block((240, 0, 0)), None, None], + [None, Block((240, 0, 0)), Block((240, 0, 0)), None], + [None, None, None, None], + [None, None, None, None] + ], + [ + [None, None, Block((240, 0, 0)), None], + [None, Block((240, 0, 0)), Block((240, 0, 0)), None], + [None, Block((240, 0, 0)), None, None], + [None, None, None, None] + ], + [ + [None, None, None, None], + [Block((240, 0, 0)), Block((240, 0, 0)), None, None], + [None, Block((240, 0, 0)), Block((240, 0, 0)), None], + [None, None, None, None] + ], + [ + [None, Block((240, 0, 0)), None, None], + [Block((240, 0, 0)), Block((240, 0, 0)), None, None], + [Block((240, 0, 0)), None, None, None], + [None, None, None, None] + ] + ], # 3, Z + [ + [ + [None, Block((160, 0, 240)), None, None], + [Block((160, 0, 240)), Block((160, 0, 240)), Block((160, 0, 240)), None], + [None, None, None, None], + [None, None, None, None] + ], + [ + [None, Block((160, 0, 240)), None, None], + [None, Block((160, 0, 240)), Block((160, 0, 240)), None], + [None, Block((160, 0, 240)), None, None], + [None, None, None, None] + ], + [ + [None, None, None, None], + [Block((160, 0, 240)), Block((160, 0, 240)), Block((160, 0, 240)), None], + [None, Block((160, 0, 240)), None, None], + [None, None, None, None] + ], + [ + [None, Block((160, 0, 240)), None, None], + [Block((160, 0, 240)), Block((160, 0, 240)), None, None], + [None, Block((160, 0, 240)), None, None], + [None, None, None, None] + ] + ], # 4, T + [ + [ + [None, None, None, None], + [Block((0, 240, 240)), Block((0, 240, 240)), Block((0, 240, 240)), Block((0, 240, 240))], + [None, None, None, None], + [None, None, None, None] + ], + [ + [None, None, Block((0, 240, 240)), None], + [None, None, Block((0, 240, 240)), None], + [None, None, Block((0, 240, 240)), None], + [None, None, Block((0, 240, 240)), None] + ], + [ + [None, None, None, None], + [None, None, None, None], + [Block((0, 240, 240)), Block((0, 240, 240)), Block((0, 240, 240)), Block((0, 240, 240))], + [None, None, None, None] + ], + [ + [None, Block((0, 240, 240)), None, None], + [None, Block((0, 240, 240)), None, None], + [None, Block((0, 240, 240)), None, None], + [None, Block((0, 240, 240)), None, None] + ] + + ], # 5, I + [ + [ + [Block((255, 240, 0)), Block((255, 240, 0)), None, None], + [Block((255, 240, 0)), Block((255, 240, 0)), None, None], + [None, None, None, None], + [None, None, None, None] + ] + ] # 6, O +] + + +class DeltaTemplate(Template): + delimiter = "%" + +def strfdelta(tdelta, fmt): + d = {"D": tdelta.days} + hours, rem = divmod(tdelta.seconds, 3600) + minutes, seconds = divmod(rem, 60) + hours += d["D"] * 24 + d["S"] = '{:02d}'.format(seconds) + d["s"] = seconds + t = DeltaTemplate(fmt) + d["Z"] = '{:02d}'.format(int(tdelta.microseconds / 10000)) + d["z"] = '{:1d}'.format(int(tdelta.microseconds / 100000)) + d["H"] = hours + d["M"] = '{:02d}'.format(minutes) + d["m"] = minutes + return t.substitute(**d) + + +def speed_and_lines_for_levels(level): + if level == 0: + return 48, 10 + elif level == 1: + return 43, 20 + elif level == 2: + return 38, 30 + elif level == 3: + return 33, 40 + elif level == 4: + return 28, 50 + elif level == 5: + return 23, 60 + elif level == 6: + return 18, 70 + elif level == 7: + return 13, 80 + elif level == 8: + return 8, 90 + elif level == 9: + return 6, 100 + elif 10 <= level <= 12: + return 5, 100 + elif 13 <= level <= 15: + return 4, 100 + elif level == 16: + return 3, 110 + elif level == 17: + return 3, 120 + elif level == 18: + return 3, 130 + elif level == 19: + return 2, 140 + elif level == 20: + return 2, 150 + elif level == 21: + return 2, 160 + elif level == 22: + return 2, 170 + elif level == 23: + return 2, 180 + elif level == 24: + return 2, 190 + elif 25 <= level <= 28: + return 2, 200 + else: + return 1, 200 + + +class TetrisGameplay: + def __init__(self, size_x=FIELD_SIZE_X, size_y=FIELD_SIZE_Y, level=0, buffer_zone=20, srs=True, lock_delay=True, seven_bag=True, ghost_piece=True, hold=True, hard_drop=True, handling=(167, 33), nes_mechanics=False, next_len=3): + self.buffer_y = buffer_zone + self.FIELD = list(range(size_y + buffer_zone)) + y = 0 + while y != len(self.FIELD): + self.FIELD[y] = list(range(size_x)) + x = 0 + while x != size_x: + self.FIELD[y][x] = None + x += 1 + y += 1 + self.current_posx = 4 + self.current_posy = self.buffer_y - 2 + self.can_hard_drop = hard_drop + self.support_combo_and_btb_bonuses = False + self.support_srs = srs + self.handling = handling + self.support_hold = hold + self.nes_mechanics = nes_mechanics + self.support_ghost_piece = ghost_piece + self.support_lock_delay = lock_delay + self.support_garbage = False + self.seven_bag_random = seven_bag + self.next_length = next_len + self.score = [ + 0, # 0, Soft Drop + 0, # 1, Hard Drop + 0, # 2, Single + 0, # 3, Double + 0, # 4, Triple + 0, # 5, Tetris + 0, # 6, T-Spin Mini no lines + 0, # 7, T-Spin Mini Single + 0, # 8, T-Spin Mini Double + 0, # 9, T-Spin no lines + 0, # 10, T-Spin Single + 0, # 11, T-Spin Double + 0, # 12, T-Spin Triple + 0, # 13, Combo Bonus + 0 # 14, Back-to-Back bonus + ] + self.cleared_lines = [ + 0, # Single + 0, # Double + 0, # Triple + 0 # Tetris + ] + self.pieces = [ + 0, # L piece + 0, # J piece + 0, # S piece + 0, # Z piece + 0, # T piece + 0, # I piece + 0 # O piece + ] + self.game_time = 0 + self.next_queue = [] + if self.seven_bag_random: + self.next_queue = [0, 1, 2, 3, 4, 5, 6] + random.shuffle(self.next_queue) + self.current_id = self.next_queue[0] + self.next_queue.pop(0) + else: + self.current_id = random.randint(0, 6) + self.next_queue = [random.randint(0, 6) for i in range(self.next_length+1)] + self.hold_id = None + self.hold_locked = False + self.spin_is_last_move = False + self.spin_is_kick_t_piece = False + self.current_spin_id = 0 + self.pieces[self.current_id] += 1 + self.lock_delay_run = False + self.lock_delay_frames = 30 + self.lines_for_level_up = speed_and_lines_for_levels(level)[1] + self.start_level = level + self.level = level + self.game_over = False + + def spawn_tetromino(self): + if self.collision(4, self.buffer_y-2, self.next_queue[0], 0): + self.game_over = True + self.current_posx = 4 + self.current_posy = self.buffer_y - 2 + self.current_id = self.next_queue[0] + self.hold_locked = False + self.spin_is_last_move = False + self.spin_is_kick_t_piece = False + self.pieces[self.current_id] += 1 + self.current_spin_id = 0 + self.next_queue.pop(0) + if len(self.next_queue) == self.next_length: + if self.seven_bag_random: + next_bag = [0, 1, 2, 3, 4, 5, 6] + random.shuffle(next_bag) + self.next_queue.extend(next_bag) + else: + ext = [random.randint(0, 6) for i in range(self.next_length+1)] + self.next_queue.extend(ext) + + def hold_tetromino(self): + self.current_spin_id = 0 + self.spin_is_kick_t_piece = False + self.reset_lock_delay() + if self.hold_id is not None: + self.current_id, self.hold_id = self.hold_id, self.current_id + self.current_posx = 4 + self.current_posy = self.buffer_y - 2 + self.hold_locked = True + else: + self.hold_id = self.current_id + self.spawn_tetromino() + self.hold_locked = True + + def __str__(self): + return f"size_x={len(self.FIELD[0])}, size_y={len(self.FIELD)}, buffer_y: {self.buffer_y}" + + def clear_lines(self): + cleared = 0 + frames_delay = 0 + t_spin = False + t_spin_mini = False + height = None + t_spin_corners = [ + [[(0, 0), (2, 0)], [(0, 2), (2, 2)]], + [[(2, 0), (2, 2)], [(0, 0), (0, 2)]], + [[(0, 2), (2, 2)], [(0, 0), (2, 0)]], + [[(0, 2), (0, 0)], [(2, 0), (2, 2)]] + ] + if self.current_id == 4 and self.spin_is_last_move: + front_col = 0 + back_col = 0 + for i in t_spin_corners[self.current_spin_id][0]: + if self.current_posy+i[1] >= len(self.FIELD) or self.current_posx+i[0] >= len(self.FIELD[self.current_posy+i[1]]) or self.current_posy+i[1] < 0 or self.current_posx+i[0] < 0 or self.FIELD[self.current_posy+i[1]][self.current_posx+i[0]] is not None: + front_col += 1 + for i in t_spin_corners[self.current_spin_id][1]: + if self.current_posy+i[1] >= len(self.FIELD) or self.current_posx+i[0] >= len(self.FIELD[self.current_posy+i[1]]) or self.current_posy+i[1] < 0 or self.current_posx+i[0] < 0 or self.FIELD[self.current_posy+i[1]][self.current_posx+i[0]] is not None: + back_col += 1 + if (front_col == 2 and back_col >= 1) or (back_col == 2 and front_col == 1 and self.spin_is_kick_t_piece): + t_spin = True + elif back_col == 2 and front_col == 1: + t_spin_mini = True + y = len(self.FIELD) + for i in self.FIELD: + ic = 0 + for k in i: + if k is not None: + ic += 1 + if ic == FIELD_SIZE_X: + cleared += 1 + self.FIELD.remove(i) + new = list(range(FIELD_SIZE_X)) + x = 0 + while x != FIELD_SIZE_X: + new[x] = None + x += 1 + self.FIELD.insert(0, new) + y -= 1 + if ic > 0 and height is None: + height = y + + if cleared > 0: + self.cleared_lines[cleared - 1] += cleared + if t_spin: + if cleared == 1: + self.score[10] += 800 * (min(self.level, 29) + 1) + elif cleared == 2: + self.score[11] += 1200 * (min(self.level, 29) + 1) + elif cleared == 3: + self.score[12] += 1600 * (min(self.level, 29) + 1) + elif t_spin_mini: + if cleared == 1: + self.score[7] += 200 * (min(self.level, 29) + 1) + elif cleared == 2: + self.score[8] += 400 * (min(self.level, 29) + 1) + if cleared == 1: + self.score[2] += 100 * (min(self.level, 29) + 1) + elif cleared == 2: + self.score[3] += 300 * (min(self.level, 29) + 1) + elif cleared == 3: + self.score[4] += 500 * (min(self.level, 29) + 1) + elif cleared == 4: + self.score[5] += 800 * (min(self.level, 29) + 1) + if sum(self.cleared_lines) >= self.lines_for_level_up: + self.level += 1 + self.lines_for_level_up += 10 + else: + if t_spin: + self.score[9] += 400 * (min(self.level, 29) + 1) + elif t_spin_mini: + self.score[6] += 100 * (min(self.level, 29) + 1) + return 0 + + def collision(self, next_posx, next_posy, next_id, next_spin_id): + i1 = next_posy + k1 = next_posx + for i in TETROMINOS[next_id][next_spin_id]: + for k in i: + if k and (i1 >= len(self.FIELD) or k1 >= len(self.FIELD[i1]) or i1 < 0 or k1 < 0 or self.FIELD[i1][k1]): + return True + k1 += 1 + k1 = next_posx + i1 += 1 + return False + + def spin(self, reverse=False): + self.reset_lock_delay() + if self.current_id != 6: + if reverse: + future_spin_id = self.current_spin_id - 1 + else: + future_spin_id = self.current_spin_id + 1 + future_spin_id %= 4 + if not self.collision(self.current_posx, self.current_posy, self.current_id, future_spin_id): + self.current_spin_id = future_spin_id + self.spin_is_last_move = True + return + if self.support_srs: + if self.current_id != 5: + if (self.current_spin_id == 0 or self.current_spin_id == 2) and future_spin_id == 1: + if not self.collision(self.current_posx-1, self.current_posy, self.current_id, future_spin_id): + self.current_spin_id = future_spin_id + self.current_posx -= 1 + self.spin_is_last_move = True + return + elif not self.collision(self.current_posx-1, self.current_posy-1, self.current_id, future_spin_id): + self.current_spin_id = future_spin_id + self.current_posx -= 1 + self.current_posy -= 1 + self.spin_is_last_move = True + return + elif not self.collision(self.current_posx, self.current_posy+2, self.current_id, future_spin_id): + self.current_spin_id = future_spin_id + self.current_posy += 2 + self.spin_is_last_move = True + return + elif not self.collision(self.current_posx-1, self.current_posy+2, self.current_id, future_spin_id): + self.current_spin_id = future_spin_id + self.current_posx -= 1 + self.current_posy += 2 + self.spin_is_last_move = True + self.spin_is_kick_t_piece = True + return + elif self.current_spin_id == 1 and (future_spin_id == 0 or future_spin_id == 2): + if not self.collision(self.current_posx+1, self.current_posy, self.current_id, future_spin_id): + self.current_spin_id = future_spin_id + self.current_posx += 1 + self.spin_is_last_move = True + return + elif not self.collision(self.current_posx+1, self.current_posy+1, self.current_id, future_spin_id): + self.current_spin_id = future_spin_id + self.current_posx += 1 + self.current_posy += 1 + self.spin_is_last_move = True + return + elif not self.collision(self.current_posx, self.current_posy-2, self.current_id, future_spin_id): + self.current_spin_id = future_spin_id + self.current_posy -= 2 + self.spin_is_last_move = True + return + elif not self.collision(self.current_posx+1, self.current_posy-2, self.current_id, future_spin_id): + self.current_spin_id = future_spin_id + self.current_posx += 1 + self.current_posy -= 2 + self.spin_is_last_move = True + self.spin_is_kick_t_piece = True + return + elif (self.current_spin_id == 0 or self.current_spin_id == 2) and future_spin_id == 3: + if not self.collision(self.current_posx+1, self.current_posy, self.current_id, future_spin_id): + self.current_spin_id = future_spin_id + self.current_posx += 1 + self.spin_is_last_move = True + return + elif not self.collision(self.current_posx+1, self.current_posy-1, self.current_id, future_spin_id): + self.current_spin_id = future_spin_id + self.current_posx += 1 + self.current_posy -= 1 + self.spin_is_last_move = True + return + elif not self.collision(self.current_posx, self.current_posy+2, self.current_id, future_spin_id): + self.current_spin_id = future_spin_id + self.current_posy += 2 + self.spin_is_last_move = True + return + elif not self.collision(self.current_posx+1, self.current_posy+2, self.current_id, future_spin_id): + self.current_spin_id = future_spin_id + self.current_posx += 1 + self.current_posy += 2 + self.spin_is_last_move = True + self.spin_is_kick_t_piece = True + return + elif self.current_spin_id == 3 and (future_spin_id == 0 or future_spin_id == 2): + if not self.collision(self.current_posx-1, self.current_posy, self.current_id, future_spin_id): + self.current_spin_id = future_spin_id + self.current_posx -= 1 + self.spin_is_last_move = True + return + elif not self.collision(self.current_posx+1, self.current_posy+1, self.current_id, future_spin_id): + self.current_spin_id = future_spin_id + self.current_posx -= 1 + self.current_posy += 1 + self.spin_is_last_move = True + return + elif not self.collision(self.current_posx, self.current_posy-2, self.current_id, future_spin_id): + self.current_spin_id = future_spin_id + self.current_posy -= 2 + self.spin_is_last_move = True + return + elif not self.collision(self.current_posx+1, self.current_posy-2, self.current_id, future_spin_id): + self.current_spin_id = future_spin_id + self.current_posx += 1 + self.current_posy -= 2 + self.spin_is_last_move = True + self.spin_is_kick_t_piece = True + return + else: + if (self.current_spin_id == 0 and future_spin_id == 1) or (self.current_spin_id == 3 and future_spin_id == 2): + if not self.collision(self.current_posx-2, self.current_posy, self.current_id, future_spin_id): + self.current_spin_id = future_spin_id + self.current_posx -= 2 + self.spin_is_last_move = True + return + elif not self.collision(self.current_posx+1, self.current_posy, self.current_id, future_spin_id): + self.current_spin_id = future_spin_id + self.current_posx += 1 + self.spin_is_last_move = True + return + elif not self.collision(self.current_posx-2, self.current_posy+1, self.current_id, future_spin_id): + self.current_spin_id = future_spin_id + self.current_posx -= 2 + self.current_posy += 1 + self.spin_is_last_move = True + return + elif not self.collision(self.current_posx+1, self.current_posy-2, self.current_id, future_spin_id): + self.current_spin_id = future_spin_id + self.current_posx += 1 + self.current_posy -= 2 + self.spin_is_last_move = True + return + elif (self.current_spin_id == 1 and future_spin_id == 0) or (self.current_spin_id == 2 and future_spin_id == 3): + if not self.collision(self.current_posx+2, self.current_posy, self.current_id, future_spin_id): + self.current_spin_id = future_spin_id + self.current_posx += 2 + self.spin_is_last_move = True + return + elif not self.collision(self.current_posx-1, self.current_posy, self.current_id, future_spin_id): + self.current_spin_id = future_spin_id + self.current_posx -= 1 + self.spin_is_last_move = True + return + elif not self.collision(self.current_posx+2, self.current_posy-1, self.current_id, future_spin_id): + self.current_spin_id = future_spin_id + self.current_posx += 2 + self.current_posy -= 1 + self.spin_is_last_move = True + return + elif not self.collision(self.current_posx-1, self.current_posy+2, self.current_id, future_spin_id): + self.current_spin_id = future_spin_id + self.current_posx -= 1 + self.current_posy += 2 + self.spin_is_last_move = True + return + elif (self.current_spin_id == 1 and future_spin_id == 2) or (self.current_spin_id == 0 and future_spin_id == 3): + if not self.collision(self.current_posx-1, self.current_posy, self.current_id, future_spin_id): + self.current_spin_id = future_spin_id + self.current_posx -= 1 + self.spin_is_last_move = True + return + elif not self.collision(self.current_posx+2, self.current_posy, self.current_id, future_spin_id): + self.current_spin_id = future_spin_id + self.current_posx += 2 + self.spin_is_last_move = True + return + elif not self.collision(self.current_posx-1, self.current_posy-2, self.current_id, future_spin_id): + self.current_spin_id = future_spin_id + self.current_posx -= 1 + self.current_posy -= 2 + self.spin_is_last_move = True + return + elif not self.collision(self.current_posx+2, self.current_posy+1, self.current_id, future_spin_id): + self.current_spin_id = future_spin_id + self.current_posx += 2 + self.current_posy += 1 + self.spin_is_last_move = True + return + elif (self.current_spin_id == 2 and future_spin_id == 1) or (self.current_spin_id == 3 and future_spin_id == 0): + if not self.collision(self.current_posx+1, self.current_posy, self.current_id, future_spin_id): + self.current_spin_id = future_spin_id + self.current_posx += 1 + self.spin_is_last_move = True + return + elif not self.collision(self.current_posx-2, self.current_posy, self.current_id, future_spin_id): + self.current_spin_id = future_spin_id + self.current_posx -= 2 + self.spin_is_last_move = True + return + elif not self.collision(self.current_posx+1, self.current_posy+2, self.current_id, future_spin_id): + self.current_spin_id = future_spin_id + self.current_posx += 1 + self.current_posy += 2 + self.spin_is_last_move = True + return + elif not self.collision(self.current_posx-2, self.current_posy-1, self.current_id, future_spin_id): + self.current_spin_id = future_spin_id + self.current_posx -= 2 + self.current_posy -= 1 + self.spin_is_last_move = True + return + + def move_side(self, x_change): + if not self.collision(self.current_posx + x_change, self.current_posy, self.current_id, self.current_spin_id): + self.current_posx += x_change + self.reset_lock_delay() + self.spin_is_last_move = False + + def save_state(self): + i1 = self.current_posy + k1 = self.current_posx + if self.current_id is not None: + for i in TETROMINOS[self.current_id][self.current_spin_id]: + for k in i: + if k: + self.FIELD[i1][k1] = k + k1 += 1 + k1 = self.current_posx + i1 += 1 + + def ghost_piece_y(self): + y = self.current_posy + while not self.collision(self.current_posx, y + 1, self.current_id, + self.current_spin_id): + y += 1 + return y + + def reset_lock_delay(self): + self.lock_delay_frames = 30 + self.lock_delay_run = False + + def move_down(self, by_player=False, instant=False): + if not self.collision(self.current_posx, self.current_posy + 1, self.current_id, self.current_spin_id): + if instant: + add_to_score = 0 + while not self.collision(self.current_posx, self.current_posy + 1, self.current_id, + self.current_spin_id): + self.current_posy += 1 + add_to_score += 2 + self.score[1] += add_to_score + self.spin_is_last_move = False + else: + self.current_posy += 1 + self.spin_is_last_move = False + if by_player: + self.score[0] += 1 + return True + else: + return False + + def draw_game(self): + win.fill((25, 25, 25)) + pygame.draw.rect(win, (0, 0, 0), + (5, (BLOCK_SIZE * 2 + 5), BLOCK_SIZE * FIELD_SIZE_X, BLOCK_SIZE * FIELD_SIZE_Y)) + x = 0 + y = -self.buffer_y + for i in self.FIELD: + for k in i: + window_x = 5 + BLOCK_SIZE * x + window_y = (BLOCK_SIZE * 2 + 5) + BLOCK_SIZE * y + if k is not None: + pygame.draw.rect(win, k.color, (window_x, window_y, BLOCK_SIZE, BLOCK_SIZE)) + pygame.draw.rect(win, (0, 0, 0), (window_x, window_y, BLOCK_SIZE, BLOCK_SIZE), width=2) + else: + pygame.draw.rect(win, (25, 25, 25), (window_x, window_y, BLOCK_SIZE, BLOCK_SIZE), width=1) + x += 1 + x = 0 + y += 1 + i1 = self.current_posy - self.buffer_y + k1 = self.current_posx + if self.current_id is not None: + for i in TETROMINOS[self.current_id][self.current_spin_id]: + for k in i: + if k is not None: + window_x = 5 + BLOCK_SIZE * k1 + window_y = (BLOCK_SIZE * 2 + 5) + BLOCK_SIZE * i1 + pygame.draw.rect(win, (int(k.color[0]*self.lock_delay_frames/30), int(k.color[1]*self.lock_delay_frames/30), int(k.color[2]*self.lock_delay_frames/30)), (window_x, window_y, BLOCK_SIZE, BLOCK_SIZE)) + pygame.draw.rect(win, (0, 0, 0), (window_x, window_y, BLOCK_SIZE, BLOCK_SIZE), width=2) + k1 += 1 + k1 = self.current_posx + i1 += 1 + if self.support_ghost_piece: + i1 = self.ghost_piece_y() - self.buffer_y + k1 = self.current_posx + for i in TETROMINOS[self.current_id][self.current_spin_id]: + for k in i: + if k is not None: + window_x = 5 + BLOCK_SIZE * k1 + window_y = (BLOCK_SIZE * 2 + 5) + BLOCK_SIZE * i1 + pygame.draw.rect(win, (k.color[0], k.color[1], k.color[2]), (window_x+5, window_y+5, BLOCK_SIZE-10, BLOCK_SIZE-10), width=10, border_radius=1) + k1 += 1 + k1 = self.current_posx + i1 += 1 + x_offset = 0 + for q in range(0, self.next_length): + i1 = 0 + k1 = 0 + for i in TETROMINOS[self.next_queue[q]][0]: + for k in i: + if k is not None: + window_x = 5 + BLOCK_SIZE * FIELD_SIZE_X + 20 + 10 * k1 + x_offset + window_y = (BLOCK_SIZE * 2 + 30) + 10 * i1 + pygame.draw.rect(win, k.color, (window_x, window_y, 10, 10)) + pygame.draw.rect(win, (0, 0, 0), (window_x, window_y, 10, 10), width=1) + k1 += 1 + k1 = 0 + i1 += 1 + x_offset += 45 + if self.support_hold: + if self.hold_id is not None: + i1 = 0 + k1 = 0 + for i in TETROMINOS[self.hold_id][0]: + for k in i: + if k is not None: + window_x = 5 + BLOCK_SIZE * FIELD_SIZE_X + 20 + 10 * k1 + window_y = (BLOCK_SIZE * 2 + 75) + 10 * i1 + pygame.draw.rect(win, k.color, (window_x, window_y, 10, 10)) + pygame.draw.rect(win, (0, 0, 0), (window_x, window_y, 10, 10), width=1) + k1 += 1 + k1 = 0 + i1 += 1 + win.blit(FONT.render("SCORE", 1, (255, 255, 255)), + (5 + BLOCK_SIZE * FIELD_SIZE_X + 20 + BLOCK_SIZE * k1, 25 + BLOCK_SIZE * 7)) + total_score = sum(self.score) + win.blit(FONT.render(f"{total_score:06d}", 1, (255, 255, 255)), + (5 + BLOCK_SIZE * FIELD_SIZE_X + 20 + BLOCK_SIZE * k1, 50 + BLOCK_SIZE * 7)) + win.blit(FONT.render("LINES", 1, (255, 255, 255)), + (5 + BLOCK_SIZE * FIELD_SIZE_X + 20 + BLOCK_SIZE * k1, 90 + BLOCK_SIZE * 7)) + total_lines = sum(self.cleared_lines) + win.blit(FONT.render(f"{total_lines:03d}", 1, (255, 255, 255)), + (5 + BLOCK_SIZE * FIELD_SIZE_X + 20 + BLOCK_SIZE * k1, 115 + BLOCK_SIZE * 7)) + win.blit(FONT.render("LV", 1, (255, 255, 255)), + (5 + BLOCK_SIZE * FIELD_SIZE_X + 20 + BLOCK_SIZE * k1, 155 + BLOCK_SIZE * 7)) + win.blit(FONT.render(f"{self.level:02d}", 1, (255, 255, 255)), + (5 + BLOCK_SIZE * FIELD_SIZE_X + 20 + BLOCK_SIZE * k1, 180 + BLOCK_SIZE * 7)) + try: + tetris_rate = int((self.cleared_lines[3] / total_lines) * 100) + except ZeroDivisionError: + tetris_rate = 0 + win.blit(FONT.render("TRT", 1, (255, 255, 255)), + (5 + BLOCK_SIZE * FIELD_SIZE_X + 120 + BLOCK_SIZE * k1, 155 + BLOCK_SIZE * 7)) + if tetris_rate == 100: + win.blit(FONT.render(f"{tetris_rate}", 1, (255, 255, 255)), + (5 + BLOCK_SIZE * FIELD_SIZE_X + 120 + BLOCK_SIZE * k1, 180 + BLOCK_SIZE * 7)) + else: + win.blit(FONT.render(f"{tetris_rate:02d}%", 1, (255, 255, 255)), + (5 + BLOCK_SIZE * FIELD_SIZE_X + 120 + BLOCK_SIZE * k1, 180 + BLOCK_SIZE * 7)) + win.blit(FONT.render("TIME", 1, (255, 255, 255)), + (5 + BLOCK_SIZE * FIELD_SIZE_X + 20 + BLOCK_SIZE * k1, 220 + BLOCK_SIZE * 7)) + if self.game_time < 10: + win.blit(FONT.render(strfdelta(datetime.timedelta(seconds=self.game_time), '%s.%Z'), 1, (255, 255, 255)), + (5 + BLOCK_SIZE * FIELD_SIZE_X + 20 + BLOCK_SIZE * k1, 245 + BLOCK_SIZE * 7)) + elif self.game_time < 60: + win.blit(FONT.render(strfdelta(datetime.timedelta(seconds=self.game_time), '%S.%z'), 1, (255, 255, 255)), + (5 + BLOCK_SIZE * FIELD_SIZE_X + 20 + BLOCK_SIZE * k1, 245 + BLOCK_SIZE * 7)) + elif self.game_time < 3600: + win.blit(FONT.render(strfdelta(datetime.timedelta(seconds=self.game_time), '%m:%S'), 1, (255, 255, 255)), + (5 + BLOCK_SIZE * FIELD_SIZE_X + 20 + BLOCK_SIZE * k1, 245 + BLOCK_SIZE * 7)) + else: + win.blit(FONT.render(strfdelta(datetime.timedelta(seconds=self.game_time), '%H:%M:%S'), 1, (255, 255, 255)), + (5 + BLOCK_SIZE * FIELD_SIZE_X + 20 + BLOCK_SIZE * k1, 245 + BLOCK_SIZE * 7)) + if self.game_over: + text_size_x = FONT.size("GAME")[0] + pygame.draw.rect(win, (0, 0, 0), ( + BLOCK_SIZE * (FIELD_SIZE_X / 2) - text_size_x, BLOCK_SIZE * FIELD_SIZE_Y / 2, text_size_x * 2 + 10, 60)) + win.blit(FONT.render("GAME", 1, (255, 255, 255)), + (5 + BLOCK_SIZE * FIELD_SIZE_X / 2 - text_size_x / 2, 5 + BLOCK_SIZE * FIELD_SIZE_Y / 2)) + win.blit(FONT.render("OVER", 1, (255, 255, 255)), + (5 + BLOCK_SIZE * FIELD_SIZE_X / 2 - text_size_x / 2, 5 + BLOCK_SIZE * FIELD_SIZE_Y / 2 + 25)) + pygame.display.update() + + def draw_game_stats(self, on_pause): + win.fill((25, 25, 25)) + if on_pause: + win.blit(FONT.render("GAME PAUSED", 1, (255, 255, 255)), (25, 25)) + else: + win.blit(FONT.render("STATISTIC", 1, (255, 255, 255)), (25, 25)) + total_score = sum(self.score) + win.blit(FONT.render(f"SCORE {total_score:16d}", 1, (255, 255, 255)), (25, 100)) + total_pieces = sum(self.pieces) + win.blit(FONT.render(f"PIECES {total_pieces:15d}", 1, (255, 255, 255)), (25, 130)) + total_lines = sum(self.cleared_lines) + win.blit(FONT.render(f"LINES {total_lines:16d}", 1, (255, 255, 255)), (25, 160)) + try: + tetris_rate = self.cleared_lines[3] / total_lines + except ZeroDivisionError: + tetris_rate = 0 + win.blit(FONT.render(f"TETRIS RATE {tetris_rate:10.2%}", 1, (255, 255, 255)), (25, 190)) + win.blit(FONT.render(f"LEVELS {self.start_level:02d}-{self.level:02d}", 1, (255, 255, 255)), (25, 220)) + win.blit( + FONT.render(f"TIME {strfdelta(datetime.timedelta(seconds=self.game_time), '%H:%M:%S.%Z'):>17s}", 1, (255, 255, 255)), + (25, 250)) + win.blit(SMALL_FONT.render(f"BURNED TIMES LINES PIECES TABLE", 1, (255, 255, 255)), (25, 290)) + win.blit( + SMALL_FONT.render(f"SINGLE {self.cleared_lines[0]:5d} {self.cleared_lines[0]:5d} L {self.pieces[0]:<4d} J {self.pieces[1]:<4d}", 1, (255, 255, 255)), + (25, 310)) + double_times = int(self.cleared_lines[1] / 2) + win.blit(SMALL_FONT.render(f"DOUBLE {double_times:5d} {self.cleared_lines[1]:5d} S {self.pieces[2]:<4d} Z {self.pieces[3]:<4d}", 1, (255, 255, 255)), + (25, 325)) + triple_times = int(self.cleared_lines[2] / 3) + win.blit(SMALL_FONT.render(f"TRIPLE {triple_times:5d} {self.cleared_lines[2]:5d} T {self.pieces[4]:<4d} O {self.pieces[6]:<4d}", 1, (255, 255, 255)), + (25, 340)) + tetris_times = int(self.cleared_lines[3] / 4) + win.blit(SMALL_FONT.render(f"TETRIS {tetris_times:5d} {self.cleared_lines[3]:5d} I {self.pieces[5]:<10d}", 1, (255, 255, 255)), + (25, 355)) + win.blit(SMALL_FONT.render(f"SCORE TABLE", 1, (255, 255, 255)), (25, 380)) + win.blit(SMALL_FONT.render(f"SOFT DROPS {self.score[0]:14d}", 1, (255, 255, 255)), (25, 400)) + win.blit(SMALL_FONT.render(f"HARD DROPS {self.score[1]:14d}", 1, (255, 255, 255)), (25, 415)) + win.blit(SMALL_FONT.render(f"SINGLE {self.score[2]:18d}", 1, (255, 255, 255)), (25, 430)) + win.blit(SMALL_FONT.render(f"DOUBLE {self.score[3]:18d}", 1, (255, 255, 255)), (25, 445)) + win.blit(SMALL_FONT.render(f"TRIPLE {self.score[4]:18d}", 1, (255, 255, 255)), (25, 460)) + win.blit(SMALL_FONT.render(f"TETRIS {self.score[5]:18d}", 1, (255, 255, 255)), (25, 475)) + win.blit(SMALL_FONT.render(f"T-SPIN MINI NO L. {self.score[6]:7d}", 1, (255, 255, 255)), (25, 490)) + win.blit(SMALL_FONT.render(f"T-SPIN MINI SINGLE {self.score[7]:6d}", 1, (255, 255, 255)), (25, 505)) + win.blit(SMALL_FONT.render(f"T-SPIN MINI DOUBLE {self.score[8]:6d}", 1, (255, 255, 255)), (25, 520)) + win.blit(SMALL_FONT.render(f"T-SPIN NO LINES {self.score[9]:9d}", 1, (255, 255, 255)), (25, 535)) + win.blit(SMALL_FONT.render(f"T-SPIN SINGLE {self.score[10]:11d}", 1, (255, 255, 255)), (25, 550)) + win.blit(SMALL_FONT.render(f"T-SPIN DOUBLE {self.score[11]:11d}", 1, (255, 255, 255)), (25, 565)) + win.blit(SMALL_FONT.render(f"T-SPIN TRIPLE {self.score[12]:11d}", 1, (255, 255, 255)), (25, 580)) + win.blit(SMALL_FONT.render(f"COMBO BONUS {self.score[13]:13d}", 1, (255, 255, 255)), (25, 595)) + win.blit(SMALL_FONT.render(f"BACK-TO-BACK BONUS {self.score[14]:6d}", 1, (255, 255, 255)), (25, 610)) + pygame.display.update() + + + +class NesLikeTetris(TetrisGameplay): + def __init__(self, size_x=FIELD_SIZE_X, size_y=FIELD_SIZE_Y, level=0): + super().__init__(size_x, size_y, level, 2, False, False, False, False, False, False, (267, 100), True, 1) + + def __str__(self): + ans = f"size_x={len(self.FIELD[0])}, size_y={len(self.FIELD)}, Field:" + for i in self.FIELD: + ans += f"\n{i}" + return ans + + def clear_lines(self): + cleared = 0 + frames_delay = 0 + height = None + y = len(self.FIELD) + for i in self.FIELD: + ic = 0 + for k in i: + if k is not None: + ic += 1 + if ic == FIELD_SIZE_X: + cleared += 1 + self.FIELD.remove(i) + new = list(range(FIELD_SIZE_X)) + x = 0 + while x != FIELD_SIZE_X: + new[x] = None + x += 1 + self.FIELD.insert(0, new) + y -= 1 + if ic > 0 and height is None: + height = y + frames_delay += 10 + (2 * int(height / 4)) + + if cleared >= 0: + self.cleared_lines[cleared - 1] += cleared + frames_delay += 18 + if cleared == 1: + self.score[2] += 40 * (min(self.level, 29) + 1) + elif cleared == 2: + self.score[3] += 100 * (min(self.level, 29) + 1) + elif cleared == 3: + self.score[4] += 300 * (min(self.level, 29) + 1) + elif cleared == 4: + self.score[5] += 1200 * (min(self.level, 29) + 1) + if sum(self.cleared_lines) >= self.lines_for_level_up: + self.level += 1 + self.lines_for_level_up += 10 + return frames_delay + + +def draw_main_menu(selected, sel_lvl, sel_gl): + win.fill((25, 25, 25)) + win.blit(FONT.render("TETRIS by dan63047", 1, (255, 255, 255)), (25, 25)) + win.blit(FONT.render("›", 1, (255, 255, 255)), (25, 100 + 30 * selected)) + win.blit(FONT.render("Start", 1, (255, 255, 255)), (50, 100)) + win.blit(FONT.render(f"Level: {sel_lvl:02d}", 1, (255, 255, 255)), (50, 130)) # ↑↓ + win.blit(FONT.render(f"Guideline: {GUIDELINES[sel_gl]}", 1, (255, 255, 255)), (50, 160)) + pygame.display.update() + + +def main(): + GAME_RUN = True + selected_level = 0 + selected_gl = 0 + ticks_before_stats = 180 + ticks_gone = 0 + menu_select = 0 + on_pause = False + corrupt_hard_drop = False + field = None + state = "main menu" + pygame.key.set_repeat(267, 100) + while GAME_RUN: + clock.tick(60) + pressed_keys = [] + for event in pygame.event.get(): + if event.type == pygame.QUIT: + GAME_RUN = False + if event.type == pygame.KEYDOWN: + pressed_keys.append(event.key) + if pygame.K_SPACE in pressed_keys and corrupt_hard_drop: + pressed_keys.remove(pygame.K_SPACE) + if event.type == pygame.KEYUP and event.key == pygame.K_SPACE: + corrupt_hard_drop = False + keys = pygame.key.get_pressed() + if state == "main menu": + draw_main_menu(menu_select, selected_level, selected_gl) + if pygame.K_RETURN in pressed_keys: + if menu_select == 0: + state = "pregameplay" + if pygame.K_DOWN in pressed_keys and menu_select != 2: + menu_select += 1 + if pygame.K_UP in pressed_keys and menu_select != 0: + menu_select -= 1 + if pygame.K_RIGHT in pressed_keys and selected_level != 29 and menu_select == 1: + selected_level += 1 + elif pygame.K_LEFT in pressed_keys and selected_level != 0 and menu_select == 1: + selected_level -= 1 + if pygame.K_RIGHT in pressed_keys and selected_gl != 1 and menu_select == 2: + selected_gl += 1 + elif pygame.K_LEFT in pressed_keys and selected_gl != 0 and menu_select == 2: + selected_gl -= 1 + elif state == "pregameplay": + ticks_before_stats = 300 + if selected_gl == 0: + field = NesLikeTetris(level=selected_level) + elif selected_gl == 1: + field = TetrisGameplay(level=selected_level) + pygame.key.set_repeat(field.handling[0], field.handling[1]) + state = "gameplay" + elif state == "gameplay": + field.draw_game() + ticks_for_down = speed_and_lines_for_levels(field.level)[0] + if not field.game_over: + if pygame.K_r in pressed_keys and ticks_gone >= 0: + state = "pregameplay" + if pygame.K_p in pressed_keys and ticks_gone >= 0: + on_pause = True + state = "gameplay_stats" + if (pygame.K_UP in pressed_keys or pygame.K_x in pressed_keys) and ticks_gone >= 0: + field.spin() + if pygame.K_z in pressed_keys and ticks_gone >= 0: + field.spin(True) + if pygame.K_c in pressed_keys and ticks_gone >= 0 and field.support_hold and not field.hold_locked: + field.hold_tetromino() + if pygame.K_DOWN in pressed_keys and ticks_gone >= 0: + ticks_gone -= field.move_down(True) + if pygame.K_LEFT in pressed_keys and ticks_gone >= 0: + field.move_side(-1) + if pygame.K_RIGHT in pressed_keys and ticks_gone >= 0: + field.move_side(1) + if pygame.K_SPACE in pressed_keys and not corrupt_hard_drop and field.can_hard_drop: + field.move_down(True, True) + field.save_state() + ticks_gone -= field.clear_lines() + field.spawn_tetromino() + field.lock_delay_run = False + field.lock_delay_frames = 30 + corrupt_hard_drop = True + field.game_time += clock.get_time()/1000 + if field.game_over: + ticks_before_stats -= 1 + ticks_gone += 1 + if ticks_gone >= ticks_for_down: + ticks_gone = 0 + if not field.game_over: + if field.support_lock_delay: + if not field.move_down(): + field.lock_delay_run = True + else: + if not field.move_down(): + field.save_state() + ticks_gone -= field.clear_lines() + field.spawn_tetromino() + if field.lock_delay_run: + field.lock_delay_frames -= 1 + if field.lock_delay_frames <= 0 or not field.support_lock_delay: + field.save_state() + ticks_gone -= field.clear_lines() + field.spawn_tetromino() + field.reset_lock_delay() + if ticks_before_stats <= 0: + state = "gameplay_stats" + elif state == "gameplay_stats": + field.draw_game_stats(on_pause) + if pygame.K_BACKSPACE in pressed_keys: + state = "main menu" + elif pygame.K_r in pressed_keys: + state = "pregameplay" + if pygame.K_p in pressed_keys: + on_pause = False + state = "gameplay" + + +if __name__ == "__main__": + pygame.init() + win = pygame.display.set_mode((600, 800)) + clock = pygame.time.Clock() + pygame.display.set_caption("dan63047 Tetris") + pygame.font.init() + FONT = pygame.font.Font("PressStart2P-vaV7.ttf", 25) + SMALL_FONT = pygame.font.Font("PressStart2P-vaV7.ttf", 15) + main() + pygame.quit()