### Ansi.py ###
class Ansi:
    """
    A class to handle ANSI escape codes for terminal text formatting.
    """

    def set_foreground(color: int, intensity: bool) -> None:
        """
        Set the foreground color of the text.

        Parameters:
        - color: The color code (0-7 for standard colors).
        - intensity: Boolean indicating if the color should be bright (True) or normal (False).
        """
        if intensity:
            print(f"\033[{color + 90}m", end='', flush=True)
        else:
            print(f"\033[{color + 30}m", end='', flush=True)

    def set_background(color: int, intensity: bool) -> None:
        """
        Set the background color of the text.

        Parameters:
        - color: The color code (0-7 for standard colors).
        - intensity: Boolean indicating if the color should be bright (True) or normal (False).
        """
        if intensity:
            print(f"\033[{color + 100}m", end='', flush=True)
        else:
            print(f"\033[{color + 40}m", end='', flush=True)

    def reset() -> None:
        """Reset all text formatting to default settings."""
        print(f"\033[0m", end='', flush=True)

    def clear_line() -> None:
        """Clear the current line in the terminal."""
        print(f"\033[2K", end='', flush=True)

    def clear_screen() -> None:
        """Clear the entire screen in the terminal."""
        print(f"\033[2J", end='', flush=True)

    def gotoXY(x: int, y: int) -> None:
        """
        Move the cursor to a specific position in the terminal.

        Parameters:
        - x: The horizontal position (column).
        - y: The vertical position (row).
        """
        print(f"\033[{y};{x}H", end='', flush=True)

### Coordinator.py ###
# from player_console import PlayerConsoleSimple
from player_console import PlayerConsole
from game_token import GameToken
from game_logic import GameLogicLocal
from game_logic_base import GameLogicBase
from game_state import GameState
from drop_state import DropState
import time, sys

from util import Util 
if Util.isRaspberry():
    pass

class Coordinator:
    def __init__(self):
        # red and yellow player
        self._player_red = PlayerConsole(GameToken.RED)
        self._player_yellow = PlayerConsole(GameToken.YELLOW)
        self._game = GameLogicLocal()

    def run(self): # plays game until won or draw
        while True:
            # checks the game state
            state = self._game.get_state()
            
            # checks if game is over
            if state in [GameState.WON_RED, GameState.WON_YELLOW, GameState.DRAW]:
                if state == GameState.WON_RED:
                    print("Red is the winner")
                elif state == GameState.WON_YELLOW:
                    print("Yellow is the winner")
                else:
                    print("Draw")
                break
            
            # determines which player is playing
            current_player = self._player_red if state == GameState.TURN_RED else self._player_yellow
            current_token = GameToken.RED if state == GameState.TURN_RED else GameToken.YELLOW
            
            # board
            current_player.draw_board(self._game.get_board(), state)
            
            # moves player
            try:
                column = current_player.play_turn()
                # drops token
                drop_result = self._game.drop_token(current_token, column)
                if drop_result != DropState.DROP_OK:
                    print(f"Invalid move: {drop_result}")
            except KeyboardInterrupt:
                print("\nGame interrupted by player")
                break

# start a local game
if __name__ == '__main__':

    print("Welcome to Connect 4, starting game")

    coordinator = Coordinator()
    coordinator.run()

### display_base.py ###
from ansi import Ansi
from game_token import GameToken


class DisplayBase:
    """
    Base class for displaying a grid and tokens in the game.
    """

    def draw_grid(self) -> None:
        """
        Draw the grid on the display.

        This method should be implemented by subclasses to provide 
        specific grid drawing logic.
        """
        raise NotImplementedError("Subclasses must implement this method.")

    def draw_token(self, x: int, y: int, token) -> None:
        """
        Draw a token at the specified coordinates on the grid.

        Parameters:
        - x: The horizontal position (column) to place the token.
        - y: The vertical position (row) to place the token.
        - token: The token to be drawn (e.g., player token).

        This method should be implemented by subclasses to provide 
        specific token drawing logic.
        """
        raise NotImplementedError("Subclasses must implement this method.")

### display_console.py ###
from ansi import Ansi
from game_token import GameToken
from display_base import DisplayBase


