def start(self): """ Begins the primary loop for the server.""" outboundData = {"gameServer host": None, "gameServer port": None} counter = 0 while True: # Each time a person joins, iterates over the current list of active games. # If any of the games has finished, removes that game and re-opens the port. for thread in self.activeGames: if thread.isAlive() == False: self.activeGames.remove(thread) print("CONNECTOR: Active port number is", self.activePort, "Iteration", counter) counter += 1 print('CONNECTOR: Trying to recieve data...') inboundData = self.socket.recvfrom( 1024) # Gets bundle of data from clients data = inboundData[0] # Separates data from address address = inboundData[1] # Separates address from data data = pickle.loads(data) # Unpickles data back into a python dict print("CONNECTOR: Message:", data, "From:", address) self.waitingList.append(address) # Creates a new Game server here if len(self.waitingList) >= 2: print("CONNECTOR: Someone wants to play, creating a game...") g = GameServer(self.activePort) # Creates game print(g.start()) # Starts game self.activeGames.append(g) # Adds the game a list # Tells the first person to start the game where it's being hosted outboundData["gameServer host"] = self.HOST outboundData["gameServer port"] = self.activePort # Keeps track of how many people have successfully connected self.connected += 1 print( "CONNECTOR: Keeping track of the number of people who have connected:", self.connected) # Connects players to the new game for x in range(2): address = self.waitingList[x] # Packages up data and sends it back to the player out = pickle.dumps(outboundData) self.socket.sendto(out, address) print("CONNECTOR: Sent data out to", address) self.waitingList = self.waitingList[ 2:] # Removes players from waiting list once they're in a game
def start_game_by_json(self, req): field = self.get_int_field((req['arena'])['field']) temp_pos = self.get_position_for(field, 1) temp = req['robot1'] bot_stat1 = BotMapper(temp['charge'], temp['weight'], temp['speed'], temp['tiredness'], temp['damage'], temp['range'], temp_pos[0], temp_pos[1], field) bot1_string = (temp['botAlgorithm'])['algorithm'] temp_pos = self.get_position_for(field, 2) temp = req['robot2'] bot_stat2 = BotMapper(temp['charge'], temp['weight'], temp['speed'], temp['tiredness'], temp['damage'], temp['range'], temp_pos[0], temp_pos[1], field) bot2_string = (temp['botAlgorithm'])['algorithm'] game = GameServer(bot1_string, bot2_string, bot_stat1, bot_stat2, field) res = game.start() return jsonify({"winner": res[0], "fight_map": res[1]})
class AdminGui(Gui): """ Signals are used to communicate with the GameServer, which is in a different thread. Any instance where a GameServer method is called directly is a _bug_. Please report it. """ answerShown = pyqtSignal() answerChecked = pyqtSignal(str, bool) mutePlayers = pyqtSignal(list) unmutePlayers = pyqtSignal(list) def __init__(self, parent=None): super(AdminGui, self).__init__(parent) self.game = GameServer(self, 'jeopardy') self.loadRules() """ Setup the player table, and wait for players to login before the game may be started. """ self.playerAdmin = PlayerAdminDialog(self) self.playerAdmin.startGame.connect(self.startGame) self.playerAdmin.show() # does not block thread, __init__ continues # starts the GameServer thread self.game.start() # prepare to tell the time elapsed since the game started self.time = QTime(0, 0, 0) self.timer = QTimer(self) self.timer.timeout.connect(self.displayTime) """ loadRules is where the user selects a game file (*.jeop) and where it is validated by the RuleLoader module. After (if) this function finishes, the GameServer object, self.game will contain the rules read from the file. The RuleLoader may be used on its own to test the validity of .jeop files. """ def loadRules(self): fileName = '' while fileName == '': fileName = QFileDialog.getOpenFileName(self, 'Select a Jeopardy game file', 'example.jeop', 'Jeopardy game files (*.jeop)') if fileName == '': sys.exit() self.log('Validating ' + fileName) # validateFile is the actual function from RuleLoader that is called if validateFile(self.game, fileName) == False: ans = QMessageBox.warning(self, '', 'The selected game file is invalid.\nPlease choose a different one.', QMessageBox.Ok | QMessageBox.Cancel) if ans == QMessageBox.Cancel: sys.exit() fileName = '' else: self.log('Loaded ' + fileName) """ The actual game ui is set up and the AdminGui instructs the GameServer to startGames for all players """ def startGame(self): self.playerAdmin.close() self.game.loginEnabled = False self.game.nextRound() self.setupGui('Show Answer', self.game.width, self.game.height) self.getDisplayButton().clicked.connect(self.game.showAnswer) self.timer.start(1000) self.show() self.gameStarted.emit() """ Virtual methods required in Gui implemented here. In this case, all the data is in the GameServer. No mutexes are used because GameServer cannot ever modify these fields at the same time AdminGui reads them. """ def getRound(self): return self.game.round def getQuestion(self): return self.game.question def getTempPath(self): return self.game.tempPath def getTemplate(self): return self.game.template def getScores(self): return self.game.scores """ Custom table used in the AdminGui. Players can be muted and their Status is displayed. Use it to determine who can select a question, who can answer and who is disconnected. """ def setupTable(self): table = PlayerTable(['Nickname', 'IP', 'Status', 'Score'], 'mute') for player in self.game.players.items(): table.addPlayer((player[0], player[1][1], player[1][2], player[1][3])) return table """ Communication between AdminGui and GameServer is setup here. The actual logic behind most operations pertaining players is in the GameServer. - player Mute/Unmute, from table buttons - answerChecked, for score modification - showing the answer to everyone from QuestionDisplay button; note: this signal is not always connected as that button is also used for progressing to the next question in addition to showing the answer - question selection from ButtonGrid """ def setupSignals(self): self.gameStarted.connect(self.game.startGame) self.getTable().playersMuted.connect(self.game.mutePlayers) self.getTable().playersUnmuted.connect(self.game.unmutePlayers) self.answerChecked.connect(self.game.checkAnswer) self.getDisplay().buttonClicked.connect(self.game.showAnswer) self.getGrid().buttonClicked.connect(self.game.selectQuestion) """ The admin user decides whether a question is correct or not regardless of the actual answer's formulation. """ def playerBuzzed(self, name): ans = QMessageBox.information(self, '', 'Player ' + name + ' is answering.\nIs the answer correct?', QMessageBox.Yes | QMessageBox.No) if ans == QMessageBox.Yes: add = True else: add = False self.answerChecked.emit(name, add) # refreshes the timer every second def displayTime(self): self.time = self.time.addSecs(1) self.getLabel().setText(self.time.toString()) """ Same functions as in Gui class, but also handling the double-use button """ def displayQuestion(self, i): Gui.displayQuestion(self, i) Gui.displayAnswer(self) self.displayShowAnswerButton() def displayAnswer(self): Gui.displayAnswer(self) self.displayNextQuestionButton() def displayNextQuestionButton(self): self.getDisplayButton().clicked.disconnect() self.getDisplayButton().setText('Next Question') self.getDisplayButton().clicked.connect(self.game.nextQuestion) def displayShowAnswerButton(self): self.getDisplayButton().clicked.disconnect() self.getDisplayButton().setText('Show Answer') self.getDisplayButton().clicked.connect(self.game.showAnswer) """ Since the ButtonGrid is used for question selection, when a new one is created, because of a change of round, the buttonClicked signal must be reconnected. """ def updateGrid(self): self.getGrid().buttonClicked.disconnect() Gui.updateGrid(self) self.getGrid().buttonClicked.connect(self.game.selectQuestion) """ The AdminGui can also save the scores in addition to displaying them. """ def displayEndGame(self): self.getTable().hideButtons() Gui.displayEndGame(self) def displayPlot(self, path): Gui.displayPlot(self, path) w = QPushButton('Save scores as PNG') w.clicked.connect(self.saveScoresPng) self.layout().addWidget(w, 2, 0) w = QPushButton('Save scores as text file') w.clicked.connect(self.saveScoresText) self.layout().addWidget(w, 2, 1) def saveScoresText(self): fileName = QFileDialog.getSaveFileName(self, 'Save Scores', 'scores.txt', 'Text files (*.txt)') fp = open(fileName, 'w') for player in self.game.players.items(): fp.write(player[0] + '\t' + str(player[1][3]) + '\n') fp.close() def saveScoresPng(self): fileName = QFileDialog.getSaveFileName(self, 'Save Scores', 'scores.png', 'Images (*.png)') self.plotThread = PlotRenderer(self.getScores(), str(fileName), True, 300, self) self.plotThread.start()
class ServerSocket: def __init__(self): self._header_length = ConstantVariables.NETWORK_HEADER_LENGTH self._server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self._server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self._server_socket.bind((ConstantVariables.NETWORK_IP, ConstantVariables.NETWORK_PORT)) self._server_socket.listen() self._sockets_list = [self._server_socket] self._clients = {} self._game = GameServer() self._client_count = 0 print(f'Listening for connections on {ConstantVariables.NETWORK_IP}:{ConstantVariables.NETWORK_PORT}...') def start(self): self._game.start() while True: read_sockets, _, exception_sockets = select.select(self._sockets_list, [], self._sockets_list) for notified_socket in read_sockets: # new connection if notified_socket == self._server_socket: client_socket, client_address = self._server_socket.accept() client_message = self.receive_message(client_socket) if client_message is False: continue if client_message['data'].decode('utf-8') == "ask_id": self.create_client_and_send_id(client_socket) print('Accepted new connection from {}:{}, request: {}'.format(*client_address, client_message['data'].decode( 'utf-8'))) # known connection else: message = self.receive_message(notified_socket) if message is False: print('Closed connection from: {}'.format(self._clients[notified_socket])) # remove client for snake in self._game.snakes: if snake.id_client == self._clients[notified_socket]: self._game.snakes.remove(snake) self._sockets_list.remove(notified_socket) del self._clients[notified_socket] continue # client_message = self._clients[notified_socket] print(f"Received message from {client_message}") # update the snake of the client for snake in self._game.snakes: if snake.id_client == client_message: # change direction of the snake snake.direction_current = message['data'].decode('utf-8') # move the snake snake.move_head() if self._game.is_apple_caught(snake): self._game.create_new_apple() snake.growth() else: snake.move_body() # if the client has lost the game if self.is_client_lost(snake): # send to client message_game = "".encode('utf-8') message_game = bytes(f"{len(message_game):<{self._header_length}}", 'utf-8') + message_game notified_socket.send(message_game) # remove client print('Closed connection from: {}'.format(self._clients[notified_socket])) self._game.snakes.remove(snake) self._sockets_list.remove(notified_socket) del self._clients[notified_socket] break # send game to the client message_game = pickle.dumps(self._game) message_game = bytes(f"{len(message_game):<{self._header_length}}", 'utf-8') + message_game notified_socket.send(message_game) # remove clients of the notifications for notified_socket in exception_sockets: self._sockets_list.remove(notified_socket) del self._clients[notified_socket] def receive_message(self, client_socket): try: message_header = client_socket.recv(self._header_length) if not len(message_header): # if no data receive, client closed connection return False message_length = int(message_header.decode('utf-8').strip()) message = client_socket.recv(message_length) return {'header': message_header, 'data': message} except: return False def create_client_and_send_id(self, client_socket): self._client_count += 1 client_id = str(self._client_count) # the id must be a str for sending self._clients[client_socket] = client_id # creation of the snake of the client self._game.create_snake(client_id) # add the client id ine the list of clients self._sockets_list.append(client_socket) # send the id to the client message = client_id.encode('utf-8') message_header = f"{len(message):<{self._header_length}}".encode('utf-8') client_socket.send(message_header + message) def is_client_lost(self, snake_client): for snake in self._game.snakes: if snake != snake_client: if snake.get_head_coordinate() == snake_client.get_head_coordinate(): return True elif snake_client.get_head_coordinate() in snake.body_coordinates: return True else: if snake.get_head_coordinate() in snake.body_coordinates: return True return False