Kamisado

Play Kamisado

  • Full code with requirements available on GitHub!

    🔗 View on GitHub
        # Purpose: Driver File
        # %------------------------------------------ Packages ------------------------------------% #
        import board, game   
        import pygame
        from dataclasses import dataclass 
        # %------------------------------------------- Structs -------------------------------------------% #
        @dataclass
        class Settings:
            DIM       : int
            LENGTH    : int
            CELL_SIZE : int  
            MAX_FPS   : int
            BOARD_STARTING_COLOR : str = "default"
            BOARD_PIECE_SHAPE    : str = "simple_circle"
            
        # %--------------------------------------- Global Settings ---------------------------------------% #
        COLOR_MAP = {
            "orange": (255, 105, 0),
            "blue": (0, 0, 255),
            "purple": (128, 0, 128),
            "pink": (255, 16, 240),
            "yellow": (255, 255, 0),
            "red": (255, 0, 0),
            "green": (0, 128, 0),
            "brown": (165, 42, 42),
            "black": (0, 0, 0),
            "white": (255, 255, 255),
            "gold": (255, 215, 0),
            "gray": (128, 128, 128),
        }
            
        # %------------------------------------------ GUI Classes ----------------------------------------% #
        class Window():
            def __init__(self, Game, SETTINGS : Settings) -> None:
                self.Game = Game
                self.SETTINGS = SETTINGS 
                
                # Initialize Pygame
                pygame.init()
                pygame.display.set_caption("Kamisado!")
                
                # Setup Screen
                self.screen = pygame.display.set_mode((self.SETTINGS.LENGTH,
                                                    self.SETTINGS.LENGTH),
                                                    pygame.RESIZABLE)
                # Setup Clock
                self.clock  = pygame.time.Clock()
                
                # Setup Board themes
                self.BOARD_COLORS = self.get_board_colors()
                self.PIECE_SHAPE  = self.get_piece_theme()
                
                # Initialize Board bitmap
                self.piece_bitmap, self.EMPTY_SQUARE = self.get_initial_piece_bitmap()
                self.PIECE_ID_TO_POSITION, self.PIECE_ID_TO_COLOR, self.PIECE_COLOR_TO_ID = self.get_piece_maps()
                
                # Piece Click Handler
                self.last_moved_piece_pos   = None   # (row, col) of last moved piece
                self.selected_piece_pos     = None   # (row, col) of currently selected piece
                self.next_piece_to_move_pos = None   # (row, col) of next piece to move
                self.dragging_piece_pos     = None   # (row, col) of piece being dragged
                
                # Game ending
                self.game_over = False
    
            # Purpose: resize Window based on User Event
            def resize(self, event) -> None:
                # Resize the window to smallest dimension to keep it square
                self.SETTINGS.LENGTH = min(event.w, event.h)
                self.SETTINGS.CELL_SIZE = self.SETTINGS.LENGTH // self.SETTINGS.DIM
                self.screen = pygame.display.set_mode((self.SETTINGS.LENGTH, self.SETTINGS.LENGTH),
                                                    pygame.RESIZABLE)
                
            # Purpose: Render Window
            def render_window(self) -> None:
                self.clock.tick(self.SETTINGS.MAX_FPS)
                pygame.display.update()
    
            # Purpose: Render Board
            def render_background(self) -> None:
                cs = self.SETTINGS.CELL_SIZE
                for r in range(self.SETTINGS.DIM):
                    for c in range(self.SETTINGS.DIM):
                        color = self.BOARD_COLORS[r][c]
                        pygame.draw.rect(self.screen, color,
                                        pygame.Rect(c * cs, r * cs, 
                                                        cs,     cs))
                        
            # Purpose: Render Pieces
            def render_pieces(self):
                for row in range(self.SETTINGS.DIM):
                    for col in range(self.SETTINGS.DIM):
                        piece_id = self.piece_bitmap[row][col]
                        if piece_id != self.EMPTY_SQUARE:
                            # Skip rendering the piece if it is being dragged
                            if self.dragging_piece_pos == (row, col):
                                continue
                            self.PIECE_SHAPE(self.SETTINGS.CELL_SIZE, row, col, piece_id=piece_id)
    
            # Purpose: Render Selection Highlighting
            def render_selection_highlighting(self, piece):
                if piece:
                    self.PIECE_SHAPE(self.SETTINGS.CELL_SIZE, *piece, IS_SELECTED=True)
            
            # Purpose: Render Dragging Piece
            def render_dragging_piece(self):
                if self.dragging_piece_pos:
                    # Get the current mouse position
                    x, y = pygame.mouse.get_pos()
                    cs   = self.SETTINGS.CELL_SIZE
                    
                    # Get piece ID to render
                    row, col = self.dragging_piece_pos
                    piece_id = self.piece_bitmap[row][col]
                    self.selected_piece_pos = None
                    
                    # Draw piece and selection ring
                    self.PIECE_SHAPE(cs, row, col, piece_id, drag_coords=(x, y))
                    self.PIECE_SHAPE(cs, row, col, piece_id, drag_coords=(x, y), IS_SELECTED=True)
            
            # Purpose: Render All Legal Moves
            def render_legal_moves(self):
                cs = self.SETTINGS.CELL_SIZE
                
                # Draw circles for each legal move
                radius = cs // 10
                for row, col in self.Game.cached_legal_moves:
                    center = (col * cs + cs // 2, row * cs + cs // 2)
                    pygame.draw.circle(self.screen, COLOR_MAP["gray"], center, radius)
                    pygame.draw.circle(self.screen, COLOR_MAP["black"], center, radius + 1, width=2)
                    
            # Purpose: Display game over screen
            def render_game_over(self):
                if self.game_over:
                    if self.Game.turn_skipped:
                        # Currently turn skip condition is checked after turn is passed:
                        # So the winner is the player who just passed their turn
                        WINNER = "Black" if self.Game.turn_player == self.Game.BLACK_PLAYER else "White"
                    else:
                        WINNER = "White" if self.Game.turn_player == self.Game.BLACK_PLAYER else "Black"
                    font_size = int(self.SETTINGS.CELL_SIZE * 0.6)
                    font = pygame.font.SysFont(None, font_size)
                    text_surface = font.render(f"Game Over - {WINNER} Wins!", True, (0, 0, 0), (255, 255, 255))
                    text_rect = text_surface.get_rect(center=(self.SETTINGS.LENGTH // 2, self.SETTINGS.LENGTH // 2))
                    self.screen.blit(text_surface, text_rect)
    
            # Purpose: Render the Entire Board + Pieces
            def render_board(self) -> None:
                self.render_background()
                self.render_pieces()
                if not self.game_over:
                    self.render_legal_moves()
                    self.render_dragging_piece()
                    self.render_selection_highlighting(self.last_moved_piece_pos)
                    self.render_selection_highlighting(self.selected_piece_pos)
                    self.render_selection_highlighting(self.next_piece_to_move_pos)
                self.render_game_over()
                self.render_window()
                
            # Purpose: Handle Mouse Dragging
            def handle_mouse_down(self, pos):
                # Stop handling input if game is over
                if self.game_over:
                    return
                
                # Get row and col from mouse position
                row, col = self.get_row_col_from_mouse_pos(pos)
                
                # Click outside board, ignore
                if not self.move_is_in_bounds(row, col):
                    return
                
                # If clicked piece is not the next piece to move, ignore
                if self.next_piece_to_move_pos and (row, col) != self.next_piece_to_move_pos:
                    return
    
                # Determin Clicked piece
                clicked_piece = self.piece_bitmap[row][col]
                
                # Start dragging if clicked on your own piece
                if clicked_piece * self.Game.turn_player > 0:
                    self.selected_piece_pos = (row, col)
                    self.dragging_piece_pos = (row, col)
                    self.Game.set_cached_legal_moves(self.selected_piece_pos)
                
            # Purpose: Handle Mouse Dragging
            def handle_mouse_up(self, pos):
                # Stop handling input if game is over
                if self.game_over:
                    return
                
                # Get row and col from mouse position
                row, col = self.get_row_col_from_mouse_pos(pos)
    
                # Click outside board, ignore
                if not self.move_is_in_bounds(row, col):
                    return
    
                # Handle dragging piece
                if self.dragging_piece_pos:
                    start_row, start_col = self.dragging_piece_pos
                    clicked_piece = self.piece_bitmap[row][col]
    
                    # CASE 1: Dragging to empty square
                    if clicked_piece == self.EMPTY_SQUARE:
                        # Check if move is valid
                        if (row, col) in self.Game.cached_legal_moves:
                            # Reset skip turn since a valid can be made
                            self.Game.turn_skipped = False
                            
                            # Move piece
                            moving_piece_id = self.piece_bitmap[start_row][start_col]
                            self.piece_bitmap[row][col] = moving_piece_id
                            self.PIECE_ID_TO_POSITION[moving_piece_id] = (row, col)
                            self.piece_bitmap[start_row][start_col] = self.EMPTY_SQUARE
                                
                            # Check for game over
                            if self.Game.check_for_game_end_back_rank(row):
                                self.game_over = True
                                
                            # Update last moved piece position and pass turn
                            self.update_lcn_piece_pos(row, col)
                            self.Game.pass_turn()
                                
                    # CASE 2: Released back on your own piece
                    elif (row, col) == (start_row, start_col):
                        # Deselect the piece if released on the same square
                        if self.selected_piece_pos == (row, col):
                            self.selected_piece_pos = None
                        else:
                            # Check for skip turn
                            if len(self.Game.cached_legal_moves) == 0:
                                # Check for turn skip loop
                                if self.Game.check_for_game_end_loop(clicked_piece):
                                    self.game_over = True
                                
                                # Skip turn
                                self.update_lcn_piece_pos(row, col)
                                self.Game.pass_turn()
                                self.Game.turn_skipped = True
                            # Select piece
                            else:
                                self.selected_piece_pos = (row, col)
                else:
                    # No dragging, just clicked
                    clicked_piece = self.piece_bitmap[row][col]
                    
                    # CASE 1: Clicked your own piece
                    if clicked_piece * self.Game.turn_player > 0:
                        if self.selected_piece_pos == (row, col):
                            self.selected_piece_pos = None
                        else:
                            # If clicked piece is not the next piece to move, ignore
                            if self.next_piece_to_move_pos and (row, col) != self.next_piece_to_move_pos:
                                return
                            # Check if the piece has legal moves
                            if len(self.Game.cached_legal_moves) == 0:
                                # Check for turn skip loop
                                if self.Game.check_for_game_end_loop(clicked_piece):
                                    self.game_over = True
                                    
                                # Skip turn
                                self.update_lcn_piece_pos(row, col)
                                self.Game.pass_turn()
                                self.Game.turn_skipped = True
                            else:
                                self.selected_piece_pos = (row, col)
    
                    # CASE 2: Clicked empty square while a piece is selected
                    elif clicked_piece == self.EMPTY_SQUARE and self.selected_piece_pos:
                        # Get the piece ID from position
                        sel_row, sel_col = self.selected_piece_pos
                        moving_piece_id = self.piece_bitmap[sel_row][sel_col]
    
                        # Move piece
                        if (row, col) in self.Game.cached_legal_moves:
                            self.Game.turn_skipped = False
                            self.piece_bitmap[row][col] = moving_piece_id
                            self.PIECE_ID_TO_POSITION[moving_piece_id] = (row, col)
                            self.piece_bitmap[sel_row][sel_col] = self.EMPTY_SQUARE
    
                            # Check for game over
                            if self.Game.check_for_game_end_back_rank(row):
                                self.game_over = True
                                
                            # Update last moved piece position and pass turn
                            self.update_lcn_piece_pos(row, col)
                            self.Game.pass_turn()
                            
                # Always reset drag state after mouse up
                self.dragging_piece_pos = None
                
            # Purpose: Check if move is in bounds
            def move_is_in_bounds(self, row, col):
                return 0 <= row < self.SETTINGS.DIM and 0 <= col < self.SETTINGS.DIM
            
            # Purpose: Check if square is empty
            def square_is_empty(self, row, col):
                return self.piece_bitmap[row][col] == self.EMPTY_SQUARE
            
            # Purpose: Update last/current/next piece to move
            def update_lcn_piece_pos(self, row, col):
                # Update last moved piece position
                self.last_moved_piece_pos = (row, col)
                
                # Find the RGB color at the landing square
                color = self.BOARD_COLORS[row][col]
    
                # Find which piece corresponds to that color for the current player
                piece_id = self.PIECE_COLOR_TO_ID[self.Game.turn_player][color]
                self.next_piece_to_move_pos = self.PIECE_ID_TO_POSITION[piece_id]
                self.selected_piece_pos = self.next_piece_to_move_pos
                
            # Purpose: Get row and col from mouse position
            def get_row_col_from_mouse_pos(self, pos):
                x, y = pos
                col = x // self.SETTINGS.CELL_SIZE
                row = y // self.SETTINGS.CELL_SIZE
                return row, col
            
            # Purpose: Get Piece Theme/shape
            def get_piece_theme(self):
                match self.SETTINGS.BOARD_PIECE_SHAPE:
                    case "simple_circle":
                        return self.piece_theme_simple_circle
                    
                    case "rooks":
                        return self.piece_theme_rooks
                    
                    case _:
                        raise NotImplementedError("This is not implemented yet")
            
            # Purpose: Draw rooks
            def piece_theme_rooks(self, cs, row, col, piece_id = 1, IS_SELECTED = False, drag_coords = None):
                surface = self.screen
                color_main = COLOR_MAP["black"] if piece_id > 0 else COLOR_MAP["white"]
                color_ring = self.PIECE_ID_TO_COLOR[piece_id]
    
                # Calculate center and base size
                if drag_coords:
                    cx, cy = drag_coords
                else:
                    cx = col * cs + cs // 2
                    cy = row * cs + cs // 2
    
                base_width        = cs // 2
                tower_height      = cs // 2
                battlement_height = cs // 8
                segment_width     = cs // 9 
    
                # Main tower rectangle
                tower_rect = pygame.Rect(0, 0, base_width, tower_height)
                tower_rect.center = (cx, cy)
                
                # Optional selection ring
                if IS_SELECTED:
                    pygame.draw.circle(surface, COLOR_MAP["gold"], (cx, cy), cs // 2, width=2)
                    pygame.draw.circle(surface, COLOR_MAP["black"], (cx, cy), cs // 2 + 1, width=1)
                else:
                    # Draw main tower
                    pygame.draw.rect(surface, color_main, tower_rect)
    
                    # Top battlements (aligned left, center, right)
                    top_y = tower_rect.top
                    battlements = [
                        (cx - base_width // 2, top_y - battlement_height),                 # left
                        (cx - segment_width // 2, top_y - battlement_height),              # center
                        (cx + base_width // 2 - segment_width, top_y - battlement_height)  # right
                    ]
                    
                    for x, y in battlements:
                        rect = pygame.Rect(x, y, segment_width, battlement_height)
                        pygame.draw.rect(surface, color_main, rect)
                        pygame.draw.rect(surface, COLOR_MAP["black"], rect, width=1)
    
                    # Add colored circle in the middle
                    pygame.draw.circle(surface, color_ring, (cx, cy), cs // 6)
                    pygame.draw.circle(surface, COLOR_MAP["black"], (cx, cy), cs // 6, width=1)
    
                    # Draw tower outline
                    pygame.draw.rect(surface, COLOR_MAP["black"], tower_rect, width=1)
            
            # Purpose: Draw a simple circle piece
            def piece_theme_simple_circle(self, cs, row, col, piece_id = 1, IS_SELECTED = False, drag_coords = None):
                    radius = cs // 3
                    color_radius = cs // 5
                    if drag_coords:
                        center = drag_coords
                    else:
                        center = (col * cs + cs // 2, 
                                row * cs + cs // 2)
                    if IS_SELECTED:
                        pygame.draw.circle(self.screen, COLOR_MAP["gold"], center, radius + 2, width=2)
                        pygame.draw.circle(self.screen, COLOR_MAP["black"], center, radius + 3, width=1)
                    else:
                        color = COLOR_MAP["black"] if piece_id > 0 else COLOR_MAP["white"]
                        pygame.draw.circle(self.screen, color, center, radius)
                        pygame.draw.circle(self.screen, self.PIECE_ID_TO_COLOR[piece_id], center, color_radius)
                        pygame.draw.circle(self.screen, COLOR_MAP["black"], center, radius, width=1)
                        pygame.draw.circle(self.screen, COLOR_MAP["black"], center, color_radius, width=1)
                        
            # Purpose: Initialize Board bitmap 
            def get_initial_piece_bitmap(self):
                DIM = self.SETTINGS.DIM
                EMPTY_SQUARE = 0
                
                # Populate the board
                board = [[EMPTY_SQUARE for _ in range(DIM)] for _ in range(DIM)]
                for c in range(DIM):
                    # Assign pieces to the first and last rows
                    board[0][c] = c + 1     # Black pieces: 1, 2, 3, ..., 8
                    board[-1][c] = -(c + 1) # White pieces: -1, -2, -3, ..., -8
                return board, EMPTY_SQUARE
            
            # Purpose: Get Piece Maps to color and position
            def get_piece_maps(self):
                DIM = self.SETTINGS.DIM
                PIECE_ID_TO_POSITION = {}
                PIECE_ID_TO_COLOR = {}
                PIECE_COLOR_TO_ID = {}
                PIECE_COLOR_TO_ID_White = {}
                PIECE_COLOR_TO_ID_Black = {}
                for c in range(DIM):
                    # Assign postion to piece IDs
                    PIECE_ID_TO_POSITION[c + 1] = (0, c)
                    PIECE_ID_TO_POSITION[-(c + 1)] = (DIM - 1, c)
                    
                    # Assign colors to piece IDs
                    PIECE_ID_TO_COLOR[c + 1] = self.BOARD_COLORS[0][c]
                    PIECE_ID_TO_COLOR[-(c + 1)] = self.BOARD_COLORS[-1][c]
                    
                    # Assign piece IDs to colors
                    PIECE_COLOR_TO_ID_Black[self.BOARD_COLORS[0][c]] = c + 1
                    PIECE_COLOR_TO_ID_White[self.BOARD_COLORS[-1][c]] = -(c + 1)
                PIECE_COLOR_TO_ID[-1] = PIECE_COLOR_TO_ID_Black
                PIECE_COLOR_TO_ID[1] = PIECE_COLOR_TO_ID_White
                return PIECE_ID_TO_POSITION, PIECE_ID_TO_COLOR, PIECE_COLOR_TO_ID
                
            # Purpose: Get Board Square Colors
            def get_board_colors(self):
                default_board_colors = [
                    ["orange", "blue",   "purple", "pink",   "yellow", "red",    "green",  "brown"],
                    ["red",    "orange", "pink",   "green",  "blue",   "yellow", "brown",  "purple"],
                    ["green",  "pink",   "orange", "red",    "purple", "brown",  "yellow", "blue"],
                    ["pink",   "purple", "blue",   "orange", "brown",  "green",  "red",    "yellow"],
                    ["yellow", "red",    "green",  "brown",  "orange", "blue",   "purple", "pink"],
                    ["blue",   "yellow", "brown",  "purple", "red",    "orange", "pink",   "green"],
                    ["purple", "brown",  "yellow", "blue",   "green",  "pink",   "orange", "red"],
                    ["brown",  "green",  "red",    "yellow", "pink",   "purple", "blue",   "orange"],
                ]
                match self.SETTINGS.BOARD_STARTING_COLOR:
                    case "default":
                        board_colors = default_board_colors
                    case "rotate 90":
                        board_colors = [list(row)[::-1] for row in zip(*default_board_colors)]
                    case "rotate 180":
                        board_colors = [row[::-1] for row in default_board_colors[::-1]]
                    case "rotate 270":
                        board_colors = [list(row) for row in zip(*default_board_colors)][::-1]
                    case _:
                        raise NotImplementedError("This is not implemented yet")
                return [[COLOR_MAP[color] for color in row] for row in board_colors]
        # %-------------------------------------------- Game  ------------------------------------% #
        class Kamisado():
            def __init__(self, SETTINGS) -> None:
                # Setup GUI
                self.Board = board.Window(self, SETTINGS)
                
                # Game Player setup
                self.WHITE_PLAYER = -1                      # Used to identify the player
                self.BLACK_PLAYER = 1                       # Used to identify the player
                self.turn_player  = self.WHITE_PLAYER 
                self.turn_skipped = False                   # Turn skip if no legal moves
                self.cached_legal_moves = []                # Cache legal moves for current piece
                
            # Purpose: run Game
            def run(self) -> None:
                running = True
                while running:
                    for event in pygame.event.get():
                        match event.type:
                            # Stop Game
                            case pygame.QUIT:
                                running = False
                                
                            # Scale Board
                            case pygame.VIDEORESIZE:
                                self.Board.resize(event)
                                
                            # Handle Click
                            case pygame.MOUSEBUTTONDOWN:
                                self.Board.handle_mouse_down(event.pos)
    
                            case pygame.MOUSEBUTTONUP:
                                self.Board.handle_mouse_up(event.pos)
                    self.Board.render_board()
                    
            # Purpose: Pass Turn to the other player
            def pass_turn(self):
                # Update turn player 
                self.turn_player = self.BLACK_PLAYER if self.turn_player == self.WHITE_PLAYER else self.WHITE_PLAYER
                
                # Determine the legal moves
                self.cached_legal_moves = self.get_legal_moves(self.Board.next_piece_to_move_pos)
            
            # Purpose: Get the legal moves for a piece
            def get_legal_moves(self, piece_pos):
                # Determine the piece to move
                row, col = piece_pos
                piece_id = self.Board.piece_bitmap[row][col]
    
                legal_moves = []
                directions = []
    
                # Determine movement direction based on player
                if piece_id > 0:
                    directions = [(1, 0),       # Black moves down
                                (1, -1),      # Black moves down-left
                                (1, 1)]       # Black moves down-right
                else:                           
                    directions = [(-1, 0),      # White moves up
                                (-1, -1),     # White moves up-left
                                (-1, 1)]      # White moves up-right
    
                # Check for legal moves in each direction
                for dr, dc in directions:
                    # Increment move in the direction
                    row_i, col_i = row + dr, col + dc
                    while self.Board.move_is_in_bounds(row_i, col_i):
                        # Check if the square is empty or occupied
                        if self.Board.square_is_empty(row_i, col_i):
                            legal_moves.append((row_i, col_i))
                        else:
                            break
                        row_i += dr
                        col_i += dc
                return legal_moves
    
            # Purpose: Set cached legal moves
            def set_cached_legal_moves(self, piece_pos):
                self.cached_legal_moves = self.get_legal_moves(piece_pos)
    
            # Purpose: Check for game end condition when a player reaches the back rank
            def check_for_game_end_back_rank(self, row):
                # Check if the player has reached the back rank
                if (row == 0 and self.turn_player == self.WHITE_PLAYER) or \
                (row == self.Board.SETTINGS.DIM - 1 and self.turn_player == self.BLACK_PLAYER):
                    return True
                return False
            
            # Purpose: Check for game end condition when a player causes a loop
            def check_for_game_end_loop(self, piece_id):
                # Check if both players have no legal moves
                if self.turn_skipped and len(self.cached_legal_moves) == 0:
                    # Get color of the last moved piece
                    row, col = self.Board.last_moved_piece_pos
                    last_piece_id = self.Board.piece_bitmap[row][col]
                    last_piece_color = self.Board.PIECE_ID_TO_COLOR[last_piece_id]
                    last_piece_pos_color = self.Board.BOARD_COLORS[row][col]
                    
                    # Get color of current piece
                    current_piece_color = self.Board.PIECE_ID_TO_COLOR[piece_id]
                    current_piece_pos = self.Board.PIECE_ID_TO_POSITION[piece_id]
                    current_piece_pos_color = self.Board.BOARD_COLORS[current_piece_pos[0]][current_piece_pos[1]]
                    
                    # Check for loop conditions:
                    # 1) last moved piece color is the same as the current piece position color
                    # 2) last moved piece position color is the same as the current piece color
                    if (last_piece_color == current_piece_pos_color) and (last_piece_pos_color == current_piece_color):
                        return True
                return False
        # %-------------------------------------------- Main  ------------------------------------% #
        def main():
            # Set GUI Settings
            DIM       = 8                                  # Board of (DIM X DIM)
            LENGTH    = 512                                # Height and Width of the Board
            CELL_SIZE = LENGTH//DIM       
            MAX_FPS   = 60
            
            # Set Board Settings
            BOARD_COLOR_OPTIONS  = ["default", "rotate 90", "rotate 180", "rotate 270"]
            BOARD_STARTING_COLOR = BOARD_COLOR_OPTIONS[0]  # Default Board Color is default   
            BOARD_PIECE_SHAPE    = "rooks"                 # Default Piece Shape is simple_circle
            SETTINGS  = board.Settings(DIM=DIM,
                                    LENGTH=LENGTH,
                                    CELL_SIZE=CELL_SIZE,
                                    MAX_FPS=MAX_FPS,
                                    BOARD_STARTING_COLOR=BOARD_STARTING_COLOR,
                                    BOARD_PIECE_SHAPE=BOARD_PIECE_SHAPE)
            # Run Game
            Kamisado = game.Kamisado(SETTINGS)
            Kamisado.run()
        # %--------------------------------------------- Run -------------------------------------% #
        if __name__ == '__main__':
            print(f'{"Start":-^{50}}')
            main()
            print(f'{"End":-^{50}}')