예제 #1
0
 def game_handler(self):
     """
    This function controls the game. if the game is running and there is
    no winner yet, checks if the player is ai or human. if the player is ai
    it preforms the ai move by calling the functions "find legal move" from
    the AI class and the "make move" function from the Game class.
    if there is a winner' the function presents the winner by calling the
    "win state" function.
    """
     if self.__game_is_running:
         state = self.game.get_winner()
         current_player = self.game.get_current_player()
         self.update_info(current_player)
         if state is not None:
             self.__game_is_running = False
             self.win_state(state)
         else:
             tk.Label(self.__info_frame,
                      text='Player {} turn!'.format(current_player)).place(
                          x=350, y=0)
             if self.__players[current_player]:
                 comp = AI(self.game, current_player)
                 try:
                     chosen_move = comp.find_legal_move()
                     # Sleeping for 0.5 second before making the computer move
                     sleep(0.5)
                     self.__make_move(chosen_move)
                 # If exception is returned - The game is finished, using pass for moving to move forward
                 # (The finished game situation will be managed by the next call of game_handler)
                 except Exception:
                     pass
     self.__parent.after(100, self.game_handler)
예제 #2
0
class FourInARow:
    """
    The high level application object, handling game events,
    turn order and legality, communication between instances 
    and controlling the objects that manage GUI, gameplay and AI.
    """
    PLAYERS = ARG_PLAYERS
    MSG_NOT_TURN = 'Not your turn!'
    MSG_DRAW = 'draw'
    MSG_WIN = 'winner!'
    MSG_LOSE = 'loser :('

    def __init__(self, root, player, port, ip):
        """
        The function initialize all object's private values.
        :param player: A string which decide if the player is human or ai.
        :param port: An integer between 0 to 65535. better use ~8000
        :param ip: The host IP. can be None if the player is the
        host or the host ip address.
        """
        self.__root = root
        self.__game = Game()
        # decide whether the player is AI or not
        if player == self.PLAYERS[1]:
            self.__is_ai = True
        else:
            self.__is_ai = False
            # Set both Players colors
        if ip:
            self.__my_color = Game.PLAYER_TWO
            self.__op_color = Game.PLAYER_ONE

        else:
            self.__my_color = Game.PLAYER_ONE
            self.__op_color = Game.PLAYER_TWO

        self.__communicator = Communicator(root, port, ip)
        self.__communicator.connect()
        self.__communicator.bind_action_to_message(self.handle_message)

        if self.__is_ai:  # If the player is AI we initialize an AI object
            self.__screen = Screen(root, self.__my_color, lambda y: None)
            self.__ai = AI()
            if self.__my_color == Game.PLAYER_ONE:
                self.ai_find_move(
                )  # and call ai_find_move to make the first move.
        else:
            self.__screen = Screen(root, self.__my_color, self.play_my_move)

    def ai_find_move(self):
        """
        The function handles the AI turn.
        It creates a copy of the game object and sends it to the AI instance.
        Then, it makes the next AI move using the AI find_legal_move method.
        :return: None
        """
        # creates a copy of the game instance and sends it to the AI.
        sim_game = Game()
        board, register, cell_set, counter = self.__game.get_attr_for_sim()
        sim_game.set_attr_for_sim(board, register, cell_set, counter)

        try:
            self.__ai.find_legal_move(sim_game, self.play_my_move)
        except:
            self.__screen.print_to_screen(self.__ai.NO_AI_MOVE,
                                          self.__my_color)

    def play_my_move(self, column):
        """
        The function handles a certain game move, both by the AI instance
        and when a GUI button is pressed, by calling the class one_turn method
        and sending the opponent a message using the communicator instance.
        :param column: column in board to play (0 <= int <= 6)
        :return: None
        """
        if self.one_turn(column, self.__my_color):
            self.__communicator.send_message(str(column))

    def one_turn(self, column, player):
        """
        The function handles one turn of both kinds of players,
        AI and human by preforming the following actions:
        1. Try to make the given move(column).
        2. Update the screen instance according to the move.
        3. Send the opponent an message about the move it made.
        4. Checks if the game ended using the game get_winner method.
        :param column: column in board (0 <= int <= 6)
        :param player: The player which made the turn. Player_one/Player_two.
        :return: True if the move was done (may be illegal move). None otherwise.
        """
        # The below if make sure that both players can play only when its their turn.
        if self.__game.get_current_player() == player:
            #Try to make the move, if the move is illegal raise an exception
            try:
                self.__game.make_move(column)
                row, col = self.__game.get_last_coord()
                move_done = True

                if self.__is_ai:
                    self.__screen.update_cell(row, col, player, anim=False)
                else:
                    self.__screen.update_cell(row, col, player, anim=True)

            except:
                self.__screen.print_to_screen(self.__game.ILLEGAL_MOVE_MSG,
                                              player)
                return

        else:
            self.__screen.print_to_screen(self.MSG_NOT_TURN, player)
            return
        # check if the game is ended by win/loss/draw.
        winner = self.__game.get_winner()
        if winner is not None:
            self.end_game(winner)

        return move_done

    def end_game(self, winner):
        """
        The function handles the situation where the game is done.
        Its using the screen instance to print the game result 
        (win/loss/draw) to the graphical interface.
        :param winner: The game result (PLAYER_ONE / PLAYER_TWO / DRAW).
        :return: None
        """
        win_coord, win_dir = self.__game.get_win_info(
        )  # Ask the game instance for the win_coord and direction
        self.__screen.win(
            win_coord, win_dir,
            winner)  # In order to display the winning sequence (FLASH!)

        if winner == Game.DRAW:
            self.__screen.print_to_screen(self.MSG_DRAW,
                                          self.__my_color,
                                          end=True)
            self.__screen.print_to_screen(self.MSG_DRAW,
                                          self.__op_color,
                                          end=True)

        elif winner == self.__my_color:
            self.__screen.print_to_screen(self.MSG_WIN,
                                          self.__my_color,
                                          end=True)
            self.__screen.print_to_screen(self.MSG_LOSE,
                                          self.__op_color,
                                          end=True)

        elif winner == self.__op_color:
            self.__screen.print_to_screen(self.MSG_WIN,
                                          self.__op_color,
                                          end=True)
            self.__screen.print_to_screen(self.MSG_LOSE,
                                          self.__my_color,
                                          end=True)

    def handle_message(self, message=None):
        """
        The function specifies the event handler for the message getting
        event in the communicator. When it is invoked, it calls the one_turn
        method in order to update the opponent move on its screen instance
        or end the game if needed. it invoked by the
        communicator when a message is received.
        :param message: The last move the opponent made (0 <= int <= 6) Default is None.
        :return: None
        """
        if message:
            self.one_turn(int(message[0]), self.__op_color)
            if self.__is_ai:  # If the player is AI we call ai_find_move to make the AI next move.
                if self.__game.get_win_info()[1] is None:
                    self.ai_find_move()
예제 #3
0
class Gui():
    """
	class of the Gui wrapping the game
	"""

    SUM_OF_ALL_BOARD_CELLS = 7 * 6
    RANGE_BALL_CLICK = 60
    END_BARRIER_AXIS_X = 435
    START_BARRIER_BALLS_AXIS_X = 15
    END_OF_AXIS_Y_BALLS = 440
    START_SPACE_AXIS_Y = 90
    START_SPACE_AXIS_X = 20
    SPLITTER_OF_BALLS_AXIS_Y = 60
    SPLITTER_OF_BALLS_AXIS_X = 60
    NUM_OR_ROWS = 6
    NUM_OF_COLUNM = 7
    CANVAS_SIZE = 450
    BALL_SIZE = 50
    STEP_SIZE = 2
    WINNER_FONT = ("Helvetica", 20)
    WINNER_MSG = "The winner is the"
    BACKGROUND_COL = "#6495ED"
    DEFAULT_DISC_COL = 'white'
    PLAYER1_COL = "red"
    WIN_COL = 'yellow'
    PLAYER2_COL = "#008000"
    NO_WIN_MSG = "no one wins"
    WIN_MSG = "You win"
    LOOSE_MSG = 'You loose'
    BEST_AI_DEFAULT = 0
    YOUR_TURN_LABEL = 'For playing your turn\nclick on the wanted column'
    LABEL_COLOR = 'white'
    LABEL_X_PLACE = 65
    LABEL_Y_PLACE = 15

    def __init__(self, parent, port, is_ai, game, ip=None):
        """
		constructor of Gui
		:param parent: root of the platform
		:param port: to conect
		:param is_ai: True if this player is the computer
		:param ip: of the server.
		"""
        #
        self.game = game

        self.best_ai_move = self.BEST_AI_DEFAULT

        self.ip = ip

        # server begins the game
        if not ip:
            self.__my_turn = True

        else:
            self.__my_turn = False

        # initial interface
        self._parent = parent
        self._canvas = tk.Canvas(parent,
                                 width=self.CANVAS_SIZE,
                                 height=self.CANVAS_SIZE,
                                 bg=self.BACKGROUND_COL)
        self._disc_places = []
        self._prepeare_canvas()

        if is_ai:
            self.is_ai = True
            self.ai = AI()
        else:
            self.is_ai = False
            self._canvas.bind("<Button-1>", self.click_bind)
        self._canvas.pack()

        # communication:
        self.__communicator = Communicator(parent, port, ip)
        self.__communicator.connect()
        self.__communicator.bind_action_to_message(self.__handle_message)

        # if self is server and ai then responds immediately
        if is_ai and not ip:
            self.modify_my_turn(True)
            self.respond()

        if not is_ai:

            self.__put_labal()

    def __put_labal(self):

        self.__label = tk.Label(self._parent,
                                text=self.YOUR_TURN_LABEL,
                                fg=self.LABEL_COLOR,
                                font=("Garamond", 20, "bold"),
                                bg=self.BACKGROUND_COL)
        self.__label.pack()
        self.__label.place(x=self.LABEL_X_PLACE, y=self.LABEL_Y_PLACE)

    def __handle_message(self, col=None):
        """
		sends the massage of the player from the other side to the board
		:param col: number of column
		:return: None
		"""

        self.game.make_move(int(col))
        self._update_interface()
        if self.game.get_winner()[0] != self.game.NO_WINNER:
            self.declare_winner()
            return
        self.modify_my_turn(True)

        if self.is_ai:
            self.respond()

    def click_bind(self, event):
        """
		for every click of the left button of the mouse the func
		interperts it to the column that the player wanted
		:param event: the coordinates of the click on the canvas
		:return: transfer the column to the func  board.make_move()
		"""
        if self.is_my_turn():
            self.game.set_turn(True)
            if self.START_SPACE_AXIS_Y <= event.y <= self.END_OF_AXIS_Y_BALLS and \
              self.START_BARRIER_BALLS_AXIS_X <= event.x <= self.END_BARRIER_AXIS_X:
                col = (event.x - self.START_BARRIER_BALLS_AXIS_X
                       ) // self.RANGE_BALL_CLICK
                self.respond(col)

    def respond(self, col=None):
        """make the next current player move"""
        if not self.is_ai:
            try:
                column = self.game.make_move(col)
                self.send_to_other_player(col)
                self._update_interface()
                winner = self.game.get_winner()[0]
                if winner != self.game.NO_WINNER and not self.is_ai:
                    self._canvas.unbind("<Button-1>")
                    self.declare_winner()
                self.modify_my_turn(False)
            except:
                return None
        else:
            col = self.ai.find_legal_move(self.game, self.ai_func)
            self._update_interface()
            if self.game.get_winner()[0] != self.game.NO_WINNER:
                self.declare_winner()
            self.send_to_other_player(col)
            self.modify_my_turn(False)

    def ai_func(self, column):
        """The next function is not necessary to the course team
		It's the function which records every potential ai moves
		 it the ai will finish all moves it will put -1 in the
		function and the ouput will be the last ai move"""

        if column not in range(len(self.game.board[0])):
            return self.game.make_move(self.best_ai_move)
        else:
            self.best_ai_move = column

    def _update_interface(self):
        """updates Gui interface so it will fit the acutal board"""
        for col in range(len(self.game.board[0])):
            for row in range(len(self.game.board)):
                if self.game.board[row][col] == self.game.PLAYER_ONE:
                    self.paint_the_disc(col, row, self.game.PLAYER_ONE)
                elif self.game.board[row][col] == self.game.PLAYER_TWO:
                    self.paint_the_disc(col, row, self.game.PLAYER_TWO)

    def _prepeare_canvas(self):
        """
		paints on the canvas ovals(discs)
		:return: None
		"""
        balls = []
        for col_x in range(self.NUM_OF_COLUNM):
            for row_y in range(self.NUM_OR_ROWS):
                x = self.START_SPACE_AXIS_X + self.SPLITTER_OF_BALLS_AXIS_X * col_x
                y = self.START_SPACE_AXIS_Y + self.SPLITTER_OF_BALLS_AXIS_Y * row_y
                self._canvas.create_oval(x,
                                         y,
                                         x + self.BALL_SIZE,
                                         y + self.BALL_SIZE,
                                         fill=self.DEFAULT_DISC_COL)
                balls.append((x, y))
            self._disc_places.append(balls)
            balls = []

    def paint_the_disc(self, col_x, row_y, player, color=None):
        """
		the func gets the place of the disc that suppose to be paint,
		by the color of the player
		:param col_x:
		:param row_y:
		:return:
		"""
        x = self.START_SPACE_AXIS_X + self.SPLITTER_OF_BALLS_AXIS_X * col_x
        y = self.START_SPACE_AXIS_Y + self.SPLITTER_OF_BALLS_AXIS_Y * row_y
        if not color:
            if player == self.game.PLAYER_ONE:
                color = self.PLAYER1_COL
            else:
                color = self.PLAYER2_COL
        self._canvas.create_oval(x,
                                 y,
                                 x + self.BALL_SIZE,
                                 y + self.BALL_SIZE,
                                 fill=color)

    def declare_winner(self):
        """when a player wins it adds title
		Who is the winner"""

        winner_player = self.game.get_winner()
        if winner_player[0] == self.game.PLAYER_ONE:
            winner = self.WIN_MSG
        elif winner_player[0] == self.game.PLAYER_TWO:
            winner = self.LOOSE_MSG
        else:
            winner = self.NO_WIN_MSG
        if winner_player[0] != self.game.DRAW:

            for ball in winner_player[1]:
                self.paint_the_disc(ball[1],
                                    ball[0],
                                    winner_player[0],
                                    color=self.WIN_COL)
        label = tk.Label(self._parent, text=winner, font=self.WINNER_FONT)
        label.pack(side=tk.TOP)

    def send_to_other_player(self, column):
        """
		sends to the other player the column that I clicked
		:param column:
		:return:
		"""
        if self.__my_turn:
            self.__communicator.send_message(column)

    def modify_my_turn(self, is_my_turn):
        """
		boolenic changer if it's my turn or not.
		:param is_my_turn: True or False
		:return:
		"""

        self.__my_turn = is_my_turn
        self.game.set_turn(is_my_turn)

    def is_my_turn(self):
        """
		:return: Ture or False if it's my turn
		"""
        return self.__my_turn