class DisplayConsole(DisplayBase):
    def __init__(self):
        Ansi.clear_screen()

    def draw_token(self, x: int, y: int, token: GameToken = GameToken.EMPTY) -> None:
        row = 3 + (y * 2)  # y-position inside the grid

        col = 3 + (x * 5) # x-position inside the grid

        if x < 0 or x >= 7 or y < -1 or y >= 6:
            raise ValueError("Invalid position for token.")
        
        Ansi.gotoXY(col, row)
        
        if token == GameToken.RED:
            Ansi.set_foreground(1, True)
            token_str = "██"
        elif token == GameToken.YELLOW:
            Ansi.set_foreground(3, True)
            token_str = "██"
        else:
            token_str = "  "
        
        print(token_str, end="")
        Ansi.reset()

    def draw_grid(self) -> None:
        # YOUR CODE HERE
        Ansi.reset()
        Ansi.gotoXY(1, 2)

        print(f"┌────┬────┬────┬────┬────┬────┬────┐\n│    │    │    │    │    │    │    │\n├────┼────┼────┼────┼────┼────┼────┤\n│    │    │    │    │    │    │    │\n├────┼────┼────┼────┼────┼────┼────┤\n│    │    │    │    │    │    │    │\n├────┼────┼────┼────┼────┼────┼────┤\n│    │    │    │    │    │    │    │\n├────┼────┼────┼────┼────┼────┼────┤\n│    │    │    │    │    │    │    │\n├────┼────┼────┼────┼────┼────┼────┤\n│    │    │    │    │    │    │    │\n└────┴────┴────┴────┴────┴────┴────┘")


if __name__ == '__main__':
    Ansi.clear_screen()
    Ansi.reset()
    fc = DisplayConsole()
    fc.draw_grid()
    fc.draw_token(0, 0, GameToken.RED)
    fc.draw_token(5, 2, GameToken.YELLOW)
    #print(type(GameToken.RED))
    #print(GameToken.RED)
    Ansi.gotoXY(0,15)

### drop_state.py ###
from enum import Enum


class DropState(Enum):
    """
    Enum class representing different states after drop of a token
    """
    DROP_OK = 0          # token dropped successfully
    COLUMN_INVALID = 1   # The selected column is invalid
    COLUMN_FULL = 2      # The selected column is already full
    WRONG_PLAYER = 3     # A player made a move that is not theirs

### game_logic_base.py ###
from game_state import GameState
from game_token import GameToken
from drop_state import DropState
from display_console import DisplayConsole


class GameLogicBase:
    """
        GameLogicBase provides basic methods for interacting with the game's logic.
    """

    def __init__(self):
        # print("GameLogicBase initialized")
        pass

    def get_board(self) -> list:
        """
        Returns the board as a list of lists. The board positions are displayed as a string. They can contain one of 3 values:
        - 'X' or '0': the game position is occupied by player 'X' or '0', respectively
        - ' ': the game position is still unoccupied
        """
        return self._board

    def get_state(self) -> GameState:
        raise NotImplementedError("")

    def drop_token(self, player: GameToken, column: int) -> DropState:
        """
        The current player (identified by either 'X' or 'Y') makes their move by dropping their token into the specified column. 
        Parameters:
        - player: The token representing the player (i.e., RED or YELLOW).
        - column: The column (0..6) into which to drop the token.
        Returns:
        DropState: The state of the drop (e.g., okay, invalid position, column full).

        """
        if column < 0 or column >= self.COLS:
            return DropState.COLUMN_INVALID
        
        # Find the lowest empty row
        row = -1
        for r in range(self.ROWS):
            if self._board[r][column] == GameToken.EMPTY:
                row = r
                break
        
        if row == -1:
            return DropState.COLUMN_FULL

        # Animate the drop
        display = DisplayConsole()  # You might want to pass this as a parameter instead
        for y in range(row + 1):
            if y > 0:
                display.draw_token(column, y-1, GameToken.EMPTY)  # Clear previous position
            display.draw_token(column, y, player)  # Draw current position
            time.sleep(0.1)  # Add a small delay for animation
        
        self._board[row][column] = player
        return DropState.OK

        raise NotImplementedError("You need to subclass GameLogicBase to use move().")

