def game_has_round_and_question(session): """ verify that this session has at least one round and question """ game_id = session.get(GAME_ID, None) if not game_id: return fail(f"Session {session[ID]} does not have a {GAME}_id") game = get_game(game_id) # probably don't need to test validity because setting game_id illegal is prevented if not game[SUCCESS]: return game game = game[OBJECT] rounds = game.get(ROUNDS, []) if len(rounds) == 0: return fail(f"{GAME}_id {game_id} does not have any {ROUNDS}") first_round_id = rounds[0] round_obj = get_round(first_round_id) if not round_obj[SUCCESS]: return round_obj round_obj = round_obj[OBJECT] questions = round_obj.get(QUESTIONS, []) if len(questions) == 0: return fail(f"{ROUND}_id {first_round_id} does not have any {QUESTIONS}") first_question_id = questions[0] return succeed({ROUND_ID: first_round_id, QUESTION_ID: first_question_id, ROUNDS: rounds})
def get_players(session_id, player_id=None): """ args: session_id returns: [ {player_name: name, player_id: uuid4}, {player_name: name, player_id: uuid4}, ... ] """ session = get_session(session_id) if session[SUCCESS]: session = session[OBJECT] players = session.get(PLAYERS, []) ret = [] for player_id in players: player = get_player(player_id) if player[SUCCESS]: player = player[OBJECT] ret.append(player) else: return fail(f"Failed to get player {player_id}") resp = succeed(ret) resp[MODERATOR] = session[MODERATOR] return resp return fail("Failed to get session")
def update_game(game_id, data, game={}): round_names = data.get(ROUND_NAMES, {}) rounds = data.get(ROUNDS, []) if len(round_names) != len(rounds): return fail( f"{ROUND_NAMES} is different length ({len(round_names)}) than {ROUNDS} ({len(rounds)})" ) for round_id in rounds: if round_names.get(round_id, None) is None: return fail(f"missing round name for round with id {round_id})") prev_rounds = game.get(ROUNDS, []) target_rounds = data.get(ROUNDS, []) for round_id in prev_rounds: if round_id not in target_rounds: set_round_and_game_id(round_id, game_id, False) # remove game_id from round for round_id in target_rounds: if round_id not in prev_rounds: set_round_and_game_id(round_id, game_id, True) # add game_id to round if len(round_names) == 0 or len(rounds) == 0: data[ROUND_NAMES] = {} data[ROUNDS] = {} success = mongo.update("game", game_id, data) if success: game.update(data) return succeed(game) return fail(f"Failed to update game with data {data}")
def create_game(data): round_names = data[ROUND_NAMES] rounds = data[ROUNDS] if len(round_names) != len(rounds): return fail( f"{ROUND_NAMES} is different length ({len(round_names)}) than {ROUNDS} ({len(rounds)})" ) for round_id in rounds: if round_names.get(round_id, None) is None: return fail(f"missing round name for round with id {round_id})") created = mongo.create("game", data) if not created: return fail("Failed to create game") # add game_id to all rounds, and return error if this fails game_id = created[ID] rupdate = rounds_have_game_id(rounds, game_id) if not rupdate: return rupdate return succeed(created)
def set_round_and_game_id(round_id, game_id, present=True): """ set round so that it has or does not have game_id in its games list args: round_id: ID of target round game_id: ID of target game present: true if round X is marked as in game Y, false if not """ round = get_round(round_id) if not round: return fail( f"Can't set game_id {game_id} in nonexistent round_id {round_id}") round = round[OBJECT] games = round.get(GAMES, []) if present and game_id not in games: if not mongo.push("round", round_id, GAMES, game_id): return fail( f"Failed to add game_id {game_id} to round_id {round_id}") if not present and game_id in games: if not mongo.pull("round", round_id, GAMES, game_id): return fail( f"Failed to remove game_id {game_id} to round_id {round_id}") return succeed(round)
def get_answer_status(session_id): player_id = request.args.get(PLAYER_ID, None) round_id = request.args.get(ROUND_ID, None) question_id = request.args.get(QUESTION_ID, None) try: round_id = int(round_id) question_id = int(question_id) except: return _resp(fail("Bad question/round ID")) session = get_session(session_id)[OBJECT] question = session[ROUNDS][round_id][QUESTIONS][question_id] answers = question.get(ANSWERS, None) if answers is None: return _resp(fail("Question is not open")) mod = session[MODERATOR] players = get_players2(session) if player_id != mod: # scored questions will provide answers and wagers if question.get(SCORED, False): return _resp(get_answers_scored(players, answers, player_id)) else: # unscored questions will provide only answered:true/false return _resp(get_answers_unscored(players, answers, player_id)) else: # mod always gets complete player_id/answer/wager return _resp(get_answers_as_mod(players, answers))
def score_question(session_id, data, session={}): """ data = { question_id: x round_id: y players: team1: {correct: true} team2: {correct: false} ... } """ question_id = data[QUESTION_ID] round_id = data[ROUND_ID] question = session.get(ROUNDS)[round_id][QUESTIONS][question_id] rescore = question.get(SCORED, False) answers = question.get(ANSWERS, None) if answers is None: return fail(f"{QUESTION}_id {question_id} is not open") session_players = session.get(PLAYERS, []) given_players = data[PLAYERS] for player_id in session_players: if player_id not in given_players: return fail(f"{PLAYER_ID} {player_id} was not scored.") is_correct = given_players[player_id].get(CORRECT, None) if is_correct is None: return fail(f"Did not set correct True/False for {PLAYER_ID} {player_id}") players = get_players2(session) answers = get_answers_as_mod(players, answers)[OBJECT] for answer in answers: print(answer) if answer['answered'] is False: return fail(f"{PLAYER_ID} {answer[PLAYER_ID]} has not answered question {question_id}") last_answer = answer[ANSWERS][-1] player_id = answer[PLAYER_ID] score_override = given_players[player_id].get(SCORE_OVERRIDE, None) wager = last_answer[WAGER] if score_override is None else score_override # hack here to override is_correct = given_players[player_id].get(CORRECT, None) points_awarded = wager if is_correct else 0 mongo.update("answer", last_answer['answer_id'], {CORRECT: is_correct, POINTS_AWARDED: points_awarded}) award_points(session_id, player_id, points_awarded, rescore) score_flag = f"{ROUNDS}.{round_id}.{QUESTIONS}.{question_id}.{SCORED}" answ = f"{ROUNDS}.{round_id}.{QUESTIONS}.{question_id}.{ANSWER}" real_answer = get_real_question(session, round_id, question_id)[ANSWER] mongo.update("session", session_id, {score_flag: True, answ: real_answer}) mongo.incr_state(session_id) return succeed(data)
def create_session(data): """ POST /session """ mod = create_player({TEAM_NAME: "mod", REAL_NAME: "mod", ICON: ""}) if not mod[SUCCESS]: return mod mod_id = str(mod[OBJECT][ID]) # create session with this moderator ID data[STARTED] = False data[MODERATOR] = mod_id created = mongo.create("session", data) if not created: return fail("Failed to create session") success = mongo.create("answer", data) if not success: return fail("Failed to create session") # created = created # resp = update_player(mod_id, {SESSION_ID: created[ID]}) # if not resp[SUCCESS]: # return fail(errors=resp[ERRORS]) mongo.incr_state(created[ID]) return succeed(created)
def validate_wagers(data): qlen = len(data.get(QUESTIONS, [])) wlen = len(data.get(WAGERS, [])) if qlen != wlen: error = f"{WAGERS} length ({wlen}) does not equal {QUESTIONS} length ({qlen}) (data: {data}))" return fail(error) for wager in data.get(WAGERS, []): if wager <= 0: return fail(f"Wager '{wager}' is not positive int") return succeed(data)
def _set_current_question(session_id, data, session={}): """ validate that question ID in round set question.open = True set session.current_question = question_id return new question_id """ qindex = data[QUESTION_ID] rindex = data[ROUND_ID] prev = get_question_in_round(session, rindex, qindex) if prev[SUCCESS]: prev = prev[OBJECT].get(ANSWERS, {}) else: prev = {} r = get_current_round(session_id) if r[SUCCESS]: r = r[OBJECT] round_id = r[ROUND_ID] round_obj = get_round(round_id) if round_obj[SUCCESS]: round_obj = round_obj[OBJECT] question_ids = round_obj.get(QUESTIONS, []) if qindex > len(question_ids): return fail(f"{QUESTION} with index {qindex} is not in current round {r}.") question_id = question_ids[qindex] question = get_question(question_id) if not question[SUCCESS]: return question question = question[OBJECT] spot = f"{ROUNDS}.{rindex}.{QUESTIONS}.{qindex}" data_to_update = { CURRENT_QUESTION: qindex, f"{spot}.{QUESTION}": question[QUESTION], f"{spot}.{ANSWERS}": prev } mongo.update("session", session_id, data_to_update) # if not success: # return fail(f"Failed to set question for question_id {question_id}") mongo.incr_state(session_id) return succeed({CURRENT_QUESTION: qindex}) return fail(f"Failed to get round with ID {session.get(ROUND_ID)}")
def _set_current_round(session_id, data, session={}): round_index = data[ROUND_ID] # round must be in game.rounds game = get_game(session[GAME_ID]) if game[SUCCESS]: game = game[OBJECT] round_ids = game.get(ROUNDS) if round_index > len(round_ids): return fail(f"{ROUND} with index '{round_index}' is not in {GAME} '{game}'") round_id = round_ids[round_index] # get round from DB and set in session obj r = get_round(round_id) if r[SUCCESS]: r = r[OBJECT] data_to_update = { f"{ROUNDS}.{round_index}.{WAGERS}": r[WAGERS], ROUND_ID: round_id, CURRENT_ROUND: round_index, } success = mongo.update("session", session_id, data_to_update) if not success: fail(f"Failed to add update round with index {i} in session") if session[ROUNDS][round_index].get(QUESTIONS, None) is None: spot = f"{ROUNDS}.{round_index}.{QUESTIONS}" questions = r[QUESTIONS] for i, question_id in enumerate(questions): question = get_question(question_id) if not question[SUCCESS]: return question category = question[OBJECT][CATEGORY] success = mongo.push("session", session_id, spot, {CATEGORY: category}) if not success: fail(f"Failed to add question with index {i} to round") set_first_q = set_current_question(session_id, round_index, 0) if not set_first_q[SUCCESS]: return set_first_q mongo.incr_state(session_id) return succeed(r) fail(f"Failed to update {SESSION}") return fail(f"Failed to get {ROUND} with id {round_id}") return fail(f"Failed to get game with id '{session[GAME_ID]}'")
def get_question_in_round(session, round_index, question_index): """ get question at index X in round with index Y """ rounds = session[ROUNDS] if round_index >= len(rounds): return fail(f"{ROUND} index {round_index} not found in {SESSION} {session}") r = rounds[round_index] questions = r[QUESTIONS] if question_index >= len(questions): return fail(f"{QUESTION} index {question_index} not found in round {r}") question = questions[question_index] question[ID] = question_index return succeed(question)
def update_session(session_id, data, session={}): success = mongo.update("session", session_id, data) if success: session.update(data) return succeed(session) mongo.incr_state(session_id) return fail(f"Failed to update session with data {data}")
def update_question(question_id, data, question={}): """ returns True/false if update successful """ success = mongo.update("question", question_id, data) if success: question.update(data) return succeed(question) return fail(f"Failed to update {QUESTION} with data {data}")
def remove_question_from_all_rounds(question): rounds_used = question.get(ROUNDS_USED, []) question_id = question.get(ID) for round_id in rounds_used: modified_count = mongo.pull("round", round_id, QUESTIONS, question_id) if modified_count == 0: return fail( f"Failed to remove {QUESTION} {question_id} from {ROUND} {round_id}" ) return succeed(question)
def legal_wagers_for_player(session_id): player_id = request.args.get(PLAYER_ID, None) round_id = request.args.get(ROUND_ID, None) session = get_session(session_id)[OBJECT] try: round_id = int(round_id) except: return _resp(fail("Bad round ID")) return _resp(succeed(get_legal_wagers(session, round_id, player_id)))
def create_round(data): validate = validate_wagers(data) if not validate[SUCCESS]: return validate created = mongo.create("round", data) if not created: return fail("Failed to create round") set_round_in_questions(created, []) # no original questions, new round return succeed(created)
def verify_mod(session_id, player_id): # get session to make sure you are the mod # TODO: refactor this somehow into helper method to avoid double get session = get_session(session_id) if not session[SUCCESS]: return session mod = session[OBJECT][MODERATOR] if player_id != mod: return fail(f"only mod can start session") return succeed({})
def update_player(player_id, data, player={}): success = mongo.update("player", player_id, data) if success: player.update(data) # dirty the session state, so other clients will see this update session_id = player.get(SESSION_ID, None) if session_id: mongo.incr_state(session_id) return succeed(player) return fail(f"Failed to update player with data {data}")
def _start_session(session_id, data, session={}): if session[STARTED]: return fail(f"Session {session_id} is already started.") start = data[STARTED] if start: startable = game_has_round_and_question(session) if not startable[SUCCESS]: return startable rounds = startable[OBJECT][ROUNDS] for i, round_id in enumerate(rounds): success = mongo.push("session", session_id, ROUNDS, {ROUND_ID: round_id}) if not success: fail(f"Failed to add round with index {i} to session") success = mongo.update("session", session_id, {STARTED: True}) if not success: return fail("Failed tto start session") # set current round to 0th round ID in game # this will also set first question to 0th question in round set_round = set_current_round(session_id, 0) if not set_round[SUCCESS]: return set_round mongo.incr_state(session_id) return succeed(session) else: fail("Cannot start session with data {data}")
def update_round(round_id, data, round_obj={}, set_questions=True): validate = validate_wagers(data) if not validate[SUCCESS]: return validate orig_questions = round_obj.get(QUESTIONS, []) success = mongo.update("round", round_id, data) if success: if set_questions: round_obj[QUESTIONS] = data.get(QUESTIONS, orig_questions) set_round_in_questions(round_obj, orig_questions) return succeed(round_obj) return fail(f"Failed to update round")
def remove_round_from_question(question_id, data, question={}): round_id = data[ROUND_ID] rounds_used = question.get(ROUNDS_USED, []) if round_id not in rounds_used: raise RuntimeError( f"Round {round_id} is not added to question {question_id}") success = mongo.pull("question", question_id, ROUNDS_USED, round_id) if not success: return fail(f"Failed to remove {ROUND} from {QUESTION}") rounds_used.remove(round_id) question[ROUNDS_USED] = rounds_used return succeed(question)
def remove_from_session(session_id, data, session={}): """ POST /session/:id/remove """ admin_id = data[ADMIN_ID] player_id = data[PLAYER_ID] if player_id not in session.get(PLAYERS, []): return fail(f"Player id {player_id} is not in session") mod = session[MODERATOR] if admin_id != mod: return fail("Given admin ID does not match session") success = mongo.pull("session", session_id, PLAYERS, player_id) if not success: return fail("Failed to remove player from session") # delete player? data[SESSION_ID] = session_id mongo.incr_state(session_id) return succeed(data)
def add_to_session(session_id, data, session={}): """ POST /session/:id/join """ if session[STARTED]: return fail("Cannot add player to already-started session") if data[PLAYER_ID] in session.get(PLAYERS, []): return fail(f"Player id {data[PLAYER_ID]} is already in session") player_id = data[PLAYER_ID] success = mongo.push("session", session_id, PLAYERS, player_id) if not success: return fail("Failed to add player to session") data[SESSION_ID] = session_id mongo.incr_state(session_id) update = update_player(player_id, {"session_id": session_id}) if not update: return update return succeed(data)
def add_round_to_question(question_id, data, question={}): round_id = data[ROUND_ID] rounds_used = question.get(ROUNDS_USED, []) if round_id in rounds_used: raise RuntimeError( f"Round {round_id} is already added to question {question_id}") success = mongo.push("question", question_id, ROUNDS_USED, round_id) if not success: return fail(f"Failed to add {ROUND} to {QUESTION}") rounds_used.append(round_id) question[ROUNDS_USED] = rounds_used return succeed(question)
def answer_question(session_id, data, session={}): """ validate wager is legal validate question is answerable create answer record push answer ID to session.question_id.player_id.answers return success: true """ player_id = data[PLAYER_ID] rindex = data[ROUND_ID] qindex = data[QUESTION_ID] legal_wagers = get_legal_wagers(session, rindex, player_id) wager = data[WAGER] if wager not in legal_wagers: return fail(f"Wager {wager} is illegal") answers = session[ROUNDS][rindex][QUESTIONS][qindex].get(ANSWERS, None) if answers is None: return fail(f"{QUESTION}_id {qindex} is not open") answer = create_answer(data) if not answer[SUCCESS]: return answer answer = answer[OBJECT] answer_id = answer[ID] array = f"{ROUNDS}.{rindex}.{QUESTIONS}.{qindex}.{ANSWERS}.{player_id}" success = mongo.push("session", session_id, array, answer_id) if not success: return fail(f"Failed to add add {answer_id} for player {player_id}") mongo.incr_state(session_id) return succeed(answer)
def create_question(data): created = mongo.create("question", data) if not created: return fail("Failed to create question") return succeed(created)
def create_answer(data): success = mongo.create("answer", data) if not success: return fail("Failed to create answer") return succeed(success)