예제 #4
0
class GUI:
    ELEMENT_SIZE = 50
    MESSAGE_DISPLAY_TIMEOUT = 250
    GRID_COLOR = "#AAA"
    PLAYER_1_COLOR = "blue"
    PLAYER_2_COLOR = "red"
    RED_WIN_COLOR = "#B22222"
    BLUE_WIN_COLOR = "#008080"
    DEFAULT_COLOR = "white"
    BACKGROUND_COLOR = "green"

    def __init__(self, root, game, human_or_ai, port=None, ip=None):
        """
        Initializes the GUI and connects the communicator.
        :param parent: the tkinter root.
        :param ip: the ip to connect to.
        :param port: the port to connect to.
        :param server: true if the communicator is a server, otherwise false.
        """
        self.game = game
        self.ai = AI()
        self.root = root
        if human_or_ai:
            self.ai_on = False
        else:
            self.ai_on = True
        self.ip = ip
        self.port = port
        self.my_turn = True
        """The top image in the gui"""
        image_path = r"intro2cs.gif"
        photo = PhotoImage(file=image_path)
        label = Label(image=photo)
        label.image = photo  # keep a reference
        label.grid(row=0, column=0, pady=10)

        self.canvas = Canvas(
            root,
            width=200,
            height=50,
            background=self.BACKGROUND_COLOR,
            highlightthickness=0,
        )
        self.canvas.grid(row=2)

        self.current_player_var = StringVar(self.root, value="")
        self.currentPlayerLabel = Label(self.root,
                                        textvariable=self.current_player_var,
                                        anchor=W)
        self.currentPlayerLabel.grid(row=3)
        """when the user click on the canvas do action according to the
         _action_when_canvas_clicked function"""
        self.canvas.bind('<Button-1>', self._action_when_canvas_clicked)
        self.new_game()

        self.__communicator = Communicator(root, port, ip)
        self.__communicator.connect()
        self.__communicator.bind_action_to_message(self.__handle_message)

    def __handle_message(self, text=None):
        """
        Specifies the event handler for the message getting event in the
        communicator. Prints a message when invoked (and invoked by the
        communicator when a message is received). The message will
        automatically disappear after a fixed interval.
        :param text: the text to be printed.
        :return: None.
        """
        """If got a text - column, do that move in the self board, so the
         opponent board and the self board would be synchronized"""
        if text:
            self.game.make_move(int(text[0]))
            """The enemy made his turn, and now self.my_turn should changed to
             true and so on the current_player indicator in the gui"""
            self.my_turn = not self.my_turn
            self.current_player_var.set('Current player: ' + "Your Turn")
        if self.ai_on and self.game.game_over != self.game.YES:
            self.make_ai_move()
        # draw the board again after all the changes has been made
        self.draw()

    def draw(self):
        """Draw all the board with the disks and there's color"""
        """The two for loop runs on all the game.board and create disks
         according to the situation in the board"""
        for c in range(self.game.cols, -1, -1):
            """changes the board direction so the disks would be at the
             bottom and not at the top"""
            self.game.board = self.game.board[::-1]
            for r in range(self.game.rows, -1, -1):
                if r >= self.game.cols:
                    continue

                x0 = c * self.ELEMENT_SIZE
                y0 = r * self.ELEMENT_SIZE
                x1 = (c + 1) * self.ELEMENT_SIZE
                y1 = (r + 1) * self.ELEMENT_SIZE
                """Create each disk according to the game board, if at the
                 location the board is empty, then create disk with the
                  default color - white, else - create the disk red/blue if
                  the board at that location is red/blue """
                if self.game.board[r][c] == self.game.BLUE:
                    fill = self.PLAYER_1_COLOR
                elif self.game.board[r][c] == self.game.RED:
                    fill = self.PLAYER_2_COLOR
                elif self.game.board[r][c] == self.game.RED + self.game.RED:
                    fill = self.RED_WIN_COLOR
                elif self.game.board[r][c] == self.game.BLUE + self.game.BLUE:
                    fill = self.BLUE_WIN_COLOR
                else:
                    fill = self.DEFAULT_COLOR
                self.canvas.create_oval(x0 + 2,
                                        self.canvas.winfo_height() - (y0 + 2),
                                        x1 - 2,
                                        self.canvas.winfo_height() - (y1 - 2),
                                        fill=fill,
                                        outline=self.GRID_COLOR)
            self.game.board = self.game.board[::-1]

        if self.game.game_over == self.game.YES:
            """If the game is over, checks who won/lose/draw and print to the
             canvas message that says that"""
            text_width = text_height = self.canvas.winfo_height() / 2
            # giving default win msg
            win_msg = ""
            # checks if there was a draw
            if self.game.get_winner() == self.game.DRAW:
                win_msg = "There Was a :" + "\n" + "Draw"
                """if when the game is over, the gui current var is
                 "your turn" that mean the you lost the game """
            elif self.current_player_var.get() == "Current player: Your Turn":
                win_msg = "You Lost" + "\n" + "The Game"
            elif self.current_player_var.get() ==\
                    "Current player: The Enemy Turn":
                win_msg = "You Won" + "\n" + "The Game"
            self.canvas.create_text(text_height,
                                    text_width,
                                    text=win_msg,
                                    font=("Helvetica", 32),
                                    fill="black")

    def draw_grid(self):
        """Draw the grid"""
        x0, x1 = 0, self.canvas.winfo_width()
        for r in range(1, self.game.rows):
            y = r * self.ELEMENT_SIZE
            self.canvas.create_line(x0, y, x1, y, fill=self.GRID_COLOR)

        y0, y1 = 0, self.canvas.winfo_height()
        for c in range(1, self.game.cols + 1):
            x = c * self.ELEMENT_SIZE
            self.canvas.create_line(x, y0, x, y1, fill=self.GRID_COLOR)

    def drop(self, column):
        """Make the move with on the game board with the given column
        and send a message to the opponent about the move that he just made,
        so the opponent gui board would be updated and update the current turn
         in the gui"""
        if self.game.board[0][column] == self.game.EMPTY:
            self.current_player_var.set('Current player: ' + "The Enemy Turn")
            self.__communicator.send_message(str(column))
            return self.game.make_move(column)
        else:
            return

    def new_game(self):
        """Create the new game"""
        self.game = Game()

        self.canvas.delete(ALL)
        self.canvas.config(width=self.ELEMENT_SIZE * self.game.rows,
                           height=self.ELEMENT_SIZE * self.game.cols)
        self.root.update()
        self.draw_grid()
        self.draw()

    def _action_when_canvas_clicked(self, event):
        """This function responsible for the action that happens, when the user
         click the board (the canvas)"""
        # do something only if its my_turn
        if self.my_turn:
            if self.game.game_over:
                # when the game is over, click would do nothing
                return

            c = event.x // self.ELEMENT_SIZE
            """if it the client/server my_turn is true, and the client/server
             clicked on a column then put the disk in that column"""
            if 0 <= c < self.game.rows:
                if not self.ai_on:
                    self.drop(c)
                """After the client/server did his turn, his turn expired and
                 changed to false, because after he did his move, the enemy
                 move has come"""
                self.my_turn = not self.my_turn
                # draw the board after the move has been made
                self.draw()
        return

    def make_ai_move(self):
        """Do the ai move """
        if self.game.game_over == self.game.YES:
            return
        column = self.ai.find_legal_move(self.game, self.game.make_move)
        self.__communicator.send_message(str(column))
        # update the turn on display (on the GUI)
        self.current_player_var.set('Current player: ' + "The Enemy Turn")
        self.draw()
예제 #5
0
class Game:
    """
    This class represents the game 4 in a row we were asked to implement.
    """

    PLAYER_ONE = 0
    PLAYER_TWO = 1
    DRAW = 2
    NUMBER_TO_PLAYER = {PLAYER_ONE: "Server", PLAYER_TWO: "Client"}
    CONTINUE_GAME = 3
    ILLEGAL_MOVE = "Illegal move"
    PLAYER_ONE_WON_MSG = NUMBER_TO_PLAYER[PLAYER_ONE] + " has won the game"
    PLAYER_TWO_WON_MSG = NUMBER_TO_PLAYER[PLAYER_TWO] + " has won the game"
    DRAW_MSG = "There is a draw"
    DEFAULT_NUMBER_OF_ROWS = 6
    DEFAULT_NUMBER_OF_COLS = 7
    NUMBER_OF_ELEMENTS_IN_SEQUENCE = 4
    WIN_NOT_FOUND = -2
    DEFAULT_IP = socket.gethostbyname(socket.gethostname())
    AI_DEFAULT_VALUE = None

    def __init__(self,
                 is_human,
                 is_server,
                 port,
                 ip_of_server=DEFAULT_IP):
        """
        :param is_human: string. Describes if the player is human or ai.
        :param is_server: boolean value. If True, the user is the server
        and plays first, otherwise the player is the client and plays second.
        :param port: the port number the players uses for the game.
        goes between 1 to 65355.
        :param ip_of_server: the ip number of the server.
        the client insert this number.
        """
        # Creates the board game
        self.board = Game_Board(Game.DEFAULT_NUMBER_OF_COLS,
                                Game.DEFAULT_NUMBER_OF_ROWS,
                                Game.PLAYER_ONE,
                                Game.PLAYER_TWO,
                                Game.DRAW,
                                Game.WIN_NOT_FOUND,
                                Game.NUMBER_OF_ELEMENTS_IN_SEQUENCE)

        self.root = tki.Tk()
        screen_width = self.root.winfo_screenwidth()
        screen_height = self.root.winfo_screenheight()

        # Creates the GUI method of the game
        self.gameGui = FourInARowGui(self.root,
                                     self.make_move,
                                     Game.DEFAULT_NUMBER_OF_ROWS,
                                     Game.DEFAULT_NUMBER_OF_COLS,
                                     screen_height,
                                     screen_width)

        # Defines which player begins.
        if is_server:
            self.root.title("Server")
            self.__communicator = Communicator(self.root, port)
        else:
            self.root.title("Client")
            self.__communicator = Communicator(self.root,
                                               port,
                                               ip_of_server)
            self.gameGui.lock_player()

        self.__communicator.bind_action_to_message(self.act_upon_message)
        self.__communicator.connect()

        # sets whose turn it is
        current_player = self.get_current_player()
        self.change_whose_turn_label(self.get_other_player(current_player))

        # Creates AI user if needed
        self.ai = Game.AI_DEFAULT_VALUE
        if not is_human:
            self.ai = AI()
            if is_server:
                self.make_ai_move()
            else:
                self.gameGui.lock_player()

        self.run_game()

    def run_game(self):
        """
        The function runs the game.
        """
        self.root.mainloop()

    def make_ai_move(self):
        """
        Makes an AI move if there is an ai player.
        :return:
        """
        col = self.ai.find_legal_move(self, self.make_ai_func())
        self.gameGui.simulate_press_by_ai(col)

    def act_upon_message(self, message):
        """
        :param message: a message given by the client or the server
        the message is expected to be the number of the column that was pressed
        :return:
        """
        current_player = self.get_current_player()
        self.change_whose_turn_label(current_player)

        self.gameGui.unlock_player()
        self.gameGui.simulate_press(int(message))

        if self.ai is not Game.AI_DEFAULT_VALUE:
            self.make_ai_move()

    def send_action_to_other_player(self, column):
        """
        :param column: the column the user has pressed
        locks the current player from pressing any other column
        and sends the column that was pressed on as a message to the
        other player
        """
        current_player = self.get_current_player()
        self.change_whose_turn_label(current_player)

        self.gameGui.lock_player()
        self.__communicator.send_message(str(column))

    def change_whose_turn_label(self, current_player):
        """
        :param current_player: the code of the current player
        changes the label such that it will say that the turn
        is that of the other player
        """
        whose_turn = \
            Game.NUMBER_TO_PLAYER[self.get_other_player(current_player)] + " Turn"
        self.gameGui.change_top_label(whose_turn)

    def get_other_player(self, current_player):
        """
        :param current_player: the player who is playing now
        :return: the other player
        """
        if current_player == Game.PLAYER_ONE:
            return Game.PLAYER_TWO
        if current_player == Game.PLAYER_TWO:
            return Game.PLAYER_ONE

    def make_move(self,
                  column,
                  me_has_pressing=True,
                  illegal_move_was_made=False):
        """
        This function implement single move in the game
        :param column: the column the player choose
        :param me_has_pressing: if the player pressed something
        :param illegal_move_was_made: a boolean, if its true an exception
        will be raised
        """
        if illegal_move_was_made:
            raise Exception(Game.ILLEGAL_MOVE)

        if me_has_pressing:
            self.send_action_to_other_player(column)

        current_player = self.get_current_player()

        if not self.board.valid_player(current_player):
            raise Exception(Game.ILLEGAL_MOVE)
        else:
            self.board.move_of_player(current_player, column)

            self.check_status()

    def check_status(self):
        """
        The function deals with cases of winning or draw in the game.
        Than it shuts the game down.
        """
        win_check = self.board.check_if_there_is_win()
        if win_check:
            # The winner player and the sequence of slots that won the game
            winner = win_check[0]
            win_sequence = win_check[1]
            win_sequence = Game.reverse_tuples_in_list(win_sequence)
            # Displaying the message to the players
            self.gameGui.game_over(self.final_stage_msg(winner), win_sequence)
            self.reset_ai()

        if self.board.check_board_if_full():
            # Displaying draw message to the players
            self.gameGui.game_over(self.final_stage_msg(Game.DRAW))
            self.reset_ai()

    def get_winner(self):
        """
        this function checks if there is a winner or a draw
        if there is a winner it returns the code of the player that won
        if there is a draw it returns the code for a draw
        if none of the above it returns None
        """
        win_check = self.board.check_if_there_is_win()
        if win_check:
            winner = win_check[0]
            return winner
        if self.board.check_board_if_full():
            return Game.DRAW

    def reset_ai(self):
        """
        Changes the ai to None
        """
        self.ai = Game.AI_DEFAULT_VALUE

    def final_stage_msg(self, winner_num):
        """
        printing message to the screen for win or draw
        """
        if winner_num == Game.PLAYER_ONE:
            return Game.PLAYER_ONE_WON_MSG

        if winner_num == Game.PLAYER_TWO:
            return Game.PLAYER_TWO_WON_MSG

        if winner_num == Game.DRAW:
            return Game.DRAW_MSG

    def get_player_at(self, row, col):
        """
        returns which player is on specific location
        """
        board = self.board.get_board()
        return board[row][col]

    def get_current_player(self):
        """
        returns whose turn is it now
        """
        return self.board.board_status()

    def make_ai_func(self):
        """
        Implement the moves of the AI.
        """

        def make_ai_move(col):
            self.gameGui.simulate_press(col)

        return make_ai_move

    @staticmethod
    def reverse_tuples_in_list(list_of_tuples):
        """
        :param list_of_tuples
        returns the same list with the same tuples but repositioned.
        """
        new_list = []
        for tpl in list_of_tuples:
            new_list.append(tuple(reversed(tpl)))
        return new_list
