class GameServer(object): def __init__(self, ip, port): self.ip = ip self.port = port self.buffsize = 2048**2 self.main_socket = s.socket(s.AF_INET, s.SOCK_STREAM) # Create socketTCP ### self.main_socket.setsockopt(s.SOL_SOCKET, s.SO_REUSEADDR, self.port) ### self.main_socket.bind((self.ip, self.port)) self.main_socket.listen(5) self.server_is_open = True # is the game server on? self.games = [] # list of on-going games self.names = {} # dictionary of client and his game name self.timers = {} # dictionary of each game and its time state (True/False) {Game:T/F} self.question_start_time = {} # dictionary of {Game:current_question_start_time} self.wall_displays = {} # dictionary of all wall display sockets and their games {socket:Game} self.next_question_request = [] # list of wall displays that requested a next question (for a specific round) self.validated = {} # dictionary of clients and their validated games self.validated_login = {} # dictionary of validated username and password clients, values: T/number of failed logins self.messages = {} # {socket:[messages],...} self.inputs = [self.main_socket] self.outputs = [] self.connected = {} # dictionary of connected clients (including clients not in games) {clientobj:(Game, player_name),...} self.m = Module() # module object for database interaction @staticmethod def get_current_time(): return time.clock() @staticmethod def run_command(cmd): """ Spawn new processes, connect to their input/output/error pipes, and obtain their return codes arg: cmd arg type: string ret type: string """ return subprocess.Popen(cmd, shell=True, # not recommended, but does not open a window stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE).communicate() @staticmethod def get_host_ip(): """ :return: the computer's ip address """ ip = GameServer.run_command('ipconfig /all')[0].split("\n") for i in ip: if 'IPv4 Address' in i and '(Preferred)' in i: ip = i.split()[-1] ip = ip[0:ip.find('(')] break else: ip = 'localhost' # no specific address was found use localhost return ip @staticmethod def get_open_ports(): ''' :return: first open port number ''' sock = s.socket(s.AF_INET, s.SOCK_STREAM) sock.bind(("", 0)) sock.listen(1) port = sock.getsockname()[1] sock.close() return port @staticmethod def update_host_and_port(host, port): try: print 'Updating Server information on Kahoot_db Server...\r\nKahoot_play Server at:\r\nHost: %s; Port: %d' \ % (host, port) result = kht.update(name='YuvalStein', ip=host, port=port) if result['status'] == 'OK': print 'Information updated successfully.' else: print(result['status']) raise result['status'] except Exception as e: print e print 'Connection to kahoot_db Server failed, Please manually update the client.' def handle_game(self): while self.inputs and self.server_is_open: readables, writeables, exceptions = select(self.inputs, self.outputs, []) for sockobj in readables: if sockobj is self.main_socket: clientobj, addr = self.main_socket.accept() print 'connected from', addr self.inputs.append(clientobj) self.outputs.append(clientobj) self.messages[clientobj] = [] # this is where the clients messages will be saved self.connected[clientobj] = None # client not in game currently else: # Client socket data = None try: data = sockobj.recv(self.buffsize) # receive the data the client sent print 'Got', (data), 'on', id(sockobj) except Exception as e: print e if not data: # close connection self.inputs.remove(sockobj) self.outputs.remove(sockobj) del self.connected[sockobj] # client exits in the middle of the game if sockobj in self.validated: game = self.validated[sockobj] player_name = self.names[sockobj] del game.players[player_name] if len(game.players) < 2: for socketobj, player in self.names.items(): if player != player_name: self.add_data(socketobj, ['error', 'Not enough players in game.']) for socketobj, _game in self.wall_displays.items(): if _game == game and sockobj != socketobj: self.add_data(socketobj, ['error', 'Not enough players in game.']) del self.validated[sockobj] del self.names[sockobj] if game in self.games: del self.games[self.games.index(game)] if sockobj in self.validated_login and self.validated_login[sockobj] is True: del self.validated_login[sockobj] # wall clients if sockobj in self.wall_displays: game = self.wall_displays[sockobj] del self.wall_displays[sockobj] # disconnect players for socketobj in self.names: # inform all connected players that game is over if socketobj in self.validated and self.validated[socketobj] == game: self.add_data(socketobj, ['error', 'Wall Display died.']) if game in self.games: del self.games[self.games.index(game)] if sockobj in self.next_question_request: del self.next_question_request[self.next_question_request.index(sockobj)] # update everyone with the information self.send_all_data() else: # handle data num = 0 prev_index = 0 tmp_data = "" orig_data = data for i in range(len(orig_data)): if orig_data[i] == '[': num += 1 elif orig_data[i] == ']': num -= 1 if num == 0: if prev_index == 0: tmp_data = orig_data[:i + 1] else: tmp_data = orig_data[prev_index:i + 1] data = eval(tmp_data) self.handle_data(data, sockobj) prev_index = i + 1 self.send_all_data() # self.server_is_open = False: self.close_clients() self.close() def handle_data(self, data, sockobj): if data[0] == 'close_server': self.server_is_open = False elif data[0] == 'wall_display': if data[1] == 'start_update': # get important data in the beginning of the game self.get_connected_players(sockobj) elif data[1] == 'game_update': if data[2] == 'first_question': game = self.wall_displays[sockobj] game.started = True # update boolean 'started' self.timers[game] = True self.question_start_time[game] = self.get_current_time() # information for wall display info = ['wall_display', 'game_update', 'first_question'] + self.get_current_question_info(sockobj) self.add_data(sockobj, info) # information for all the connected clients to this game (possible answers list) current_answers = game.get_current_answers() for clientobj in self.connected: # if client is in the same game of the wall display if self.connected[clientobj] and self.connected[clientobj][0] == self.wall_displays[sockobj]: # send the client the current answers self.add_data(clientobj, ['game', 'answers'] + current_answers) elif data[2] == 'game_update': self.get_number_of_answers(sockobj) # returns the current question and the number of answers elif data[2] == 'round_results': self.timers[self.wall_displays[sockobj]] = False self.get_round_info(sockobj) self.next_question_request = [] elif data[1] == 'next_question': if sockobj not in self.next_question_request: game = self.wall_displays[sockobj] self.timers[game] = True self.question_start_time[game] = self.get_current_time() game.player_answers = {} # new round of answers game.current_question += 1 # update question number info = ['wall_display', 'next_question'] + self.get_current_question_info(sockobj) self.next_question_request.append(sockobj) self.add_data(sockobj, info) # information for all the connected clients to this game (possible answers list) current_answers = game.get_current_answers() for clientobj in self.connected: # if client is in the same game of the wall display if self.connected[clientobj] and self.connected[clientobj][0] == self.wall_displays[sockobj]: # send the client the current answers info = ['game', 'answers'] + current_answers self.add_data(clientobj, info) elif data[1] == 'end_questionnaire': # game ended self.timers[self.wall_displays[sockobj]] = False self.end_game(sockobj) else: # unknown data pass elif data[0] == 'game': if data[1] == 'answer': player_name = data[3] # check if player already gave an answer or question time is over if self.timers[self.validated[sockobj]]: # don't take answer if question time is up if player_name not in self.validated[sockobj].player_answers or \ not self.validated[sockobj].player_answers[player_name]: self.update_player_answer(sockobj, data[2], player_name) elif data[0] == 'join': if data[1] == 'validate': self.validate_game(sockobj, data[2], data[3]) else: # player wants to join a game if sockobj in self.validated and self.validated[sockobj]: # final validation self.join_game(sockobj, self.validated[sockobj], data[1]) # if first player connected set automatic timer (if requested for game) if len(self.validated[sockobj].players) == 1 and \ not self.validated[sockobj].wait_for_players: self.timers[self.validated[sockobj]] = ('Start', self.get_current_time()) elif data[0] == 'login': login_request = data[1] if login_request == 'login': # user wants to login self.validate_login(sockobj, data[2], data[3]) elif login_request == 'get_all_kahoots': self.add_data(sockobj, ['login', 'get_all_kahoots'] + self.m.get_all_questionnaries()) elif login_request == 'get_my_kahoots': self.add_data(sockobj, ['login', 'get_my_kahoots'] + self.m.get_user_questionnaires(data[2])) elif login_request == 'host_game': self.open_new_game(sockobj, data[2]) else: # create new game (not to play right now) self.create_new_game(sockobj, data[2], data[3], data[4], data[5]) elif data[0] == 'sign_up': self.add_new_user(sockobj, data[1], data[2]) else: # invalid request self.add_data(sockobj, 'Invalid request') def initiate(self): self.main_socket.bind((self.ip, self.port)) self.main_socket.listen(4) def add_data(self, socketobj, data): if type(data) != list: data = [data] self.messages[socketobj] += data def send_all_data(self): for socketobj in self.messages: if self.messages[socketobj]: data = str(self.messages[socketobj]) try: socketobj.send(data) except: pass # delete the sent data self.messages[socketobj] = [] def validate_game(self, socketobj, game_name, game_pin): game = None info = None for g in self.games: # if game name and pin are correct, and the game hasn't started yet then joining is valid if str(g.name) == str(game_name) and str(g.pin) == str(game_pin): if not g.started: self.validated[socketobj] = g # validate game for client game = g else: info = 'game in progress, no more new players accepted' if game and not info: self.add_data(socketobj, ['join', 'request', True]) else: self.add_data(socketobj, ['join', 'request', False]) if not info: info = "couldn't find requested game, please check the name and pin." data = [{'join': info}] self.add_data(socketobj, data) def validate_login(self, socketobj, username, password): correct_password = self.m.get_user_password(username) if correct_password and correct_password == password: # correct password was entered self.validated_login[socketobj] = True self.add_data(socketobj, ['login', 'login', True]) # return client successful login elif not correct_password: # no such user in database self.add_data(socketobj, ['login', 'login', False, 'User not found']) else: self.validated_login[socketobj] = self.validated_login[socketobj] + 1 if socketobj in self.validated_login \ else 1 # number of unsuccessful login attempts self.add_data(socketobj, ['login', 'login', False]) # return client unsuccessful def create_new_game(self, socketobj, publisher, game_name, questions_and_answers, correct_answers_and_times): result = self.m.add_new_questionnaire(publisher, game_name, questions_and_answers, correct_answers_and_times) if result: self.add_data(socketobj, 'successfully added new questionnaire') else: self.add_data(socketobj, "couldn't add the questionnaire because name is not unique, " "please choose a different name.") def open_new_game(self, socketobj, game_name): print 'Opening new game...' game_pin = random.randrange(10**5, 10**6) # game_pin is a random number between 100000 and 999999 print 'Game Name: ',game_name,'; Game Pin:', game_pin game = Game(game_name, game_pin) self.games.append(game) self.wall_displays[socketobj] = game game.match_question_and_answer(self.m.get_questions_and_answers(game_name)) # update game questions # timing settings time_settings = eval(self.m.get_time_settings(game_name)) # dictionary of time settings game.wait_for_players = time_settings['wait_for_players'] game.interval_between_questions = time_settings['interval'] # get starting info and update the wall display self.add_data(socketobj, ['login', 'host_game', game_pin, game.wait_for_players, game.interval_between_questions]) def join_game(self, socketobj, game, player_name): if player_name not in game.players: game.players[player_name] = 0 # every player starts with 0 points self.names[socketobj] = player_name self.connected[socketobj] = (game, player_name) # save the player and the game he is connected to self.add_data(socketobj, ['join', True, player_name, 'Successfully added to game!\r\nWaiting for more players...']) else: self.add_data(socketobj, ['join', False, player_name, 'Name already taken, please choose a different name']) def get_connected_players(self, socketobj): game = self.wall_displays[socketobj] players_in_game = str(game.players) self.add_data(socketobj, ['wall_display', 'start_update', players_in_game]) def get_number_of_answers(self, socketobj): game = self.wall_displays[socketobj] all_answers = len(game.player_answers) self.add_data(socketobj, ['wall_display', 'game_update', 'game_update', str(all_answers)]) def get_round_info(self, socketobj): game = self.wall_displays[socketobj] # change the current question and immediately return to what it was before, # in order to check if their is a next question game.current_question += 1 if game.get_current_question() == [None]: # last question info = ['wall_display', 'game_update', 'round_results', game.get_game_scores(), True] else: # their is another question after this one info = ['wall_display', 'game_update', 'round_results', game.get_game_scores(), False] game.current_question -= 1 self.add_data(socketobj, info) # update all the connected clients if they were right or wrong, and give them their scores. for clientobj in self.connected: # if client is in the same game of the wall display if self.connected[clientobj] and self.connected[clientobj][0] == game: # send the client the current answers player_name = self.names[clientobj] if player_name in game.player_answers: client_answer = game.player_answers[player_name] in game.correct_answers[game.current_question] else: client_answer = False points = game.players[player_name] self.add_data(clientobj, ['game', 'round_results', client_answer, points]) def update_player_answer(self, socketobj, answer, player_name): game = self.validated[socketobj] correct_answer = game.correct_answers[game.current_question] # single answer or multiple correct answers if (type(correct_answer) == str and answer == correct_answer) or (answer in correct_answer): points = self.calculate_points(True, self.question_start_time[game], self.get_current_time(), game.times[game.current_question]) if player_name in game.correct_player_answers: game.correct_player_answers[player_name] += 1 else: # first correct answer for player game.correct_player_answers[player_name] = 1 else: points = self.calculate_points(False) game.update_player_and_points(player_name, points) game.player_answers[player_name] = answer # player gave an answer, don't allow more def calculate_points(self, answer, start_time=None, time=None, question_time=None): if not answer: # if answer was incorrect don't give the player any points return 0 time_left = question_time - (time - start_time) return int(100*(time_left/question_time)) def get_current_question_info(self, socketobj): game = self.wall_displays[socketobj] all_answers = len(game.player_answers) info = [str(all_answers)] + game.get_current_question() return info def add_new_user(self, socketobj, username, password): result = self.m.add_user(username, password) if result == 'user added successfully': self.add_data(socketobj, ['sign_up', True]) else: # username taken result += "Please enter a different username." self.add_data(socketobj, ['sign_up', False, result]) def message_all_game_participants(self, game, message): # message clients for sockobj in self.validated: if self.validated[sockobj] == game: self.add_data(sockobj, message) # message all wall display clients for sockobj in self.wall_displays: if self.wall_displays[sockobj] == game: self.add_data(sockobj, message) def client_score(self, client): return self.validated[client].players[self.names[client]] def end_game(self, socketobj): game = self.wall_displays[socketobj] game.ended = True info = ['wall_display', 'end_questionnaire', game.get_winners()] self.add_data(socketobj, info) #self.save_game(game) # if game ended and more than 2 players where in it, save the game # send players their places clients = [] for clientobj in self.connected: # if client is in the same game of the wall display if self.connected[clientobj] and self.connected[clientobj][0] == game: clients.append(clientobj) clients = sorted(clients, key=self.client_score, reverse=True) for i in range(len(clients)): player_name = self.names[clients[i]] points = game.players[player_name] self.add_data(clients[i], ['game', 'final_results', i+1, points]) def save_game(self, game): if len(game.players) >= 2 and game.ended: self.m.add_questionnaire(self.m.get_all_info(game.name)[0][0], self.m.get_all_info(game.name)[0][1], self.m.get_all_info(game.name)[0][2], game.players, self.m.get_all_info(game.name)[0][-3]) def close_clients(self): print 'Sending Shut-Down' for client in self.connected: client.send(str(['server_down'])) def close(self): self.main_socket.close()