def handle_rejection_assent(info): """Handle when a player assents to a rejection.""" rejecter = info['rejecter'] assented = info['assented'] MapsLock() [name, room] = get_name_and_room(flask.request.sid) if name not in rooms_info[room]['endgame']['review_status'][rejecter] or \ rooms_info[room]['endgame']['review_status'][rejecter][name] != REVIEWSTATUS_ASSENTING: emit( "server_message", "You have already accepted whether or not your solution is incorrect. " + "The button you just clicked had no effect.") return if assented: emit("server_message", f"{name} has accepted that his/her solution is incorrect.", room=room) rooms_info[room]['endgame']['review_status'][rejecter][ name] = REVIEWSTATUS_REJECTED track_decided_solution(room, name, False) return assert name in rooms_info[room]['endgame']['solutions'] reevaluate_msg = { "writer": name, "solution": rooms_info[room]['endgame']['solutions'][name], "rejecter": rejecter, } del rooms_info[room]['endgame']['review_status'][rejecter][name] emit("reevaluate_solution", reevaluate_msg, room=room)
def handle_cube_click(pos): """Highlight cube if it's clicker's turn and clicker hasn't clicked yet.""" MapsLock() [user, room] = get_name_and_room(flask.request.sid) turn_user = get_current_mover(room) if rooms_info[room]["game_finished"] or rooms_info[room]["challenge"] \ or rooms_info[room]["resources"][pos] == MOVED_CUBE_IDX \ or turn_user != user: return do_not_rehighlight = False if rooms_info[room]['touched_cube'] is not None: if rooms_info[room]['touched_cube'] == pos: do_not_rehighlight = True emit("unhighlight_cube", rooms_info[room]['touched_cube'], room=room) rooms_info[room]['touched_cube'] = None if not do_not_rehighlight: if not rooms_info[room]["goalset"] and len(rooms_info[room]["goal"]) >= 6: emit("server_message", "Max number of cubes on goal set! Please press \"Goal Set!\"", room=room) return else: rooms_info[room]['touched_cube'] = pos emit("highlight_cube", pos, room=room)
def handle_no_goal_siding(agreed): """Player agreed or disagreed with the no_goal declaration.""" MapsLock() [name, room] = get_name_and_room(flask.request.sid) if name not in rooms_info[room]["endgame"]["siders"]: emit( "server_message", "You have already sided on the No Goal. The button you just clicked had no effect." ) return if not agreed: if rooms_info[room]["endgame"]["challenger"] is None: rooms_info[room]["endgame"]["challenger"] = name rooms_info[room]["endgame"]["writers"].append(name) rooms_info[room]["endgame"]["solution_decisions"][name] = [] else: rooms_info[room]["endgame"]["nonwriters"].append(name) rooms_info[room]["endgame"]["siders"].remove(name) if len(rooms_info[room]["endgame"]["siders"]) == 0: rooms_info[room]["endgame"]["endgame_stage"] = "waiting_for_solutions" if rooms_info[room]["endgame"]["challenger"] is None: emit("end_shake_no_goal", room=room) rooms_info[room]["shake_ongoing"] = False rooms_info[room]["endgame"]["endgame_stage"] = "no_goal_finished" return msg_diff = "" if not agreed else "not " emit("server_message", f"{name} has decided " + msg_diff + "to challenge the No Goal.", room=room) check_if_ready_to_present(room, True)
def handle_siding(writing): """Player sided.""" MapsLock() [name, room] = get_name_and_room(flask.request.sid) # Case where perhaps user had two windows open and sided on both. # Second click to the siding buttons would trigger this if statement. if rooms_info[room]["endgame"]["sider"] == None: emit( "server_message", "You have already sided. The button you just clicked had no effect." ) return if writing: rooms_info[room]["endgame"]["writers"].append(name) rooms_info[room]["endgame"]["solution_decisions"][name] = [] else: rooms_info[room]["endgame"]["nonwriters"].append(name) rooms_info[room]["endgame"]["sider"] = None rooms_info[room]["endgame"]["endgame_stage"] = "waiting_for_solutions" msg_diff = "" if writing else "not " emit("server_message", f"{name} has decided " + msg_diff + "to write a solution!", room=room) check_if_ready_to_present(room, False)
def handle_solution_decision(info): """Handle when a player accepts or rejects a solution.""" writer = info['name'] accepted = info['accepted'] MapsLock() [name, room] = get_name_and_room(flask.request.sid) # This is true player cannot decide on a solution but somehow clicks a button to decide. # (maybe from a second open window). Clientside should ensure this never happens though... # and make buttons that shouldn't be clicked not clickable if writer in rooms_info[room]['endgame']['review_status'][name]: emit( "server_message", "You have already decided on the correctness of this solution. " + "The button you just clicked had no effect.") return accepted_str = "accepted" if accepted else "rejected" emit("server_message", f"{name} " + accepted_str + f" {writer}'s solution!", room=room) if not accepted: rooms_info[room]['endgame']['review_status'][name][ writer] = REVIEWSTATUS_ASSENTING emit("rejection_assent", { "rejecter": name, "writer": writer }, room=room) else: rooms_info[room]['endgame']['review_status'][name][ writer] = REVIEWSTATUS_ACCEPTED track_decided_solution(room, writer, True)
def handle_cube_click(pos): """Highlight cube if it's clicker's turn and clicker hasn't clicked yet.""" MapsLock() [user, room] = get_name_and_room(flask.request.sid) if rooms_info[room]["game_finished"]: return if rooms_info[room]["challenge"]: return # Reject if a cube has already been clicked. You touch it you move it! if rooms_info[room]['touched_cube'] is not None: return if rooms_info[room]["resources"][pos] == MOVED_CUBE_IDX: return rooms_info[room]["started_move"] = True turn_idx = rooms_info[room]['turn'] turn_user = rooms_info[room]['players'][turn_idx] if turn_user == user: if not rooms_info[room]["goalset"] and len( rooms_info[room]["goal"]) >= 6: emit("server_message", "Max number of cubes on goal set! Please press \"Goal Set!\"", room=room) return else: rooms_info[room]['touched_cube'] = pos emit("highlight_cube", pos, room=room)
def handle_game_time_up(): """Thirty five minute mark has been reached.""" MapsLock() [name, room] = get_name_and_room(flask.request.sid) if not rooms_info[room]["time_up"]: rooms_info[room]["time_up"] = True if rooms_info[room][ "shake_ongoing"] and rooms_info[room]["endgame"] is None: handle_force_out(room)
def handle_five_minute_warning(): """Thirty minute mark has been reached.""" MapsLock() [name, room] = get_name_and_room(flask.request.sid) if not rooms_info[room]["five_minute_warning_called"]: rooms_info[room]["five_minute_warning_called"] = True emit("five_minute_warning_message", room=room) if not rooms_info[room]["shake_ongoing"]: clean_up_finished_room(room)
def handle_bonus_click(): """Bonus button was clicked.""" MapsLock() [name, room] = get_name_and_room(flask.request.sid) if get_current_mover(room) != name: print("non mover somehow clicked the bonus button. hacker") return assert not rooms_info[room]["bonus_clicked"] rooms_info[room]["bonus_clicked"] = True rooms_info[room]["started_move"] = True
def handle_set_goal(): """Handle goal set.""" MapsLock() [name, room] = get_name_and_room(flask.request.sid) assert name == get_current_mover(room) if rooms_info[room]["game_finished"]: return rooms_info[room]["goalset"] = True next_turn(room)
def receive_message(message_info): """Receive a chat message from client.""" name = message_info['name'] message = message_info['message'] print(f"{name} send the message: {message}") MapsLock() [stored_name, room] = get_name_and_room(flask.request.sid) assert name == stored_name # Send the message to everyone in the room emit('message', message_info, room=room)
def handle_solution_submit(solution): """A player submitted a solution.""" MapsLock() [name, room] = get_name_and_room(flask.request.sid) if name not in rooms_info[room]["endgame"]["solutions"]: rooms_info[room]["endgame"]["solutions"][name] = solution check_if_ready_to_present( room, rooms_info[room]["endgame"]["challenge"] == "no_goal") else: emit( "server_message", "You have already submitted a solution. " + "The solution you just submitted was not counted. ")
def handle_bonus_click(): """Bonus button was clicked.""" MapsLock() [name, room] = get_name_and_room(flask.request.sid) if get_current_mover(room) != name: print("non mover somehow clicked the bonus button. hacker") return if rooms_info[room]["started_move"]: print("started move but somehow clicked bonus button") return rooms_info[room]["bonus_clicked"] = not rooms_info[room]["bonus_clicked"] print("bonus clicked set to ", rooms_info[room]["bonus_clicked"])
def update_cube_orientation(info): """Update a goalline cube's orientation.""" MapsLock() [name, room] = get_name_and_room(flask.request.sid) i = info['order'] new_orientation = info['orientation'] rooms_info[room]["goal"][i]["orientation"] = new_orientation update_msg = { "type": "orientation", "order": i, "new_val": new_orientation, } emit("update_goalline", update_msg, room=room, include_self=False)
def update_cube_xpos(info): """Update a goalline cube's x position.""" MapsLock() [name, room] = get_name_and_room(flask.request.sid) i = info['order'] new_x_pos = info['x_pos_per_mille'] rooms_info[room]["goal"][i]["x"] = new_x_pos update_msg = { "type": "x_pos", "order": i, "new_val": new_x_pos, } emit("update_goalline", update_msg, room=room, include_self=False)
def handle_set_goal(): """Handle goal set.""" MapsLock() [name, room] = get_name_and_room(flask.request.sid) assert name == get_current_mover(room) if len(rooms_info[room]['goal']) == 0: emit("server_message", "You must set at least one cube on the goal!") return if rooms_info[room]["game_finished"]: return emit("hide_goal_setting_buttons", room=room) rooms_info[room]["goalset"] = True next_turn(room)
def handle_sector_click(sectorid): """Receive a click action on a playable area of the board.""" MapsLock() [name, room] = get_name_and_room(flask.request.sid) print(f"{name} clicked {sectorid} in room {room}") if rooms_info[room]["game_finished"] or rooms_info[room]["touched_cube"] is None: return if name != get_current_mover(room): print(f"Not {name}'s turn. Do nothing.") return if rooms_info[room]["bonus_clicked"]: if sectorid != "forbidden-sector": emit("server_message", "To bonus you must first place a cube in forbidden!", room=room) return else: move_cube(room, sectorid) rooms_info[room]["bonus_clicked"] = False return if not rooms_info[room]["goalset"]: if sectorid != "goal-sector": print(f"Goalsetter clicked on a non-goal area.") return elif sectorid == "goal-sector": print(f"Someone clicked on goal area but goal is already set") return move_cube(room, sectorid) if rooms_info[room]["goalset"]: next_turn(room)
def on_disconnect(): """Handle disconnect.""" print(f"Client {flask.request.sid} disconnected!") socketid = flask.request.sid MapsLock() if socketid not in socket_info: # Note: MapsLock makes register_player atomic, so we can safely say # in this case socket disconnected before register_player *started*. print("Socket disconnected before register_player started") return [username, room] = get_name_and_room(socketid) # Leave the room leave_room(room) # Update socket_info del socket_info[socketid] # Update room info if username in rooms_info[room]['spectators']: # Remove spectators on disconnect. But do not remove player until game finishes. rooms_info[room]['spectators'].remove(username) rooms_info[room]["sockets"].remove(socketid) if len(rooms_info[room]["sockets"]) == 0: # If all players and spectators leave, then game is considered finished even if it's not. # TODO Right now this is only way a game ends. When implement timing, may need to reconsider this. if not rooms_info[room]["game_finished"]: rooms_info[room]["game_finished"] = True db_insert(room, rooms_info[room]) for player in rooms_info[room]["players"]: assert player in user_info assert room in user_info[player]["gamerooms"] user_info[player]["gamerooms"].remove(room) del rooms_info[room] # Update user_info # On a refresh on a game page, due to delay in socket disconnect, new socket # (most likely) has connected by the time this code runs and (potentially) deletes # the user from the user_info dict. Even if not, worst thing that can happen # is that the game is marked as finished. assert room in user_info[username]["latest_socketids"].keys() user_info[username]["latest_socketids"][room].remove(socketid) if len(user_info[username]["latest_socketids"][room]) == 0: print(f"{username} is no longer connected to {room}") del user_info[username]["latest_socketids"][room] assert room in user_info[username]["room_modes"].keys() del user_info[username]["room_modes"][room] # If a player leaves before a game is started, then remove him/her as a player if room in rooms_info and not rooms_info[room]["game_started"] \ and username in rooms_info[room]["players"]: if room in user_info[username]["gamerooms"]: # Need if statement because could gotten removed above in update rooms_info user_info[username]["gamerooms"].remove(room) if room in rooms_info: rooms_info[room]["players"].remove(username) emit("player_left", rooms_info[room]["players"], room=room) if room in user_info[username]["gamerooms"] and room not in rooms_info: user_info[username]["gamerooms"].remove(room) # Unmap user if he/she is not in any rooms and he/she is not playing a game if len(user_info[username]["latest_socketids"]) == 0 and \ len(user_info[username]["gamerooms"]) == 0: del user_info[username] emit("server_message", f"{username} has left.", room=room) print(f"Client {socketid}: {username} left room {room}") else: print(f"Socket {socketid} for user {username} disconnected, but user " "still has another connection to the room open")
def handle_flip_timer(): """Player pressed flip_timer.""" MapsLock() [name, room] = get_name_and_room(flask.request.sid) print(f"{name} pressed flip_timer!")
def handle_claim_warning(): """Player pressed claim_warning.""" MapsLock() [name, room] = get_name_and_room(flask.request.sid) print(f"{name} pressed claim_warning!")
def handle_claim_minus_one(): """Player pressed claim_minus_one.""" MapsLock() [name, room] = get_name_and_room(flask.request.sid) print(f"{name} pressed claim_minus_one!")
def handle_game_over(): """The game is finished now.""" MapsLock() [_, room] = get_name_and_room(flask.request.sid) clean_up_finished_room(room)
def handle_challenge(socketid, challenge): """Handle a challenge.""" MapsLock() [name, room] = get_name_and_room(socketid) print(f"{name} pressed {challenge}") assert room in rooms_info if rooms_info[room][ "challenge"] is not None and challenge != "p_flub": # TODO can't p flub after 1st minute. TEST!! print(f"{name} tried to challenge {challenge} but was too late") return defender = get_previous_mover(room) challenge_message = f"{name} has called {challenge_translation[challenge]}" if challenge == "no_goal": challenge_message += "!" else: if not rooms_info[room]['goalset']: emit("server_message", "You cannot challenge; the goal has not been set!") return challenge_message += f" on {defender}!" sider = None if rooms_info[room]["started_move"]: # TODO minus 1? challenge_message += f" But {name} has started their move so {name} " \ f"must finish the move and cannot challenge!" emit("server_message", challenge_message, room=room) return elif defender == name: # TODO minus 1? challenge_message += f" But {name} just moved so {name} cannot challenge!" emit("server_message", challenge_message, room=room) return elif challenge != "no_goal" and len(rooms_info[room]['players']) == 3: sider_list = list( filter(lambda x: x != name and x != defender, rooms_info[room]['players'])) assert len(sider_list) == 1 sider = sider_list[0] challenge_message += f" {sider} has two minutes to side!" if challenge == "no_goal": if rooms_info[room]["goalset"]: no_goal_msg = f"{name} has called Challenge No Goal! But Goal has already been set, so challenge cannot be made!" # TODO minus one? emit("server_message", no_goal_msg, room=room) return else: assert defender is None if name != get_current_mover(room): no_goal_err_msg = f"{name} called Challenge No Goal but that is " \ f"not allowed because {name} is not setting the goal!" emit("server_message", no_goal_err_msg, room=room) return else: if not rooms_info[room]["goalset"]: assert defender is None emit("server_message", "Goal has not been set yet! You cannot challenge.", room=room) # TODO handle this return assert defender is not None if challenge == "a_flub": cubes_left = len([x for x in rooms_info[room]["resources"] if x != -1]) # TODO magic bad if cubes_left < 2: emit("server_message", f"{name} called Challenge Now but that is not allowed!", room=room) # TODO minus 1 return if challenge == "p_flub": pass # TODO Gotta make sure not called after first minute in a force out rooms_info[room]["challenge"] = challenge initialize_endgame(room, challenge, name, defender, sider) emit("server_message", challenge_message, room=room) challenge_info = { "challenge": challenge, "defender": defender, "caller": name, "sider": sider, "writers": rooms_info[room]["endgame"]["writers"], "nonwriters": rooms_info[room]["endgame"]["nonwriters"], } emit("handle_challenge", challenge_info, room=room)
def start_shake(new_game, is_restart): """Handle logic for starting a shake. new_game specifies if shake is the first shake in a game.""" MapsLock() [name, room] = get_name_and_room(flask.request.sid) print(f"{name} pressed start_game for room {room}!") if rooms_info[room]["game_finished"]: return if not new_game and rooms_info[room]["five_minute_warning_called"]: emit("server_message", "Five minute warning has been called so a new shake cannot be started.", room=room) return if room not in rooms_info or (new_game and rooms_info[room]['game_started']) \ or (not new_game and rooms_info[room]['shake_ongoing']): print("Game start rejected") return current_players = rooms_info[room]['players'] if len(current_players) < 2: emit('server_message', f"{name} clicked \"Start Game\" but a game can only be started with 2 or 3 players.", room=room) return assert name in current_players assert len(current_players) <= 3 random.seed(time.time()) rolled_cubes = [random.randint(0, 5) for _ in range(24)] if is_restart: # Goalsetter needs to be the same as the previous shake. rooms_info[room]['goalsetter_index'] = \ (rooms_info[room]['goalsetter_index'] - 1) % len(rooms_info[room]['players']) if new_game: rooms_info[room] = { "game_started": True, "game_finished": False, "players": current_players, "spectators": rooms_info[room]["spectators"], "sockets": rooms_info[room]["sockets"], "p1scores": [0], "p2scores": [0], "p3scores": [0], "starttime": time.time(), # cube_index is poorly named. fixed length of 24, index is cube's id. # first six are red, next six are blue, next six are green, last six are black. # so if the first element of cube_index was x (where 0 <= x <= 5), the # corresponding cube is given by the file "rx.png" (where x is replaced w/#) "cube_index": rolled_cubes[:], "resources": rolled_cubes, # fixed length of 24 "goal": [], # stores cube id, x pos on canvas, orientation of cube "required": [], # stores cube ids (based on cube_index); same for 2 below "permitted": [], "forbidden": [], "turn": random.randint(0, len(current_players) - 1), "goalset": False, "num_timer_flips": 0, "10s_warning_called": False, "challenge": None, "touched_cube": None, "bonus_clicked": False, "started_move": False, "endgame": None, "shake_ongoing": True, "five_minute_warning_called": False, "time_up": False, } rooms_info[room]['goalsetter_index'] = rooms_info[room]['turn'] game_begin_instructions = { 'cubes': rolled_cubes, 'players': current_players, 'starter': name, 'goalsetter': get_current_mover(room), 'starttime': rooms_info[room]['starttime'], } emit("begin_game", game_begin_instructions, room=room) else: rooms_info[room]["cube_index"] = rolled_cubes[:] rooms_info[room]["resources"] = rolled_cubes rooms_info[room]["goal"] = [] rooms_info[room]["required"] = [] rooms_info[room]["permitted"] = [] rooms_info[room]["forbidden"] = [] rooms_info[room]['goalsetter_index'] = \ (rooms_info[room]['goalsetter_index'] + 1) % len(rooms_info[room]['players']) rooms_info[room]["turn"] = rooms_info[room]['goalsetter_index'] rooms_info[room]["goalset"] = False rooms_info[room]["num_timer_flips"] = 0 rooms_info[room]["10s_warning_called"] = False rooms_info[room]["challenge"] = None rooms_info[room]["touched_cube"] = None rooms_info[room]["bonus_clicked"] = False rooms_info[room]["started_move"] = False rooms_info[room]["endgame"] = None rooms_info[room]["shake_ongoing"] = True goalsetter = get_current_mover(room) shake_begin_instructions = { 'cubes': rolled_cubes, 'players': current_players, 'goalsetter': goalsetter, 'show_bonus': not is_leading(room, goalsetter), } emit("begin_shake", shake_begin_instructions, room=room)
def on_disconnect(): """Handle disconnect.""" print(f"Client {flask.request.sid} disconnected!") socketid = flask.request.sid MapsLock() if socketid not in socket_info: # Note: MapsLock makes register_player atomic, so we can safely say # in this case socket disconnected before it *started*. print("Socket disconnected before register_player started") return [username, room] = get_name_and_room(socketid) # Leave the room leave_room(room) # Update socket_info del socket_info[socketid] # Update room info filter(lambda x: x != username, rooms_info[room]["spectators"]) rooms_info[room]["sockets"].remove(socketid) if len(rooms_info[room]["sockets"]) == 0: # If all players leave, then game is considered finished even if it's not. if rooms_info[room][ "game_started"] and not rooms_info[room]["game_finished"]: rooms_info[room]["game_finished"] = True db_insert(room, rooms_info[room]) for player in rooms_info[room]["players"]: assert player in user_info user_info[player]["gameroom"] = None del rooms_info[room] # Update user_info if room in user_info[username]["latest_socketids"] and \ user_info[username]["latest_socketids"][room] == socketid: print(f"{username} is no longer connected to {room}") del user_info[username]["latest_socketids"][room] # If a player leaves before a game is started, then remove him/her as a player current_game = user_info[username]["gameroom"] if current_game is not None: if current_game not in rooms_info: user_info[username]["gameroom"] = None elif not rooms_info[current_game]["game_started"]: user_info[username]["gameroom"] = None rooms_info[current_game]["players"].remove(username) # Unmap user if he/she is not in any rooms and he/she is not playing a game if len(user_info[username]["latest_socketids"]) == 0 and \ user_info[username]["gameroom"] is None: del user_info[username] emit("server_message", f"{username} has left.", room=room) print(f"Client {socketid}: {username} left room {room}") else: # User made a new connection to the room, so current socket is outdated print(f"Outdated socket {socketid} for user {username} disconnected")
def handle_start_game(): """Player pressed start_game.""" MapsLock() [name, room] = get_name_and_room(flask.request.sid) print(f"{name} pressed start_game for room {room}!") if rooms_info[room]["game_finished"]: return if room not in rooms_info or rooms_info[room]['game_started']: print("Game start rejected") return current_players = rooms_info[room]['players'] if len(current_players) < 2: emit('server_message', "You can only start a game with 2 or 3 players.", room=room) return assert name in current_players assert len(current_players) <= 3 assert not rooms_info[room]['game_started'] random.seed(time.time()) rolled_cubes = [random.randint(0, 5) for _ in range(24)] rooms_info[room] = { "game_started": True, "game_finished": False, "players": current_players, "spectators": rooms_info[room]["spectators"], "sockets": rooms_info[room]["sockets"], "p1scores": [0], "p2scores": [0], "p3scores": [0], "starttime": time.time(), "cube_index": rolled_cubes[:], # fixed length of 24, index is cube's id "resources": rolled_cubes, # fixed length of 24 "goal": [], # stores cube ids (based on cube_index); same for 3 below "required": [], "permitted": [], "forbidden": [], "turn": random.randint(0, len(current_players) - 1), "goalset": False, "num_timer_flips": 0, "10s_warning_called": False, "challenge": None, "touched_cube": None, "bonus_clicked": False, "started_move": False, } game_begin_instructions = { 'cubes': rolled_cubes, 'players': current_players, 'firstmove': rooms_info[room]['turn'], } emit("begin_game", game_begin_instructions, room=room) start_instruction = f"{name} started the game! The cubes have been rolled! \ {get_current_mover(room)} is chosen to be the goal setter. \ Move cubes by clicking a cube in resources, \ then clicking the area on the mat you want to move it to. \ If you touch a cube you must move it! \ Press \"Goal Set!\" when you're done!" emit("server_message", start_instruction, room=room)