### game_logic_client.py ###
from game_logic_base import GameLogicBase
from game_state import GameState
from drop_state import DropState
from game_token import GameToken
import requests

class GameLogicClient(GameLogicBase):

    def __init__(self, host):
        super().__init__()
        print( f"GameLogicClient initialized with host {host}" )
        self._url = f'http://{host}:5000/api'

    def get_board(self) -> list:
        # call remote API
        response = requests.get( f"{self._url}/board")
        # return result to local caller
        return response.json().get("board")

    def get_state(self) -> GameState:
        # IMPLEMENT METHOD HERE!
        return GameState.TURN_RED

    def drop_token(self, player, column) -> DropState:
        # IMPLEMENT METHOD HERE
        return DropState.DROP_OK


if __name__ == '__main__':
    """
    Test programm to manually check if GameLogicClient is working.
    Limitations:
    - Implements both players at once--no distributed gameplay possible
    - Does not handle errors
    - Does not handle end of game gracefully
    """
    # local function
    def draw_board( board: list, state: GameState) -> None:
        print("0|1|2|3|4|5|6")
        for row in board:
            print('|'.join(row))
        print( f"GameState: {state}" )

    client = GameLogicClient("127.0.0.1")
    while( True ):
        game_state = client.get_state()
        board = client.get_board()

        draw_board( board, game_state )

        if game_state == GameState.TURN_RED or  game_state == GameState.TURN_YELLOW:
            player = GameToken.RED if game_state == GameState.TURN_RED else GameToken.YELLOW  
            column = int(input("Which colum to drop? "))    
            drop_state = client.drop_token( player, column )
            print( "drop_state:", drop_state )
        else: break # bail out if its neither RED's nor YELLOW's turn, i.e. WON or DRAW
    
    print("Game Over")

### game_logic_server.py ###
from game_logic import GameLogicLocal
from game_state import GameState
from drop_state import DropState
from flask import Flask, request, jsonify
from flasgger import Swagger


