1831 lines
89 KiB
Python
Executable File
1831 lines
89 KiB
Python
Executable File
import time
|
||
from typing import List
|
||
import pygame, random, datetime, threading
|
||
from string import Template
|
||
|
||
BLOCK_SIZE = 30
|
||
GUIDELINES = ["Modern", "Classic"]
|
||
MODES = ["Endless", "Time limited", "Lines limited", "vs Bot (Garbage)", "vs Bot (Score)"]
|
||
TIME_LIMITS_SEC = [120, 180, 300, 600, 1800, 3600, 86400]
|
||
LINES_LIMITS = [40, 80, 120, 150, 300, 500, 1000]
|
||
state = "main menu"
|
||
menu_select = 0
|
||
selected_gl = 0
|
||
selected_mode = 0 # 0 - Endless; 1 - Time limit; 2 - Lines limit
|
||
selected_target = 0
|
||
session = []
|
||
|
||
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}"
|
||
|
||
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 + hours*60
|
||
return t.substitute(**d)
|
||
|
||
|
||
class TetrisGameplay:
|
||
def __init__(self, mode=0, lvl=1, buffer_zone=20, player="P1", srs=True, lock_delay=True, seven_bag=True, ghost_piece=True, hold=True, hard_drop=True, handling=(167, 33), nes_mechanics=False, next_len=4):
|
||
self.buffer_y = buffer_zone
|
||
self.FIELD = list(range(20 + buffer_zone))
|
||
y = 0
|
||
while y != len(self.FIELD):
|
||
self.FIELD[y] = list(range(10))
|
||
x = 0
|
||
while x != len(self.FIELD[y]):
|
||
self.FIELD[y][x] = None
|
||
x += 1
|
||
y += 1
|
||
self.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
|
||
]
|
||
self.g = 0
|
||
self.current_posx = 4
|
||
self.player = player
|
||
self.current_posy = self.buffer_y - 2
|
||
self.can_hard_drop = hard_drop
|
||
self.mode = mode
|
||
self.support_combo_and_btb_bonuses = False
|
||
self.support_srs = srs
|
||
self.handling = handling
|
||
self.support_hold = hold
|
||
self.soft_drop = False
|
||
self.soft_drop_speed = 0.5
|
||
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.score_up = 0
|
||
self.for_what_id = 0
|
||
self.for_what_score = ["SINGLE", "DOUBLE", "TRIPLE", "QUAD", "T-SPIN MINI", "T-SPIN MINI SINGLE", "T-SPIN MINI DOUBLE", "T-SPIN", "T-SPIN SINGLE", "T-SPIN DOUBLE", "T-SPIN TRIPLE"]
|
||
self.notification = {"for_what": None, "mode": None, "number": None, "combo": None, "b2b": None, "t-spin": False, "t-spin_mini": False, "pc": False, "game_time": None}
|
||
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.combo = -1
|
||
self.back_to_back = -1
|
||
self.next_queue = []
|
||
self.garbage_queue = []
|
||
self.attack = 0
|
||
self.send_attack = 0
|
||
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.lock_delay_run = False
|
||
if self.mode == 0:
|
||
self.level = lvl
|
||
self.start_level = lvl
|
||
else:
|
||
self.level = 1
|
||
self.start_level = 1
|
||
self.level_limit = 30
|
||
self.lock_delay_f_limit = min(30, 90 - 3 * self.level)
|
||
self.lock_delay_frames = self.lock_delay_f_limit
|
||
self.lock_delay_times_left = 15
|
||
if self.mode == 1:
|
||
self.target = TIME_LIMITS_SEC[lvl]
|
||
elif self.mode == 2:
|
||
self.target = LINES_LIMITS[lvl]
|
||
elif self.mode == 3:
|
||
self.target = 1
|
||
self.support_garbage = True
|
||
self.lines_for_level_up = self.gravity_and_lines_table()[1]
|
||
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.lock_delay_times_left = 15
|
||
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
|
||
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 gravity_and_lines_table(self):
|
||
return 1 / (0.8 - ((self.level - 1) * 0.007)) ** (self.level - 1) * 0.016666, self.level * 10
|
||
|
||
def clear_lines(self):
|
||
cleared = 0
|
||
self.send_attack = 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 = sum(
|
||
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
|
||
for i in t_spin_corners[self.current_spin_id][0]
|
||
)
|
||
|
||
back_col = sum(
|
||
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
|
||
for i in t_spin_corners[self.current_spin_id][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 = sum(k is not None for k in i)
|
||
if ic == 10:
|
||
cleared += 1
|
||
self.FIELD.remove(i)
|
||
new = list(range(10))
|
||
x = 0
|
||
while x != 10:
|
||
new[x] = None
|
||
x += 1
|
||
self.FIELD.insert(0, new)
|
||
y -= 1
|
||
if ic > 0 and height is None:
|
||
height = y
|
||
|
||
if cleared > 0:
|
||
difficult = False
|
||
self._extracted_from_clear_lines_58(cleared, t_spin, difficult, t_spin_mini)
|
||
else:
|
||
self.combo = -1
|
||
if t_spin:
|
||
self._extracted_from_clear_lines_125(9, 400, 7, t_spin, t_spin_mini)
|
||
elif t_spin_mini:
|
||
self._extracted_from_clear_lines_125(6, 100, 4, t_spin, t_spin_mini)
|
||
self.attack += self.send_attack
|
||
if self.support_garbage:
|
||
return 0, self.send_attack
|
||
else:
|
||
return 0
|
||
|
||
def _extracted_from_clear_lines_58(self, cleared, t_spin, difficult, t_spin_mini):
|
||
self.cleared_lines[cleared - 1] += cleared
|
||
wt = 0
|
||
if self.mode == 2:
|
||
self.target -= cleared
|
||
if self.target <= 0:
|
||
self.target = 0
|
||
self.game_over = True
|
||
self.combo += 1
|
||
self.score_up = 0
|
||
if t_spin:
|
||
difficult = True
|
||
if cleared == 1:
|
||
self.send_attack = 2
|
||
self.score[10] += 800 * min(self.level, self.level_limit)
|
||
self.score_up += 800 * min(self.level, self.level_limit)
|
||
wt = 8
|
||
elif cleared == 2:
|
||
self.send_attack = 4
|
||
self.score[11] += 1200 * min(self.level, self.level_limit)
|
||
self.score_up += 1200 * min(self.level, self.level_limit)
|
||
wt = 9
|
||
elif cleared == 3:
|
||
self.send_attack = 6
|
||
self.score[12] += 1600 * min(self.level, self.level_limit)
|
||
self.score_up += 1600 * min(self.level, self.level_limit)
|
||
wt = 10
|
||
elif t_spin_mini:
|
||
difficult = True
|
||
if cleared == 1:
|
||
self.send_attack = 0
|
||
self.score[7] += 200 * min(self.level, self.level_limit)
|
||
self.score_up += 200 * min(self.level, self.level_limit)
|
||
wt = 5
|
||
elif cleared == 2:
|
||
self.send_attack = 1
|
||
self.score[8] += 400 * min(self.level, self.level_limit)
|
||
self.score_up += 400 * min(self.level, self.level_limit)
|
||
wt = 6
|
||
elif cleared == 1:
|
||
self.send_attack = 0
|
||
self.score[2] += 100 * min(self.level, self.level_limit)
|
||
self.score_up += 100 * min(self.level, self.level_limit)
|
||
wt = 0
|
||
elif cleared == 2:
|
||
self.send_attack = 1
|
||
self.score[3] += 300 * min(self.level, self.level_limit)
|
||
self.score_up += 300 * min(self.level, self.level_limit)
|
||
wt = 1
|
||
elif cleared == 3:
|
||
self.send_attack = 2
|
||
self.score[4] += 500 * min(self.level, self.level_limit)
|
||
self.score_up += 500 * min(self.level, self.level_limit)
|
||
wt = 2
|
||
elif cleared == 4:
|
||
self.send_attack = 4
|
||
self.score[5] += 800 * min(self.level, self.level_limit)
|
||
self.score_up += 800 * min(self.level, self.level_limit)
|
||
wt = 3
|
||
difficult = True
|
||
if sum(self.cleared_lines) >= self.lines_for_level_up and self.level != self.level_limit and self.mode != 3:
|
||
self.level += 1
|
||
self.lines_for_level_up += 10
|
||
self.lock_delay_f_limit = min(30, 90 - 3 * self.level)
|
||
if difficult:
|
||
self.back_to_back += 1
|
||
if self.back_to_back > 0:
|
||
self.score[14] += int((self.score_up*3/2) - self.score_up)
|
||
self.score_up += int((self.score_up*3/2) - self.score_up)
|
||
self.send_attack += 1
|
||
else:
|
||
self.back_to_back = -1
|
||
if self.combo > 0:
|
||
self.send_attack += int(self.combo / 3) + 1
|
||
self.score[13] += 50 * self.combo * min(self.level, self.level_limit)
|
||
self.score_up += 50 * self.combo * min(self.level, self.level_limit)
|
||
self.notification = {"for_what": wt, "mode": "score", "number": self.score_up, "combo": self.combo,
|
||
"b2b": self.back_to_back, "t-spin": t_spin, "t-spin_mini": t_spin_mini,
|
||
"pc": False, "game_time": self.game_time}
|
||
self.for_what_delay = 3
|
||
|
||
def _extracted_from_clear_lines_125(self, wt_id, scr, what, t_spin, t_spin_mini):
|
||
self.score[wt_id] += scr * min(self.level, self.level_limit)
|
||
self.score_up = scr * min(self.level, self.level_limit)
|
||
self.notification = {"for_what": what, "mode": "score", "number": scr, "combo": self.combo, "b2b": self.back_to_back, "t-spin": t_spin, "t-spin_mini": t_spin_mini, "pc": False, "game_time": self.game_time}
|
||
|
||
def collision(self, next_posx, next_posy, figure_id, spin_id):
|
||
i1 = next_posy
|
||
k1 = next_posx
|
||
for i in self.TETROMINOS[figure_id][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):
|
||
if self.current_id is not None and 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 %= len(self.TETROMINOS[self.current_id])
|
||
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
|
||
if self.support_srs:
|
||
if self.current_id is not None and self.current_id != 5:
|
||
if self.current_spin_id in [0, 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
|
||
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
|
||
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
|
||
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
|
||
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
|
||
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
|
||
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
|
||
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
|
||
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
|
||
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
|
||
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
|
||
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
|
||
elif self.current_spin_id == 3 and future_spin_id in [0, 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
|
||
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
|
||
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
|
||
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
|
||
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
|
||
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
|
||
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
|
||
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
|
||
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
|
||
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
|
||
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
|
||
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
|
||
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
|
||
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
|
||
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
|
||
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
|
||
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
|
||
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
|
||
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
|
||
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
|
||
if self.lock_delay_run:
|
||
if self.lock_delay_times_left > 0:
|
||
self.reset_lock_delay()
|
||
if self.collision(self.current_posx, self.current_posy+1, self.current_id, self.current_spin_id):
|
||
self.lock_delay_run = True
|
||
else:
|
||
self.lock_delay_frames = 0
|
||
|
||
def move_side(self, x_change):
|
||
if self.current_id is not None and not self.collision(self.current_posx + x_change, self.current_posy, self.current_id, self.current_spin_id):
|
||
self.current_posx += x_change
|
||
if self.lock_delay_run:
|
||
if self.lock_delay_times_left > 0:
|
||
self.reset_lock_delay()
|
||
if self.collision(self.current_posx, self.current_posy+1, self.current_id, self.current_spin_id):
|
||
self.lock_delay_run = True
|
||
else:
|
||
self.lock_delay_frames = 1
|
||
self.spin_is_last_move = False
|
||
|
||
def save_state(self):
|
||
k1 = self.current_posx
|
||
if self.current_id is not None:
|
||
i1 = self.current_posy
|
||
for i in self.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
|
||
self.pieces[self.current_id] += 1
|
||
if len(self.garbage_queue) > 0:
|
||
garbage_row = [None, Block((30, 30, 30)), Block((30, 30, 30)), Block((30, 30, 30)), Block((30, 30, 30)), Block((30, 30, 30)), Block((30, 30, 30)), Block((30, 30, 30)), Block((30, 30, 30)), Block((30, 30, 30))]
|
||
random.shuffle(garbage_row)
|
||
for i in range(self.garbage_queue[0]):
|
||
self.FIELD.append(garbage_row)
|
||
self.FIELD.remove(self.FIELD[0])
|
||
self.garbage_queue.remove(self.garbage_queue[0])
|
||
|
||
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 = self.lock_delay_f_limit
|
||
self.lock_delay_run = False
|
||
self.lock_delay_times_left -= 1
|
||
|
||
def move_down(self, instant=False):
|
||
if self.current_id is not None and 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 self.soft_drop:
|
||
self.score[0] += 1
|
||
return True
|
||
else:
|
||
return False
|
||
|
||
def bot_move(self):
|
||
bot_move = self.player.run_ai(self)
|
||
if bot_move == "S+":
|
||
self.spin()
|
||
elif bot_move == "R":
|
||
self.move_side(1)
|
||
elif bot_move == "L":
|
||
self.move_side(-1)
|
||
elif bot_move == "HD":
|
||
self.move_down(True)
|
||
self.save_state()
|
||
self.clear_lines()
|
||
self.spawn_tetromino()
|
||
self.lock_delay_run = False
|
||
self.lock_delay_frames = 30
|
||
self.lock_delay_times_left = 15
|
||
|
||
|
||
def draw_game(self):
|
||
win.fill((25, 25, 25))
|
||
pygame.draw.rect(win, (0, 0, 0),
|
||
(130, (BLOCK_SIZE * 2 + 5), BLOCK_SIZE * 10, BLOCK_SIZE * 20))
|
||
x = 0
|
||
y = -self.buffer_y
|
||
for i in self.FIELD:
|
||
for k in i:
|
||
window_x = 130 + 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 self.TETROMINOS[self.current_id][self.current_spin_id]:
|
||
for k in i:
|
||
if k is not None:
|
||
window_x = 130 + BLOCK_SIZE * k1
|
||
window_y = (BLOCK_SIZE * 2 + 5) + BLOCK_SIZE * i1
|
||
pygame.draw.rect(win, (int(k.color[0]*self.lock_delay_frames/max(self.lock_delay_f_limit, 1)), int(k.color[1]*self.lock_delay_frames/max(self.lock_delay_f_limit, 1)), int(k.color[2]*self.lock_delay_frames/max(self.lock_delay_f_limit, 1))), (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 self.TETROMINOS[self.current_id][self.current_spin_id]:
|
||
for k in i:
|
||
if k is not None:
|
||
window_x = 130 + 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=5, border_radius=1)
|
||
k1 += 1
|
||
k1 = self.current_posx
|
||
i1 += 1
|
||
y_offset = 0
|
||
for q in range(0, self.next_length):
|
||
i1 = 0
|
||
k1 = 0
|
||
for i in self.TETROMINOS[self.next_queue[q]][0]:
|
||
for k in i:
|
||
if k is not None:
|
||
window_x = 470 + 25 * k1
|
||
window_y = 65 + 25 * i1 + y_offset
|
||
pygame.draw.rect(win, k.color, (window_x, window_y, 25, 25))
|
||
pygame.draw.rect(win, (0, 0, 0), (window_x, window_y, 25, 25), width=2)
|
||
k1 += 1
|
||
k1 = 0
|
||
i1 += 1
|
||
y_offset += 60
|
||
if self.support_hold:
|
||
if self.hold_id is not None:
|
||
i1 = 0
|
||
k1 = 0
|
||
for i in self.TETROMINOS[self.hold_id][0]:
|
||
for k in i:
|
||
if k is not None:
|
||
window_x = 20 + 25 * k1
|
||
window_y = 65 + 25 * i1
|
||
if self.hold_locked:
|
||
pygame.draw.rect(win, (0, 0, 0), (window_x, window_y, 25, 25))
|
||
else:
|
||
pygame.draw.rect(win, k.color, (window_x, window_y, 25, 25))
|
||
pygame.draw.rect(win, (0, 0, 0), (window_x, window_y, 25, 25), width=1)
|
||
k1 += 1
|
||
k1 = 0
|
||
i1 += 1
|
||
win.blit(MEDIUM_FONT.render("SCORE", 1, (255, 255, 255)), (440, 562))
|
||
win.blit(MEDIUM_FONT.render(f"{sum(self.score):5d}", 1, (255, 255, 255)), (440, 582))
|
||
win.blit(MEDIUM_FONT.render("LINES", 1, (255, 255, 255)), (440, 622))
|
||
win.blit(MEDIUM_FONT.render(f"{sum(self.cleared_lines):5d}", 1, (255, 255, 255)), (440, 642))
|
||
win.blit(MEDIUM_FONT.render("LV", 1, (255, 255, 255)), (80, 502))
|
||
win.blit(MEDIUM_FONT.render(f"{self.level:02d}", 1, (255, 255, 255)), (80, 522))
|
||
win.blit(MEDIUM_FONT.render("PPS", 1, (255, 255, 255)), (60, 562))
|
||
try:
|
||
pps = sum(self.pieces) / self.game_time
|
||
except ZeroDivisionError:
|
||
pps = 0
|
||
win.blit(MEDIUM_FONT.render(f"{pps:6.2f}", 1, (255, 255, 255)), (0, 582))
|
||
win.blit(MEDIUM_FONT.render("TIME", 1, (255, 255, 255)), (40, 622))
|
||
win.blit(MEDIUM_FONT.render(f"{strfdelta(datetime.timedelta(seconds=self.game_time), '%m:%S'):>6s}", 1, (255, 255, 255)), (0, 642))
|
||
if self.mode > 0:
|
||
if self.mode == 1:
|
||
win.blit(MEDIUM_FONT.render("TIME", 1, (255, 255, 255)), (440, 482))
|
||
win.blit(MEDIUM_FONT.render(f"{strfdelta(datetime.timedelta(seconds=self.target), '%m:%S')}", 1, (255, 255, 255)), (440, 522))
|
||
elif self.mode == 2:
|
||
win.blit(MEDIUM_FONT.render("LINES", 1, (255, 255, 255)), (440, 482))
|
||
win.blit(MEDIUM_FONT.render(f"{self.target:5d}", 1, (255, 255, 255)), (440, 522))
|
||
win.blit(MEDIUM_FONT.render("LEFT", 1, (255, 255, 255)), (440, 502))
|
||
|
||
if self.notification['for_what'] is not None and self.notification['game_time']+2.9 >= self.game_time:
|
||
win.blit(FONT.render(self.for_what_score[self.notification['for_what']], 1, (230*(min(self.notification['game_time']+3-self.game_time, 1))+25, 230*(min(self.notification['game_time']+3-self.game_time, 1))+25, 230*(min(self.notification['game_time']+3-self.game_time, 1))+25)),
|
||
(300-int(FONT.size(self.for_what_score[self.notification['for_what']])[0]/2), 670))
|
||
win.blit(
|
||
FONT.render(f"+{self.notification['number']}", 1, (230*(min(self.notification['game_time']+3-self.game_time, 1))+25, 230*(min(self.notification['game_time']+3-self.game_time, 1))+25, 230*(min(self.notification['game_time']+3-self.game_time, 1))+25)),
|
||
(300-int(FONT.size(f"+{self.notification['number']}")[0]/2), 695))
|
||
if self.notification["combo"] > 0:
|
||
win.blit(
|
||
FONT.render(f"COMBO × {self.notification['combo']}", 1, (
|
||
230 * (min(self.notification['game_time']+3-self.game_time, 1)) + 25, 230 * (min(self.notification['game_time']+3-self.game_time, 1)) + 25,
|
||
230 * (min(self.notification['game_time']+3-self.game_time, 1)) + 25)),
|
||
(300-int(FONT.size(f"COMBO × {self.notification['combo']}")[0]/2), 720))
|
||
if self.notification['b2b'] > 0:
|
||
win.blit(
|
||
FONT.render(f"BACK-TO-BACK × {self.notification['b2b']}", 1, (
|
||
230 * (min(self.notification['game_time']+3-self.game_time, 1)) + 25, 230 * (min(self.notification['game_time']+3-self.game_time, 1)) + 25,
|
||
230 * (min(self.notification['game_time']+3-self.game_time, 1)) + 25)),
|
||
(300-int(FONT.size(f"BACK-TO-BACK × {self.notification['b2b']}")[0]/2), 745))
|
||
if self.game_over:
|
||
text_size_x = FONT.size("GAME")[0]
|
||
pygame.draw.rect(win, (0, 0, 0), (223, 327, text_size_x+10, 60))
|
||
if self.mode > 0 and self.target <= 0:
|
||
pygame.draw.rect(win, (0, 255, 0), (223, 327, text_size_x + 10, 60), width=2)
|
||
win.blit(FONT.render("WELL", 1, (255, 255, 255)), (230, 335))
|
||
win.blit(FONT.render("DONE", 1, (255, 255, 255)), (230, 360))
|
||
else:
|
||
pygame.draw.rect(win, (255, 0, 0), (223, 327, text_size_x + 10, 60), width=2)
|
||
win.blit(FONT.render("GAME", 1, (255, 255, 255)), (230, 335))
|
||
win.blit(FONT.render("OVER", 1, (255, 255, 255)), (230, 360))
|
||
pygame.display.update()
|
||
|
||
def draw_game_stats(self):
|
||
win.fill((25, 25, 25))
|
||
if self.game_over:
|
||
win.blit(FONT.render("STATISTIC", 1, (255, 255, 255)), (25, 25))
|
||
else:
|
||
win.blit(FONT.render("GAME PAUSED", 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:
|
||
pps = total_pieces / self.game_time
|
||
except ZeroDivisionError:
|
||
pps = 0
|
||
win.blit(FONT.render(f"PIECES PER SECOND {pps:0.2f}", 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"QUAD {tetris_times:7d} {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"QUAD {self.score[5]:20d}", 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 ClassicTetris(TetrisGameplay):
|
||
def __init__(self, mode=0, target=0, player="P1"):
|
||
super().__init__(mode, target, 2, player, False, False, False, False, False, False, (267, 100), True, 1)
|
||
self.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]
|
||
]
|
||
], # 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]
|
||
]
|
||
], # 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],
|
||
[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, 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]
|
||
]
|
||
], # 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
|
||
]
|
||
|
||
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 gravity_and_lines_table(self):
|
||
if self.level == 0:
|
||
return 1/48, 10
|
||
elif self.level == 1:
|
||
return 1/43, 20
|
||
elif self.level == 2:
|
||
return 1/38, 30
|
||
elif self.level == 3:
|
||
return 1/33, 40
|
||
elif self.level == 4:
|
||
return 1/28, 50
|
||
elif self.level == 5:
|
||
return 1/23, 60
|
||
elif self.level == 6:
|
||
return 1/18, 70
|
||
elif self.level == 7:
|
||
return 1/13, 80
|
||
elif self.level == 8:
|
||
return 1/8, 90
|
||
elif self.level == 9:
|
||
return 1/6, 100
|
||
elif 10 <= self.level <= 12:
|
||
return 1/5, 100
|
||
elif 13 <= self.level <= 15:
|
||
return 1/4, 100
|
||
elif self.level == 16:
|
||
return 1/3, 110
|
||
elif self.level == 17:
|
||
return 1/3, 120
|
||
elif self.level == 18:
|
||
return 1/3, 130
|
||
elif self.level == 19:
|
||
return 1/2, 140
|
||
elif self.level == 20:
|
||
return 1/2, 150
|
||
elif self.level == 21:
|
||
return 1/2, 160
|
||
elif self.level == 22:
|
||
return 1/2, 170
|
||
elif self.level == 23:
|
||
return 1/2, 180
|
||
elif self.level == 24:
|
||
return 1/2, 190
|
||
elif 25 <= self.level <= 28:
|
||
return 1/2, 200
|
||
else:
|
||
return 1, 200
|
||
|
||
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 == 10:
|
||
cleared += 1
|
||
self.FIELD.remove(i)
|
||
new = list(range(10))
|
||
x = 0
|
||
while x != 10:
|
||
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
|
||
if self.mode == 2:
|
||
self.target -= cleared
|
||
if self.target <= 0:
|
||
self.target = 0
|
||
self.game_over = True
|
||
frames_delay += 18
|
||
self.score_up = 0
|
||
self.for_what_delay = 3
|
||
if cleared == 1:
|
||
self.score[2] += 40 * (min(self.level, 29) + 1)
|
||
self.score_up += 40 * (min(self.level, 29) + 1)
|
||
self.for_what_id = 0
|
||
elif cleared == 2:
|
||
self.score[3] += 100 * (min(self.level, 29) + 1)
|
||
self.score_up += 100 * (min(self.level, 29) + 1)
|
||
self.for_what_id = 1
|
||
elif cleared == 3:
|
||
self.score[4] += 300 * (min(self.level, 29) + 1)
|
||
self.score_up += 300 * (min(self.level, 29) + 1)
|
||
self.for_what_id = 2
|
||
elif cleared == 4:
|
||
self.score[5] += 1200 * (min(self.level, 29) + 1)
|
||
self.score_up += 1200 * (min(self.level, 29) + 1)
|
||
self.for_what_id = 3
|
||
if sum(self.cleared_lines) >= self.lines_for_level_up:
|
||
self.level += 1
|
||
self.lines_for_level_up += 10
|
||
return frames_delay
|
||
|
||
def draw_game(self):
|
||
win.fill((25, 25, 25))
|
||
pygame.draw.rect(win, (0, 0, 0),
|
||
(130, (BLOCK_SIZE * 2 + 5), BLOCK_SIZE * 10, BLOCK_SIZE * 20))
|
||
x = 0
|
||
y = -self.buffer_y
|
||
for i in self.FIELD:
|
||
for k in i:
|
||
window_x = 130 + 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)
|
||
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 self.TETROMINOS[self.current_id][self.current_spin_id]:
|
||
for k in i:
|
||
if k is not None:
|
||
window_x = 130 + 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
|
||
x_offset = 0
|
||
for q in range(0, self.next_length):
|
||
i1 = 0
|
||
k1 = 0
|
||
for i in self.TETROMINOS[self.next_queue[q]][0]:
|
||
for k in i:
|
||
if k is not None:
|
||
window_x = 130 + BLOCK_SIZE * 10 + 20 + BLOCK_SIZE * k1 + x_offset
|
||
window_y = (BLOCK_SIZE * 2 + 30) + BLOCK_SIZE * i1
|
||
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=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 self.TETROMINOS[self.hold_id][0]:
|
||
for k in i:
|
||
if k is not None:
|
||
window_x = 10 + BLOCK_SIZE * k1
|
||
window_y = (BLOCK_SIZE * 2 + 30) + 10 * i1
|
||
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=1)
|
||
k1 += 1
|
||
k1 = 0
|
||
i1 += 1
|
||
win.blit(MEDIUM_FONT.render("SCORE", 1, (255, 255, 255)), (440, 562))
|
||
win.blit(MEDIUM_FONT.render(f"{sum(self.score):5d}", 1, (255, 255, 255)), (440, 582))
|
||
win.blit(MEDIUM_FONT.render("LINES", 1, (255, 255, 255)), (440, 622))
|
||
win.blit(MEDIUM_FONT.render(f"{sum(self.cleared_lines):5d}", 1, (255, 255, 255)), (440, 642))
|
||
win.blit(MEDIUM_FONT.render("LV", 1, (255, 255, 255)), (80, 502))
|
||
win.blit(MEDIUM_FONT.render(f"{self.level:02d}", 1, (255, 255, 255)), (80, 522))
|
||
win.blit(MEDIUM_FONT.render("TRT", 1, (255, 255, 255)), (60, 562))
|
||
try:
|
||
tetris_rate = int((self.cleared_lines[3] / sum(self.cleared_lines)) * 100)
|
||
except ZeroDivisionError:
|
||
tetris_rate = 0
|
||
if tetris_rate == 100:
|
||
win.blit(MEDIUM_FONT.render(f"{tetris_rate}", 1, (255, 255, 255)), (60, 582))
|
||
else:
|
||
win.blit(MEDIUM_FONT.render(f"{tetris_rate:02d}%", 1, (255, 255, 255)), (60, 582))
|
||
win.blit(MEDIUM_FONT.render("TIME", 1, (255, 255, 255)), (40, 622))
|
||
win.blit(MEDIUM_FONT.render(f"{strfdelta(datetime.timedelta(seconds=self.game_time), '%m:%S'):>6s}", 1, (255, 255, 255)), (0, 642))
|
||
if self.mode > 0:
|
||
if self.mode == 1:
|
||
win.blit(MEDIUM_FONT.render("TIME", 1, (255, 255, 255)), (440, 482))
|
||
win.blit(MEDIUM_FONT.render(f"{strfdelta(datetime.timedelta(seconds=self.target), '%m:%S')}", 1, (255, 255, 255)), (440, 522))
|
||
elif self.mode == 2:
|
||
win.blit(MEDIUM_FONT.render("LINES", 1, (255, 255, 255)), (440, 482))
|
||
win.blit(MEDIUM_FONT.render(f"{self.target:5d}", 1, (255, 255, 255)), (440, 522))
|
||
win.blit(MEDIUM_FONT.render("LEFT", 1, (255, 255, 255)), (440, 502))
|
||
if self.notification['for_what'] is not None and self.notification['game_time'] + 2.9 >= self.game_time:
|
||
win.blit(FONT.render(self.for_what_score[self.notification['for_what']], 1, (
|
||
230 * (min(self.notification['game_time'] + 3 - self.game_time, 1)) + 25,
|
||
230 * (min(self.notification['game_time'] + 3 - self.game_time, 1)) + 25,
|
||
230 * (min(self.notification['game_time'] + 3 - self.game_time, 1)) + 25)),
|
||
(300 - int(FONT.size(self.for_what_score[self.notification['for_what']])[0] / 2), 670))
|
||
win.blit(
|
||
FONT.render(f"+{self.notification['number']}", 1, (
|
||
230 * (min(self.notification['game_time'] + 3 - self.game_time, 1)) + 25,
|
||
230 * (min(self.notification['game_time'] + 3 - self.game_time, 1)) + 25,
|
||
230 * (min(self.notification['game_time'] + 3 - self.game_time, 1)) + 25)),
|
||
(300 - int(FONT.size(f"+{self.notification['number']}")[0] / 2), 695))
|
||
if self.notification["combo"] > 0:
|
||
win.blit(
|
||
FONT.render(f"COMBO × {self.notification['combo']}", 1, (
|
||
230 * (min(self.notification['game_time'] + 3 - self.game_time, 1)) + 25,
|
||
230 * (min(self.notification['game_time'] + 3 - self.game_time, 1)) + 25,
|
||
230 * (min(self.notification['game_time'] + 3 - self.game_time, 1)) + 25)),
|
||
(300 - int(FONT.size(f"COMBO × {self.notification['combo']}")[0] / 2), 720))
|
||
if self.notification['b2b'] > 0:
|
||
win.blit(
|
||
FONT.render(f"BACK-TO-BACK × {self.notification['b2b']}", 1, (
|
||
230 * (min(self.notification['game_time'] + 3 - self.game_time, 1)) + 25,
|
||
230 * (min(self.notification['game_time'] + 3 - self.game_time, 1)) + 25,
|
||
230 * (min(self.notification['game_time'] + 3 - self.game_time, 1)) + 25)),
|
||
(300 - int(FONT.size(f"BACK-TO-BACK × {self.notification['b2b']}")[0] / 2), 745))
|
||
if self.game_over:
|
||
text_size_x = FONT.size("GAME")[0]
|
||
pygame.draw.rect(win, (0, 0, 0), (223, 327, text_size_x + 10, 60))
|
||
if self.mode > 0 and self.target <= 0:
|
||
pygame.draw.rect(win, (0, 255, 0), (223, 327, text_size_x + 10, 60), width=2)
|
||
win.blit(FONT.render("WELL", 1, (255, 255, 255)), (230, 335))
|
||
win.blit(FONT.render("DONE", 1, (255, 255, 255)), (230, 360))
|
||
else:
|
||
pygame.draw.rect(win, (255, 0, 0), (223, 327, text_size_x + 10, 60), width=2)
|
||
win.blit(FONT.render("GAME", 1, (255, 255, 255)), (230, 335))
|
||
win.blit(FONT.render("OVER", 1, (255, 255, 255)), (230, 360))
|
||
pygame.display.update()
|
||
|
||
def draw_game_stats(self):
|
||
win.fill((25, 25, 25))
|
||
if self.game_over:
|
||
win.blit(FONT.render("STATISTIC", 1, (255, 255, 255)), (25, 25))
|
||
else:
|
||
win.blit(FONT.render("GAME PAUSED", 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"QUAD {tetris_times:7d} {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"DROPS {self.score[0]:7d}", 1, (255, 255, 255)), (25, 400))
|
||
win.blit(SMALL_FONT.render(f"SINGLE {self.score[2]:6d}", 1, (255, 255, 255)), (25, 415))
|
||
win.blit(SMALL_FONT.render(f"DOUBLE {self.score[3]:6d}", 1, (255, 255, 255)), (25, 430))
|
||
win.blit(SMALL_FONT.render(f"TRIPLE {self.score[4]:6d}", 1, (255, 255, 255)), (25, 445))
|
||
win.blit(SMALL_FONT.render(f"QUAD {self.score[5]:8d}", 1, (255, 255, 255)), (25, 460))
|
||
pygame.display.update()
|
||
|
||
|
||
class BotAI:
|
||
def __init__(self):
|
||
self.skill = 0
|
||
self.name = "Bot"
|
||
self.state = "idle"
|
||
self.intersection = False
|
||
|
||
def generate_moves(self, field):
|
||
test_spin_id = 0
|
||
test_mino_id = field.current_id
|
||
test_hold_mino_id = field.hold_id if not None else field.next_queue[0]
|
||
moves = []
|
||
return 0, 4
|
||
|
||
|
||
def run_ai(self, field):
|
||
rotation, position = self.generate_moves(field)
|
||
if field.current_spin_id != rotation:
|
||
return "S+"
|
||
elif field.current_posx < position:
|
||
return "R"
|
||
elif field.current_posx > position:
|
||
return "L"
|
||
else:
|
||
return "HD"
|
||
|
||
|
||
|
||
def draw_main_menu(selected, sel_gl, sel_md, sel_trg):
|
||
win.fill((25, 25, 25))
|
||
win.blit(FONT.render("PYTRIS 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"Mode: {MODES[sel_md]}", 1, (255, 255, 255)), (50, 130))
|
||
if sel_md == 0:
|
||
win.blit(FONT.render(f"Level: {sel_trg:02d}", 1, (255, 255, 255)), (50, 160)) # ↑↓
|
||
elif sel_md == 1:
|
||
win.blit(FONT.render(f"Time: {strfdelta(datetime.timedelta(seconds=TIME_LIMITS_SEC[sel_trg]), '%H:%M:%S.%Z')}", 1, (255, 255, 255)), (50, 160))
|
||
elif sel_md == 2:
|
||
win.blit(FONT.render(f"Lines: {LINES_LIMITS[sel_trg]}", 1, (255, 255, 255)), (50, 160))
|
||
win.blit(FONT.render(f"Guideline: {GUIDELINES[sel_gl]}", 1, (255, 255, 255)), (50, 190))
|
||
win.blit(FONT.render(f"Exit", 1, (255, 255, 255)), (50, 220))
|
||
pygame.display.update()
|
||
|
||
def mind_of_stupid_idiot():
|
||
while not session[1].game_over:
|
||
try:
|
||
session[1].bot_move()
|
||
time.sleep(0.25)
|
||
except IndexError:
|
||
time.sleep(1)
|
||
except Exception as e:
|
||
print(e)
|
||
|
||
def draw_vs_field(field, offset, size=30):
|
||
pygame.draw.rect(win, (0, 0, 0), (offset + 100, (BLOCK_SIZE * 2 + 5), BLOCK_SIZE * 10, BLOCK_SIZE * 20))
|
||
x = 0
|
||
y = -field.buffer_y
|
||
for i in field.FIELD:
|
||
for k in i:
|
||
window_x = offset + 100 + 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 = field.current_posy - field.buffer_y
|
||
k1 = field.current_posx
|
||
if field.current_id is not None:
|
||
for i in field.TETROMINOS[field.current_id][field.current_spin_id]:
|
||
for k in i:
|
||
if k is not None:
|
||
window_x = offset + 100 + BLOCK_SIZE * k1
|
||
window_y = (BLOCK_SIZE * 2 + 5) + BLOCK_SIZE * i1
|
||
pygame.draw.rect(win, (
|
||
int(k.color[0] * field.lock_delay_frames / max(field.lock_delay_f_limit, 1)),
|
||
int(k.color[1] * field.lock_delay_frames / max(field.lock_delay_f_limit, 1)),
|
||
int(k.color[2] * field.lock_delay_frames / max(field.lock_delay_f_limit, 1))),
|
||
(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 = field.current_posx
|
||
i1 += 1
|
||
if field.support_ghost_piece:
|
||
i1 = field.ghost_piece_y() - field.buffer_y
|
||
k1 = field.current_posx
|
||
for i in field.TETROMINOS[field.current_id][field.current_spin_id]:
|
||
for k in i:
|
||
if k is not None:
|
||
window_x = offset + 100 + 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=5, border_radius=1)
|
||
k1 += 1
|
||
k1 = field.current_posx
|
||
i1 += 1
|
||
y_offset = 0
|
||
for q in range(field.next_length):
|
||
k1 = 0
|
||
for i1, i in enumerate(field.TETROMINOS[field.next_queue[q]][0]):
|
||
for k in i:
|
||
if k is not None:
|
||
window_x = offset + 440 + 25 * k1
|
||
window_y = 65 + 25 * i1 + y_offset
|
||
pygame.draw.rect(win, k.color, (window_x, window_y, 25, 25))
|
||
pygame.draw.rect(win, (0, 0, 0), (window_x, window_y, 25, 25), width=2)
|
||
k1 += 1
|
||
k1 = 0
|
||
y_offset += 60
|
||
garbage_meter_y_offset = 640
|
||
garbage_meter_color = (255, 0, 0)
|
||
for g in field.garbage_queue:
|
||
for i in range(g):
|
||
window_y = garbage_meter_y_offset
|
||
pygame.draw.rect(win, garbage_meter_color, (offset + 60, window_y, 30, 30), width=2)
|
||
garbage_meter_y_offset -= 30
|
||
garbage_meter_color = (255, 120, 0)
|
||
garbage_meter_y_offset -= 5
|
||
if field.support_hold and field.hold_id is not None:
|
||
k1 = 0
|
||
for i1, i in enumerate(field.TETROMINOS[field.hold_id][0]):
|
||
for k in i:
|
||
if k is not None:
|
||
window_x = offset + 20 + 25 * k1
|
||
window_y = 30 + 25 * i1
|
||
if field.hold_locked:
|
||
pygame.draw.rect(win, (0, 0, 0), (window_x, window_y, 25, 25))
|
||
else:
|
||
pygame.draw.rect(win, k.color, (window_x, window_y, 25, 25))
|
||
pygame.draw.rect(win, (0, 0, 0), (window_x, window_y, 25, 25), width=1)
|
||
k1 += 1
|
||
k1 = 0
|
||
try:
|
||
pps = sum(field.pieces) / field.game_time
|
||
except ZeroDivisionError:
|
||
pps = 0
|
||
try:
|
||
apm = field.attack / (field.game_time / 60)
|
||
except ZeroDivisionError:
|
||
apm = 0
|
||
win.blit(MEDIUM_FONT.render(f"{pps:.2f} PPS", 1, (255, 255, 255)), (offset + 410, 522))
|
||
win.blit(MEDIUM_FONT.render(f"{sum(field.pieces)} P", 1, (255, 255, 255)), (offset + 410, 542))
|
||
win.blit(MEDIUM_FONT.render(strfdelta(datetime.timedelta(seconds=field.game_time), '%m:%S'), 1, (255, 255, 255)), (offset + 410, 502))
|
||
win.blit(MEDIUM_FONT.render(f"{apm:.0f} APM", 1, (255, 255, 255)), (offset + 410, 562))
|
||
win.blit(MEDIUM_FONT.render(f"{field.attack} ATK", 1, (255, 255, 255)), (offset + 410, 582))
|
||
if field.mode == 3:
|
||
try:
|
||
win.blit(MEDIUM_FONT.render(field.player, 1, (255, 255, 255)), (offset + 410, 482))
|
||
except:
|
||
win.blit(MEDIUM_FONT.render(field.player.name, 1, (255, 255, 255)), (offset + 410, 482))
|
||
|
||
if field.notification['for_what'] is not None and field.notification['game_time'] + 2.9 >= field.game_time:
|
||
win.blit(FONT.render(field.for_what_score[field.notification['for_what']], 1, (
|
||
230 * (min(field.notification['game_time'] + 3 - field.game_time, 1)) + 25,
|
||
230 * (min(field.notification['game_time'] + 3 - field.game_time, 1)) + 25,
|
||
230 * (min(field.notification['game_time'] + 3 - field.game_time, 1)) + 25)),
|
||
(300 - int(FONT.size(field.for_what_score[field.notification['for_what']])[0] / 2), 670))
|
||
win.blit(
|
||
FONT.render(f"+{field.notification['number']}", 1, (
|
||
230 * (min(field.notification['game_time'] + 3 - field.game_time, 1)) + 25,
|
||
230 * (min(field.notification['game_time'] + 3 - field.game_time, 1)) + 25,
|
||
230 * (min(field.notification['game_time'] + 3 - field.game_time, 1)) + 25)),
|
||
(300 - int(FONT.size(f"+{field.notification['number']}")[0] / 2), 695))
|
||
if field.notification["combo"] > 0:
|
||
win.blit(
|
||
FONT.render(f"COMBO × {field.notification['combo']}", 1, (
|
||
230 * (min(field.notification['game_time'] + 3 - field.game_time, 1)) + 25,
|
||
230 * (min(field.notification['game_time'] + 3 - field.game_time, 1)) + 25,
|
||
230 * (min(field.notification['game_time'] + 3 - field.game_time, 1)) + 25)),
|
||
(300 - int(FONT.size(f"COMBO × {field.notification['combo']}")[0] / 2), 720))
|
||
if field.notification['b2b'] > 0:
|
||
win.blit(
|
||
FONT.render(f"BACK-TO-BACK × {field.notification['b2b']}", 1, (
|
||
230 * (min(field.notification['game_time'] + 3 - field.game_time, 1)) + 25,
|
||
230 * (min(field.notification['game_time'] + 3 - field.game_time, 1)) + 25,
|
||
230 * (min(field.notification['game_time'] + 3 - field.game_time, 1)) + 25)),
|
||
(300 - int(FONT.size(f"BACK-TO-BACK × {field.notification['b2b']}")[0] / 2), 745))
|
||
if field.game_over:
|
||
text_size_x = FONT.size("GAME")[0]
|
||
pygame.draw.rect(win, (0, 0, 0), (offset + 223, 327, text_size_x + 10, 60))
|
||
if field.mode > 0 and field.target <= 0:
|
||
pygame.draw.rect(win, (0, 255, 0), (offset + 223, 327, text_size_x + 10, 60), width=2)
|
||
win.blit(FONT.render("WELL", 1, (255, 255, 255)), (offset + 230, 335))
|
||
win.blit(FONT.render("DONE", 1, (255, 255, 255)), (offset + 230, 360))
|
||
else:
|
||
pygame.draw.rect(win, (255, 0, 0), (offset + 223, 327, text_size_x + 10, 60), width=2)
|
||
win.blit(FONT.render("GAME", 1, (255, 255, 255)), (offset + 230, 335))
|
||
win.blit(FONT.render("OVER", 1, (255, 255, 255)), (offset + 230, 360))
|
||
|
||
def draw_vs_gameplay(session):
|
||
win.fill((25, 25, 25))
|
||
for f in session:
|
||
if f.player == "P1":
|
||
draw_vs_field(f, 0)
|
||
elif len(session) == 2 and f.player.name == "Bot":
|
||
draw_vs_field(f, 600)
|
||
pygame.display.update()
|
||
|
||
def render_main():
|
||
while True:
|
||
if state == "main menu":
|
||
draw_main_menu(menu_select, selected_gl, selected_mode, selected_target)
|
||
elif state == "gameplay":
|
||
if selected_mode == 3:
|
||
draw_vs_gameplay(session)
|
||
else:
|
||
session[0].draw_game()
|
||
elif state == "gameplay_stats":
|
||
session[0].draw_game_stats()
|
||
|
||
|
||
def main():
|
||
GAME_RUN = True
|
||
global session
|
||
ticks_before_stats = 180
|
||
global state
|
||
global menu_select
|
||
global selected_gl
|
||
global selected_mode
|
||
global selected_target
|
||
delay_before_spawn = -1
|
||
on_pause = False
|
||
corrupted_keys = []
|
||
field = None
|
||
piece_movement = 0
|
||
incoming_garbage = []
|
||
first_movement = True
|
||
render_tread = threading.Thread(name="drawing", target=render_main, daemon=True)
|
||
render_tread.start()
|
||
movement_delay = 0
|
||
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:
|
||
if event.key == pygame.K_LEFT:
|
||
piece_movement = -1
|
||
elif event.key == pygame.K_RIGHT:
|
||
piece_movement = 1
|
||
pressed_keys.append(event.key)
|
||
if event.type == pygame.KEYUP:
|
||
if event.key == pygame.K_SPACE and pygame.K_SPACE in corrupted_keys:
|
||
corrupted_keys.remove(pygame.K_SPACE)
|
||
if event.key == pygame.K_DOWN and state == "gameplay":
|
||
corrupted_keys.append(pygame.K_DOWN)
|
||
if event.key == pygame.K_UP and state == "gameplay" and pygame.K_UP in corrupted_keys:
|
||
corrupted_keys.remove(pygame.K_UP)
|
||
if event.key == pygame.K_x and state == "gameplay" and pygame.K_x in corrupted_keys:
|
||
corrupted_keys.remove(pygame.K_x)
|
||
if event.key == pygame.K_z and pygame.K_z in corrupted_keys:
|
||
corrupted_keys.remove(pygame.K_z)
|
||
if event.key in [pygame.K_LEFT, pygame.K_RIGHT]:
|
||
piece_movement = 0
|
||
first_movement = True
|
||
try:
|
||
movement_delay = field.handling[0]
|
||
except:
|
||
pass
|
||
for i in pressed_keys:
|
||
if i in corrupted_keys:
|
||
pressed_keys.remove(i)
|
||
keys = pygame.key.get_pressed()
|
||
if state == "main menu":
|
||
if pygame.K_RETURN in pressed_keys and menu_select == 0:
|
||
state = "pregameplay"
|
||
if pygame.K_DOWN in pressed_keys and menu_select != 4:
|
||
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_mode != 3 and menu_select == 1:
|
||
selected_mode += 1
|
||
selected_target = 0
|
||
elif pygame.K_LEFT in pressed_keys and selected_mode != 0 and menu_select == 1:
|
||
selected_mode -= 1
|
||
selected_target = 0
|
||
if selected_mode == 0:
|
||
if pygame.K_RIGHT in pressed_keys and menu_select == 2 and selected_target != 30:
|
||
selected_target += 1
|
||
elif pygame.K_LEFT in pressed_keys and menu_select == 2 and selected_target != 0:
|
||
selected_target -= 1
|
||
elif selected_mode == 1:
|
||
if pygame.K_RIGHT in pressed_keys and menu_select == 2 and selected_target != len(TIME_LIMITS_SEC)-1:
|
||
selected_target += 1
|
||
elif pygame.K_LEFT in pressed_keys and menu_select == 2 and selected_target != 0:
|
||
selected_target -= 1
|
||
elif selected_mode == 2:
|
||
if pygame.K_RIGHT in pressed_keys and menu_select == 2 and selected_target != len(LINES_LIMITS)-1:
|
||
selected_target += 1
|
||
elif pygame.K_LEFT in pressed_keys and menu_select == 2 and selected_target != 0:
|
||
selected_target -= 1
|
||
if pygame.K_RIGHT in pressed_keys and selected_gl != 1 and menu_select == 3:
|
||
selected_gl += 1
|
||
elif pygame.K_LEFT in pressed_keys and selected_gl != 0 and menu_select == 3:
|
||
selected_gl -= 1
|
||
if pygame.K_RETURN in pressed_keys and menu_select == 4:
|
||
GAME_RUN = False
|
||
elif state == "pregameplay":
|
||
ticks_before_stats = 300
|
||
delay_before_spawn = -1
|
||
if selected_gl == 0:
|
||
session = [TetrisGameplay(selected_mode, max(selected_target, 1))] if selected_mode == 0 else [TetrisGameplay(selected_mode, selected_target)]
|
||
if selected_mode == 3:
|
||
session.append(TetrisGameplay(selected_mode, selected_target, player=BotAI()))
|
||
bots_tread = threading.Thread(name="pendehos", target=mind_of_stupid_idiot, daemon=True)
|
||
bots_tread.start()
|
||
pygame.display.set_mode((1200, 800))
|
||
elif selected_gl == 1:
|
||
session = [ClassicTetris(selected_mode, selected_target)]
|
||
if selected_mode == 3:
|
||
session.append(ClassicTetris(selected_mode, selected_target, BotAI()))
|
||
bots_tread = threading.Thread(name="pendehos", target=mind_of_stupid_idiot, daemon=True)
|
||
bots_tread.start()
|
||
pygame.display.set_mode((1200, 800))
|
||
pygame.key.set_repeat(session[0].handling[0], session[0].handling[1])
|
||
movement_delay = session[0].handling[0]
|
||
state = "gameplay"
|
||
elif state == "gameplay":
|
||
field = session[0]
|
||
frame_time = clock.get_time()/1000
|
||
if not field.game_over:
|
||
if field.player == "P1":
|
||
if pygame.K_r in pressed_keys:
|
||
state = "pregameplay"
|
||
if pygame.K_p in pressed_keys:
|
||
on_pause = True
|
||
state = "gameplay_stats"
|
||
if pygame.K_UP in pressed_keys:
|
||
field.spin()
|
||
corrupted_keys.append(pygame.K_UP)
|
||
if pygame.K_x in pressed_keys:
|
||
field.spin()
|
||
corrupted_keys.append(pygame.K_x)
|
||
if pygame.K_z in pressed_keys:
|
||
field.spin(True)
|
||
corrupted_keys.append(pygame.K_z)
|
||
if pygame.K_c in pressed_keys and field.support_hold and not field.hold_locked:
|
||
field.hold_tetromino()
|
||
if pygame.K_DOWN in pressed_keys:
|
||
field.soft_drop = True
|
||
if pygame.K_DOWN in corrupted_keys:
|
||
field.soft_drop = False
|
||
corrupted_keys.remove(pygame.K_DOWN)
|
||
if pygame.K_SPACE in pressed_keys and field.can_hard_drop:
|
||
field.move_down(True)
|
||
_extracted_from_main_136(field, incoming_garbage)
|
||
field.lock_delay_run = False
|
||
field.lock_delay_frames = 30
|
||
corrupted_keys.append(pygame.K_SPACE)
|
||
if piece_movement != 0:
|
||
movement_delay -= frame_time * 1000
|
||
if first_movement:
|
||
field.move_side(piece_movement)
|
||
first_movement = False
|
||
elif movement_delay <= 0:
|
||
field.move_side(piece_movement)
|
||
movement_delay = field.handling[1]
|
||
for field in session:
|
||
field.game_time += frame_time
|
||
for gb in incoming_garbage:
|
||
if gb[1] != field.player:
|
||
field.garbage_queue.append(gb[0])
|
||
incoming_garbage.remove(gb)
|
||
if field.mode == 1:
|
||
field.target -= frame_time
|
||
if field.target <= 0:
|
||
field.target = 0
|
||
field.game_over = True
|
||
elif field.mode == 3 and field.target == 0:
|
||
field.game_over = True
|
||
if not field.game_over:
|
||
field.g += field.gravity_and_lines_table()[0]
|
||
if field.soft_drop:
|
||
field.g += field.soft_drop_speed
|
||
field.g = min(field.g, 22)
|
||
while field.g >= 1:
|
||
if field.support_lock_delay:
|
||
if not field.move_down() or field.collision(field.current_posx, field.current_posy+1, field.current_id, field.current_spin_id):
|
||
field.lock_delay_run = True
|
||
elif (
|
||
not field.move_down()
|
||
and delay_before_spawn == -1
|
||
):
|
||
field.save_state()
|
||
delay_before_spawn = field.clear_lines()
|
||
field.current_id = None
|
||
field.g -= 1
|
||
if field.nes_mechanics:
|
||
if delay_before_spawn > -1:
|
||
delay_before_spawn -= 1
|
||
if delay_before_spawn == 0:
|
||
field.spawn_tetromino()
|
||
if field.lock_delay_run:
|
||
if field.lock_delay_frames > 0:
|
||
field.lock_delay_frames -= 1
|
||
if (field.lock_delay_frames == 0 or not field.support_lock_delay) and field.collision(field.current_posx, field.current_posy+1, field.current_id, field.current_spin_id):
|
||
_extracted_from_main_136(field, incoming_garbage)
|
||
field.reset_lock_delay()
|
||
field.reset_lock_delay()
|
||
for field in session:
|
||
if field.game_over:
|
||
if field.player == "P1":
|
||
ticks_before_stats -= 1
|
||
elif field.mode == 3:
|
||
session[0].target -= 1
|
||
if ticks_before_stats <= 0:
|
||
state = "gameplay_stats"
|
||
elif state == "gameplay_stats":
|
||
if pygame.K_BACKSPACE in pressed_keys:
|
||
pygame.key.set_repeat(267, 100)
|
||
pygame.display.set_mode((600, 800))
|
||
state = "main menu"
|
||
elif pygame.K_r in pressed_keys:
|
||
state = "pregameplay"
|
||
if pygame.K_p in pressed_keys and not session[0].game_over:
|
||
on_pause = False
|
||
state = "gameplay"
|
||
|
||
def _extracted_from_main_136(field, ig):
|
||
field.save_state()
|
||
if field.mode == 3:
|
||
atk =field.clear_lines()[1]
|
||
if atk > 0:
|
||
ig.append([atk, field.player])
|
||
else:
|
||
field.clear_lines()
|
||
field.spawn_tetromino()
|
||
|
||
|
||
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)
|
||
MEDIUM_FONT = pygame.font.Font("PressStart2P-vaV7.ttf", 20)
|
||
SMALL_FONT = pygame.font.Font("PressStart2P-vaV7.ttf", 15)
|
||
main()
|
||
pygame.quit()
|