예제 #6
0
class Game:
    """
        Game class, handles gameplay, gui and communicator
    """
    PLAYER_ONE = 0
    PLAYER_TWO = 1
    DRAW = 2
    EMPTY = 3
    MIN_PORT_VALUE = 1000
    MAX_PORT_VALUE = 65535

    WINNING_SEQ_LENGTH = 4

    ILLEGAL_MOVE = "Illegal move"
    INVALID_MESSAGE = "Invalid message received"
    INVALID_GAME_STATE = "Invalid game state"
    CHECK_WINNER_FLAG_INDEX = 29
    ADD_CHIP_FAILED = "Failed to add chip to specified column"
    COMMUNICATOR_MESSAGE_1 = "CHIP_DATA: "
    COMMUNICATOR_MESSAGE_2 = "CHECK_WINNER: "

    EXPECTED_AMOUNT_OF_ARGUMENTS_FOR_CLIENT = 4
    EXPECTED_LENGTH_OF_MESSAGE = 30

    def __init__(self):
        """ Initializes the game class """
        # If server mode is on (no IP address provided)
        if len(argv) == 3:
            self.__player = self.PLAYER_ONE
            self.__enemy_player = self.PLAYER_TWO
            self.__current_player = self.__player
        else:
            self.__player = self.PLAYER_TWO
            self.__enemy_player = self.PLAYER_ONE
            self.__current_player = self.__enemy_player

        # Create board
        self.__board = Board(self.get_current_player)
        self.__game_over = False

        # initializes AI if AI was chosen at run
        self.__ai_flag = False
        if argv[1] == "ai":
            self.__ai = AI()
            self.__ai_flag = True

        # Create gui
        self.__gui = GUI(self.__player, self.__make_move,
                         self.get_current_player, self.__ai_flag)

        # If this is the client, disable buttons until player turn
        if self.__player == self.PLAYER_TWO:
            self.__gui.disable_column_buttons()

        # TODO:: Ugly code, find a workaround
        self.__last_inserted_chip = None

        # Parse data for communicator
        port = int(argv[2])
        ip = None

        if len(argv) == self.EXPECTED_AMOUNT_OF_ARGUMENTS_FOR_CLIENT:
            ip = argv[3]

        # Initializes communicator
        self.__communicator = Communicator(self.__gui.get_root(), port, ip)
        self.__communicator.connect()
        self.__communicator.bind_action_to_message(self.parse_rival_message)

        # If AI on server start the game, make a move
        if self.__ai_flag and self.__player == self.PLAYER_ONE:
            self.__ai.find_legal_move(self, self.__make_move)

        # Start the gui and game
        self.__gui.get_root().mainloop()

    def __make_move(self, column):
        """ :param column: Column in whivh to place chip """

        # if game over flag on, returns
        if self.__game_over:
            return

        # attempts to place chip in column
        success, row = self.__board.check_legal_move_get_row(
            column,
            self.PLAYER_ONE if not self.__current_player else self.PLAYER_TWO)
        if not success:
            raise Exception(self.ILLEGAL_MOVE)

        # Store move for other functions
        self.__last_inserted_chip = column, row

        # Relay move to enemy
        self.__communicator.send_message(self.COMMUNICATOR_MESSAGE_1 +
                                         str(column) + "," + str(row) + " " +
                                         self.COMMUNICATOR_MESSAGE_2 +
                                         "1" if not self.__game_over else "0")

        self.__check_winner(column, row)

    def __check_winner(self, column, row):
        """ :param column: Column of newest chip
            :param row: Row of newest chip """
        # Get data if a winning state was reached
        winner, winning_chips = self.__board.find_connected_and_winner(
            column, row)

        # Get pixel location for newest chip
        x, y = self.__board.get_chip_location(column, row)
        if winner is None:  # If game is still ongoing
            # Create the chip on board
            self.__gui.create_chip_on_board(x,
                                            y,
                                            self.__current_player,
                                            board=self.__board)

            # Toggle __player in class members
            self.__toggle_player()

            # Disable full columns
            self.__gui.disable_illegal_columns(self.__board)

        else:  # Game ended
            self.__game_over = True
            if winner == self.DRAW:
                self.__gui.create_chip_on_board(x,
                                                y,
                                                self.__current_player,
                                                board=self.__board)
                self.__gui.disable_column_buttons()
                self.__gui.show_game_over_label(self.DRAW)
            else:
                self.__gui.create_chip_on_board(x,
                                                y,
                                                self.__current_player,
                                                winning_chips=winning_chips,
                                                board=self.__board,
                                                winner=winner)

    def __toggle_player(self):
        """ Toggles members in the class, also make gui show switching of
            turns """
        self.__current_player = self.PLAYER_TWO \
            if self.__current_player == self.PLAYER_ONE \
            else self.PLAYER_ONE

        flag = self.__current_player == self.__player

        self.__gui.end_turn_switch_player(flag)

    def get_winner(self):
        """ Gets the winner if there is one.
            This function is not used by the game """
        return self.__board.find_connected_and_winner(
            self.__last_inserted_chip[0], self.__last_inserted_chip[1])[0]

    def get_player_at(self, row, col):
        """ :param row: Row to check
            :param col: Column to check
            :return: Player at place
        """
        player = int(self.__board.get_columns()[col][row])
        return None if player == self.EMPTY else player

    def get_current_player(self):
        """ Getter for current __player """
        return self.__current_player

    def get_board(self):
        """ Getter for board """
        return self.__board

    def parse_rival_message(self, message):
        """ :param message: Message received from enemy """
        # Check message of corect length
        if len(message) != self.EXPECTED_LENGTH_OF_MESSAGE:
            raise Exception(self.INVALID_MESSAGE)

        # Parse data from message
        column = int(message[11])
        expected_row = int(message[13])

        # Update board and check if same row was returned
        success, row = self.__board.check_legal_move_get_row(
            column,
            self.PLAYER_ONE if not self.__current_player else self.PLAYER_TWO)

        # Assert it
        assert row == expected_row

        if success:
            # Update member
            self.__last_inserted_chip = column, row
            check_winner_flag = message[self.CHECK_WINNER_FLAG_INDEX]
            if check_winner_flag:
                self.__check_winner(column, row)

        else:
            raise Exception(self.ADD_CHIP_FAILED)

        self.__gui.disable_illegal_columns(self.__board)

        # If the AI is playing, make another move
        if self.__ai_flag and not self.__game_over:
            self.__ai.find_legal_move(self, self.__make_move)

    def get_last_inserted_chip(self):
        """ Getter for last inserted chip """
        return self.__last_inserted_chip