if __name__ == "__main__":
    game = GameLogicLocal()
    app = Flask(__name__)
    swagger = Swagger(app)

    @app.route('/api/board', methods=['GET'])
    def get_board():
        """
        Get the current state of the game board.
        ---
        tags:
           - Getting information about the current game 
        description: |
            The response is a dictionary with a single entry:
            - `board`: A 6x7 grid (as a list of lists) representing the game board.

            **Response Details**: 
            The response contains:
            - a dictionary which contains a single key-value pair whose key is `board`.
            - The value represents the current state of the game board. It is a
              list containing 6 lists, each of which contain 7 strings. Each
              string represents a game token:
                - `'X'`: Player token for red.
                - `'0'`: Player token for yellow.
                - `' '`: An empty cell.

            *Example*:
            ```json
            {
                "board": [
                    [' ',' ',' ',' ',' ',' ',' '],
                    [' ',' ',' ',' ',' ',' ',' '],
                    [' ',' ',' ',' ',' ',' ',' '],
                    [' ',' ',' ',' ','0',' ',' '],
                    [' ',' ',' ','0','X',' ',' '],
                    [' ','X','0','X','X','0',' ']
                ]
            }
            ```
        responses:
            200:
                description: The current game board
                schema:
                    type: object
                    properties:
                        board:
                            type: array
                            description: The game board, represented as a list of 6 rows.
                            items:
                                type: array
                                description: A row of the game board, represented as 7 game tokens.
                                items:
                                    type: string
                                    description: A token in the game, represented by one of 'X', 'O', ' '
        """
        # working implementation for GET /api/board
        # get the board from the local GameLogic object
        board = game.get_board()
        # return board to the caller via JSON response
        return jsonify( {"board": board} ), 200 # status code: 200 Ok

    @app.route('/api/state', methods=['GET'])
    def get_state():
        """
        Retrieve the current state of the game.
        ---
        tags:
           - Getting information about the current game 
        description: |
            The response is a dictionary with a single entry:
            - `state`: An integer number indicating the current state of the game. Possible values are:
                - `0` (TURN_RED):    It's Red's turn to play.
                - `1` (TURN_YELLOW): It's Yellow's turn to play.
                - `2` (WON_RED):     Red has won the game.
                - `3` (WON_YELLOW):  Yellow has won the game.
                - `4` (DRAW):        The game ends in a draw.

            *Example*:
            ```json
            { "state": 1 }
            ```
        responses:
            200:
                description: The current game state
                schema:
                    type: object
                    properties:
                        state:
                            type: integer
                            description: The current state of the game, represented as integer number [0..4]
                            example: 1
        """
        # IMPLEMENT GET /api/state HERE
        @app.route('/api/state', methods=['GET'])
        def get_state():
            # get the state from the local GameLogic object
            state = game.get_state()
            # return state to the caller via JSON response
            return jsonify({"state": state.value}), 200
        return jsonify({"game_state": "not implemented!"}), 501 # status code: 501: Not implemented

    @app.route('/api/drop', methods=['POST'])
    def drop_token():
        """
        A player makes their move by dropping their token into the specified column.
        ---
        tags:
           - A player making their move
        description: |
            **Request Details**:
            The request body contains a dictionary containing the player's move details.

            *Example*:
            ```json
            { "column": 3, "player_id": "X" }
            ```
            **Response Details**: 
            The response is a dictionary with a single entry:
            - `drop_state`: An integer number indicating the state of drop. Possible values are:
                - `0` (DROP_OK):        Token dropped successfully
                - `1` (COLUMN_INVALID): The selected column is invalid
                - `2` (COLUMN_FULL):    The selected column is already full
                - `3` (WRONG_PLAYER):   A player made a move that is not theirs
            
            *Example*:
            ```json
            { "drop_state": 1 }
            ```
        parameters:
            - in: body
              name: body
              required: true
              description: The dictionary containing the player's move details.
              schema:
                type: object
                properties:
                    player_id:
                        type: string
                        description: The token of the player making the move, either 'X' or '0'.
                        example: 'X'
                    column:
                        type: integer
                        description: The column in which to drop the token (0-indexed).
                        example: 3
        responses:
            200:
                description: The current game state
                schema:
                    type: object
                    properties:
                        drop_state:
                            type: integer
                            description: A status code describing the result of the drop [0..3]
                            example: 1
            400:
                description: Fields 'player_id' and/or 'column' missing in request body.
                schema:
                    type: object
                    properties:
                        Error:
                            type: string
                            description: Textual desription of the error.
                            example: Fields 'player_id' and/or 'column' missing in request body.
        """
        # IMPLEMENT POST /api/drop HERE!
        @app.route('/api/drop', methods=['POST'])
        def drop_token():
            # Get the request data
            data = request.get_json()
            
            # Check if required fields are present
            if 'player_id' not in data or 'column' not in data:
                return jsonify({
                    "Error": "Fields 'player_id' and/or 'column' missing in request body."
                }), 400
            
            # Extract player and column from request
            player_id = data['player_id']
            column = data['column']
            
            # Convert player_id to GameToken
            player = GameToken.RED if player_id == 'X' else GameToken.YELLOW
            
            # Attempt to drop the token
            drop_state = game.drop_token(player, column)
            
            # Return the result
            return jsonify({"drop_state": drop_state.value}), 200

        return jsonify({"drop_state": "not implemented!"}), 501
    
    
    # starting the server on all interfaces
    print("Game server start")
    app.run(host="0.0.0.0", debug=True)
    print("Game server exit")

### game_logic.py ###
from game_state import GameState
from game_token import GameToken
from drop_state import DropState
from game_logic_base import GameLogicBase
from display_console import DisplayConsole
import time


