class Game: """ Game class, handles gameplay, gui and communicator """ PLAYER_ONE = 0 PLAYER_TWO = 1 DRAW = 2 EMPTY = 3 MIN_PORT_VALUE = 1000 MAX_PORT_VALUE = 65535 WINNING_SEQ_LENGTH = 4 ILLEGAL_MOVE = "Illegal move" INVALID_MESSAGE = "Invalid message received" INVALID_GAME_STATE = "Invalid game state" CHECK_WINNER_FLAG_INDEX = 29 ADD_CHIP_FAILED = "Failed to add chip to specified column" COMMUNICATOR_MESSAGE_1 = "CHIP_DATA: " COMMUNICATOR_MESSAGE_2 = "CHECK_WINNER: " EXPECTED_AMOUNT_OF_ARGUMENTS_FOR_CLIENT = 4 EXPECTED_LENGTH_OF_MESSAGE = 30 def __init__(self): """ Initializes the game class """ # If server mode is on (no IP address provided) if len(argv) == 3: self.__player = self.PLAYER_ONE self.__enemy_player = self.PLAYER_TWO self.__current_player = self.__player else: self.__player = self.PLAYER_TWO self.__enemy_player = self.PLAYER_ONE self.__current_player = self.__enemy_player # Create board self.__board = Board(self.get_current_player) self.__game_over = False # initializes AI if AI was chosen at run self.__ai_flag = False if argv[1] == "ai": self.__ai = AI() self.__ai_flag = True # Create gui self.__gui = GUI(self.__player, self.__make_move, self.get_current_player, self.__ai_flag) # If this is the client, disable buttons until player turn if self.__player == self.PLAYER_TWO: self.__gui.disable_column_buttons() # TODO:: Ugly code, find a workaround self.__last_inserted_chip = None # Parse data for communicator port = int(argv[2]) ip = None if len(argv) == self.EXPECTED_AMOUNT_OF_ARGUMENTS_FOR_CLIENT: ip = argv[3] # Initializes communicator self.__communicator = Communicator(self.__gui.get_root(), port, ip) self.__communicator.connect() self.__communicator.bind_action_to_message(self.parse_rival_message) # If AI on server start the game, make a move if self.__ai_flag and self.__player == self.PLAYER_ONE: self.__ai.find_legal_move(self, self.__make_move) # Start the gui and game self.__gui.get_root().mainloop() def __make_move(self, column): """ :param column: Column in whivh to place chip """ # if game over flag on, returns if self.__game_over: return # attempts to place chip in column success, row = self.__board.check_legal_move_get_row( column, self.PLAYER_ONE if not self.__current_player else self.PLAYER_TWO) if not success: raise Exception(self.ILLEGAL_MOVE) # Store move for other functions self.__last_inserted_chip = column, row # Relay move to enemy self.__communicator.send_message(self.COMMUNICATOR_MESSAGE_1 + str(column) + "," + str(row) + " " + self.COMMUNICATOR_MESSAGE_2 + "1" if not self.__game_over else "0") self.__check_winner(column, row) def __check_winner(self, column, row): """ :param column: Column of newest chip :param row: Row of newest chip """ # Get data if a winning state was reached winner, winning_chips = self.__board.find_connected_and_winner( column, row) # Get pixel location for newest chip x, y = self.__board.get_chip_location(column, row) if winner is None: # If game is still ongoing # Create the chip on board self.__gui.create_chip_on_board(x, y, self.__current_player, board=self.__board) # Toggle __player in class members self.__toggle_player() # Disable full columns self.__gui.disable_illegal_columns(self.__board) else: # Game ended self.__game_over = True if winner == self.DRAW: self.__gui.create_chip_on_board(x, y, self.__current_player, board=self.__board) self.__gui.disable_column_buttons() self.__gui.show_game_over_label(self.DRAW) else: self.__gui.create_chip_on_board(x, y, self.__current_player, winning_chips=winning_chips, board=self.__board, winner=winner) def __toggle_player(self): """ Toggles members in the class, also make gui show switching of turns """ self.__current_player = self.PLAYER_TWO \ if self.__current_player == self.PLAYER_ONE \ else self.PLAYER_ONE flag = self.__current_player == self.__player self.__gui.end_turn_switch_player(flag) def get_winner(self): """ Gets the winner if there is one. This function is not used by the game """ return self.__board.find_connected_and_winner( self.__last_inserted_chip[0], self.__last_inserted_chip[1])[0] def get_player_at(self, row, col): """ :param row: Row to check :param col: Column to check :return: Player at place """ player = int(self.__board.get_columns()[col][row]) return None if player == self.EMPTY else player def get_current_player(self): """ Getter for current __player """ return self.__current_player def get_board(self): """ Getter for board """ return self.__board def parse_rival_message(self, message): """ :param message: Message received from enemy """ # Check message of corect length if len(message) != self.EXPECTED_LENGTH_OF_MESSAGE: raise Exception(self.INVALID_MESSAGE) # Parse data from message column = int(message[11]) expected_row = int(message[13]) # Update board and check if same row was returned success, row = self.__board.check_legal_move_get_row( column, self.PLAYER_ONE if not self.__current_player else self.PLAYER_TWO) # Assert it assert row == expected_row if success: # Update member self.__last_inserted_chip = column, row check_winner_flag = message[self.CHECK_WINNER_FLAG_INDEX] if check_winner_flag: self.__check_winner(column, row) else: raise Exception(self.ADD_CHIP_FAILED) self.__gui.disable_illegal_columns(self.__board) # If the AI is playing, make another move if self.__ai_flag and not self.__game_over: self.__ai.find_legal_move(self, self.__make_move) def get_last_inserted_chip(self): """ Getter for last inserted chip """ return self.__last_inserted_chip