class GUI: """ Designed to handle the GUI aspects (creating a window, buttons and pop-ups. Also initializes the communicator object. """ MESSAGE_DISPLAY_TIMEOUT = 250 def __init__(self, parent, port, 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._parent = parent self._canvas = t.Canvas(self._parent, width=300, height=300) self._canvas.pack() self.__communicator = Communicator(parent, port, ip) self.__communicator.connect() self.__communicator.bind_message_to_action(self.__handle_message) self.__place_widgets() def __place_widgets(self): self.__button = t.Button( self._parent, text="YO", font=("Garamond", 20, "bold"), command=lambda: self.__communicator.send_message("YO")) self.__button.pack() self.__button.place(x=120, y=120) self.__label = t.Label(self._parent, text="", fg="red", font=("Garamond", 40, "bold")) self.__label.pack() self.__label.place(x=109, y=200) 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 text: self.__label["text"] = text self._parent.after(self.MESSAGE_DISPLAY_TIMEOUT, self.__handle_message) else: self.__label["text"] = ""
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()
class GUI(): """ A class representing the game Graphical User Interface including all the graphics of the game and the visual parts, including popping messages, and painting ovals """ HUMAN = "human" COMPUTER = "ai" OPENING_MSG = "WELCOME!" GAME_INSTRUCTIONS = "Please press at the button of the column you would " \ "like to put your disc in it." EXIT_MSG = "Exit" CANVAS_BACKGROUND = "MediumPurple1" EMPTY_OVAL = "white" SERVER_COLOR = "blue" CLIENT_COLOR = "red" WINNER_MARK = "gold" TITLE_MSG = "MESSAGE" WIN_MSG = "Congratulation, you won!" LOSE_MSG = "That's too bad, you lost!" DRAW_MSG = "It's a draw!" BOARD_HEIGHT = int(544) BOARD_WIDTH = int(634) FRAME_HEIGHT = int(1000) FRAME_WIDTH = int(1000) WIDGET_LOCATION = int(90) def __init__(self, player, port, server, ip=None): """ a constructor of the graphics of the game :param player: either human of computer :param port: a number between 1000 and 65535 :param server: a boolean value that is True for the server and False for the client. :param ip: None for the server and an ip for the client """ self.game = Game() self.ai = ai.AI() self.root = tk.Tk() self.frame = tk.Frame(self.root, height=self.FRAME_HEIGHT, width=self.FRAME_WIDTH) self.game_board = tk.Canvas(self.frame, bg=self.CANVAS_BACKGROUND, height=self.BOARD_HEIGHT, width=self.BOARD_WIDTH) self.game_board.pack() self.game_board.place(x=200, y=50) self.__communicator = Communicator(self.root, port, ip) self.__communicator.connect() self.__communicator.bind_action_to_message(self.handle_message) self.player = player self.server = server def is_human(self): """ this function update the player to be human or computer considering what was given at the args line. :return: True if the player is human and False if the player is ai """ if sys.argv[1] == self.COMPUTER: self.player = self.COMPUTER return False if sys.argv[1] == self.HUMAN: self.player = self.HUMAN return True def show_message(self, title, msg): """ This is a method used to show messages in the game. :param title: The title of the message box. :type title: str :param msg: The message to show in the message box. :type msg: str """ tk.messagebox.showinfo(str(title), str(msg)) def end_game(self): """ This ends the current game. """ self.root.destroy() self.root.quit() def paint_oval(self, col, color): """ this function painting the ovals at the color of the current player :param col: get the col that the player chose to paint :param color: the color of the player(each player has his own color :return: an oval at the size of all the ovals and at the color given """ row = self.game.make_move(col) + 1 self.game_board.create_oval(5 + col * self.WIDGET_LOCATION, 5 + row * self.WIDGET_LOCATION, col * self.WIDGET_LOCATION + 95, row * self.WIDGET_LOCATION + 95, fill=color) def paint_winner_oval(self, row, col): """ this function painting a winning oval with a gold outline. :param row: the row of the winning oval :param col: the col of the winning oval :return: a gold outline for a winning oval """ self.game_board.create_oval(5 + col * self.WIDGET_LOCATION, 5 + row * self.WIDGET_LOCATION, col * self.WIDGET_LOCATION + 95, row * self.WIDGET_LOCATION + 95, outline=self.WINNER_MARK, width=5) def mark_win(self): """ this function activates the paint_winner_oval(that marking the ovals with a gold outline) at each one of the winning ovals. """ if self.game.check_col()[0] in [ self.game.PLAYER_ONE, self.game.PLAYER_TWO ]: for row, col in self.game.check_col()[1]: self.paint_winner_oval(row, col) if self.game.check_row()[0] in [ self.game.PLAYER_ONE, self.game.PLAYER_TWO ]: for row, col in self.game.check_row()[1]: col = col - 1 self.paint_winner_oval(row, col) if self.game.check_diagonals()[0] in [ self.game.PLAYER_ONE, self.game.PLAYER_TWO ]: for row, col in self.game.check_diagonals()[1]: self.paint_winner_oval(row, col) def win_step(self): """ this function mark the winner discs, shows the winner the message that he won, send message to the other player that he lose and ends the game. """ self.mark_win() self.show_message(self.TITLE_MSG, self.WIN_MSG) self.__communicator.send_message(self.LOSE_MSG) self.end_game() def lose_step(self): """ this function mark the winner discs, shows the loser the message that he lose and ends the game. """ self.mark_win() self.show_message(self.TITLE_MSG, self.LOSE_MSG) self.end_game() def update_game(self, col): """ this function checks if the player is the server of the client and than checks if it is his turn. If so, it activate the paint_oval function with the col given and with the player color :param col: a col that the player chose to place his disc at :return: an painted oval at the color of the player and message at the end of the game(win of draw) """ cur_player = self.game.get_current_player() if self.server: if cur_player == self.game.PLAYER_ONE: self.paint_oval(col, self.SERVER_COLOR) self.__communicator.send_message(col) if self.game.get_winner() == self.game.PLAYER_ONE: self.win_step() elif not self.server: if cur_player == self.game.PLAYER_TWO: self.paint_oval(col, self.CLIENT_COLOR) self.__communicator.send_message(col) if self.game.get_winner() == self.game.PLAYER_TWO: self.win_step() if self.game.get_winner() == self.game.DRAW: self.show_message(self.TITLE_MSG, self.DRAW_MSG) self.__communicator.send_message(self.DRAW_MSG) self.end_game() def create_board(self): """ this function creates the screen- the visual board. including it's labels and empty ovals. :return: the visual initial screen """ label_start = tk.Label(self.root, text=self.OPENING_MSG, font=('Arial', 18)) label_start.pack() label_play = tk.Label(self.root, text=self.GAME_INSTRUCTIONS, font=('David', 15)) label_play.pack() for j in range(5, 600, 90): for i in range(5, 700, 90): self.game_board.create_oval(i, j, i + self.WIDGET_LOCATION, j + self.WIDGET_LOCATION, fill=self.EMPTY_OVAL) def handle_message(self, text=None): """ this function receive a message from the other player that says where he put his disc and if the game ends it shows the matching message :param text: the string of the location that the player want's to put his disc at :return: the painting oval at the other player's screen and if the game ends it shows the matching message(win, lose or draw) """ if text: if self.server: color = self.CLIENT_COLOR self.paint_oval(int(text), color) if not self.is_human(): self.ai.find_legal_move(self.game, self.update_game) elif not self.server: color = self.SERVER_COLOR self.paint_oval(int(text), color) if not self.is_human(): self.ai.find_legal_move(self.game, self.update_game) if self.game.get_winner() == self.game.PLAYER_ONE: if not self.server: self.lose_step() if self.game.get_winner() == self.game.PLAYER_TWO: if self.server: self.lose_step() if self.game.get_winner() == self.game.DRAW: self.show_message(self.TITLE_MSG, self.DRAW_MSG) self.end_game() def run_game(self): """ this function runs the game. it creates the buttons of the columns and the quit button. :return: the updating screen, with the buttons so pressing on them will paint the wanted disc """ col_but_lst = [] for index in range(1, 8): col_but = tk.Button( self.frame, text=("col", index), command=lambda x=index: self.update_game(x - 1)) col_but.pack() col_but_lst.append(col_but) for i in range(len(col_but_lst)): col_but_lst[i].place(x=235 + self.WIDGET_LOCATION * i, y=25) quit_but = tk.Button(self.root, text=self.EXIT_MSG, command=self.end_game) quit_but.pack() quit_but.place(x=950, y=30) self.frame.pack() self.root.mainloop()
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
class Graphics: """class that operate the graphic interface and communication of four_in_a_row game""" FIRST_COLUMN = 0 TEXT1 = 'Player 1 turn' TEXT2 = 'Player 2 turn' TEXT3 = 'use LEFT/RIGHT keys to choose a column' TEXT4 = 'use DOWN key to drop a disc' TEXT5 = 'ILLEGAL MOVE' FIRST_TURN = 0 MESSAGE_DISPLAY_TIMEOUT = 1000 MAIN_WINDOW_WIDTHE = 350 SQUARE_SIZE = 50 TOP_FRAME_HEIGHT = 100 MIDDLE_FRAME_HEIGHT = 300 OVAL_1 = 5 OVAL_2 = 45 BLUE1 = 'dodger blue' FILL_WHITE = "white" TAG_OVAL = "oval" CANVAS_WIDTH = 370 FILL_RED = "red" W_ANCHOR = "w" NW_ANCHOR = "nw" TAG_PLAYER = "player" BD10 = 10 CANVAS5 = 5 CANVAS_0 = 0 FONT16 = 16 FONT12 = 12 CANVAS40 = 40 CANVAS70 = 70 SW_ANCHOR = "sw" CANVAS185 = 185 CANVAS45 = 45 FILL_GOLD = "gold" CANVAS85 = 85 ILLEGAL_MSG = "illegal_msg" FONT18 = 18 CANVAS25 = 25 MIN_MOVE = 0 MAX_MOVE = 6 TAG_ARROW = "arrow" CHANGE_ARROW = 54 FILL_GREEN = 'lawn green' TAG_HUMAN = 'human' TAG_AI = "ai" MOVE_RIGHT = "<Right>" MOVE_LEFT = "<Left>" MOVE_DOWN = '<Down>' CHANGE = 1 def __init__(self, parent, port, player, ip=None, player_turn=True): """initiate a graphics object :param parent: a tkinter root(main window) :param port: the port the player choose that manage the communication :param player: type of player- human or ai :param ip: an ip required if the player defines as the client :param player_turn: defines if its the player turn to play- changes through the game""" self.__player = player self.__player_turn = player_turn if ip: self.__player_turn = False # in order the server to get the # first turn self.__parent = parent self.__communicator = Communicator(self.__parent, port, ip) # to # unblock the communication self.__communicator.connect() self.__communicator.bind_action_to_message(self.__handle_messege) self.__game_board = Game() # initiate a game object # now dividing the main window to three frames- top for title and # announcements, middle for the board an bottom for rules and state self.__top_frame = tki.Frame(self.__parent, width=self.MAIN_WINDOW_WIDTHE, height=self.TOP_FRAME_HEIGHT, bd=10, relief=tki.GROOVE) self.__top_frame.pack(side=tki.TOP) self.__middle_frame = tki.Frame(self.__parent, width=self.MAIN_WINDOW_WIDTHE, height=self.MIDDLE_FRAME_HEIGHT, bd=self.BD10, relief=tki.GROOVE) self.__middle_frame.pack() self.__bottom_frame = tki.Frame(self.__parent, width=self.MAIN_WINDOW_WIDTHE, height=self.TOP_FRAME_HEIGHT, bd=self.BD10, relief=tki.GROOVE) self.__bottom_frame.pack(side=tki.BOTTOM) self.create_widgets() self.create_ovals(self.__middle_frame) self.moves() self.__arrow_point = self.FIRST_COLUMN # initiate arrow at first # column def create_ovals(self, frame): """initiate canvases represnt the discs container""" for i in range(self.__game_board.LENTGH): for j in range(self.__game_board.WIDTH): square = tki.Canvas(frame, width=self.SQUARE_SIZE, height=self.SQUARE_SIZE, bg=self.BLUE1) square.create_oval(self.OVAL_1, self.OVAL_1, self.OVAL_2, self.OVAL_2, fill=self.FILL_WHITE, tag=self.TAG_OVAL) square.grid(row=i, column=j) def create_widgets(self): """creates and pack all main canvases texts and images that make up the design""" self.__top_canvas = tki.Canvas(self.__top_frame, width=self.CANVAS_WIDTH, height=self.TOP_FRAME_HEIGHT, bg=self.FILL_WHITE) self.__top_canvas.pack() self.__bottom_canvas = tki.Canvas(self.__bottom_frame, width=self.CANVAS_WIDTH, height=self.TOP_FRAME_HEIGHT, bg=self.FILL_WHITE) self.__bottom_canvas.pack() self.__bottom_canvas.create_text(self.CANVAS5, self.CANVAS_0, anchor=self.NW_ANCHOR, text=self.TEXT1, fill=self.FILL_RED, tag=self.TAG_PLAYER, font=(False, self.FONT16)) self.__bottom_canvas.create_text(self.CANVAS5, self.CANVAS40, anchor=self.W_ANCHOR, text=self.TEXT3, fill=self.BLUE1, font=(False, self.FONT12)) self.__bottom_canvas.create_text(self.CANVAS5, self.CANVAS70, anchor=self.SW_ANCHOR, text=self.TEXT4, fill=self.BLUE1, font=(False, self.FONT12)) self.__title_file = tki.PhotoImage(file='title.gif') self.__top_canvas.create_image(self.CANVAS185, self.CANVAS40, image=self.__title_file) self.__arrow = tki.PhotoImage(file="giphy.gif") self.__win_file = tki.PhotoImage(file='youwin.png') self.__lose_file = tki.PhotoImage(file='lose.png') self.__draw_file = tki.PhotoImage(file='draw.png') self.__top_canvas.focus_set() # focusing key_board to top canvas def check_for_winner(self): """using the game_board function of checking winner and show the winning state on the screen""" if self.__game_board.get_winner() == \ self.__game_board.get_other_player(): # after making a move # the turn changes so other player means winning self.mark_sequence() # marking the winning discs self.__top_canvas.create_image(self.CANVAS185, self.CANVAS45, image=self.__win_file) self.__player_turn = False # allowing no more turns elif self.__game_board.get_winner() == \ self.__game_board.get_current_player(): self.mark_sequence() self.__top_canvas.create_image(self.CANVAS185, self.CANVAS45, image=self.__lose_file) self.__player_turn = False elif self.__game_board.get_winner() == self.__game_board.DRAW: self.__top_canvas.create_image(self.CANVAS185, self.CANVAS45, image=self.__draw_file) self.__player_turn = False def callback(self, event): """the function bind to the event of pressing the down key in order to drop a disc. it updates the move on the game board and update the screen in accordance""" if self.__player_turn: # allows the play only in turn try: self.__game_board.make_move(self.__arrow_point) # making # the move according to the place of the arrow x, y = self.__game_board.get_last_disc() message = str(self.__arrow_point) # sending the message after making a move in order the # other player's board to update self.__communicator.send_message(message) # update the place in the board chosen to the appropriate # color if self.__game_board.get_current_player(): self.__middle_frame.grid_slaves( row=x, column=y)[0].itemconfig(self.TAG_OVAL, fill=self.FILL_RED) else: self.__middle_frame.grid_slaves( row=x, column=y)[0].itemconfig(self.TAG_OVAL, fill=self.FILL_GOLD) # now we update the indication of player_turn if not self.__game_board.get_winner(): if self.__game_board.get_current_player(): self.__bottom_canvas.itemconfig(self.TAG_PLAYER, text=self.TEXT2, fill=self.FILL_GOLD) else: self.__bottom_canvas.itemconfig(self.TAG_PLAYER, text=self.TEXT1, fill=self.FILL_GOLD) self.check_for_winner() self.__player_turn = False # changing the turn except Exception: # display a message for an instance when a # wrong column was chosen self.__bottom_canvas.create_text(self.CANVAS185, self.CANVAS85, text=self.TEXT5, fill=self.FILL_RED, tag=self.ILLEGAL_MSG, font=(False, self.FONT18)) self.__bottom_canvas.after( self.MESSAGE_DISPLAY_TIMEOUT, lambda: self.__bottom_canvas.delete(self.ILLEGAL_MSG)) def ai_move(self, ai): """makes a move and update the screen automatically using the ai object gives as parameter""" if self.__player_turn: # the move is set according to the find_legal_move_func' ai.find_legal_move(self.__game_board, self.__game_board.make_move) # same as callback apart from the exception which handled in # 'find_legal_move' x, y = self.__game_board.get_last_disc() message = str(y) self.__communicator.send_message(message) if self.__game_board.get_current_player(): self.__middle_frame.grid_slaves(row=x, column=y)[0].itemconfig( self.TAG_OVAL, fill=self.FILL_RED) else: self.__middle_frame.grid_slaves(row=x, column=y)[0].itemconfig( self.TAG_OVAL, fill=self.FILL_GOLD) if not self.__game_board.get_winner(): if self.__game_board.get_current_player(): self.__bottom_canvas.itemconfig(self.TAG_PLAYER, text=self.TEXT2, fill=self.FILL_GOLD) else: self.__bottom_canvas.itemconfig(self.TAG_PLAYER, text=self.TEXT1, fill=self.FILL_RED) self.check_for_winner() self.__player_turn = False def moves(self): """distinguish between a human and ai player and allows the moves accordingly. called in the init function""" if self.__player == self.TAG_HUMAN: self.create_arrow() else: ai = AI() # creating the ai object for this running self.ai_move(ai) def create_arrow(self): """in charge of all the bindings while the player is human""" self.__top_canvas.create_image(self.CANVAS25, self.CANVAS85, image=self.__arrow, tag=self.TAG_ARROW) # creating the arrow first self.__top_canvas.bind(self.MOVE_RIGHT, self.move_arrow_right) self.__top_canvas.bind(self.MOVE_LEFT, self.move_arrow_left) self.__top_canvas.bind(self.MOVE_DOWN, self.callback) self.__top_canvas.register(self.__arrow) def move_arrow_right(self, event): """moves arrow to the right while pressing RIGHT""" if self.MIN_MOVE <= self.__arrow_point < self.MAX_MOVE: self.__arrow_point += self.CHANGE # updates arrow location event.widget.move(self.TAG_ARROW, self.CHANGE_ARROW, self.CANVAS_0) def move_arrow_left(self, event): """moves arrow to the left while pressing LEFT""" if self.MIN_MOVE < self.__arrow_point <= self.MAX_MOVE: self.__arrow_point -= self.CHANGE event.widget.move(self.TAG_ARROW, -self.CHANGE_ARROW, self.CANVAS_0) def mark_sequence(self): """using the winning_sec_lst variable of the game_board and marks the winning strait""" for coor in self.__game_board.get_winning_sec_lst(): self.__middle_frame.grid_slaves( row=coor[0], column=coor[1])[0].config(bg=self.FILL_GREEN) def __handle_messege(self, text): """when receiving a message updates the game_board and screen in accordance and makes all the checks for winner like in callback""" if text: arrow_point = int(text) self.__game_board.make_move(arrow_point) x, y = self.__game_board.get_last_disc() if self.__game_board.get_current_player(): self.__middle_frame.grid_slaves(row=x, column=y)[0].itemconfig( self.TAG_OVAL, fill=self.FILL_RED) else: self.__middle_frame.grid_slaves(row=x, column=y)[0].itemconfig( self.TAG_OVAL, fill=self.FILL_GOLD) if not self.__game_board.get_winner(): if self.__game_board.get_current_player(): self.__bottom_canvas.itemconfig(self.TAG_PLAYER, text=self.TEXT2, fill=self.FILL_GOLD) else: self.__bottom_canvas.itemconfig(self.TAG_PLAYER, text=self.TEXT1, fill=self.FILL_RED) # changing the terms of win and lose-opposite than callback if self.__game_board.get_winner() == \ self.__game_board.get_current_player(): self.mark_sequence() self.__top_canvas.create_image(self.CANVAS185, self.CANVAS45, image=self.__win_file) self.__player_turn = False return # in order to freeze the game for the second player elif self.__game_board.get_winner() == \ self.__game_board.get_other_player(): self.mark_sequence() self.__top_canvas.create_image(self.CANVAS185, self.CANVAS45, image=self.__lose_file) self.__player_turn = False return elif self.__game_board.get_winner() == self.__game_board.DRAW: self.__top_canvas.create_image(self.CANVAS185, self.CANVAS45, image=self.__draw_file) self.__player_turn = False return self.__player_turn = True if self.__player == self.TAG_AI: # in case its an ai the message # activates the moves func again self.moves()
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 Gui(): def __init__(self, root, port, player, ip=None, ai=None): self.root = root self.player = player self.game = Game() self.ai = ai self.__communicator = Communicator(root, port, ip) self.__communicator.connect() self.__communicator.bind_action_to_message(self.__handle_message) self.pattern = tk.PhotoImage(file=PATTERN_IMAGE) self.red = tk.PhotoImage(file=RED_TOKEN_IMAGE) self.blue = tk.PhotoImage(file=BLUE_TOKEN_IMAGE) self.player_one = tk.PhotoImage(file=PLAYER_ONE_IMAGE) self.player_two = tk.PhotoImage(file=PLAYER_TWO_IMAGE) self.blue_win = tk.PhotoImage(file=BLUE_WIN_IMAGE) self.red_win = tk.PhotoImage(file=RED_WIN_IMAGE) self.no_win = tk.PhotoImage(file=NO_WINNER_IMAGE) self.illegal = tk.PhotoImage(file=ILLEGAL_ACTION_IMAGE) self.wait = tk.PhotoImage(file=WAIT_IMAGE) self.win_symbol = tk.PhotoImage(file=WIN_SYMBOL_IMAGE) self.canvas = tk.Canvas(self.root, width=CANVAS_WIDTH, height=CANVAS_HEIGHT) self.canvas.pack() self.canvas.create_image(0, 0, anchor=tk.NW, image=self.pattern) self.canvas.bind('<Button-1>', self.callback) if ip is None and self.ai: column = self.ai.find_legal_move(self.game, self.update) self.__communicator.send_message(column) def draw_coin(self, row, column, player): """ This function draw the tokens in the board, and assigns one to each player """ if player == 0: coin = self.blue self.player_b = self.canvas.create_image(57, 140, image=self.player_two) self.player_a = self.canvas.create_image(744, 140, image=self.player_one) else: coin = self.red self.canvas.delete(self.player_a) self.canvas.delete(self.player_b) self.canvas.create_image(FIRST_ROW + (column * COLUMN_STEP), FIRST_COLUMN + (row * ROW_STEP), image=coin) def check_winner(self): winner = self.game.get_winner() if winner == self.game.PLAYER_ONE: blue_win = self.canvas.create_image(379, 300, image=self.blue_win) self.canvas.after(1500, self.delete_msg, blue_win) elif winner == self.game.PLAYER_TWO: red_win = self.canvas.create_image(379, 300, image=self.red_win) self.canvas.after(1500, self.delete_msg, red_win) elif winner == self.game.DRAW: self.canvas.create_image(400, 300, image=self.no_win) if winner is not None and winner != self.game.DRAW: self.canvas.after(1500, self.design_win) def design_win(self): direction, coord = self.game.found_winner() if direction == 'h': self.horizontal_win(coord) if direction == 'v': self.vertical_win(coord) if direction == 'd': self.diagonal_win(coord) def horizontal_win(self, coord): for i in range(coord[1], coord[1] - 4, -1): self.canvas.create_image(FIRST_ROW + i * COLUMN_STEP, FIRST_COLUMN + coord[0] * ROW_STEP, image=self.win_symbol) def vertical_win(self, coord): for i in range(coord[1], coord[1] - 4, -1): self.canvas.create_image(FIRST_ROW + coord[0] * COLUMN_STEP, FIRST_COLUMN + i * ROW_STEP, image=self.win_symbol) def diagonal_win(self, coord): for coord_tup in coord: self.canvas.create_image(FIRST_ROW + coord_tup[1] * COLUMN_STEP, FIRST_COLUMN + coord_tup[0] * ROW_STEP, image=self.win_symbol) def put_disk(self, column_coord): if column_coord > 134: column = (column_coord - 134) // COLUMN_STEP self.update(column) self.__communicator.send_message(column) else: raise Exception(self.game.ILLEGAL_MOVE) def delete_msg(self, illegal_image): self.canvas.delete(illegal_image) def callback(self, event): if self.game.get_winner() is None: if self.player == self.game.get_current_player(): try: self.put_disk(event.x) except Exception: illegal_image = self.canvas.create_image( 400, 300, image=self.illegal) self.canvas.after(MSG_TIME, self.delete_msg, illegal_image) else: wait_image = self.canvas.create_image(400, 300, image=self.wait) self.canvas.after(MSG_TIME, self.delete_msg, wait_image) def update(self, column): row, cur_player = self.game.make_move(column) self.draw_coin(row, column, cur_player) self.check_winner() def __handle_message(self, msg): self.update(int(msg)) if self.ai and self.game.get_winner() is None: column = self.ai.find_legal_move(self.game, self.update) self.__communicator.send_message(column)
class GUI: """ The GUI of the battle ship game and run the game. """ def __init__(self, root, game, port, ip=None): """ :param root: tkinter root. :param game: the game object. :param port: for connection. :param ip: for connection. """ self._root = root self.__game = game self.__state = CONNECTING self.__enemy_tiles = [] self.__self_tiles = [] self.__ship_dir = Board.H_DIR self.__communicator = Communicator(self._root, port, ip) self.__communicator.connect() self.__random_player = (player_type == LEGAL_PLAYER[1]) self.__random_possible_targets = [(x, y) for x in range(NUM_OF_COL) for y in range(NUM_OF_ROW)] self.__communicator.bind_action_to_message(self.__handle_message) self.__place_widgets() self.__check_connection() def __place_widgets(self): """ place the widgets on screen. """ # background self.__background_img = tk.PhotoImage(file=BACKGROUND_IMG) self.__tile_img = tk.PhotoImage(file=TILE_IMG) self._canvas = tk.Canvas(self._root, width=WINDOW_W, height=WINDOW_H) self._canvas.create_image((0, 0), anchor=tk.NW, image=self.__background_img) self._canvas.pack(side=tk.TOP) # massage display self.__display_msg = self._canvas.create_text(MSG_DISPLAY_CORD, font=("Jockey One", 20), fill="white", text=CONNECTING_MSG) # enemy board for x in range(NUM_OF_COL): for y in range(NUM_OF_ROW): self.__enemy_tiles.append(EnemyTile(x, y, self._root, self)) # player board for x in range(NUM_OF_COL): for y in range(NUM_OF_ROW): self.__self_tiles.append(SelfTile(x, y, self._root, self)) def __fix_display_msg(self): """ replace the shown massage to the correct one. """ if self.__state == CONNECTING: self._canvas.itemconfigure(self.__display_msg, text=CONNECTING_MSG) elif self.__state == SETTING_UP: self._canvas.itemconfigure(self.__display_msg, text=SETTING_UP_MSG) elif self.__state == WAITING_FOR_OPPONENT: self._canvas.itemconfigure(self.__display_msg, text=WAITING_FOR_OPPONENT_MSG) elif self.__state == PLAYER_TURN: self._canvas.itemconfigure(self.__display_msg, text=PLAYER_TURN_MSG) elif self.__state == OPPONENT_TURN: self._canvas.itemconfigure(self.__display_msg, text=OPPONENT_TURN_MSG) elif self.__state == GAME_OVER: if self.__game.get_winner() == player_num: self._canvas.itemconfigure(self.__display_msg, text=WIN_MSG) else: self._canvas.itemconfigure(self.__display_msg, text=LOSE_MSG) def __check_connection(self): """ check if connection was established and start the game. if not, check again after a period of time. """ if self.__communicator.is_connected(): self.__state = SETTING_UP self.__enemy_placed_ships = 0 self.__ship_index = 0 self._canvas.itemconfigure(self.__display_msg, text=STARTING_GAME_MSG) self._root.after(LONGER_WAIT_PERIOD, self.__fix_display_msg) self._root.bind("<ButtonPress-3>", self.__switch_v_h) self._root.bind("<space>", self.__switch_v_h) if self.__random_player: self._root.after(WAIT_PERIOD, self.__random_place_ship) else: self._root.after(WAIT_PERIOD, self.__check_connection) def __random_place_ship(self): """ placing a ship for a random player. """ self.__ship_dir = choice([Board.V_DIR, Board.H_DIR]) self.player_place_a_ship(randint(0, NUM_OF_ROW - 1), randint(0, NUM_OF_COL - 1)) def player_place_a_ship(self, row, col): """ placing a ship on the player board. :param row: the chosen row. :param col: the chosen column. """ if self.__game.set_a_ship(player_num, col, row, SHIPS_SIZES[self.__ship_index], self.__ship_dir): self.__communicator.send_message( "%d,%d,%d," % (col, row, SHIPS_SIZES[self.__ship_index]) + self.__ship_dir) self.__create_a_ship(SELF, col, row, SHIPS_SIZES[self.__ship_index], self.__ship_dir) self.__ship_index += 1 if self.__ship_index == len(SHIPS_SIZES): self.__state = WAITING_FOR_OPPONENT self.__fix_display_msg() self.__start_game() elif self.__random_player: self._root.after(LONGER_WAIT_PERIOD, self.__random_place_ship) else: self._canvas.itemconfigure(self.__display_msg, text=WRONG_SHIP_SETTING_MSG) self._root.after(LONGER_WAIT_PERIOD, self.__fix_display_msg) if self.__random_player: self._root.after(LONGER_WAIT_PERIOD, self.__random_place_ship) def __create_a_ship(self, player, col, row, length, direction): """ creating a new ship on board. :param player: SELF or OPPONENT :param col: integer. :param row: integer. :param length: integer. :param direction: Board.H_DIR or Board.V_DIR """ for i in range(length): current_tile = self.get_tile(row, col, player) if i == 0: if direction == Board.H_DIR: current_tile.add_ship(SHIP_FRONT_H) elif direction == Board.V_DIR: current_tile.add_ship(SHIP_FRONT_V) elif i == length - 1: if direction == Board.H_DIR: current_tile.add_ship(SHIP_BOTTOM_H) elif direction == Board.V_DIR: current_tile.add_ship(SHIP_BOTTOM_V) else: if direction == Board.H_DIR: current_tile.add_ship(SHIP_MIDDLE_H) elif direction == Board.V_DIR: current_tile.add_ship(SHIP_MIDDLE_V) if player == SELF: current_tile.show_ship() if direction == Board.H_DIR: col += 1 if direction == Board.V_DIR: row += 1 def __switch_v_h(self, event): """ switch between placing a ship horizontally or vertically. """ if self.__ship_dir == Board.H_DIR: self.__ship_dir = Board.V_DIR elif self.__ship_dir == Board.V_DIR: self.__ship_dir = Board.H_DIR def __start_game(self): """ start the game after the ships where placed. """ if self.__enemy_placed_ships == len(SHIPS_SIZES) and\ self.__ship_index == len(SHIPS_SIZES): self.__game.start_game() if player_num == Game.PLAYER1: self.__state = PLAYER_TURN if self.__random_player: self._root.after(LONGER_WAIT_PERIOD, self.__play_a_random_turn) elif player_num == Game.PLAYER2: self.__state = OPPONENT_TURN self.__fix_display_msg() def __play_a_random_turn(self): """ plays a random turn for a random player. """ location = choice(self.__random_possible_targets) self.__random_possible_targets.remove(location) self.play_a_turn(location[0], location[1]) def play_a_turn(self, col, row): """ plays a turn based on a chosen target. :param row: the chosen row. :param col: the chosen column. """ if self.__state == PLAYER_TURN: coords_got_hit = self.__game.play_a_turn(col, row) if len(coords_got_hit) == 0: self.get_tile(row, col, OPPONENT).got_a_hit() self._canvas.itemconfigure(self.__display_msg, text=MISS_MSG) self.__state = OPPONENT_TURN self._root.after(LONGER_WAIT_PERIOD, self.__fix_display_msg) else: self.get_tile(row, col, OPPONENT).got_a_hit() if len(coords_got_hit) == 1: self._canvas.itemconfigure(self.__display_msg, text=SUCCESS_MSG) else: for c in coords_got_hit: self.get_tile(c[1], c[0], OPPONENT).show_ship() self._canvas.itemconfigure(self.__display_msg, text=DROWNING_OPPONENT_SHIP_MSG) if self.__random_player: self._root.after(LONGER_WAIT_PERIOD, self.__play_a_random_turn) self.__communicator.send_message("%d,%d" % (col, row)) if self.__game.game_over(): self.__state = GAME_OVER self.__game_over() def state(self): """ :return: integer representing the current state of the game. """ return self.__state def get_tile(self, row, col, player): """ gets the tile object based on coordinates and player. :param row: the chosen row. :param col: the chosen column. :param player: self or opponent. :return: tile object. """ if 0 <= col < NUM_OF_COL and 0 <= row < NUM_OF_ROW: if player: return self.__enemy_tiles[row + col * NUM_OF_ROW] else: return self.__self_tiles[row + col * NUM_OF_ROW] def __handle_message(self, text=None): """ handle incoming massages from the other side of the network. :param text: the incoming massage. """ if text: if (self.__state == SETTING_UP or self.__state == WAITING_FOR_OPPONENT) and\ self.__enemy_placed_ships < len(SHIPS_SIZES): opponent_move = text.split(",") if player_num == game.PLAYER1: opponent = game.PLAYER2 else: opponent = game.PLAYER1 self.__game.set_a_ship(opponent, int(opponent_move[0]), int(opponent_move[1]), int(opponent_move[2]), opponent_move[3]) self.__create_a_ship(OPPONENT, int(opponent_move[0]), int(opponent_move[1]), int(opponent_move[2]), opponent_move[3]) self.__enemy_placed_ships += 1 if self.__enemy_placed_ships == len(SHIPS_SIZES): self.__start_game() elif self.__state == OPPONENT_TURN: opponent_move = text.split(",") coords_got_hit = self.__game.play_a_turn( int(opponent_move[0]), int(opponent_move[1])) self.get_tile(int(opponent_move[1]), int(opponent_move[0]), SELF).got_a_hit() if len(coords_got_hit) == 1: self._canvas.itemconfigure(self.__display_msg, text=HIT_MSG) elif len(coords_got_hit) > 1: self._canvas.itemconfigure(self.__display_msg, text=LOSE_A_SHIP_MSG) else: self.__state = PLAYER_TURN self._canvas.itemconfigure(self.__display_msg, text=PLAYER_TURN_MSG) if self.__random_player: self.__play_a_random_turn() if self.__game.game_over(): self.__state = GAME_OVER self.__game_over() def __game_over(self): """ handle end of game. """ self.__fix_display_msg() if self.__game.get_winner() != player_num: if player_num == game.PLAYER1: opponent = game.PLAYER2 else: opponent = game.PLAYER1 list_of_cords = self.__game.get_all_ships_cord(opponent) for i in list_of_cords: self.get_tile(i[1], i[0], True).show_ship()
class AI: def __init__(self, player, game, port, ip=None): self.game = game self.canvas = game.canvas self.player = player self.__communicator = Communicator(self.canvas, port, ip) self.__communicator.connect() self.__communicator.bind_action_to_message(self.read_message) self.difficulty = DIFFICULTY if self.player == PLAYER_1: self.color = 'b' if self.game.turn_counter % 2 == 0: self.game.make_move(3) self.__communicator.send_message(str(3)) self.game.player2_message = 0 self.game.mylist_player2 = [] self.game.waiting_player_2() else: self.color = 'r' self.counter = 0 self.b_column_counter = 0 self.b_counter = 0 self.canvas.bind("<Button-1>", self.illegal_move) self.b_illegal_counter = 0 self.canvas.mainloop() def read_message(self, text=None): if text: if len(text) == 1: column = int(text) self.game.make_move(column) if self.color == 'b': self.game.player2_message = 1 if self.color == 'r': self.game.player1_message = 1 self.find_legal_move(self.game, func=None) else: text_list = [] for i in range(len(text)): if text[i].is_numeric(): text_list.append(text[i]) if len(text_list) > 0: column = int(text_list[0]) self.game.make_move(column) if self.color == 'b': self.game.player2_message = 1 if self.color == 'r': self.game.player1_message = 1 self.find_legal_move(self.game, func=None) def illegal_move(self, event): if self.b_illegal_counter < 1: b = Button(text="Illegal move", height=4, width=20) self.b_illegal_counter += 1 def forget(): b.place_forget() self.b_illegal_counter = 0 b.place(relx=0.4, rely=0.435) b.config(command=forget) def find_legal_move(self, g, func, timeout=None): if g.win_status == 0: if timeout == None: try: column = self.get_ai_move(g.new_board, DIFFICULTY, func=None) g.make_move(column) self.__communicator.send_message(str(column)) if self.color == 'b': g.player2_message = 0 g.mylist_player2 = [] g.waiting_player_2() else: g.player1_message = 0 g.mylist_player1 = [] g.waiting_player_1() except IndexError: self.no_move() else: column = self.get_ai_move(g.new_board, DIFFICULTY, func) g.make_move(column) self.__communicator.send_message(str(column)) def get_ai_move(self, board, depth, func): """ Returns the best move (as a column number) and the associated alpha Calls search() """ # determine opponent's color if self.color == 'b': enemy_tile = 'r' else: enemy_tile = 'b' # enumerate all legal moves legal_moves = {} # will map legal move states to their alpha values for col in range(BOARDWIDTH): # if column i is a legal move... if self.move_check(board, col): # make the move in column 'col' for curr_player temp = self.make_a_move(board, self.color, col) legal_moves[col] = -self.search(depth - 1, temp, enemy_tile) max_move = max(legal_moves.values()) if func != None: func( list(legal_moves.keys())[list( legal_moves.values()).index(max_move)]) best_alpha = -99999999 best_move = [] moves = legal_moves.items() for move, alpha in moves: if alpha > best_alpha: best_alpha = alpha best_move = [] best_move.append(move) if alpha == best_alpha: best_move.append(move) if len(best_move) > 1: return random.choice(best_move) if len(best_move) == 1: return best_move[0] else: raise IndexError def search(self, depth, board, tile): """ Searches the tree at depth 'depth' Returns the alpha value """ # enumerate all legal moves from this state legal_moves = [] for column in range(BOARDWIDTH): # if column is a legal move... if self.move_check(board, column): # make the move in column for curr_player temp = self.make_a_move(board, tile, column) legal_moves.append(temp) # if this node (state) is a terminal node or depth == 0... if depth == 0 or len(legal_moves) == 0 or self.board_full_check(board): # return the heuristic value of node return self.value(board, tile) # determine opponent's color if tile == 'b': enemy_tile = 'r' else: enemy_tile = 'b' alpha = -99999999 for child in legal_moves: try: if child != None: alpha = max(alpha, -self.search(depth - 1, child, enemy_tile)) else: raise TypeError except TypeError: self.no_move() return alpha def value(self, board, tile): if tile == 'b': enemy_tile = 'r' else: enemy_tile = 'b' my_fours = self.check_for_streak(board, tile, 4) my_threes = self.check_for_streak(board, tile, 3) my_twos = self.check_for_streak(board, tile, 2) enemy_fours = self.check_for_streak(board, enemy_tile, 4) enemy_threes = self.check_for_streak(board, enemy_tile, 3) enemy_twos = self.check_for_streak(board, enemy_tile, 2) if enemy_fours > 0: return -100000 - DIFFICULTY else: return (my_fours*100000 + my_threes*100 + my_twos*10) - \ (enemy_threes*100 + enemy_twos*10)\ + DIFFICULTY def check_for_streak(self, board, color, streak): count = 0 # for each piece in the board... for i in range(BOARDWIDTH): for j in range(BOARDHEIGHT - 1, -1, -1): # ...that is of the color we're looking for... if board[i][j] == color: # check if a vertical streak starts at (i, j) count += self.vertical_streak(j, i, board, streak) # check if a horizontal four-in-a-row starts at (i, j) count += self.horizontal_streak(j, i, board, streak) # check if a diagonal (either way) four-in-a-row starts at (i, j) count += self.diagonal_check(j, i, board, streak) # return the sum of streaks of length 'streak' return count def vertical_streak(self, row, col, board, streak): consecutive_count = 0 for i in range(streak): if row - i < 0: break if board[col][row] == board[col][row - i]: consecutive_count += 1 if consecutive_count >= streak: return 1 else: return 0 def horizontal_streak(self, row, col, board, streak): consecutive_count = 0 for i in range(streak): if col + i >= BOARDWIDTH: break if board[col][row] == board[col + i][row]: consecutive_count += 1 if consecutive_count >= streak: return 1 else: return 0 def diagonal_check(self, row, col, board, streak): total = 0 # check for diagonals with positive slope consecutive_count = 0 for i in range(streak): # check the first 4 columns if col + i > BOARDWIDTH - 1 or row - i < 0: break if board[col][row] == board[col + i][row - i]: consecutive_count += 1 if consecutive_count >= streak: total += 1 # check for diagonals with negative slope consecutive_count = 0 for i in range(streak): if col + i > BOARDWIDTH - 1 or row + i > BOARDHEIGHT - 1: break if board[col][row] == board[col + i][row + i]: consecutive_count += 1 if consecutive_count >= streak: total += 1 return total def make_a_move(self, board, player, column): practice_board = copy.deepcopy(board) lowest = self.get_lowest_empty_space(practice_board, column) if lowest != -1: practice_board[column][lowest] = player return practice_board def get_lowest_empty_space(self, board, column): # Return the row number of the lowest empty row in the given column. for y in range(BOARDHEIGHT - 1, -1, -1): if board[column][y] == 'e': return y return -1 def board_full_check(self, board): # Returns True if there are no empty spaces anywhere on the board. for x in range(BOARDWIDTH): if board[x][0] == 'e': return False return True def move_check(self, board, column): # Returns True if there is an empty space in the given column. # Otherwise returns False. if column < 0 or column >= (BOARDWIDTH) or board[column][0] != 'e': return False return True def no_move(self): """ a graphic representations of an illegal move :return: """ if self.b_counter < 1: self.b = Button(text="No possible AI moves", height=4, width=20) self.b_counter += 1 def forget(): self.b.place_forget() self.b_column_counter = 0 self.b.place(relx=0.4, rely=0.435) self.b.config(command=forget)
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()
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()
class GUI: """We defined this class using the tkinter class, here we defined the design of the board: colors, visibility, size, mode of operation...""" PICTURE_WIDTH = 800 PICTURE_HEIGHT = 600 SHIFT_RIGHT = 134 SHIFT_LEFT = 134 SHIFT_BOTTOM = 78 SHIFT_UP = 75 COIN_WIDTH = 76 COIN_LENGTH = 90 COIN_SHIFT = 173 PLAYER_ONE_TURN_X = 744 PLAYER_TWO_TURN_X = 58 PLAYER_TURN_Y = 139 LINES_LEN = 5 COLUMN_NUM = 7 COIN_WIN_NUMBER = 4 INVALID_COLUMN = 10 TIME_SLEEP = 0.001 MOVE_INDEX = 10 BOARD_IMAGE = 'img/board.png' BLUE_COIN_IMAGE = 'img/BLUE.png' RED_COIN_IMAGE = 'img/RED.png' BLUE_WIN_COIN_IMAGE = 'img/BLUE_WIN.png' RED_WIN_COIN_IMAGE = 'img/RED_WIN.png' TIE_IMAGE = 'img/Tie.png' PLAYER_ONE_NO_TURN_IMAGE = 'img/player_one_no.png' PLAYER_TWO_TURN_IMAGE = 'img/player_two_yes.png' BOARD_FROM_BOTTOM = PICTURE_HEIGHT - SHIFT_BOTTOM BUTTON_LENGTH = int( (PICTURE_WIDTH - SHIFT_LEFT - SHIFT_RIGHT) / COLUMN_NUM) def __init__(self, parent, port, my_turn, ip=None, ai=None): """ Initiation function of the GUI Class :param parent: original board :param port: port for communication between computers :param my_turn: True if it's the turn of the relevant player :param ip: ip address for communication between computers """ self.__parent = parent self.__ai = ai self.__my_turn = my_turn self.__parent.resizable(width=False, height=False) self.__game = Game() # limits of the board self.__canvas = tki.Canvas(self.__parent, width=self.PICTURE_WIDTH, height=self.PICTURE_HEIGHT) self.__background = tki.PhotoImage(file=self.BOARD_IMAGE) self.__canvas.create_image(0, 0, anchor=tki.NW, image=self.__background) # Anchor NW will position the text so that the reference point # coincides with the northwest self.__canvas.grid() self.__canvas.bind('<Button-1>', self.__callback) self.__communicator = Communicator(parent, port, ip) self.__communicator.connect() # import message self.__communicator.bind_action_to_message(self.__handle_message) self.__blue_coin = tki.PhotoImage(file=self.BLUE_COIN_IMAGE) self.__red_coin = tki.PhotoImage(file=self.RED_COIN_IMAGE) self.__blue_coin_win = tki.PhotoImage(file=self.BLUE_WIN_COIN_IMAGE) self.__red_coin_win = tki.PhotoImage(file=self.RED_WIN_COIN_IMAGE) self.__tie_image = tki.PhotoImage(file=self.TIE_IMAGE) # We change a part of the background to indicate the player turn self.__player_one_no = tki.PhotoImage( file=self.PLAYER_ONE_NO_TURN_IMAGE) self.__one_no = None self.__player_two_yes = tki.PhotoImage(file=self.PLAYER_TWO_TURN_IMAGE) self.__two_yes = None if ip is None and self.__ai: column = self.__ai.find_legal_move(self.__game, self.__add_coin) self.__communicator.send_message(column) def __handle_message(self, column=None): """This function allows to add a coin in the screen of the non turn player according to the choice of the turn player and it allows the next player to play after that""" column = int(column) if column < self.__game.WIDTH: self.__add_coin(column) self.__my_turn = not self.__my_turn if self.__ai: column = self.__ai.find_legal_move(self.__game, self.__add_coin) self.__communicator.send_message(column) self.__my_turn = not self.__my_turn def __callback(self, event): """This function allows the player to add a coin according to a *valid* column that he has chosen, the "event" value is given from the user click according to the function defined in the __init__ it also sends to the second player his column choice that will be process in the previous handle_message function, and the turn of the actual player will be end""" position_x = event.x - self.SHIFT_LEFT column = position_x // self.BUTTON_LENGTH if position_x < 0: column = self.INVALID_COLUMN if self.__my_turn and column < self.__game.WIDTH: self.__add_coin(column) self.__communicator.send_message(str(column)) self.__my_turn = not self.__my_turn def __add_coin(self, column): """This function call the make_move function that update the game status and change the screen displaying according to coin adding""" image_coin = None if column < self.__game.WIDTH: # check that is a valid column try: tuple_move = self.__game.make_move(column) # make_move function returns the coordinates of the new coin # and the player turn if tuple_move[1] == self.__game.PLAYER_ONE: image_coin = self.__blue_coin # display who have to play self.__one_no = self.__canvas.\ create_image(self.PLAYER_ONE_TURN_X, self.PLAYER_TURN_Y, image=self.__player_one_no) self.__two_yes = self.__canvas.\ create_image(self.PLAYER_TWO_TURN_X, self.PLAYER_TURN_Y, image=self.__player_two_yes) elif tuple_move[1] == self.__game.PLAYER_TWO: image_coin = self.__red_coin self.__canvas.delete(self.__one_no) self.__canvas.delete(self.__two_yes) coin = self.__canvas.\ create_image(self.COIN_SHIFT + column * self.COIN_WIDTH, self.SHIFT_UP, image=image_coin) # make move (animation) for i in range( int(tuple_move[0][1] * self.COIN_LENGTH / self.MOVE_INDEX)): self.__canvas.move(coin, 0, self.MOVE_INDEX) time.sleep(self.TIME_SLEEP) self.__canvas.update() self.__print_winner() # Display winner if there is one except: pass def __print_winner(self): """When the 4 coins from the same player are aligned, this function allows to show on the screen which player won and what""" winner = self.__game.get_winner_status() if winner: # if there is the end of the game if winner[0] == self.__game.DRAW: self.__canvas.create_image(0, 0, anchor=tki.NW, image=self.__tie_image) return # if someone won win_coo = winner[1] if winner[0] == str(self.__game.PLAYER_ONE): coin_win = self.__blue_coin_win else: coin_win = self.__red_coin_win if winner[2] == self.__game.HORIZONTAL_WINNING: for i in range(self.COIN_WIN_NUMBER): self.__canvas.\ create_image(self.COIN_SHIFT+(win_coo[0]+i) * self.COIN_WIDTH, self.BOARD_FROM_BOTTOM - (self.LINES_LEN - win_coo[1]) * self.COIN_LENGTH, image=coin_win) elif winner[2] == self.__game.VERTICAL_WINNING: for i in range(self.COIN_WIN_NUMBER): self.__canvas.\ create_image(self.COIN_SHIFT+win_coo[0] * self.COIN_WIDTH, self.BOARD_FROM_BOTTOM - (self.LINES_LEN-win_coo[1] - i) * self.COIN_LENGTH, image=coin_win) elif winner[2] == self.__game.DIAGONAL_RIGHT: for i in range(self.COIN_WIN_NUMBER): self.__canvas.\ create_image(self.COIN_SHIFT+(win_coo[0]+i) * self.COIN_WIDTH, self.BOARD_FROM_BOTTOM - (self.LINES_LEN-win_coo[1]+i) * self.COIN_LENGTH, image=coin_win) elif winner[2] == self.__game.DIAGONAL_LEFT: for i in range(self.COIN_WIN_NUMBER): self.__canvas.\ create_image(self.COIN_SHIFT+(win_coo[0]+i) * self.COIN_WIDTH, self.BOARD_FROM_BOTTOM - (self.LINES_LEN-win_coo[1]-i) * self.COIN_LENGTH, image=coin_win)
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()
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
class GUI: """ Designed to handle the GUI aspects (creating a window, buttons and pop-ups. Also initializes the communicator object. """ MESSAGE_DISPLAY_TIMEOUT = 250 def __init__(self, root, parent, port, ip=None): self.game_obj = Game() self._ai_mode = False print('parent:', parent) print('port:', port) """ 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._root = root self._parent = parent self.__communicator = Communicator(root, port, ip) self.__communicator.connect() self.__communicator.bind_action_to_message(self.__handle_message) self.__place_widgets() #self.frame = t.Frame(self._parent, width=800, height=800) self._canvas = t.Canvas(root, width=700, height=600, bg='blue') self._grid = t.Grid() #self.frame.pack() self._canvas.pack() self._create_circle() if parent == 'ai': self.ai_obj = Ai() self._ai_mode = True if (server == True and self.game_obj.get_current_player() == 0) or (server == False and self.game_obj.get_current_player() == 1): self.ai_move() else: self._canvas.bind("<Button-1>", self.callback) def __place_widgets(self): pass def popup_showinfo(self, obj): showinfo("Window", "The winner is: " + str(obj)) def _create_circle(self): board = self.game_obj.get_board() for j in range(6): for i in range(7): x_place = (100 * i) + 50 y_place = (100 * j) + 50 r = 47.5 color = "white" if board[i][j] == "0": color = "red" if board[i][j] == "1": color = "yellow" self._canvas.create_oval(x_place - r, y_place - r, x_place + r, y_place + r, fill=color) def callback(self, event): print('column=', math.floor(event.x / 100)) column = math.floor(event.x / 100) if (server == True and self.game_obj.get_current_player() == 0) \ or (server == False and self.game_obj.get_current_player() == 1): self.game_obj.make_move(column) self.color_circle(self.game_obj.get_current_player(), column) message = str(column) is_winner = self.game_obj.get_winner() if is_winner == 0 or is_winner == 1 or is_winner == 2: message += str(is_winner) self.popup_showinfo(is_winner) self.__communicator.send_message(message) # return column def color_circle(self, player, column): x_place = (100 * column) + 50 y_place = (100 * self.game_obj.get_empty_row(column)) + 50 r = 47.5 if player == 0: self._canvas.create_oval(x_place - r, y_place - r, x_place + r, y_place + r, fill='red') if player == 1: self._canvas.create_oval(x_place - r, y_place - r, x_place + r, y_place + r, fill='yellow') pass # tk.Canvas.create_circle = _create_circle 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 text: if len(text) > 1: self.popup_showinfo(text[1]) else: column = int(text[0]) print("The column i got is:", column) self.game_obj.make_move(column) self.color_circle(self.game_obj.get_current_player(), column) if self._ai_mode: self.ai_move() self._root.after(self.MESSAGE_DISPLAY_TIMEOUT, self.__handle_message) # else: #self.__label["text"] = "" pass def ai_move(self): self.ai_obj.find_legal_move(self.game_obj, self.ai_move) column = self.game_obj.get_last_move() self.color_circle(self.game_obj.get_current_player(), column) message = str(column) is_winner = self.game_obj.get_winner() print("who is the winner: ", self.game_obj.get_winner()) if is_winner == 0 or is_winner == 1 or is_winner == 2: message += str(is_winner) self.popup_showinfo(is_winner) self.__communicator.send_message(message)
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()
upper_range = np.array([30, 255, 255], dtype=np.uint8) while (cap.isOpened()): ret, img = cap.read() # cv2.rectangle(img,(300,300),(100,100),(0,255,0),0) img = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) mask = cv2.inRange(img, lower_range, upper_range) img = cv2.bitwise_and(img, img, mask=mask) sq, click = find_squares(img) if sq: M = cv2.moments(sq[0]) cx = int(M['m10'] / M['m00']) cy = int(M['m01'] / M['m00']) if click: com.send_message([cx, cy, 1]) else: com.send_message([cx, cy, 1]) cv2.drawContours(img, sq, -1, (0, 255, 0), 3) print 'Clicked ? %r' % click # print sq cv2.imshow('Img', img) k = cv2.waitKey(10) if k == 27: break