class GameLogicLocal(GameLogicBase):
    ROWS = 6
    COLS = 7

    def __init__(self):
        # Initialize the game board with empty spaces
        self._board = [[' ' for _ in range(self.COLS)] for _ in range(self.ROWS)]
        # Initialize the game state to RED's turn
        self._state = GameState.TURN_RED

    def _check_winner(self, player: GameToken) -> bool:
        for r in range(self.ROWS):
            for c in range(self.COLS - 3):
                if (self._board[r][c] == player and 
                    self._board[r][c+1] == player and 
                    self._board[r][c+2] == player and 
                    self._board[r][c+3] == player):
                    return True

        # vertical check
        for c in range(self.COLS):
            for r in range(self.ROWS - 3):
                if (self._board[r][c] == player and 
                    self._board[r+1][c] == player and 
                    self._board[r+2][c] == player and 
                    self._board[r+3][c] == player):
                    return True

        # positive diagonal check
        for r in range(self.ROWS - 3):
            for c in range(self.COLS - 3):
                if (self._board[r][c] == player and 
                    self._board[r+1][c+1] == player and 
                    self._board[r+2][c+2] == player and 
                    self._board[r+3][c+3] == player):
                    return True

        # negative diagonal check
        for r in range(3, self.ROWS):
            for c in range(self.COLS - 3):
                if (self._board[r][c] == player and 
                    self._board[r-1][c+1] == player and 
                    self._board[r-2][c+2] == player and 
                    self._board[r-3][c+3] == player):
                    return True

        return False
    
    def _check_draw(self) -> bool:
        return all(self._board[self.ROWS-1][c] != ' ' for c in range(self.COLS))

    def get_board(self) -> list:
        return self._board

    def get_state(self) -> GameState:
        return self._state

    def drop_token(self, player: GameToken, column: int) -> DropState:
        if column < 0 or column >= self.COLS:
            return DropState.COLUMN_INVALID
        
        # checks the lowest empty row in column
        row = 5
        for r in range(self.ROWS):
            if self._board[r][column] == ' ':
                row = r
            else:
                r -= 1
                break
        
        if r == -1:
            return DropState.COLUMN_FULL

        # drop animation
        display = DisplayConsole()
        for y in range(row + 1):
            display.draw_grid()  # redraws the grid background
            # redraws dropped tokens
            for r in range(self.ROWS):
                for c in range(self.COLS):
                    token = self._board[r][c]
                    if token != ' ':
                        display.draw_token(c, r, token)
            # board with new token
            display.draw_token(column, y, player)
            time.sleep(0.075)

        # token in last row
        self._board[row][column] = player
            
        if self._check_winner(GameToken.RED):
            self._state = GameState.WON_RED
        elif self._check_winner(GameToken.YELLOW):
            self._state = GameState.WON_YELLOW
        elif self._check_draw():
            self._state = GameState.DRAW
        else:
            # switches turns only if game hasn't ended
            self._state = GameState.TURN_YELLOW if self._state == GameState.TURN_RED else GameState.TURN_RED

        return DropState.DROP_OK

### game_state.py ###
from enum import Enum


class GameState(Enum):
    """
    Enum class representing different states of the game.

    This class defines the possible states that the game can be in,
    including the current turn and the outcome of the game.
    """
    TURN_RED = 0          # It's Red's turn to play
    TURN_YELLOW = 1       # It's Yellow's turn to play
    WON_RED = 2           # Red has won the game
    WON_YELLOW = 3        # Yellow has won the game
    DRAW = 4              # The game ends in a draw


if __name__ == '__main__':
    s = GameState.TURN_RED
    print(f"GameState {s}, Type: {type(s)}, Value: {s.value}")

### game_token.py ###
from strenum import StrEnum


class GameToken(StrEnum):
    """
    Enum class representing the two different game tokens.
    """
    EMPTY = ' '     # An empty token (placeholder)
    RED = 'X'       # Token for the red player
    YELLOW = '0'    # Token for the yellow player


if __name__ == '__main__':
    t = GameToken.RED
    print(t)
    st = str(t)
    print(f"GameToken {t}, Type: {type(t)}, Value: {t.value}, Type of Value: {type(t.value)}")
    print(f"GameToken {st}, Type: {type(st)}")

### input_base.py ###
from enum import Enum


class Keys(Enum):
    """
    Enum class representing different key codes for input handling.
    """
    UNKNOWN = -1   # Unknown key code
    UP = -2       # Key code for the up arrow
    DOWN = -3     # Key code for the down arrow
    LEFT = -4     # Key code for the left arrow
    RIGHT = -5    # Key code for the right arrow
    ENTER = 13    # Key code for the Enter key
    ESC = 27      # Key code for the Escape key


