def data_received(self, data): if self.data: data = self.data + data self.data = None if self.parse_data(data): log.info('[REQUEST] Player: {}, action: {!r}, message:\n{}'.format( self.player.idx if self.player is not None else self.client_address, Action(self.action), self.message), game=self.game) try: data = json.loads(self.message) if not isinstance(data, dict): raise errors.BadCommand( 'The command\'s payload is not a dictionary') if self.observer: self.write_response( *self.observer.action(self.action, data)) else: if self.action not in self.ACTION_MAP or self.action in CONFIG.HIDDEN_COMMANDS: raise errors.BadCommand('No such action: {}'.format( self.action)) method = self.ACTION_MAP[self.action] result, message = method(self, data) self.write_response(result, message) if not self.observer and self.action in self.REPLAY_ACTIONS: game_db.add_action(self.game_idx, self.action, message=data, player_idx=self.player.idx) # Handle errors: except (json.decoder.JSONDecodeError, errors.BadCommand) as err: self.error_response(Result.BAD_COMMAND, err) except errors.AccessDenied as err: self.error_response(Result.ACCESS_DENIED, err) except errors.InappropriateGameState as err: self.error_response(Result.INAPPROPRIATE_GAME_STATE, err) except errors.Timeout as err: self.error_response(Result.TIMEOUT, err) except errors.ResourceNotFound as err: self.error_response(Result.RESOURCE_NOT_FOUND, err) except Exception: log.exception( 'Got unhandled exception on client command execution', game=self.game) self.error_response(Result.INTERNAL_SERVER_ERROR) finally: self.action = None self.message_len = None self.message = None
def on_observer(self, _): if self.game or self.observer: raise errors.BadCommand('Impossible to connect as observer') else: self.observer = Observer() message = self.observer.games_to_json_str() return Result.OKEY, message
def on_login(self, data: dict): if self.game is not None or self.player is not None: raise errors.BadCommand('You are already logged in') self.check_keys(data, ['name']) player_name = data['name'] password = data.get('password', None) player = Player.get(player_name, password=password) if not player.check_password(password): raise errors.AccessDenied('Password mismatch') game_name = data.get('game', 'Game of {}'.format(player_name)) num_players = data.get('num_players', CONFIG.DEFAULT_NUM_PLAYERS) num_turns = data.get('num_turns', CONFIG.DEFAULT_NUM_TURNS) game = Game.get(game_name, num_players=num_players, num_turns=num_turns) game.check_state(GameState.INIT, GameState.RUN) player = game.add_player(player) self.game = game self.game_idx = game.game_idx self.player = player log.info('Player successfully logged in: {}'.format(player), game=self.game) message = self.player.to_json_str() return Result.OKEY, message
def check_keys(data: dict, keys, agg_func=all): if not agg_func([k in data for k in keys]): raise errors.BadCommand( 'The command\'s payload does not contain all needed keys, ' 'following keys are expected: {}'.format(keys)) else: return True
def action(self, action, data): """ Interprets observer's actions. """ if action not in self.ACTION_MAP: raise errors.BadCommand('No such action: {}'.format(action)) method = self.ACTION_MAP[action] return method(self, data)
def __init__(self, name, observed=False, map_name=None, num_players=CONFIG.DEFAULT_NUM_PLAYERS, num_turns=CONFIG.DEFAULT_NUM_TURNS): super(Game, self).__init__(name=name) log.info('Create game, name: \'{}\''.format(self.name)) self.name = name self.state = GameState.INIT self.current_tick = 0 self.observed = observed self.num_players = num_players self.num_turns = num_turns self.map = Map(use_active=True) if map_name is None else Map( name=map_name) if self.num_players > len(self.map.towns): raise errors.BadCommand( 'Unable to create game with {} players, maximum players count is {}' .format(self.num_players, len(self.map.towns))) if self.observed: self.game_idx = 0 else: self.game_idx = game_db.add_game(name, self.map.idx, num_players=num_players, num_turns=num_turns) self.players = {} self.trains = {} self.next_train_moves = {} self.event_cooldowns = CONFIG.EVENT_COOLDOWNS_ON_START.copy() self._lock = Lock() self._stop_event = Event() self._start_tick_event = Event() self._tick_done_condition = Condition() random.seed()
def make_upgrade(self, player: Player, posts_idx=(), trains_idx=()): """ Upgrades given Posts and Trains to next level. """ # Get posts from request: posts = [] for post_idx in posts_idx: if post_idx not in self.map.posts: raise errors.ResourceNotFound( 'Post index not found, index: {}'.format(post_idx)) post = self.map.posts[post_idx] if post.type != PostType.TOWN: raise errors.BadCommand( 'The post is not a Town, post: {}'.format(post)) if post.player_idx != player.idx: raise errors.AccessDenied('Town\'s owner mismatch') posts.append(post) # Get trains from request: trains = [] for train_idx in trains_idx: if train_idx not in self.trains: raise errors.ResourceNotFound( 'Train index not found, index: {}'.format(train_idx)) train = self.trains[train_idx] if train.player_idx != player.idx: raise errors.AccessDenied('Train\'s owner mismatch') trains.append(train) # Check existence of next level for each entity: posts_has_next_lvl = all( [p.level + 1 in CONFIG.TOWN_LEVELS for p in posts]) trains_has_next_lvl = all( [t.level + 1 in CONFIG.TRAIN_LEVELS for t in trains]) if not all([posts_has_next_lvl, trains_has_next_lvl]): raise errors.BadCommand( 'Not all entities requested for upgrade have next levels') # Check armor quantity for upgrade: armor_to_up_posts = sum([p.next_level_price for p in posts]) armor_to_up_trains = sum([t.next_level_price for t in trains]) armor_to_up = sum([armor_to_up_posts, armor_to_up_trains]) if player.town.armor < armor_to_up: raise errors.BadCommand( 'Not enough armor resource for upgrade, player\'s armor: {}, ' 'armor needed to upgrade: {}'.format(player.town.armor, armor_to_up)) # Check that trains are in town now: for train in trains: if not self.is_train_at_post(train, post_to_check=player.town): raise errors.BadCommand( 'The train is not in Town now, train: {}'.format(train)) # Upgrade entities: for post in posts: player.town.armor -= post.next_level_price post.set_level(post.level + 1) log.info('Post has been upgraded, post: {}'.format(post), game=self) for train in trains: player.town.armor -= train.next_level_price train.set_level(train.level + 1) log.info('Train has been upgraded, post: {}'.format(train), game=self)
def move_train(self, player, train_idx, speed, line_idx): """ Process action MOVE. Changes path or speed of the Train. """ if train_idx not in self.trains: raise errors.ResourceNotFound( 'Train index not found, index: {}'.format(train_idx)) if line_idx not in self.map.lines: raise errors.ResourceNotFound( 'Line index not found, index: {}'.format(line_idx)) train = self.trains[train_idx] if train.player_idx != player.idx: raise errors.AccessDenied('Train\'s owner mismatch') if train_idx in self.next_train_moves: self.next_train_moves.pop(train_idx) # Check cooldown for the train: if train.cooldown > 0: raise errors.BadCommand( 'The train is under cooldown, cooldown: {}'.format( train.cooldown)) # Stop the train; reverse direction on move; continue run the train: if speed == 0 or train.line_idx == line_idx: train.speed = speed # The train is standing: elif train.speed == 0: # The train is standing at the end of the line: if self.map.lines[train.line_idx].length == train.position: line_from = self.map.lines[train.line_idx] line_to = self.map.lines[line_idx] if line_from.points[1] in line_to.points: train.line_idx = line_idx train.speed = speed if line_from.points[1] == line_to.points[0]: train.position = 0 else: train.position = line_to.length else: raise errors.BadCommand( 'The end of the train\'s line is not connected to the next line, ' 'train\'s line: {}, next line: {}'.format( line_from, line_to)) # The train is standing at the beginning of the line: elif train.position == 0: line_from = self.map.lines[train.line_idx] line_to = self.map.lines[line_idx] if line_from.points[0] in line_to.points: train.line_idx = line_idx train.speed = speed if line_from.points[0] == line_to.points[0]: train.position = 0 else: train.position = line_to.length else: raise errors.BadCommand( 'The beginning of the train\'s line is not connected to the next line, ' 'train\'s line: {}, next line: {}'.format( line_from, line_to)) # The train is standing on the line (between line's points), player have to continue run the train. else: raise errors.BadCommand( 'The train is standing on the line (between line\'s points), ' 'player have to continue run the train') # The train is moving on the line (between line's points): elif train.speed != 0 and train.line_idx != line_idx: switch_line_possible = False line_from = self.map.lines[train.line_idx] line_to = self.map.lines[line_idx] if train.speed > 0 and speed > 0: switch_line_possible = ( line_from.points[1] == line_to.points[0]) elif train.speed > 0 and speed < 0: switch_line_possible = ( line_from.points[1] == line_to.points[1]) elif train.speed < 0 and speed > 0: switch_line_possible = ( line_from.points[0] == line_to.points[0]) elif train.speed < 0 and speed < 0: switch_line_possible = ( line_from.points[0] == line_to.points[1]) # This train move request is valid and will be applied later: if switch_line_possible: self.next_train_moves[train_idx] = { 'speed': speed, 'line_idx': line_idx } # This train move request is invalid: else: raise errors.BadCommand( 'The train is not able to switch the current line to the next line, ' 'or new speed is incorrect, train\'s line: {}, next line: {}, ' 'train\'s speed: {}, new speed: {}'.format( line_from, line_to, train.speed, speed))
def wrapped(self, *args, **kwargs): if self.game is None: raise errors.BadCommand('A game is not chosen') else: return func(self, *args, **kwargs)