def delete(db: StandardDatabase, game: Game, requester=None): """ delete all objects associated with a game :param db: connection :param game: target :param requester: player requesting the delete """ logger.info(f'delete called for {game.title}') db_npcs = db.collection('npcs') db_dialogs = db.collection('dialogs') tasks = db.collection('tasks') games = db.collection('games') db_game = next(games.find({'_key': game.title}), None) if not db_game: logger.warning(f'game {game.title} not in metadata') raise GameStateException(f'game {game.title} not in metadata') if not db.has_graph(f'game_{game.title}'): logger.warning(f'game {game.title} not in metadata') raise GameStateException(f'game {game.title} does not exist') if db_game['creator'] and not db_game['creator'] == requester: raise GameStateException(f'cannot delete game {game.title}, you are not the owner') for db_npc in db_npcs.find({'game': f'game_{game.title}'}): db_dialog = next(db_dialogs.find({'_key': db_npc['dialog']}), None) if not db.has_graph(f"dialog_{db_npc['dialog']}"): logger.warning(f"dialog {db_npc['dialog']} does not exist") raise GameStateException(f"dialog {db_npc['dialog']} does not exist") for k, v in dict(db.graph(f"dialog_{db_npc['dialog']}").traverse(start_vertex=f"interactions/{db_dialog['start']}", direction='outbound', strategy='dfs', edge_uniqueness='global', vertex_uniqueness='global')).items(): if k == 'vertices': for interaction in v: if 'task' in interaction and interaction['task']: db_task = next(tasks.find({'_key': interaction['task']})) if db_task: db.delete_document(db_task) db.delete_document(db_dialog) db.delete_graph(f"dialog_{db_npc['dialog']}", drop_collections=True) db.delete_document(db_npc) for node in nx.dfs_tree(game.graph): for task in node.tasks: db_task = next(tasks.find({'_key': task.id.hex}), None) if db_task: db.delete_document(db_task) db.delete_document(db_game) db.delete_graph(f'game_{game.title}', drop_collections=True)
def read(db: StandardDatabase, title: str) -> Game: """ load an existing game back from the database :param db: connection :param title: game title :return: game object """ logger.info(f'read called for {title}') npcs = [] db_npcs = db.collection('npcs') db_dialogs = db.collection('dialogs') for db_npc in db_npcs.find({'game': f'game_{title}'}): logger.debug(f'reading back npc {db_npc["_key"]}') db_dialog = next(db_dialogs.find({'_key': db_npc['dialog']}), None) if not db_dialog: logger.warning(f"dialog {db_npc['dialog']} not in metadata") raise GameStateException(f"dialog {db_npc['dialog']} not in metadata") if not db.has_graph(f"dialog_{db_npc['dialog']}"): logger.warning(f"dialog {db_npc['dialog']} does not exist") raise GameStateException(f"dialog {db_npc['dialog']} does not exist") vertices = [] path = [] for k, v in dict(db.graph(f"dialog_{db_npc['dialog']}").traverse(start_vertex=f"interactions/{db_dialog['start']}", direction='outbound', strategy='dfs', edge_uniqueness='global', vertex_uniqueness='global')).items(): if k == 'vertices': for vertex in v: vertices.append(vertex) elif k == 'paths': for edge in v: path.append(edge) interactions = [] for v in vertices: interaction = json.loads(json.dumps(v), cls=MarugotoDecoder) logger.debug(f'got {repr(interaction)}') for task in db.collection('tasks'): if task['for'] == interaction.id.hex: interaction.task = json.loads(json.dumps(task), cls=MarugotoDecoder) logger.debug(f'got {repr(interaction.task)} for {repr(interaction)}') interactions.append(interaction) start_index = -1 for e in path: if not e['edges']: start = next(iter([i for i in interactions if i.id == UUID(e['vertices'][0]['_key'])]), None) if start: start_index = interactions.index(start) logger.debug(f'setting start index to {start_index}') continue for edge in e['edges']: source = next(iter([i for i in interactions if i.id == UUID(edge['_from'][13:])]), None) if not source: logger.warning(f"malformed source for dialog {db_npc['dialog']}") raise GameStateException(f"malformed source for dialog {db_npc['dialog']}") idx = interactions.index(source) destination = next(iter([i for i in interactions if i.id == UUID(edge['_to'][13:])]), None) if not destination: logger.warning(f"malformed destination for {repr(source)} in dialog {db_npc['dialog']}") raise GameStateException(f"malformed destination for {repr(source)} in dialog {db_npc['dialog']}") logger.debug(f'adding follow up interaction {repr(destination)} to {repr(source)}') source.add_follow_up(destination) logger.debug(f'setting index for {repr(source)} to {idx}') interactions[idx] = source if start_index == -1: logger.warning(f"could not determine start for dialog {db_npc['dialog']}") raise GameStateException(f"could not determine start for dialog {db_npc['dialog']}") npc = NonPlayableCharacter(db_npc['first_name'], db_npc['last_name'], Dialog(interactions[start_index])) npc.salutation = db_npc['salutation'] npc.mail = db_npc['mail'] npc.image = base64.decodebytes(db_npc['image']) if db_npc['image'] else None logger.debug(f'adding {repr(npc)}') npcs.append(npc) games = db.collection('games') db_game = next(games.find({'_key': title}), None) if not db_game: logger.warning(f'game {title} not in metadata') raise GameStateException(f'game {title} not in metadata') if not db.has_graph(f'game_{title}'): logger.warning(f'game {title} does not exist') raise GameStateException(f'game {title} does not exist') vertices = [] path = [] for k, v in dict(db.graph(f'game_{title}').traverse(start_vertex=f"waypoints/{db_game['start']}", direction='outbound', strategy='dfs', edge_uniqueness='global', vertex_uniqueness='global')).items(): if k == 'vertices': for vertex in v: vertices.append(vertex) elif k == 'paths': for edge in v: path.append(edge) waypoints = [] for v in vertices: waypoint = json.loads(json.dumps(v), cls=MarugotoDecoder) logger.debug(f'got {repr(waypoint)}') for task in db.collection('tasks'): if task['for'] == waypoint.id.hex: waypoint.tasks.remove(UUID(task['_key'])) task = json.loads(json.dumps(task), cls=MarugotoDecoder) logger.debug(f'got {repr(task)} for {repr(waypoint)}') waypoint.tasks.append(task) if waypoint.interactions: for i in waypoint.interactions: if isinstance(i, UUID): interaction = next(iter([ita for ita in [d.all_path_nodes() for d in [npc.dialog for npc in npcs]] if ita.id == i]), None) if interaction: logger.debug(f'adding {repr(interaction)} to {repr(waypoint)}') waypoint.interactions.append(interaction) else: logger.warning(f'could not locate interaction {i}') raise GameStateException(f'could not locate interaction {i}') waypoints.append(waypoint) # glue back the task destinations for waypoints for task in [ts for t in [w.tasks for w in waypoints if w.tasks] for ts in t]: if task.destination and isinstance(task.destination, UUID): destination = next(iter([w for w in waypoints if w.id == task.destination]), None) if not destination: logger.warning(f'could not find destination {task.destination} for task {task.id} in game {title}') raise GameStateException(f'could not find destination {task.destination} for task {task.id} in game {title}') logger.debug(f'adding {repr(destination)} to {repr(task)}') task.destination = destination # glue back interaction destinations for waypoints for interaction in [ias for ia in [w.interactions for w in waypoints if w.interactions] for ias in ia]: if interaction.destination and isinstance(interaction.destination, UUID): destination = next(iter([w for w in waypoints if w.id == interaction.destination]), None) if not destination: logger.warning(f'could not find destination {interaction.destination} for interaction {interaction.id} in game {title}') raise GameStateException(f'could not find destination {interaction.destination} for interaction {interaction.id} in game {title}') logger.debug(f'adding {repr(destination)} to {repr(interaction)}') interaction.destination = destination # glue back interaction and task destinations, and waypoints for dialogs for start in [d.start for d in [npc.dialog for npc in npcs]]: int_visited = {} def interaction_traversal(s, i, l): for successor in start.graph.successors(s): logger.debug(f'successor {repr(successor)} for {repr(s)}') if s not in i.keys(): logger.debug(f'{repr(s)} is new, adding it.') i[s] = [] if successor not in i[s]: if s.destination and isinstance(s.destination, UUID): destination = next(iter([w for w in l if w.id == s.destination]), None) if not destination: logger.warning(f'could not find destination {s.destination} for interaction {s.id} in game {title}') raise GameStateException(f'could not find destination {s.destination} for interaction {s.id} in game {title}') logger.debug(f'adding {repr(destination)} to {repr(s)}') s.destination = destination if s.task and s.task.destination and isinstance(s.task.destination, UUID): destination = next(iter([w for w in l if w.id == s.task.destination]), None) if not destination: logger.warning(f'could not find destination {s.destination} for task {s.task.id} in interaction {s.id} in game {title}') raise GameStateException(f'could not find destination {s.destination} for task {s.task.id} in interaction {s.id} in game {title}') logger.debug(f'adding task destination {repr(destination)} to {repr(s.task)} for {repr(s)}') s.task.destination = destination if s.waypoints: wps = [] for waypoint in s.waypoints: if isinstance(waypoint, UUID): destination = next(iter([w for w in l if w.id == waypoint]), None) if not destination: logger.warning(f'could not find waypoint {waypoint} for interaction {interaction.id} in game {title}') raise GameStateException(f'could not find waypoint {waypoint} for interaction {interaction.id} in game {title}') logger.debug(f'adding {repr(destination)}') wps.append(destination) else: logger.debug(f'adding {repr(waypoint)}') wps.append(waypoint) s.waypoints = wps int_visited[s].append(successor) interaction_traversal(successor, i, l) logger.debug(f'starting interaction traversal to glue back waypoints') interaction_traversal(start, int_visited, waypoints) start_index = -1 for e in path: if not e['edges']: start = next(iter([i for i in waypoints if i.id == UUID(e['vertices'][0]['_key'])]), None) if start: start_index = waypoints.index(start) logger.debug(f'settings start index to {start_index}') continue for edge in e['edges']: source = next(iter([i for i in waypoints if i.id == UUID(edge['_from'][10:])]), None) if not source: logger.warning(f'malformed source for game {title}') raise GameStateException(f'malformed source for game {title}') idx = waypoints.index(source) destination = next(iter([i for i in waypoints if i.id == UUID(edge['_to'][10:])]), None) if not destination: logger.warning(f'malformed destination for {source} in game {title}') raise GameStateException(f'malformed destination for {source} in game {title}') logger.debug(f'adding {repr(destination)} to {repr(source)}') if 'weight' in edge and edge['weight']: source.add_destination(destination, edge['weight']) else: source.add_destination(destination) logger.debug(f'setting index for {repr(source)} to {idx}') waypoints[idx] = source if start_index == -1: logger.warning(f'could not determine start for game {title}') raise GameStateException(f'could not determine start for game {title}') game = Game(title, base64.decodebytes(db_game['image']) if db_game['image'] else None, waypoints[start_index]) game.npcs = npcs return game