예제 #7
0
class GUI:
    """
    A class responsible for handling the GUI aspects of a for in a row game.
    Also handles the connection of the communicator
    """

    IMAGE_PATHS = [
        "images/empty_cell.png", "images/player1_disk.png",
        "images/player2_disk.png", "images/player1_disk_win.png",
        "images/player2_disk_win.png"
    ]
    FONT = ("times", 24)
    HUMAN = 0
    AI = 1
    MSG_COLOR = "blue"
    EMPTY_CELL_IMG = 0
    PLAYER1_CELL_IMG = 1
    PLAYER2_CELL_IMG = 2
    PLAYER1_WIN_DISK = 3
    PLAYER2_WIN_DISK = 4
    WINDOW_SIZE = 500
    BOARD_HEIGHT = 6
    BOARD_WIDTH = 7
    P1 = "Player 1"
    P2 = "Player 2"
    HOLD_MSG = "Please wait for %s to finnish their turn"
    DRAW = "No more moves. Game ended with a draw"
    WIN = "%s won!"
    DEFAULT_MSG = ""
    TOP = 0
    DROP_RATE = 100

    def __init__(self, root, player, port, ip=None):
        """
        Initialize GUI and connect the communicator
        :param root: The tkinter root
        :param player: An integer representing if the player is human or ai
        :param port: The port to connect to
        :param ip: The ip to connect to
        """
        self.__root = root
        self.__game = Game()
        self.__bottoms = self.__game.get_board().get_bottoms()
        self.__communicator = Communicator(root, port, ip)
        self.__communicator.connect()
        self.__communicator.bind_action_to_message(self.__handle_message)
        self.__top_frame = tk.Frame(self.__root)
        self.__top_frame.pack()
        self.__col_frames = []
        self.__buttons = []
        self.__player = GUI.P1
        self.__opponent = GUI.P2
        if ip is not None:
            self.__player, self.__opponent = self.__opponent, self.__player
        self.__msg_board = tk.Message(root, font=GUI.FONT, fg=GUI.MSG_COLOR)
        self.__msg_board.pack()
        self.__images = []
        self.__load_images()
        self.__place_widgets()
        self.__ai = None
        if player == GUI.AI:
            self.__ai = AI()
        self.__start_game()

    def __start_game(self):
        """
        Allow player one to make a move
        :return: None
        """
        if self.__player == GUI.P1:
            if self.__ai is not None:
                self.__ai_play()
            else:
                self.__change_buttons_state(tk.NORMAL)
        else:
            self.__update_msg(GUI.HOLD_MSG % self.__opponent)

    def __place_widgets(self):
        """
        Place widgets in the gui
        :return: None
        """
        for col in range(GUI.BOARD_WIDTH):
            col_frame = tk.Frame(self.__top_frame)
            col_frame.pack(side=tk.LEFT)
            self.__col_frames.append(col_frame)
            for row in range(GUI.BOARD_HEIGHT):
                cell = tk.Label(col_frame,
                                image=self.__images[GUI.EMPTY_CELL_IMG])
                cell.grid(row=row)
            col_button = tk.Button(
                col_frame,
                text=str(col + 1),
                command=lambda column=col: self.__play_col(column),
                state=tk.DISABLED)
            col_button.grid(row=GUI.BOARD_HEIGHT)
            self.__buttons.append(col_button)

    def __load_images(self):
        """
        Load all images necessary for the gui
        :return: None
        """
        images = []
        for path in GUI.IMAGE_PATHS:
            self.__images.append(tk.PhotoImage(file=path))
        return images

    def __play_col(self, col, report=True):
        """
        Make a move in a column
        :param col: The column's index
        :return: None
        """
        self.__update_msg(GUI.DEFAULT_MSG)
        self.__change_buttons_state(tk.DISABLED)
        player = self.__game.get_current_player()
        frame = self.__col_frames[col]
        if self.__game.get_current_player() == Game.PLAYER_ONE:
            new_img = self.__images[GUI.PLAYER1_CELL_IMG]
        else:
            new_img = self.__images[GUI.PLAYER2_CELL_IMG]
        self.__game.make_move(col)
        bottom = self.__bottoms[col] + 1
        if bottom == GUI.TOP:
            self.__buttons[col] = None
        for cell_index in range(bottom):
            cell = frame.grid_slaves(row=cell_index)[0]
            self.__disk_through_cell(cell, player)
        last_cell = frame.grid_slaves(row=bottom)[0]
        last_cell.configure(image=new_img)
        winner = self.__game.get_winner()
        if report:
            self.__communicator.send_message(col)
            if winner is None:
                self.__update_msg(GUI.HOLD_MSG % self.__opponent)
        if winner is not None:
            self.__handle_win(winner)

    def __update_msg(self, msg):
        """
        Update the message widget with a new message
        :param msg: A string
        :return: None
        """
        self.__msg_board.configure(text=msg)

    def __handle_win(self, winner):
        """
        Change the board to fit the end state of the game
        :param winner: An integer representing the winner of the game
        :return: None
        """
        self.__change_buttons_state(tk.DISABLED)
        self.__buttons = []
        if winner == Game.DRAW:
            self.__update_msg(GUI.DRAW)
            return
        elif winner == Game.PLAYER_ONE:
            msg = GUI.WIN % GUI.P1
            img = self.__images[GUI.PLAYER1_WIN_DISK]
        else:
            msg = GUI.WIN % GUI.P2
            img = self.__images[GUI.PLAYER2_WIN_DISK]
        for cell in self.__game.get_wining_streak():
            row, col = cell
            frame = self.__col_frames[col]
            cell_widget = frame.grid_slaves(row=row)[0]
            cell_widget.configure(image=img)
        self.__update_msg(msg)

    def __handle_message(self, move):
        """
        Specifies the event handler for the message getting event in the
        communicator. Makes a move when invoked
        :param move: A string representing a single move in the game
        :return: None
        """
        move = int(move)
        self.__play_col(move, report=False)
        if self.__ai is not None:
            self.__ai_play()
        else:
            self.__change_buttons_state(tk.NORMAL)

    def __ai_play(self):
        """
        Make a play with the ai
        :return: None
        """
        if self.__game.get_winner() is None:
            self.__ai.find_legal_move(self.__game, self.__play_col)

    def __change_buttons_state(self, state):
        """
        Change all relevant buttons' state
        :param state: Either normal or disabled
        :return: None
        """
        for button in self.__buttons:
            try:
                button.configure(state=state)
            except AttributeError:
                continue

    def __disk_through_cell(self, cell, player=None):
        """
        Animate a disk going through an empty cell
        :param cell: The cell
        :param player: The player whose disk is dropping
        :return: None
        """
        if player is not None:
            if player == Game.PLAYER_ONE:
                cell.configure(image=self.__images[GUI.PLAYER1_CELL_IMG])
            else:
                cell.configure(image=self.__images[GUI.PLAYER2_CELL_IMG])
            cell.update()
            cell.after(GUI.DROP_RATE, self.__disk_through_cell(cell))
        else:
            cell.configure(image=self.__images[GUI.EMPTY_CELL_IMG])
class FourInARow:
    """Class representing a game of connect-4 with graphics"""
    def __init__(self, parent, player, port, ip=None):
        """Instructor of FourInARow object"""
        self._end_game = False
        self.__init_graphics__()
        self._game = Game()
        self._root = parent
        self._status = None
        self._player = player
        self.__init__ai_or_human()
        self.__init__ip_distinguisher(ip)
        self.__communicator = Communicator(self._root, port, ip)
        self.__communicator.connect()
        self.__communicator.bind_action_to_message(self.__handle_message)

    def __init_graphics__(self):
        """initiates the graphic of the game"""
        self._player1_disc = PhotoImage(file=PLAYER_ONE_DISK)
        self._player2_disc = PhotoImage(file=PLAYER_TWO_DISK)
        self._player1_turn = PhotoImage(file=PLAYER_ONE_TURN)
        self._player2_turn = PhotoImage(file=PLAYER_TWO_TURN)
        self._win_disc = PhotoImage(file=WIN_DISC)
        self._player1_won = PhotoImage(file=PLAYER_ONE_WIN_SCREEN)
        self._player2_won = PhotoImage(file=PLAYER_TWO_WIN_SCREEN)
        self._draw_screen = PhotoImage(file=DRAW_SCREEN)

    def __init__ai_or_human(self):
        """initiates the type of player"""
        if self._player == HUMAN_PLAYER:
            self.__init__new_canvas(BOARD)
            self._canvas.bind("<Button-1>", self.game_screen_callback)
        if self._player == AI_PLAYER:
            self.__init__new_canvas(BOARD)
            self._ai = AI()
            self._root.after(1, self.make_ai_move)

    def __init__ip_distinguisher(self, ip):
        """initiates the player num whether ip is None or not"""
        if ip is not None:
            self._player_num = self._game.PLAYER_TWO
        else:
            self._player_num = self._game.PLAYER_ONE

    def __init__new_canvas(self, img):
        """this method receives an image initiates a new canvas with it."""
        self._background = PhotoImage(file=img)
        self._canvas = Canvas(self._root,
                              height=BACKGROUND_HEIGHT,
                              width=BACKGROUND_WIDTH)
        self._canvas.create_image(3, 3, image=self._background, anchor=NW)
        self._canvas.pack()

    def make_ai_move(self):
        """makes a move for an ai player"""
        if not self._end_game:
            if self._game.get_current_player() == self._player_num:
                col = self._ai.find_legal_move(self._game, self.general_move)
                self.__communicator.send_message(str(col))
            self._root.after(1, self.make_ai_move)

    def __handle_message(self, text=None):
        """this method receives text that represent a column-index and
        operates general_move with this column."""
        if text:
            column = int(text)
            self.general_move(column)

    def game_screen_callback(self, event):
        """the callback method for the game-screen.
        The method receives an event and operates other func whether the
        event was under certain conditions or not"""

        # numbers in this function represent coordinates on the screen only!

        # if self._game.get_current_player() != self._player_num:
        #     return
        x = event.x
        y = event.y
        for col in range(game.COLUMNS):
            if x in range(39 + col * 63, 86 + col * 63) and 26 < y < 447:
                if self.general_move(col):
                    self.__communicator.send_message(str(col))

    def general_move(self, column):
        """this is the general method for making moves in the game.
        It receives a column-index and inserts the current player's disc in
        this column (in the game's board and in the graphic screen as well).
        If something went wrong during the process an exception is raised."""
        self._game.make_move(column)
        row_counter = 0
        for i in range(len(self._game.get_board()) - 1, -1, -1):
            if self._game.get_board()[i][column] == game.board.FREE_SPACE:
                break
            row_counter += 1
        self.add_disc(column, game.ROWS - row_counter)
        return True

    def add_disc(self, col, row):
        """adds a current player's graphic disc to the screen."""

        # numbers in this function represent coordinates on the screen only!

        if self._game.get_current_player() == Game.PLAYER_ONE:
            self._canvas.create_image(64 + 64 * col,
                                      70 + 56.5 * row,
                                      image=self._player1_disc)
            self._canvas.create_image(559, 211, image=self._player1_turn)
        else:
            self._canvas.create_image(64 + 64 * col,
                                      70 + 56.5 * row,
                                      image=self._player2_disc)
            self._canvas.create_image(559, 211, image=self._player2_turn)
        self.game_status()

    def game_status(self):
        """checks for the game status. Whether one of the players won or its a
        draw, and operates other methods according to the status."""
        self._status = self._game.get_winner()
        if self._status[0] in [self._game.PLAYER_ONE, self._game.PLAYER_TWO]:
            self.show_winner(self._status[0], self._status[1])
            self._canvas.bind("<Button-1>", self.exit_game)
            self._end_game = True
        if self._status[0] == self._game.DRAW:
            self._canvas.create_image(3, 3, image=self._draw_screen, anchor=NW)
            self._canvas.bind("<Button-1>", self.exit_game)
            self._end_game = True

    def show_winner(self, winner, win_discs_list):
        """if a winner was found in the game status method, this method
        show's the winner's discs that made a sequence and the winner player
        himself."""

        # numbers in this function represent coordinates on the screen only!

        for disc in win_discs_list:
            row, col = disc
            self._canvas.create_image(64 + 64 * col,
                                      70 + 56.5 * row,
                                      image=self._win_disc)
        if winner == self._game.PLAYER_ONE:
            self._canvas.create_image(3, 3, image=self._player1_won, anchor=NW)
        else:
            self._canvas.create_image(3, 3, image=self._player2_won, anchor=NW)

    def exit_game(self, event):
        """this method ends the game (including graphics)."""
        if event:
            self._root.quit()
            self._root.destroy()
