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)
Example #2
0
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)
Example #3
0
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