def _get_answers(session_id, data, session={}): """ if not is_current_question and not scored: return failure get map player_id -> [answer_ids] compose map player_id: answer: x wager: n """ question_index = data[QUESTION_ID] round_index = data[ROUND_ID] answers = get_question_by_index(session_id, round_index, question_index) if not answers[SUCCESS]: return answers answers = answers[OBJECT][ANSWERS] composed = {} for player_id in answers: composed[player_id] = [] answer_ids = answers[player_id] for answer_id in answer_ids: answer = get_answer(answer_id) if not answer[SUCCESS]: return answer answer = answer[OBJECT] composed[player_id].append({ANSWER: answer[ANSWER], ID: answer[ID], WAGER: answer[WAGER]}) return succeed(composed)
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_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 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 get_questions(text_filter=None, unused_only=True): ret = [] questions = get_all("question") for q in questions: if matches(q, text_filter, unused_only, ROUNDS_USED, QUESTION): ret.append(q) return succeed(ret)
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 _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 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 get_rounds(text_filter=None, unused_only=True): ret = [] rounds = get_all("round") for r in rounds: if matches(r, text_filter, unused_only, GAMES, ROUND): ret.append(r) return succeed(ret)
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 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 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 get_session_state(session_id): req_state = request.args.get("current", None) if req_state is None: time.sleep(10) sleep_time = .5 timeout_s = 90 attempts = 0 while attempts < (timeout_s / sleep_time): state = mongo.get_state(session_id) if str(state) != str(req_state): return _resp(succeed({"state": state})) time.sleep(sleep_time) attempts += 1 return _resp(succeed({"state": req_state}))
def delete_game(game_id, game={}): mongo.delete("game", game_id) rounds = game.get(ROUNDS, []) rupdate = rounds_do_not_have_game_id(rounds, game_id) if not rupdate: return rupdate return succeed(game)
def rounds_do_not_have_game_id(rounds, game_id): """ set state of round so that all rounds in list do not have game_id in their games list """ for round_id in rounds: added = set_round_and_game_id(round_id, game_id, False) if not added: return added return succeed(rounds)
def delete_round_from_all_games(round_id): """ don't need to validate round_id because this is called inside delete_round where round_id is already validated """ games = get_games()[OBJECT] for game in games: game_id = game[ID] mongo.pull("game", game_id, ROUNDS, round_id) return succeed({})
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 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 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 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 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 get_answers_unscored(players, answers, caller_player_id): ret = [] for player in players: player_id = player[ID] p = {TEAM_NAME: player[TEAM_NAME], ICON: player.get(ICON, None)} panswers = answers.get(player_id, None) p['answered'] = panswers is not None if caller_player_id == player_id: p[PLAYER_ID] = player_id ret.append(p) return succeed({SCORED: False, ANSWERS: ret})
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 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 get_scoreboard(session_id, session={}): scoreboard = session.get(SCOREBOARD, None) if scoreboard is None: scoreboard = {} players = get_players(session_id) if not players[SUCCESS]: return players players = players[OBJECT] for player in players: player_id = player[ID] scoreboard[player_id] = 0 return succeed(scoreboard)
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 _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 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 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)