예제 #9
0
class Gui:
    """
    This class is in charge of the graphics of the game, and operating it as
     well. It changes the graphic board accordingly to the changes in the
     Game object (a board dictionary).
    """
    TIE_SCORE = "It's a tie!"
    OPEN_MESSAGE = 'WELCOME!'
    WINNER_MESAGGE = 'YOU WON!'
    LOSER_MESSAGE = 'YOU LOST!'
    COL_0_COORD = 77
    COL_FACTOR = 90
    ROW_0_COORD = 557
    ROW_FACTOR = 67
    INDICATOR = 'column'
    TAG = 'player'
    PLAY = 'Your turn'
    DONT_PLAY = "Oppenent's turn"
    ON = 1
    OFF = 0
    INDICATOR_ROW = -1
    CANVAS_WID = 700
    CANVAS_HIGHT = 600
    ILLEGAL_TAG = 'illegal'
    OUT_OF_BOARD = 50
    TIME_OF_ILLEGAL_MOVE = 250
    WELCOME_FONT_SIZE = 50
    MAIN_TITLE_FONT_SIZE = 50
    SECOND_TITLE_FONT_SIZE = 30
    CENTER_WIDTH = 350
    PINEAPPLE_IMG = 'pineapple.png'
    COCONUT_IMG = 'coconut1.png'
    BACKGROUND_IMG = 'background.png'
    WINNER_IMG = 'like.png'
    ILLEGAL_IMG = 'illegal_move.png'
    COL_START = 31
    FACTOR = 91

    def __init__(self, root, ip, port, server, is_human):
        """
        the game starts with a board (from class Game), creates canvas with
        all the images needed, and starts a communicator object
        :param root: the Tk 'parent' object
        :param ip: servers ip
        :param port: the game port
        :param server: True or False
        """
        self.__board = Game()
        self.__root = root
        self.__is_human = is_human
        self.__coconut_img, self.__pineapple_img, self.__background_img, \
        self.__winner_img, self.__illegal_img = self.create_images()
        self.__col_lst = []  # used for the indicator
        self.__indicator = self.OFF  # a 'switch' for the indicator
        self.__illegal_sign = self.OFF  # counter for illegal move sign
        self.__canvas, self.__title, self.__second_title = self.create_canvas(
            self.__background_img)
        self.__communicator = Communicator(root, port, ip)
        self.__communicator.connect()
        self.__communicator.bind_action_to_message(self.__handle_message)
        self.__is_playing = server
        if self.__is_human == COMPUTER:
            self.__ai = AI()
        self.initialization()

    def initialization(self):
        """
        initials the players. for human player - activates his mouse,
        for ai player - starting to play
        """
        if player_type == COMPUTER and is_server:
            self.play_with_comp()
        if player_type == HUMAN:
            self.__canvas.bind("<Button-1>", self.callback)
            self.__canvas.bind("<Motion>", self.motion)

    def create_images(self):
        """
        creates the images needed for the graphic display
        :return: the images
        """
        coconut_img = tk.PhotoImage(file=self.COCONUT_IMG)
        pineapple_img = tk.PhotoImage(file=self.PINEAPPLE_IMG)
        background_img = tk.PhotoImage(file=self.BACKGROUND_IMG)
        winner_img = tk.PhotoImage(file=self.WINNER_IMG)
        illegal_img = tk.PhotoImage(file=self.ILLEGAL_IMG)
        return coconut_img, pineapple_img, background_img, winner_img, \
               illegal_img

    def create_canvas(self, background_img):
        """
        creates the canvas that will be the graphic game board
        :param background_img: the background image for the canvas
        :return: the canvas, the board title and a secondary title to be
        used later
        """
        canvas = tk.Canvas(self.__root,
                           width=self.CANVAS_WID,
                           height=self.CANVAS_HIGHT,
                           background='white')
        canvas.create_image(self.CENTER_WIDTH,
                            300,
                            image=background_img,
                            anchor=tk.CENTER)

        if self.__is_human == HUMAN:
            title = canvas.create_text(self.CENTER_WIDTH,
                                       50,
                                       text=self.OPEN_MESSAGE,
                                       font=('', self.WELCOME_FONT_SIZE),
                                       fill='white')
            if is_server:  # server starts the game.his title would be
                # accordingly
                second_title = canvas.create_text(
                    self.CENTER_WIDTH,
                    100,
                    text=self.PLAY,
                    font=('', self.SECOND_TITLE_FONT_SIZE),
                    fill='white')
            else:
                second_title = canvas.create_text(
                    self.CENTER_WIDTH,
                    150,
                    text=self.DONT_PLAY,
                    font=('', self.SECOND_TITLE_FONT_SIZE),
                    fill='white')
            canvas.pack()
            return canvas, title, second_title
        else:
            empty_title = canvas.create_text(350,
                                             100,
                                             text='',
                                             font=('', 30),
                                             fill='white')
            canvas.pack()
        return canvas, empty_title, empty_title

    def motion(self, event):
        """
        shows the current player what is the column his mouse is pointing on
        :param event: the location of the mouse (x and y coordinates)
        """
        if self.__is_playing:
            self.__col_lst.append(self.get_column(event.x))
            column = self.get_column(event.x)
            if column is None:
                return

            # Checking who is playing:
            player = self.__board.get_current_player()

            if self.__indicator == self.OFF:  # no indicator on screen
                # the screen
                self.put_image_helper(self.INDICATOR_ROW, column, player,
                                      self.INDICATOR)

            self.__indicator = self.ON
            if self.__col_lst[0] == self.__col_lst[-1]:  # first and last
                # item are the same- mouse is still on the same column
                return

            self.delete_img(self.INDICATOR)
            self.__col_lst = []
            self.__indicator = self.OFF

    def delete_img(self, name):
        """
        deleting an image from screen with a given name (tag).
        """
        tag = self.__canvas.find_withtag(name)
        if tag:  # if the image exist on the board at the moment
            self.__canvas.delete(tag)

    def __handle_message(self, column):
        """
        when receiving a message it means that one player had played and
        now it's the second one turn. this function is placing the disc that
        the first player put in his turn on the second's player board. if the
        second player is an ai player- it makes a move.
        :param column: the column the user clicked.
        """
        self.put_image(int(column))

        if self.__is_human == COMPUTER and self.__board.get_winner() is None:
            self.play_with_comp()  # calling to computer to act

    def get_column(self, event_x):
        """
        finds the column that fits the dictionary board coordinates form,
        according to the pixel the user clicked on
        :param event_x: the x-coordinate of the user click
        :return: the column in boards coordinate (int in range(7))
        """

        if event_x in range(self.COL_START,
                            self.COL_START + self.COL_FACTOR):  # range 31,122
            return 0
        elif event_x in range(self.COL_START + self.FACTOR,
                              self.COL_START + 2 * self.FACTOR):
            # range 122,213
            return 1
        elif event_x in range(self.COL_START + 2 * self.FACTOR,
                              self.COL_START + 3 * self.FACTOR):
            # range 213,304
            return 2
        elif event_x in range(self.COL_START + 3 * self.FACTOR,
                              self.COL_START + 4 * self.FACTOR):
            # range 304,395
            return 3
        elif event_x in range(self.COL_START + 4 * self.FACTOR,
                              self.COL_START + 5 * self.FACTOR):
            # range 395,486
            return 4
        elif event_x in range(self.COL_START + 5 * self.FACTOR,
                              self.COL_START + 6 * self.FACTOR):
            # range 486,577
            return 5
        elif event_x in range(self.COL_START + 6 * self.FACTOR,
                              self.COL_START + 7 * self.FACTOR):
            # range 577,668
            return 6
        else:
            return

    def put_image_helper(self, row, column, player, tag):
        """
        puts the user disc in its place according to a given 'row' and
        'column'. gets the current player, to determine which disc should be
        placed there. 'tag' is used for image that we want to remove later on.
        :param row: a given row number
        :param column: a given row number
        :param player: current player
        :param tag: the tag of the image (a string)
        :return:
        """
        if player == Game.PLAYER_ONE:
            self.__canvas.create_image(
                self.COL_0_COORD + column * self.COL_FACTOR,
                self.ROW_0_COORD - (5 - row) * self.ROW_FACTOR,
                image=self.__coconut_img,
                tag=tag)

        elif player == Game.PLAYER_TWO:
            self.__canvas.create_image(
                self.COL_0_COORD + column * self.COL_FACTOR,
                self.ROW_0_COORD - (5 - row) * self.ROW_FACTOR,
                image=self.__pineapple_img,
                tag=tag)

    def put_image(self, column):
        """
        gets the column (integer number in range(7)), and checking if its
        possible to put disc in this column. if it is- it will update
        the Game() object and update the graphic board by calling
        'put_image_helper'. after putting the disc, the is_plying will
        change to True, it means that the other player can play now.
        :param column: the column the user chose.
        :return: True if the disc was added.
        """
        if self.__board.legal_assignment(column):  # if the user move was legal
            player = self.__board.get_current_player()
            row = self.__board.make_move(column)
            self.put_image_helper(row, column, player, self.TAG)
            if self.__is_human == HUMAN:
                self.__canvas.itemconfig(self.__title,
                                         text=self.PLAY,
                                         font=('', self.MAIN_TITLE_FONT_SIZE))
                self.__canvas.delete(self.__second_title)

            self.__is_playing = True
            self.get_game_status()  # checks if the game is over or continues
            return True
        else:
            self.__canvas.create_image(self.CENTER_WIDTH,
                                       300,
                                       image=self.__illegal_img,
                                       tag=self.ILLEGAL_TAG)
            self.__canvas.after(self.TIME_OF_ILLEGAL_MOVE,
                                lambda: self.__canvas.delete(self.ILLEGAL_TAG))

    def paint_winner(self):
        """
        checks if there is a winner. if there is- it marks the 4 winning discs
        """
        win_coords = self.__board.get_win_seq()
        for coord in win_coords:
            row = coord[0]
            col = coord[1]
            self.__canvas.create_image(
                self.COL_0_COORD + col * self.COL_FACTOR,
                self.ROW_0_COORD - (5 - row) * self.ROW_FACTOR,
                image=self.__winner_img)

    def callback(self, event):
        """
        when user clicks on the board, the board should update. this
        function will react only if self.__is_playing=True, otherwise,
        clicking on the board will do nothing. its sends a message with the
        column clicked to the other player.
        :param event: x and y coordinates of the user click
        """
        if self.__is_playing:
            # user clicks outside the top border of the board - do nothing
            if event.y < self.OUT_OF_BOARD:
                return
            column = self.get_column(event.x)
            if column is None:
                return

            if self.put_image(column):
                self.delete_img(self.INDICATOR)
                self.__indicator = self.OFF  # no indicator on screen
                self.__canvas.delete(self.__second_title)
                self.__canvas.itemconfig(self.__title,
                                         text=self.DONT_PLAY,
                                         font=('', self.MAIN_TITLE_FONT_SIZE))
                # after a player had made a move, disable his mouse
                self.__is_playing = False
                # send message to the other player, so he can play:
                self.__communicator.send_message(column)

            self.get_game_status()  # checks if the game is over or continues

    def get_game_status(self):
        """
        checks if there is a winner, or draw in the game
        """
        if self.__board.get_winner() is not None:  # there is a winner
            self.__canvas.unbind("<Button-1>")  # human can't use their mouse
            self.__canvas.unbind("<Motion>")

            winner = self.__board.get_winner()

            if winner is self.__board.DRAW:
                self.__canvas.itemconfig(self.__title,
                                         text=self.TIE_SCORE,
                                         font=('', self.MAIN_TITLE_FONT_SIZE))

            else:
                if (is_server and not winner) or (not is_server and winner):
                    # display to each player if he won or lost the game
                    self.__canvas.itemconfig(self.__title,
                                             text=self.WINNER_MESAGGE,
                                             font=('',
                                                   self.MAIN_TITLE_FONT_SIZE))
                else:
                    self.__canvas.itemconfig(self.__title,
                                             text=self.LOSER_MESSAGE,
                                             font=('',
                                                   self.MAIN_TITLE_FONT_SIZE))
                self.paint_winner()  # displaying the winning discs

    def play_with_comp(self):
        """
        when this function is called, computer is playing. the function 
        determine in what column the computer should put the disc (if its 
        possible), then makes the move, and sends a message to the other
        player with the column the disc was placed in.
        :return: 
        """
        col = self.__ai.find_legal_move(self.__board, self.__board.make_move)
        self.put_comp_image(col)
        # send message to the other player, so he can play:
        self.__communicator.send_message(col)
        self.get_game_status()

    def put_comp_image(self, col):
        """
        checking which disc player should be added to the board, than check
        what is the row that the disc will be placed in, than call
        'put_image_helper' and update the graphic board. (Game() object has
        already updated).
        :param col: an integer (the column that the computer chose).
        :return: 
        """
        # disc is already updated on the board, so we need the opposite
        # player (not current one)
        num_of_discs = self.__board.count_discs()
        if num_of_discs % 2 == 0:
            player = self.__board.PLAYER_TWO
        else:
            player = self.__board.PLAYER_ONE
        row = self.__board.get_comp_row(col)
        self.put_image_helper(row, col, player, self.TAG)
        self.get_game_status()