class InputBase:
    """
    Base class for handling input operations.

    This class defines the interface for reading key inputs.
    Subclasses must implement the `read_key` method.
    """

    def read_key(self) -> Keys:
        """
        Read a key input and return its corresponding key code.

        Raises:
            NotImplementedError: This method must be implemented by subclasses.
        """
        raise NotImplementedError()

### input_console.py ###
from input_base import InputBase
from input_base import Keys
from enum import Enum
import os
from time import sleep

if os.name == 'nt':  # Windows
    import msvcrt
else:  # Posix (Linux, OS X)
    import sys
    import tty
    import termios
    from select import select


class InputConsole(InputBase):
    """
    Input handler for console applications using keyboard input. 
    """

    # def key_pressed(self) -> bool:
    #     """
    #     Check if a key has been pressed. (Only works on Winsows)

    #     Returns:
    #         bool: True if a key is pressed, False otherwise.
    #     """
    #     if os.name == 'nt':
    #         return msvcrt.kbhit()
    #     else:
    #         dr, dw, de = select([sys.stdin], [], [], 0)
    #         return dr != []
    #         # return keyboard.read_event().event_type == keyboard.KEY_DOWN

    def read_key(self) -> Enum:
        """
        Read a key from the console and return its corresponding key code. Method blocks until a key is available.

        Returns:
            Enum: The key code corresponding to the pressed key.
        """
        if os.name == 'nt':  # Windows
            key = msvcrt.getch()
            if key in (b'\xe0', b'\x00'):  # Special keys (arrow keys send two bytes)
                key = msvcrt.getch()  # Get the second byte for direction
                if key == b"H":
                    return Keys.UP
                elif key == b"H":
                    return Keys.UP
                elif key == b"P":
                    return Keys.DOWN
                elif key == b"K":
                    return Keys.LEFT
                elif key == b"M":
                    return Keys.RIGHT
            elif key == b"\r":
                return Keys.ENTER
            elif key == b"\x1b":
                return Keys.ESC
            elif key == b"\03":  # Ctrl+C
                raise KeyboardInterrupt()  # Trigger a KeyboardInterrupt
            return Keys.UNKNOWN

        else:
            fd = sys.stdin.fileno()
            old_settings = termios.tcgetattr(fd)
            try:
                tty.setraw(sys.stdin.fileno())
                ch = sys.stdin.read(1)
                if ch == "\x1b":
                    ch = sys.stdin.read(1)
                    if ch == "\x1b":
                        return Keys.ESC
                    ch += sys.stdin.read(1)
                    if ch == "[A":
                        return Keys.UP
                    elif ch == "[B":
                        return Keys.DOWN
                    elif ch == "[C":
                        return Keys.RIGHT
                    elif ch == "[D":
                        return Keys.LEFT
                elif ch == "\r":
                    return Keys.ENTER
                elif ch == "\n":
                    return Keys.ENTER
                elif ch == "\x03":
                    raise KeyboardInterrupt()
                return Keys.UNKNOWN
            finally:
                termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)


if __name__ == '__main__':
    print("press any key, ESC to exit")
    c = InputConsole()
    while True:
        key = c.read_key()
        print(f"Taste: {key}, Type: {type(key)}")
        if key == Keys.ENTER:
            print("Enter")
        if (key == Keys.ESC):  # Abort with ESC
            break

### player_base.py ###
from display_base import *
from game_state import GameState


class PlayerBase:
    """
    Base class for representing a player in the game.

    This class provides an interface for player actions, including
    making a move and drawing the game board. Subclasses must implement
    the required methods to provide specific player behavior.
    """

    def __init__(self, player: GameToken):
        """
        Initialize the player with a specific game token.

        Parameters:
        - player: The token representing the player (e.g., RED or YELLOW).
        """
        self._player = player

    def play_turn(self) -> int:
        """
        Asks the player to play their turn.

        Returns:
            int: The column index where the player drops their token.

        Raises:
            NotImplementedError: This method must be implemented by subclasses.
        """
        raise NotImplementedError("You need to subclass Player to use play_turn().")

    def draw_board(self, board) -> None:
        """
        Draw the game board for the player.

        Parameters:
        - board: The current state of the game board.

        Raises:
            NotImplementedError: This method must be implemented by subclasses.
        """
        raise NotImplementedError("You need to subclass Player to use draw_board().")

    @property
    def player_id(self) -> GameToken:
        """
        Get the player's token.

        Returns:
            GameToken: The token representing the player.
        """
        return self._player

