class Gameplay: def __init__(self) -> None: self.matrix = Matrix() self.bag = Bag() self.piece: Piece self.score = Score() self.input: Input self.cancel_input: Input self.popups: List[Popup] = [] self.holder: Optional[Piece] = None self.hold_lock = False self.level = 1 self.countdown = 3 self.countdown_last = ctx.now - 1.0 self.game_over = False self.game_over_sent = False self.last_fall: float self.fall_interval = 1.0 self.last_piece_movement_counter = 0 self.t_spin = False self.clearing = False self.clearing_rows: List[int] = [] self.clearing_last: float self.garbage_adding = False self.garbage_hole = 0 self.garbage_left = 0 self.garbage_last: float self.send = False self.cancel = False def get_matrix(self) -> Matrix: return self.matrix def get_piece(self) -> Piece: return self.piece def get_bag(self) -> Bag: return self.bag def get_popups(self) -> List[Popup]: return self.popups def clear_popups(self) -> None: self.popups = [] def set_device(self, device: Device) -> None: self.input = Input(device) self.cancel_input = Input(device) def initialize(self) -> None: self.input.bind({ "down": self.action_down, "right": self.action_right, "left": self.action_left, "rotate_right": self.action_rotate_right, "rotate_left": self.action_rotate_left, "soft_fall": self.action_soft_fall, "hard_fall": self.action_hard_fall, "hold": self.action_hold, }) self.cancel_input.bind({"cancel": self.action_cancel}) self.new_piece() def action_cancel(self) -> None: self.cancel = True def action_down(self) -> None: if self.piece.move(0, 1, self.matrix.collision): self.reset_fall() ctx.mixer.play("move") def action_right(self) -> None: if self.piece.move(1, 0, self.matrix.collision): ctx.mixer.play("move") if self.piece.touching_floor: self.reset_fall() def action_left(self) -> None: if self.piece.move(-1, 0, self.matrix.collision): ctx.mixer.play("move") if self.piece.touching_floor: self.reset_fall() def action_rotate_right(self) -> None: if self.piece.rotate(self.matrix.collision, clockwise=True): ctx.mixer.play("rotate") if self.piece.touching_floor: self.reset_fall() def action_rotate_left(self) -> None: if self.piece.rotate(self.matrix.collision, clockwise=False): ctx.mixer.play("rotate") if self.piece.touching_floor: self.reset_fall() def action_soft_fall(self) -> None: rows = self.piece.fall(self.matrix.collision) if rows > 0: ctx.mixer.play("soft_fall") self.reset_fall() self.score.update_soft_drop(rows) def action_hard_fall(self) -> None: rows = self.piece.fall(self.matrix.collision) self.lock_piece() if rows > 0: ctx.mixer.play("hard_fall") self.score.update_hard_drop(rows) def action_hold(self) -> None: if self.hold_lock: ctx.mixer.play("hold_fail") return self.hold_lock = True if self.holder is not None: self.holder, self.piece = self.piece, self.holder self.reset_piece() else: self.holder = self.piece self.new_piece() self.send = True ctx.mixer.play("hold") def new_piece(self) -> None: self.send = True self.piece = self.bag.take() self.piece.reset() self.reset_fall() if self.matrix.collision(self.piece): self.game_over = True if self.garbage_left > 0: self.garbage_adding = True self.garbage_last = ctx.now def reset_piece(self) -> None: self.piece.reset() for rows in range(self.piece.shape.grid[0].height, 0, -1): if self.piece.move(0, rows, self.matrix.collision): break self.reset_fall() def reset_fall(self) -> None: self.last_fall = ctx.now def lock_piece(self) -> None: self.hold_lock = False self.t_spin = False if self.matrix.collision( self.piece) or not self.matrix.lock(self.piece): self.game_over = True else: if TSpin.detect(self.matrix, self.piece): self.t_spin = True self.new_piece() if self.game_over: return rows = self.matrix.get_full_rows() if rows: popup = self.score.update_clear(self.level, rows, self.t_spin) ctx.mixer.play("erase" + str(len(rows))) self.clear_rows(rows) self.popups.append(popup) else: self.score.reset_combo() def clear_rows(self, rows: List[int]) -> None: self.clearing = True self.clearing_rows = rows self.clearing_last = ctx.now + 0.15 for row in rows: self.matrix.erase_row(row) def add_garbage(self, hole: int, count: int) -> None: self.garbage_hole = hole self.garbage_left = count def update(self) -> None: self.cancel_input.update() if self.clearing: if ctx.now - self.clearing_last > 0.02: self.send = True self.matrix.collapse_row(self.clearing_rows.pop(0)) self.clearing_last = ctx.now ctx.mixer.play("line_fall") if not self.clearing_rows: self.clearing = False self.reset_piece() elif self.garbage_adding: if ctx.now - self.garbage_last > 0.03: self.send = True self.matrix.add_garbage(self.garbage_hole) self.garbage_last = ctx.now ctx.mixer.play("garbage") self.garbage_left -= 1 if self.garbage_left == 0: self.garbage_adding = False self.reset_piece() if self.game_over: if not self.game_over_sent: self.game_over_sent = True self.send = True return if self.countdown >= 0: if ctx.now - self.countdown_last > 1.0: self.send = True self.countdown_last = ctx.now ctx.mixer.play("countdown") if self.countdown == 0: self.popups.append( Popup("GO!", size=6, color="green", duration=0.4)) ctx.mixer.play("go") ctx.mixer.play_music("main_theme") else: self.popups.append( Popup(str(self.countdown), size=6, duration=0.4)) self.countdown -= 1 return self.input.update() if self.piece.movement_counter != self.last_piece_movement_counter: self.send = True self.last_piece_movement_counter = self.piece.movement_counter if ctx.now - self.last_fall > self.fall_interval: self.send = True if self.piece.move(0, 1, self.matrix.collision): self.reset_fall() else: self.lock_piece() def draw(self, x: int, y: int, draw_piece=True) -> None: self.matrix.draw(x, y) if draw_piece and self.piece: self.matrix.get_ghost(self.piece).draw(x, y) self.piece.draw(x, y) self.score.draw(x, y - 70) self.bag.draw(x + 340, y + 70) if self.holder is not None: holder_x = int(x - 65 - self.holder.shape.get_width(0) * 11.25) self.holder.shape.draw(0, holder_x, y + 60, 22, 1.0) else: shape.SHAPE_HOLD_NONE.draw(0, x - 85, y + 60, 22, 1.0) Text.draw("Hold", centerx=x - 75, top=y + 20) Text.draw("Next", centerx=x + 370, top=y + 20)
class MainMenu(State): def __init__(self) -> None: self.input1 = Input(Device(config.device1)) self.input2 = Input(Device(config.device2)) self.position = 0 self.min_position = 0 self.max_position = 3 self.entered = False def is_finished(self) -> bool: return False def initialize(self) -> None: ctx.mixer.play_music("menu_theme") binds = { "down": self.position_down, "up": self.position_up, "select": self.position_enter, "hard_fall": self.position_enter, } self.input1.bind(binds) self.input2.bind(binds) def position_down(self) -> None: self.position += 1 if self.position > self.max_position: self.position = self.max_position else: ctx.mixer.play("change") def position_up(self) -> None: self.position -= 1 if self.position < self.min_position: self.position = self.min_position else: ctx.mixer.play("change") def position_enter(self) -> None: ctx.mixer.play("choose") self.entered = True def update(self, switch_state: Callable) -> None: self.input1.update() self.input2.update() if self.entered: if self.position == 0: switch_state(DevicePrompter(Marathon(), 1)) elif self.position == 1: switch_state(DevicePrompter(SplitScreen(), 2)) elif self.position == 2: switch_state(DevicePrompter(Online(), 1)) elif self.position == 3: ctx.running = False def draw(self) -> None: Text.draw("Tetris", centerx=640, top=30, size=10) Text.draw("Duel", centerx=650, top=130, size=8, color="red", gcolor="yellow") colors = ["white" for _ in range(self.max_position + 1)] colors[self.position] = "gold" Text.draw("Marathon", centerx=650, top=300, color=colors[0]) Text.draw("Split Screen", centerx=650, top=350, color=colors[1]) Text.draw("Duel Online", centerx=650, top=400, color=colors[2]) Text.draw("Quit", centerx=650, top=450, color=colors[3]) x = 460 y = 305 + self.position * 50 Text.draw("\u2192", (x, y), color="gold", size=2)
class Game: def __init__(self): self.grid_size = 100 self.board = [[0 for i in range(self.grid_size)] for j in range(self.grid_size)] self.row_width = 7 self.row_height = 3 self.num_colors = { 0: "white", 2: "red", 4: "yellow", 8: "green", 16: "blue", 32: "magenta", 64: "cyan", 128: "white", 256: "red", 512: "yellow", 1024: "green", 2048: "blue", 4096: "magenta", 8192: "cyan", } self.num_attrs = { 0: ["concealed"], 2: [], 4: [], 8: [], 16: [], 32: [], 64: [], 128: ["bold"], 256: ["bold"], 512: ["bold"], 1024: ["bold"], 2048: ["bold"], 4096: ["bold"], 8192: ["bold"], } self.directions = { "w": 1, "a": 4, "s": 3, "d": 2, } #bind keys self.input = Input(self.keyboard_listen) self.input.bind("w") self.input.bind("a") self.input.bind("s") self.input.bind("d") # start the game self.generate_tile() self.generate_tile() self.input.listen() def check_legal_move(self, direc): filled_squares = [] invalids = 0 for i in range(0, self.grid_size): for j in range(0, self.grid_size): if self.board[i][j] != 0: filled_squares.append((i, j)) for i in filled_squares: movement = self.check_adjacent(direc, i[0], i[1]) if movement == 0: invalids += 1 if invalids == len(filled_squares): return False else: return True def check_adjacent(self, direc, row, col): ''' returns an int describing allowed movement possible values for each entry: 0 - cannot move anymore in this direction 1 - can move in this direction 2 - can move in this direction/combine ''' val = self.board[row][col] if ((row - 1) == -1 and (direc == 1)) or ( (row + 1) == self.grid_size and (direc == 3)) or ((col - 1) == -1 and (direc == 4)) or ((col + 1) == self.grid_size and (direc == 2)): return 0 adj = self.board[row - 1 if direc == 1 else row + 1 if direc == 3 else row][col + 1 if direc == 2 else col - 1 if direc == 4 else col] if val == adj: return 2 if adj == 0: return 1 else: return 0 def move(self, direc): ''' directions: 1 - up 2 - right 3 - down 4 - left ''' if not self.check_legal_move(direc): count = 0 for i in range(1, 4): if not self.check_legal_move(i): count += 1 if count == 4: print("game over lmao") exit() else: return moves = 1 already_combined = [[0 for i in range(self.grid_size)] for j in range(self.grid_size)] while moves != -1: row = range(0, self.grid_size) if direc == 1 else range( self.grid_size - 1, -1, -1) if direc == 3 else range(0, self.grid_size) col = range(0, self.grid_size) if direc == 4 else range( self.grid_size - 1, -1, -1) if direc == 2 else range(0, self.grid_size) moves = 0 for i in row: for j in col: if self.board[i][j] != 0: new_row = i - 1 if direc == 1 else i + 1 if direc == 3 else i new_col = j + 1 if direc == 2 else j - 1 if direc == 4 else j movement = self.check_adjacent(direc, i, j) if movement == 1: moves += 1 self.board[new_row][new_col] = self.board[i][j] self.board[i][j] = 0 if movement == 2 and already_combined[i][j] != 1: moves += 1 self.board[new_row][new_col] = self.board[i][ j] + self.board[new_row][new_col] self.board[i][j] = 0 already_combined[new_row][new_col] = 1 if moves == 0: moves = -1 self.generate_tile() def keyboard_listen(self, key): self.move(self.directions[key]) # tile generation -------------------------------------------------- def find_blank_squares(self): # find blank tiles blanks = [] for i, v in enumerate(self.board): for j, g in enumerate(v): if g == 0: blanks.append((i, j)) return blanks def generate_tile(self): # generate a new tile blanks = self.find_blank_squares() if blanks == []: print('game over lmao') selected = random.randrange(len(blanks)) probability = random.randrange(1, 3) self.board[blanks[selected][0]][blanks[selected] [1]] = 2 if probability == 1 else 4 self.draw_board() # ------------------------------------------------------------------ # draw board ------------------------------------------------------ def draw_board(self): clear() horiz_divider = f"|{'-' * ((self.row_width*self.grid_size)+(self.grid_size-1))}|" board = horiz_divider + ''.join( self.__make_row(v, last=True if i == self.grid_size - 1 else False) for i, v in enumerate(self.board)) + horiz_divider print(board) def __make_row(self, nums, last=False): horiz_divider = f"|{'-'*(self.row_width)}" * self.grid_size + "|" row = f"| {' '*(self.row_width-1)}" * self.grid_size + "|" num_row = ''.join(self.__make_number_square(i) for i in nums) board = f"\n{row}" * ( (self.row_height // 2) - 1 if self.row_height % 2 == 0 else (self.row_height // 2)) + f"\n|{num_row}" + f"\n{row}" * ( self.row_height // 2) + f"\n{horiz_divider if not last else ''}" return board def __make_number_square(self, inp): num = colored(inp, self.num_colors[inp], attrs=self.num_attrs[inp]) num_len = len(str(inp)) mid_width = (self.row_width - num_len) // 2 num = f"{' '*mid_width}{num}{' '*mid_width if (self.row_width % 2 == 0 and (num_len % 2 == 0)) or (self.row_width % 2 == 1 and (num_len % 2 == 1)) else ' '*(mid_width+1)}|" return num