예제 #10
0
class GUI:
    """
    Designed to handle the GUI aspects (creating a window, buttons and
    pop-ups. Also initializes the communicator object, the game object and
    the AI object.
    """

    SCALAR = 99  # Determines the screen size and the sizes of objects
    # within it. Any int or float will work.
    # **The dimensions of the screen must be kept at a 6 to 7 ratio for the
    # game to work correctly.**
    WIDTH = 7 * SCALAR  # Width of the screen in pixels
    HEIGHT = 6 * SCALAR  # Height of the screen in pixels

    def __init__(self, game, master, port, player=None, ip=None):
        """
        Initializes the GUI and connects the communicator and the AI.
        :param game: The game class which includes the logic of the game.
        :param master: The tkinter root.
        :param port: The port to connect to.
        :param player: The player to start as. If server then player one,
        if client then player two.
        :param ip: The ip address of the server. to be left None if the
        instance is server.
        """
        self._master = master  # The tkinter root.

        self._canvas = t.Canvas(self._master,
                                width=self.WIDTH,
                                height=self.HEIGHT,
                                highlightthickness=0)  # The tkinter canvas.
        # The AI class which is to be activated only when the game is not
        # human vs. human.
        self._AI = AI()
        self._game = game
        # The communicator class which is in charge of communication between
        # server and client.
        self.__communicator = Communicator(root, port, ip)
        # Connects between instances of the game.
        self.__communicator.connect()
        # Binds the message handler to incoming messages.
        self.__communicator.bind_action_to_message(self.__handle_message)
        # __columns: A dictionary of keys: Column numbers.
        #                            values: The columns' item IDs.
        # __discs: A dictionary of keys: Coordinates on the board.
        #                          values: The Discs' item IDs.
        self.__columns, self.__discs = self.shapes()
        # if the instance is AI, is_human changes to False.
        self.__is_human = True
        self.__player = player

    def set_is_human(self, is_human):
        """
        Sets the __is_human attribute to true if the player is human and
        false if the player is 'AI'.
        :param is_human: True if human or False if AI
        """
        self.__is_human = is_human

    def event_handler(self, column, msg=False):
        """
        The function to be activated when a move is made by one of the players.
        Calls all the necessary functions to be executed when a
        turn has been made.
        :param column: The column which was clicked by a player.
        :param msg: Determines whether the move was made by the current
        player or by the other player.
        :return: None.
        """
        if msg:
            self._game.make_move(column)
            self.add_disc()
            self.win()
            self.tie()

        elif self.__player == self._game.get_current_player() and \
                self._game.get_winner() == None:
            self._game.make_move(column)
            self.add_disc()
            self.__communicator.send_message(str(column))
            self.win()
            self.tie()

    def add_disc(self):
        """
        Adds a disc to the screen according to the coordinates supplied by
        the "make move" function. Add a green or red disc depending on who
        the current player is.
        :return: None.
        """
        game = self._game
        y, x = game.get_last_move()
        item_id = self.__discs[x, y]
        if game.get_current_player() == game.PLAYER_ONE:
            self._canvas.itemconfig(item_id, fill="#af0707", outline='#751810')
        else:
            self._canvas.itemconfig(item_id, fill="#096300", outline='#203d11')

    def shapes(self):
        """
        The function that draws the initial board. Generates all of the
        necessary shapes by using the helper functions.
        :return: A dictionary of columns and a dictionary of discs.
        """
        self._canvas.pack()
        column_dict = {k: None for k in range(int(self.WIDTH / self.SCALAR))}
        disc_dict = self._game.create_board()
        for i in range(int(self.WIDTH / self.SCALAR)):  # Creates columns.
            color = self.color_generator \
                (index=i, RGB=(0, 0, 0, 0, 3, 0), oval=False)
            rectangle = self.draw_column(i, color)
            column_dict[i] = rectangle  # adds to column dictionary.
            for j in range(int(self.HEIGHT / self.SCALAR)):  # Creates discs.
                color = self.color_generator(index=(i, j),
                                             RGB=(1, 0, 0, 0, 2, 1),
                                             oval=True)
                oval = self.draw_oval(i, j, color)
                disc_dict[i, j] = oval  # adds to disc dictionary.
        return column_dict, disc_dict

    def key_bind(self, object, index):
        """
        Binds Left mouse
        button to event handler, as well as 'enter' and 'leave' to the current
        player signal (changing the column color).
        :param object: Type of object to bind.
        :index: Column index of the object.
        """
        self._canvas.tag_bind(object, '<Button-1>',
                              lambda event: self.event_handler(index, False))
        self._canvas.tag_bind(object, '<Enter>',
                              lambda event: self.column_config(index, True))
        self._canvas.tag_bind(object, '<Leave>',
                              lambda event: self.column_config(index, False))

    def draw_oval(self, i, j, color):
        """
        Creates The oval objects to be displayed on screen.
        :param i: Current column index.
        :param j: Current row index.
        :param color: The shade to fill the oval with.
        :return: Creates an oval object
        """
        scaled_i, scaled_j = i * self.SCALAR, j * self.SCALAR
        tlo = self.SCALAR * 0.2  # top left offset
        bro = self.SCALAR * 0.8  # bottom right offset
        oval = self._canvas.create_oval(scaled_i + tlo,
                                        scaled_j + tlo,
                                        scaled_i + bro,
                                        scaled_j + bro,
                                        fill=color,
                                        outline='',
                                        width=2)
        self.key_bind(oval, i)
        return oval

    def draw_column(self, i, color):
        """
        Used for the initial drawing of columns to the screen. Binds Left mouse
        button to event handler, as well as 'enter' and 'leave' to the current
        player signal (changing the column color).
        :param canvas: Canvas to draw on.
        :param i: Current column index.
        :param color: Fill color of the column.
        :return: Creates a column (rectangle object) on the screen.
        """
        scaled_i = self.SCALAR * i
        tlo = 0  # top left offset
        rectangle = self._canvas.create_rectangle(scaled_i,
                                                  tlo,
                                                  scaled_i + self.SCALAR,
                                                  self.HEIGHT,
                                                  fill=color,
                                                  outline='')
        self.key_bind(rectangle, i)
        return rectangle

    def column_config(self, i, enter):
        """
        Changes the color of the column on mouse over depending on the
        current player. If it is not the instance's turn, the column will be
        grayed out to avoid confusion and still let the player know that the
        game is not stuck.
        :param i: Column index.
        :param enter: True if 'enter' event, False if 'leave' event.
        """
        current_player = self._game.get_current_player()
        item_id = self.__columns[i]
        if enter and self.__player == current_player:
            if self.__player == self._game.PLAYER_ONE:
                self._canvas.itemconfig(item_id, fill='#720d0d')
            else:
                self._canvas.itemconfig(item_id, fill='#16720d')
        elif enter and self.__player != current_player:
            self._canvas.itemconfig(item_id, fill='#555555')
        elif not enter:
            color = self.color_generator(index=i,
                                         RGB=(0, 0, 0, 0, 3, 0),
                                         oval=False)
            self._canvas.itemconfig(item_id, fill=color)

    def color_generator(self, index, RGB, oval):
        """
        A function that is used to generate the various shades that are
        utilized in the initial creation of the board. The formula for the
        coloring of the ovals was found solely through rigorous trial and
        error. This gives the illusion of a gradient background.
        :param index: When sent from oval, this is a tuple of the row and
        column. This enables the illusion of a gradient background. When
        sent from a rectangle, it is the column index.
        :param RGB: The amount of Red, Green and Blue. Every two items in
        the list represent the amount of each color in hex - 00 being the
        minimum and FF being the maximum.
        :param oval: True if the object being colored is an oval, False if
        it is a rectangle.
        :return: A hexadecimal color code.
        """
        color_hex_string = '#'
        if oval == True:
            shade = int((index[0] + index[1]))
        else:
            shade = int(index)
        for ind in RGB:
            color_hex_string += hex(shade + ind)[2:]
        return color_hex_string

    def __handle_message(self, text=None):
        """
        Specifies the event handler for the message getting event in the
        communicator. Upon reception of a message, sends the column number to
        the event handler.
        :param text: the number of the column to place a disc in.
        """
        if text:
            column = int(text)
            self.event_handler(column, True)

        if not self.__is_human:
            self._AI.find_legal_move(self.event_handler)

    def win(self):
        """
        Sets the winning four animation if there is a win and calls the
        message box function, otherwise changes the current player.
        """
        game = self._game
        if game.is_win():
            tuple_list, curr_player = game.is_win()
            if curr_player == game.PLAYER_ONE:
                outline = '#ff851c'
                game.set_winner(game.PLAYER_ONE)
            else:
                outline = '#00ff00'
                game.set_winner(game.PLAYER_TWO)
            for tuple in tuple_list:
                x, y = tuple
                item_id = self.__discs[y, x]
                self._canvas.itemconfig(item_id,
                                        outline=outline,
                                        dash=5,
                                        width=3)
            self.win_message()
        if game.get_current_player() == game.PLAYER_ONE:
            game.set_current_player(game.PLAYER_TWO)
        else:
            game.set_current_player(game.PLAYER_ONE)

    def tie(self):
        """
        The Tie message box.
        """
        if self._game.is_tie():
            if messagebox.showinfo('Game Over', 'It\'s a Tie!'):
                self._master.destroy()

    def win_message(self):
        """
        The Win Message boxes.
        """
        if self._game.get_current_player() == self._game.PLAYER_ONE:
            if t.messagebox.showinfo('Game Over!', 'Red Wins'):
                self._master.destroy()
        if self._game.get_current_player() == self._game.PLAYER_TWO:
            if t.messagebox.showinfo('Game Over!', 'Green Wins'):
                self._master.destroy()
예제 #11
0
        print('‪Illegal‬‬ ‫‪program‬‬ ‫‪arguments.‬‬')
    if len(arguments) == 4:
        server = False
    return server  # False if client.


if __name__ == '__main__':
    # For some reason these imports failed on the computers in the lab when
    # they were placed up top.
    from game import Game
    from ai import AI
    server = check_arguments()
    root = t.Tk()
    root.resizable(False, False)
    game_object = Game()
    if server:
        player = game_object.PLAYER_ONE
        gui = GUI(game_object, root, int(sys.argv[2]), player, None)
        if sys.argv[1] == 'ai':
            gui.set_is_human(False)
            #if the ai has to make the first move, call the make move function.
            AI.find_legal_move(gui._AI, gui.event_handler)
        root.title("Server")
    else:
        player = game_object.PLAYER_TWO
        gui = GUI(game_object, root, int(sys.argv[2]), player, sys.argv[3])
        if sys.argv[1] == 'ai':
            gui.set_is_human(False)
        root.title("Client")
    root.mainloop()