### player_console.py ###
from display_base import DisplayBase  # base class for output methods
from input_console import InputConsole
from player_base import PlayerBase
from game_state import GameState
from game_token import GameToken
from ansi import Ansi
from display_console import DisplayConsole
from input_console import Keys

class PlayerConsole(PlayerBase):
    def __init__(self, player: GameToken):
        super().__init__(player)

        self._display = DisplayConsole()
        self._input = InputConsole()
        self._current_column = 0

    def play_turn(self) -> int:
        self._current_column = 0

        # draws token
        self._display.draw_token(self._current_column, -1, self._player)

        while True:
            key = self._input.read_key()
            
            if key == Keys.LEFT and self._current_column > 0:
                # clears position
                self._display.draw_token(self._current_column, -1, GameToken.EMPTY)
                self._current_column -= 1
                # draws at now position
                self._display.draw_token(self._current_column, -1, self._player)
            
            elif key == Keys.RIGHT and self._current_column < 6:
                # clears position
                self._display.draw_token(self._current_column, -1, GameToken.EMPTY)
                self._current_column += 1
                # draws at now position
                self._display.draw_token(self._current_column, -1, self._player)
            
            elif key == Keys.ENTER:
                # clears top token
                self._display.draw_token(self._current_column, -1, GameToken.EMPTY)
                return self._current_column
            
            # emergency exit
            elif key == Keys.ESC:
                raise KeyboardInterrupt()

    def draw_board(self, board: list, state: GameState):
        self._display.draw_grid() # draws the grid
        
        # draws tokens
        for y in range(6):
            for x in range(7):
                if board[y][x] != ' ':
                    self._display.draw_token(x, y, board[y][x])

if __name__ == '__main__':

    board = [[' ' for _ in range(7)] for _ in range(6)]
    board[5][0] = GameToken.RED  # [Y][X]
    p = PlayerConsole(GameToken.YELLOW)

    Ansi.clear_screen()
    Ansi.reset()
    p.draw_board(board, GameState.TURN_YELLOW)
    pos = p.play_turn()
    Ansi.reset()
    Ansi.gotoXY(1, 20)
    print(f"Position: {pos}")

### util.py ###
import os
import platform


class Util:

    @classmethod
    # @property
    def isRaspberry(cls) -> bool:
        if os.name == "posix":
            if platform.system() == "Linux":
                if platform.machine() == "armv7l" or platform.machine() == "aarch64":
                    return True
        return False


if __name__ == '__main__':
    print(f"Raspberry Pi: {Util.isRaspberry()}")

### SenseHat example ###
from sense_hat import SenseHat
import time

sense = SenseHat()
sense.set_rotation(180)

#text = input("Enter text to display: ")
#sense.show_message(text, text_colour=(64,64,64))

# for letter in "delia":
#     sense.show_letter(letter, text_colour=(64,64,64))
#     time.sleep(0.3)

X = (0,64,0)
O = (0,0,0)
creeper = [
    X, X, X, X, X, X, X, X,
    X, O, O, X, X, O, O, X,
    X, O, O, X, X, O, O, X,
    X, X, X, O, O, X, X, X,
    X, X, O, O, O, O, X, X,
    X, X, O, O, O, O, X, X,
    X, X, O, X, X, O, X, X,
    X, X, X, X, X, X, X, X
]

minecraft_list = [creeper]

try:
    while True:
        for i in minecraft_list:
            sense.set_pixels(i)
            time.sleep(1)
except KeyboardInterrupt:
    sense.show_message("bye", text_colour=(64,64,64), scroll_speed=0.05)
    sense.clear()