예제 #12
0
class Connect4GUI(t.Frame):
    """
    Designed to handle the GUI aspects (creating a window, buttons and
    pop-ups. Also initializes the communicator object.
    """

    ############################################################
    # Constants
    ############################################################

    #: Defines the game title.
    GAME_TITLE = "Four in a Row"

    #: Defines the game title bar text.
    GAME_TITLEBAR_FORMAT = "Four in a Row (%s player)"

    #: Defines the invalid play location message.
    INVALID_LOCATION_MESSAGE = "The selected location is invalid."

    #: Defines the current player name title.
    PLAYER_TITLE = "You"

    #: Defines the opponent player name title.
    OPPONENT_TITLE = "Opponent"

    #: Defines the message that will tell the player is playing now.
    NOW_PLAYING = "Now Playing"

    #: Defines the moves counter.
    MOVE_NUMBER_FORMAT = "Move #%d"

    #: Defines the Win message.
    CURRENT_PLAYER_WON_MESSAGE = "You've won! You Rocks!"

    #: Defines the Lose message.
    CURRENT_PLAYER_LOSE_MESSAGE = "Ugh, You lost..."

    #: Describe the server connecting message.
    SERVER_WAITING_MESSAGE = "Waiting for a client (Server IP: %s)..."

    #: Describe the client connecting message.
    CLIENT_WAITING_MESSAGE = "Connecting to the server..."

    #: Describe the opponent thinking message.
    OPPONENT_THINKING_MESSAGE = "Opponent is thinking, please wait..."

    #: Describe the AI thinking message.
    AI_THINKING_MESSAGE = "The Amazing AI is generating move..."

    #: Defines the game title Y padding.
    GAME_TITLE_PADDING_Y = 35

    #: Defines the screen width.
    SCREEN_WIDTH = 645

    #: Defines the screen height.
    SCREEN_HEIGHT = 485

    #: Defines the width for each board spot (a.k.a disc container).
    BOARD_LOCATION_SIZE_X = 50

    #: Defines the height for each board spot (a.k.a disc container).
    BOARD_LOCATION_SIZE_Y = 50

    #: Defines the board X padding.
    BOARD_PADDING_X = 20

    #: Defines the board Y padding.
    BOARD_PADDING_Y = 10

    #: Defines the status bar Y padding.
    STATUS_BAR_PADDING_Y = 20

    #: Defines the player panels Y padding.
    PLAYER_PANEL_PADDING_Y = 20

    #: Defines the Y padding between the player title and subtitle.
    PLAYER_PANEL_PADDING_TO_SUBTITLE_Y = 25

    #: Defines the Y padding between the player subtitle and image.
    PLAYER_PANEL_PADDING_TO_IMAGE_Y = 30

    #: Defines the images base path.
    IMAGES_BASE_PATH = 'images'

    #: Defines the game background image name.
    IMAGE_BACKGROUND = 'background.jpg'

    #: Defines the game board image name.
    IMAGE_BOARD_BACKGROUND = 'board_background.jpg'

    #: Defines the disc image path (should be formatted).
    IMAGE_DISC_FORMAT = 'disc_%s.jpg'

    #: Defines the column highlighter image path..
    IMAGE_COLUMN_HIGHLIGHTER = 'highlight_arrow.jpg'

    #: Defines the default text color.
    DEFAULT_COLOR = 'white'

    #: Defines the errors text color.
    ERROR_TEXT_COLOR = 'red'

    #: Defines the win message color.
    CURRENT_PLAYER_WON_COLOR = "green"

    #: Defines the lose message color.
    CURRENT_PLAYER_LOSE_COLOR = "red"

    #: Defines the status bar font.
    STATUS_BAR_FONT = "Helvetica 22 italic"

    #: Defines the game title font.
    TITLE_FONT = "Helvetica 32 bold"

    #: Defines the player title font.
    PLAYER_TITLE_FONT = "Helvetica 24"

    #: Defines the player sub-title font.
    PLAYER_SUBTITLE_FONT = "Helvetica 16"

    #: Defines the current turn font.
    CURRENT_TURN_FONT = "Helvetica 18 bold"

    #: Defines the moves counter font.
    MOVES_COUNTER_FONT = "Helvetica 18 italic"

    #: Defines the board marker fill.
    BOARD_MARKER_FILL = ''  # = transparent

    #: Defines the board marker weight.
    BOARD_MARKER_WEIGHT = 7

    #: Defines the timeout for a status bar timeout delegate invocation.
    STATUS_BAR_TIMEOUT_DURATION = 4000

    #: Define a time of AI move generation delay. This value must be used
    # to allow the rendering pipeline to work correctly.
    AI_RENDER_DELAY = 200

    #: The default AI move generation timeout, in seconds.
    AI_DEFAULT_TIMEOUT = 3

    #: Define the angle in which we're gonna move to create a circle
    # stride. Note that 2 pi radians = 60 degrees
    POLY_CIRCLE_STRIDE_ANGLE = math.pi * 2

    ############################################################
    # Ctor
    ############################################################

    def __init__(self, parent, game, player, port, ip=None):
        """
        Initializes the GUI and connects the communicator.
        :param parent: the tkinter root.
        :type parent: t.Window
        :param ip: the ip to connect to.
        :param port: the port to connect to.
        """
        t.Frame.__init__(self, parent)

        # Setup the iVars
        self.__parent = parent
        self.__game = game
        self.__ai_engine = AI()
        self.__current_player = player
        self.__board_pos_x = 0
        self.__board_pos_y = 0
        self.__current_turn_pos_y = 0
        self.__board_discs = {}
        self.__current_highlighted_row = None
        self.__highlighter_res_id = None
        self.__status_bar_res_id = None
        self.__status_bar_timeout_res_id = None
        self.__current_turn_left_res_id = None
        self.__current_turn_right_res_id = None
        self.__current_move_res_id = None
        self.__board_ui_disabled = True
        self.__is_current_turn = player.get_player_number() == Game.PLAYER_ONE
        self.__communicator = Connect4Communicator(self.__parent, port, ip)

        # Initialize the drawing canvas
        self.__canvas = t.Canvas(self.__parent,
                                 width=Connect4GUI.SCREEN_WIDTH,
                                 height=Connect4GUI.SCREEN_HEIGHT)
        self.__canvas.image_resources = {}  # Saves us from being GC'ed.

        # Setup the UI
        self.__canvas.pack(expand=t.YES, fill=t.BOTH, padx=0, pady=0)
        self.__init_ui()

        # Write the title bar text
        self.__parent.title(Connect4GUI.GAME_TITLEBAR_FORMAT %
                            self.__current_player.get_color())

        # Register UI events
        self.__parent.bind('<Button-1>', self.__handle_mouse_click)
        self.__parent.bind('<Motion>', self._handle_mouse_move)
        game.set_on_game_over_callback(self.__handle_game_end)

        # Start to work on the communication
        self.__setup_communication_channel()

    ############################################################
    # Private Methods
    ############################################################

    # region Public API

    def perform_player_move(self, column):
        """
        Perform a move by the currently playing player/
        :param column: The played column.
        :return: True if the move successfully made, false otherwise.
        """
        # Clear the status bar
        self.truncate_status_bar()

        # Firstly perform the move locally.
        if not self.perform_move(self.__current_player, column):
            return False

        # Now transfer it to the opponent client.
        self.__communicator.move_played(
            self.__current_player.get_player_number(), column)

        return True

    def perform_move(self, player, column):
        """
        Performs a single move for the given player.
        :param player: The playing player instance.
        :param column: The column to play in.
        :return: True if the move successfully made, false otherwise.
        """
        # Perform the move in the UI and the game
        if not self.__perform_move(column):
            return False

        # Mark that this is the {player}'s opponent turn
        self.__is_current_turn = not self.__is_current_turn
        self.__ui_set_current_turn(
            self.__game.get_opponent(player.get_player_number()))

        # Update the moves counter
        self.__ui_update_moves_counter()

        return True

    def add_disc(self, color, row, column):
        """
        Adds a disck to the board.
        :param color: The disc color.
        :param row: The row location.
        :param column: The column location.
        """
        # Get the disc image
        disc_img = self.__get_image(Connect4GUI.IMAGE_DISC_FORMAT % color)

        # Compute the right position
        pos_x = self.__board_pos_x + Connect4GUI.BOARD_PADDING_X + \
            Connect4GUI.BOARD_LOCATION_SIZE_X * column
        pos_y = self.__board_pos_y + Connect4GUI.BOARD_PADDING_Y + \
            Connect4GUI.BOARD_LOCATION_SIZE_Y * row

        # Draw the disc
        res_id = self.__canvas.create_image(pos_x,
                                            pos_y,
                                            image=disc_img,
                                            anchor=t.NW)
        self.__board_discs[(row, column)] = res_id

    def highlight_column(self, column):
        """
        Highlights the given column.
        :param column: The column number.
        """
        # Do we got a highlighted column?
        if self.__current_highlighted_row == column:
            return

        if self.__highlighter_res_id is not None:
            self.unhighlight_column()

        # Get the arrow picture
        arrow = self.__get_image(Connect4GUI.IMAGE_COLUMN_HIGHLIGHTER)

        # Calculate the positioning
        pos_x = self.__board_pos_x + Connect4GUI.BOARD_LOCATION_SIZE_X * \
            column + arrow.width() / 2
        pos_y = self.__board_pos_y - arrow.height()

        # Render
        self.__current_highlighted_row = column
        self.__highlighter_res_id = self.__canvas.create_image(pos_x,
                                                               pos_y,
                                                               image=arrow,
                                                               anchor=t.NW)

    def unhighlight_column(self):
        """
        Un-highlight the currently highlighted column.
        """
        if self.__highlighter_res_id is not None:
            self.__canvas.delete(self.__highlighter_res_id)
            self.__highlighter_res_id = None
            self.__current_highlighted_row = None

    def set_status_bar_text(self, text, color, timeout_delegate=None):
        """
        Sets the status bar text.
        :param text: The text to put.
        :param color: The text color.
        :param timeout_delegate: A delegate to invoke after a given timeout.
        """
        # If we already got content on our status bar, remove it first.
        if self.__status_bar_res_id is not None:
            self.truncate_status_bar()

        # Compute the positioning
        pos_x = Connect4GUI.SCREEN_WIDTH / 2
        pos_y = self.__board_pos_y / 2 + Connect4GUI.STATUS_BAR_PADDING_Y

        # Render
        self.__status_bar_res_id = self.__canvas.create_text(
            pos_x,
            pos_y,
            fill=color,
            font=Connect4GUI.STATUS_BAR_FONT,
            text=text)

        # Should we invoke any timeout delegate?
        if timeout_delegate:
            # Avoid duplication
            if self.__status_bar_timeout_res_id is not None:
                self.after_cancel(self.__status_bar_timeout_res_id)
                self.__status_bar_timeout_res_id = None

            # Schedule
            self.__status_bar_timeout_res_id = self.after(
                Connect4GUI.STATUS_BAR_TIMEOUT_DURATION, timeout_delegate)

    def truncate_status_bar(self):
        """
        Truncate the status bar.
        """
        # Did we got a status bar resource?
        if self.__status_bar_res_id is not None:
            self.__canvas.delete(self.__status_bar_res_id)
            self.__status_bar_res_id = None

        # Clear the status bar timer
        if self.__status_bar_timeout_res_id is not None:
            self.after_cancel(self.__status_bar_timeout_res_id)
            self.__status_bar_timeout_res_id = None

    # endregion

    # region UI Events

    def __handle_game_end(self, game, winner):
        """
        Handle the game over event.
        :param game: The game instance.
        :param winner: The winner player number.
        """
        # Update the status bar
        this_player_won = winner == self.__current_player.get_player_number()
        if this_player_won:
            color = Connect4GUI.CURRENT_PLAYER_WON_COLOR
            self.set_status_bar_text(Connect4GUI.CURRENT_PLAYER_WON_MESSAGE,
                                     color)
        else:
            color = Connect4GUI.CURRENT_PLAYER_LOSE_COLOR
            self.set_status_bar_text(Connect4GUI.CURRENT_PLAYER_LOSE_MESSAGE,
                                     color)

        # Mark the winning discs positions
        initial_coord, end_coord = game.get_board().get_winning_coordinates()
        self.__ui_mark_board(initial_coord, end_coord, color)

        # Clearn the current turn indicator
        self.__ui_clean_current_turn()

        # Disable the board UI
        self.__board_ui_disabled = True

    def __handle_mouse_click(self, event):
        """
        Handle the mouse click event.
        :param event: The even information.
        """
        if self.__is_board_ui_disabled():
            return

        # Clear the cursor
        self.unhighlight_column()

        # Did we selected any column to play in?
        column = self.__get_mouse_board_column(event.x, event.y)

        if column is None:
            return

        if not self.perform_player_move(column):
            self.set_status_bar_text(Connect4GUI.INVALID_LOCATION_MESSAGE,
                                     Connect4GUI.ERROR_TEXT_COLOR,
                                     lambda: self.truncate_status_bar())

    def _handle_mouse_move(self, event):
        """
        Handle the mouse click event.
        :param event: The even information.
        """
        self.__handle_column_highlight(event)

    # endregion

    # region Communication

    def __setup_communication_channel(self):
        """
        Setups the game communication channel.
        """
        # Show the connection message
        if self.__current_player.get_player_number() == Game.PLAYER_ONE:
            # This is the server program according to the instructions
            self.set_status_bar_text(
                Connect4GUI.SERVER_WAITING_MESSAGE %
                self.__communicator.get_ip_address(),
                Connect4GUI.DEFAULT_COLOR)
        else:
            # This is the client program
            self.set_status_bar_text(Connect4GUI.CLIENT_WAITING_MESSAGE,
                                     Connect4GUI.DEFAULT_COLOR)

        # Register the UI events
        self.__communicator.set_on_connected(self.__on_connected)
        self.__communicator.set_on_player_selected(self.__on_player_selected)
        self.__communicator.set_on_move_performed(self.__handle_move_played)

        # Attempt to connect
        self.__communicator.connect()

    def __on_connected(self):
        """
        Handle the "on opponent connected" event.
        """
        # Notify the other player who are we
        self.__communicator.set_player_type(
            self.__current_player.get_player_number(),
            self.__current_player.get_player_type())

    def __handle_move_played(self, player, column):
        """
        Handle the "move played" event.
        :param player: The player who performed the move.
        :param column: The move column.
        """
        # We shouldn't handle our moves, only our opponent
        if self.__current_player.get_player_number() == player:
            return

        # Perform the move
        self.perform_move(
            self.__game.get_opponent(
                self.__current_player.get_player_number()), column)

        if self.__game.get_winner() is None:
            # Do we play as AI?
            if self.__current_player.get_player_type() == \
                    Player.PLAYER_TYPE_COMPUTER:
                self.after(Connect4GUI.AI_RENDER_DELAY, self.__handle_ai_move)

    def __on_player_selected(self, player_number, player_type):
        """
        Handle the "player selected" (a.k.a. "human"/"ai" chose) event.
        :param player_number: The player number.
        :param player_type: The player type.
        """
        # We got a message for our opponent?
        if self.__current_player.get_player_number() == player_number:
            return

        # Update the opponent player information
        self.__game.get_player(player_number).set_player_type(player_type)

        # Remove the waiting status bar title.
        self.truncate_status_bar()

        # Create the players panels
        self.__ui_create_player_panel(self.__game.get_player(Game.PLAYER_ONE))
        self.__ui_create_player_panel(self.__game.get_player(Game.PLAYER_TWO))

        # Enable the UI
        self.__board_ui_disabled = False

        # Starts the game
        self.__start_game()

    # endregion

    # region Private Helpers

    def __get_image(self, image_path):
        """
        Gets an image resource.
        :param image_path: The image path.
        :return: The loaded image resource, or the cached one if the image
        was already loaded.
        :type ImageTk.PhotoImage:
        """
        image_path = os.path.join(Connect4GUI.IMAGES_BASE_PATH, image_path)
        if image_path in self.__canvas.image_resources:
            return self.__canvas.image_resources[image_path]

        img = Image.open(image_path).convert('RGBA')
        image = ImageTk.PhotoImage(img)

        self.__canvas.image_resources[image_path] = image
        return image

    def __computes_polygon_oval_coords(self,
                                       x0,
                                       y0,
                                       x1,
                                       y1,
                                       stride=25,
                                       angle=0):
        """
        Compute the coordinates used to draw a polygon based oval with the
        given axis and rotation. We'll use this function to create rotatable
        oval.
        :param x0: The initial x coordinate.
        :param y0: The initial y coordinate.
        :param x1: The end x coordinate.
        :param y1: The end y coordinate.
        :param stride: The number of steps in the polygon division used to
        make it looks like a circle.
        :param angle: The rotation angle in degrees.
        :return: An array contains the coordinates for the polygon.
        """
        # Converts the angle into radians
        angle = math.radians(angle)

        # Gets the major axes
        major_x = (x1 - x0) / 2.0
        major_y = (y1 - y0) / 2.0

        # Computes the center location (ex6, lol)
        center_x = x0 + major_x
        center_y = y0 + major_y

        # Computes the oval polygon as a list of coordinates
        coordinates_list = []
        for i in range(stride):
            # Calculate the angle for this step
            theta = Connect4GUI.POLY_CIRCLE_STRIDE_ANGLE * (float(i) / stride)

            x1 = major_x * math.cos(theta)
            y1 = major_y * math.sin(theta)

            # Rotate the X and Y coordinates
            x = (x1 * math.cos(angle)) + (y1 * math.sin(angle))
            y = (y1 * math.cos(angle)) - (x1 * math.sin(angle))

            # Append them
            coordinates_list.append(round(x + center_x))
            coordinates_list.append(round(y + center_y))

        return coordinates_list

    def __in_board_column(self, column, x, y):
        """
        Determines if the given X and Y coordinates resist in the given X
        and Y coordinates.
        :param column: The column index.
        :param x: The X coordinate.
        :param y: The Y coordinate.
        :return: True if the coordinates are in the given column at the
        board, false otherwise.
        """
        # Compute the positions
        x0 = self.__board_pos_x + Connect4GUI.BOARD_PADDING_X + \
            (column * Connect4GUI.BOARD_LOCATION_SIZE_X)
        x1 = x0 + Connect4GUI.BOARD_LOCATION_SIZE_X

        y0 = self.__board_pos_y + Connect4GUI.BOARD_PADDING_Y
        y1 = y0 + (Board.HEIGHT * Connect4GUI.BOARD_LOCATION_SIZE_Y)

        # Do we have an intersection?
        if x0 <= x <= x1 and y0 <= y <= y1:
            return True

        return False

    def __is_board_ui_disabled(self):
        """
        Determine if the board UI is disabled or not.
        :return: True if the board UI is disabled, false otherwise.
        """
        return self.__board_ui_disabled or not self.__is_current_turn \
            or self.__current_player.get_player_type() == \
            Player.PLAYER_TYPE_COMPUTER

    def __get_mouse_board_column(self, x, y):
        """
        Gets the column in which the mouse sits in based on the given
        coordinates.
        :param x: The X coordinate.
        :param y: The Y coordinate.
        :return: The mouse board column or None if the mouse isn't on the
        board.
        """
        for c in range(Board.WIDTH):
            if self.__in_board_column(c, x, y):
                return c
        return None

    def __handle_column_highlight(self, event):
        """
        Handles the row highlight task.
        :param event: The mouse event container.
        """
        # Is the board disabled?
        if self.__is_board_ui_disabled():
            return

        # Do we need to highlight any row?
        highlighted_column = self.__get_mouse_board_column(event.x, event.y)
        if highlighted_column is None:
            return

        # Remove old highlights
        if self.__current_highlighted_row is not None:
            self.__current_highlighted_row = None
            self.unhighlight_column()

        # Highlight it
        self.highlight_column(highlighted_column)

    def __handle_ai_move(self):
        """
        Generates and plays a move by the AI.
        """
        def __do_handle_ai_move():
            """
            Do the actual AI move handling (after the GUI is updated).
            """
            # Init
            selected_move = None

            def move_selected(value):
                # Notify Python that this is an outer variable.
                # See: https://stackoverflow.com/a/12182176/1497516
                nonlocal selected_move

                # Save the last value.
                selected_move = value

            # Get the move
            self.__ai_engine.find_legal_move(self.__game, move_selected,
                                             Connect4GUI.AI_DEFAULT_TIMEOUT)

            # Play it
            self.perform_player_move(selected_move)

        # Set the AI thinking label for this player so she won't get bored
        self.set_status_bar_text(Connect4GUI.AI_THINKING_MESSAGE,
                                 Connect4GUI.DEFAULT_COLOR)

        # Do the actual AI handling
        self.after(Connect4GUI.AI_RENDER_DELAY, __do_handle_ai_move)

    def __start_game(self):
        """
        Starts the game.
        """
        # Mark the current player
        self.__ui_set_current_turn(self.__game.get_player(Game.PLAYER_ONE))

        # Set the move number
        self.__ui_update_moves_counter()

        # We're starting?
        if self.__is_current_turn:
            # Is this the AI who playing?
            if self.__current_player.get_player_type() == \
                    Player.PLAYER_TYPE_COMPUTER:
                self.after(Connect4GUI.AI_RENDER_DELAY, self.__handle_ai_move)

    def __perform_move(self, column):
        """
        Performs a single move.
        :param column: The column to play in.
        :return: True if the move have successfully done, false otherwise.
        """
        # Can we perform that move?
        if not self.__game.is_valid_move(column):
            return False

        # Get the disk row
        row = Board.HEIGHT - 1 - self.__game.get_board() \
            .count_column_items(column)
        player_color = self.__game.get_player(
            self.__game.get_current_player()).get_color()

        # Put the disc in our lovely gui
        self.add_disc(player_color, row, column)

        # Perform the move
        self.__game.make_move(column)

        # Re-render the current moves counter
        self.__ui_update_moves_counter()

        return True

    def __init_ui(self):
        """
        Initialize and render the game UI.
        """
        # Create the background
        self.__ui_create_background()

        # Create the background
        self.__ui_create_board()

        # Disable the option to resize the window
        self.__parent.resizable(False, False)

    def __ui_create_background(self):
        """
        Renders the game background (and the title... haha).
        """
        # Background Image
        bg_image = self.__get_image(Connect4GUI.IMAGE_BACKGROUND)
        self.__canvas.create_image(0, 0, image=bg_image, anchor=t.NW)

        # The game title
        self.__canvas.create_text(Connect4GUI.SCREEN_WIDTH / 2,
                                  Connect4GUI.GAME_TITLE_PADDING_Y,
                                  fill=Connect4GUI.DEFAULT_COLOR,
                                  font=Connect4GUI.TITLE_FONT,
                                  text=Connect4GUI.GAME_TITLE)

    def __ui_create_board(self):
        """
        Renders the board.
        """
        # Load the background
        board_bg = self.__get_image(Connect4GUI.IMAGE_BOARD_BACKGROUND)

        # Position in the center of the screen
        self.__board_pos_x = math.floor(
            (Connect4GUI.SCREEN_WIDTH - board_bg.width()) / 2)
        self.__board_pos_y = math.floor(
            (Connect4GUI.SCREEN_HEIGHT - board_bg.height()) / 1.4)
        self.__canvas.create_image(self.__board_pos_x,
                                   self.__board_pos_y,
                                   image=board_bg,
                                   anchor=t.NW)

    def __ui_create_player_panel(self, player):
        """
        Renders the player panel.
        :param player: The player instance we're rendering the panel for.
        :param is_left_panel: True if this is the left panel, false otherwise.
        """
        # Setup
        is_left_panel = self.__ui_get_left_player(player)

        pos_x = 0
        pos_y = self.__board_pos_y + Connect4GUI.PLAYER_PANEL_PADDING_Y

        # Resolve data based on the panel location
        if is_left_panel:
            pos_x = self.__board_pos_x / 2
        else:
            pos_x = self.__board_pos_x * 1.5 + self.__get_image(
                Connect4GUI.IMAGE_BOARD_BACKGROUND).width()

        # Render the player title
        if self.__current_player.get_player_number() == \
                player.get_player_number():
            player_title = Connect4GUI.PLAYER_TITLE
        else:
            player_title = Connect4GUI.OPPONENT_TITLE

        self.__canvas.create_text(
            pos_x,
            pos_y,
            # fill=player.get_color(),
            font=Connect4GUI.PLAYER_TITLE_FONT,
            text=player_title)

        # Create the subtitle
        pos_y += Connect4GUI.PLAYER_PANEL_PADDING_TO_SUBTITLE_Y
        self.__canvas.create_text(pos_x,
                                  pos_y,
                                  fill=player.get_color(),
                                  font=Connect4GUI.PLAYER_SUBTITLE_FONT,
                                  text=player.get_name())

        # Render the disc
        disc_img = self.__get_image(Connect4GUI.IMAGE_DISC_FORMAT %
                                    player.get_color())
        pos_y += Connect4GUI.PLAYER_PANEL_PADDING_TO_IMAGE_Y + \
            disc_img.height() / 2

        # Compute the right position
        self.__canvas.create_image(pos_x, pos_y, image=disc_img)

        # Save this position so we can use it later.
        self.__current_turn_pos_y = pos_y + disc_img.height()

    def __ui_get_left_player(self, player):
        """
        Determine if this is the left panel player.
        :param player: The player instance.
        :return: True if this is the left panel player, false otherwise.
        """
        if player.get_player_number() == Game.PLAYER_ONE:
            return True
        return False

    def __ui_set_current_turn(self, player):
        """
        Update the UI to show the current player.
        :param player: The player.
        """
        # Resolve the panel
        is_left_panel = self.__ui_get_left_player(player)

        if is_left_panel:
            # We already have that panel?
            if self.__current_turn_left_res_id is not None:
                return

            # Get the x position
            pos_x = self.__board_pos_x / 2

            # Remove the other canvas
            if self.__current_turn_right_res_id is not None:
                self.__canvas.delete(self.__current_turn_right_res_id)
                self.__current_turn_right_res_id = None
        else:
            # We already have that panel?
            if self.__current_turn_right_res_id is not None:
                return

            # Resolve the position
            pos_x = self.__board_pos_x * 1.5 + self.__get_image(
                Connect4GUI.IMAGE_BOARD_BACKGROUND).width()

            # Remove the other canvas
            if self.__current_turn_left_res_id is not None:
                self.__canvas.delete(self.__current_turn_left_res_id)
                self.__current_turn_left_res_id = None

        pos_y = self.__current_turn_pos_y
        res_id = self.__canvas.create_text(pos_x,
                                           pos_y,
                                           fill=Connect4GUI.DEFAULT_COLOR,
                                           font=Connect4GUI.CURRENT_TURN_FONT,
                                           text=Connect4GUI.NOW_PLAYING)

        if is_left_panel:
            self.__current_turn_left_res_id = res_id
        else:
            self.__current_turn_right_res_id = res_id

        # If this is the opponent player, notify the user via the status bar
        if self.__current_player != player:
            self.set_status_bar_text(Connect4GUI.OPPONENT_THINKING_MESSAGE,
                                     Connect4GUI.DEFAULT_COLOR)
        else:
            self.truncate_status_bar()

    def __ui_clean_current_turn(self):
        """
        Cleans the current turn indicator.
        """
        # Left panel
        if self.__current_turn_left_res_id is not None:
            self.__canvas.delete(self.__current_turn_left_res_id)
            self.__current_turn_left_res_id = None

        # Right panel
        if self.__current_turn_right_res_id is not None:
            self.__canvas.delete(self.__current_turn_right_res_id)
            self.__current_turn_left_res_id = None

    def __ui_update_moves_counter(self):
        """
        Sets the move number in the UI.
        :param number: The move number.
        """
        # Remove the old label
        if self.__current_move_res_id is not None:
            self.__canvas.delete(self.__current_move_res_id)
            self.__current_move_res_id = None

        # Compute the next move
        move_number = self.__game.get_moves_count() + 1

        # Compute the positions
        board_bg = self.__get_image(Connect4GUI.IMAGE_BOARD_BACKGROUND)
        pos_x = Connect4GUI.SCREEN_WIDTH / 2
        pos_y = self.__board_pos_y + board_bg.height() + 20

        self.__current_move_res_id = self.__canvas.create_text(
            pos_x,
            pos_y,
            fill=Connect4GUI.DEFAULT_COLOR,
            font=Connect4GUI.MOVES_COUNTER_FONT,
            text=(Connect4GUI.MOVE_NUMBER_FORMAT % move_number))

    def __ui_mark_board(self, initial_coord, end_coord, color):
        """
        Marks the given coordinates on the board.
        :param initial_coord: The initial coordinate.
        :param end_coord: The end coordinate.
        :param color: The color to use to mark the coordinates with.
        """
        # Calculate the marking oval coordinates based on the win sequence.
        angle = 0
        is_left_diagonal = False
        is_right_diagonal = False
        if initial_coord[1] == end_coord[1]:
            # Vertical win, so we'll mark one column on the x and the
            # difference between the columns in the y.
            diff_x = 1
            diff_y = (end_coord[0] - initial_coord[0]) + 1
        elif initial_coord[0] == end_coord[0]:
            # This is an horizontal win, so we'll mark one column on the y
            # and the difference between the columns in the x.
            diff_x = (end_coord[1] - initial_coord[1]) + 1
            diff_y = 1
        else:
            # This is a digaonal win. Now we need to determine where it
            # points to? It goes from top left to bottom right ("\") or from
            # bottom left to top right ("/").
            if initial_coord[1] < end_coord[1]:
                # This is a top left to bottom right diagonal.
                diff_x = (end_coord[1] - initial_coord[1]) + 1
                diff_y = 1
                angle = 45  # 45" clockwise
                is_left_diagonal = True
            else:
                # This is a top right to bottom left diagonal. Thus we need
                #  to firstly swap between the coordinates so we make sure
                # the min coordinate is less then the big one.
                tmp = initial_coord
                initial_coord = end_coord
                end_coord = tmp

                # And now we're save to calculate as we normally do
                diff_x = (end_coord[1] - initial_coord[1]) + 1
                diff_y = 1
                angle = -45  # 45" anti-clockwise
                is_right_diagonal = True

        # Computes the (x0, y0) and (x1, y1) coordinates
        x0 = self.__board_pos_x + Connect4GUI.BOARD_PADDING_X
        x0 += (initial_coord[1] * Connect4GUI.BOARD_LOCATION_SIZE_X)
        x1 = x0 + (diff_x * Connect4GUI.BOARD_LOCATION_SIZE_X)

        y0 = self.__board_pos_y + Connect4GUI.BOARD_PADDING_Y
        y0 += (initial_coord[0] * Connect4GUI.BOARD_LOCATION_SIZE_Y)
        y1 = y0 + (diff_y * Connect4GUI.BOARD_LOCATION_SIZE_Y)

        # If we had a diagonal, we need to to re-adjust the x and y
        # coordinates based on the the rotation changes. Thus, we need to
        # push the x and y coordinates accordingly to the X difference.
        if is_left_diagonal or is_right_diagonal:
            diagonal_diff = max(diff_x - 1, 1) / 2
            if is_left_diagonal:
                x0 -= Connect4GUI.BOARD_LOCATION_SIZE_X * diagonal_diff
                x1 += Connect4GUI.BOARD_LOCATION_SIZE_X * diagonal_diff
                y0 -= Connect4GUI.BOARD_LOCATION_SIZE_Y * diagonal_diff
                y1 -= Connect4GUI.BOARD_LOCATION_SIZE_Y * diagonal_diff
            else:
                x0 -= Connect4GUI.BOARD_LOCATION_SIZE_X * diagonal_diff
                x1 += Connect4GUI.BOARD_LOCATION_SIZE_X * diagonal_diff
                y0 += Connect4GUI.BOARD_LOCATION_SIZE_Y * diagonal_diff
                y1 += Connect4GUI.BOARD_LOCATION_SIZE_Y * diagonal_diff

        # Render
        self.__canvas.create_polygon(self.__computes_polygon_oval_coords(
            x0, y0, x1, y1, angle=angle),
                                     outline=color,
                                     width=Connect4GUI.BOARD_MARKER_WEIGHT,
                                     fill=Connect4GUI.BOARD_MARKER_FILL)