Cherreads

Chapter 84 - codes 4

# quiz_app.py

from flask import (

Flask, render_template_string, request, redirect, url_for, session, jsonify

)

import secrets

import threading

from functools import wraps

app = Flask(__name__)

app.secret_key = secrets.token_hex(16)

# In-memory lobbies store (pin -> lobby data)

LOBBIES = {}

LOBBIES_LOCK = threading.Lock()

# Default questions (same as yours)

DEFAULT_QUESTIONS = [

{"q": "What's 2 + 2?", "choices": ["2", "3", "4", "5"], "answer": "4"},

{"q": "Which planet is known as the Red Planet?", "choices": ["Earth", "Mars", "Jupiter", "Venus"], "answer": "Mars"},

]

# --- Templates (kept simple and inline for single-file app) ---

T_INDEX = """

Mini Quiz - Home

Mini Quiz

Create a lobby or join one:

Host name:


Your name:

Lobby PIN:

"""

T_LOBBY_HOST = """

Lobby {{ pin }}

Lobby {{ pin }} (Host)

Share this PIN with players to join.

Players joined:

    {% for p in players %}

  • {{ p }}
  • {% endfor %}


Home

"""

T_LOBBY_PLAYER = """

Lobby {{ pin }}

Lobby {{ pin }}

Waiting for host to start the game...

Players:

    {% for p in players %}

  • {{ p }}
  • {% endfor %}

"""

T_PLAY = """

Question {{ idx+1 }}

{{ question['q'] }}

{% for c in question['choices'] %}


{% endfor %}

"""

T_WAIT = """

Waiting

Answer submitted — waiting for others...

You will be redirected when the quiz finishes.

"""

T_SCOREBOARD = """

Scoreboard

Scoreboard for Lobby {{ pin }}

{% for p,s in scoreboard %}

{% endfor %}

PlayerScore
{{ p }}{{ s }}


Home

"""

# --- Helpers ---

def require_in_lobby(f):

@wraps(f)

def wrapper(*args, **kwargs):

pin = kwargs.get("pin") or request.args.get("pin")

if not pin or 'name' not in session or session.get('pin') != pin:

return redirect(url_for('index'))

return f(*args, **kwargs)

return wrapper

def make_pin():

return secrets.choice('23456789ABCDEFGHJKMNPQRSTUVWXYZ') + secrets.token_hex(2).upper()

# --- Routes ---

@app.route("/", methods=["GET"])

def index():

return render_template_string(T_INDEX)

@app.route("/create", methods=["POST"])

def create():

name = request.form.get("name", "Host")

pin = make_pin()[:6]

with LOBBIES_LOCK:

LOBBIES[pin] = {

"host": name,

"players": [name],

"questions": DEFAULT_QUESTIONS.copy(),

"started": False,

"current_idx": 0,

"answers": {}, # player -> list of their given answers

"finished": False

}

session['name'] = name

session['pin'] = pin

return redirect(url_for("lobby", pin=pin))

@app.route("/join", methods=["POST"])

def join():

name = request.form.get("name", "Player")

pin = request.form.get("pin", "").strip().upper()

with LOBBIES_LOCK:

lobby = LOBBIES.get(pin)

if not lobby:

return f"Lobby {pin} not found. Back", 404

if name in lobby["players"]:

pass

else:

lobby["players"].append(name)

lobby["answers"][name] = []

session['name'] = name

session['pin'] = pin

return redirect(url_for("lobby", pin=pin))

@app.route("/lobby/")

@require_in_lobby

def lobby(pin):

with LOBBIES_LOCK:

lobby = LOBBIES.get(pin)

if not lobby:

return redirect(url_for('index'))

players = lobby["players"]

if session.get('name') == lobby["host"]:

return render_template_string(T_LOBBY_HOST, pin=pin, players=players)

else:

return render_template_string(T_LOBBY_PLAYER, pin=pin, players=players)

@app.route("/lobby//status")

def lobby_status(pin):

with LOBBIES_LOCK:

lobby = LOBBIES.get(pin)

if not lobby:

return jsonify({"error":"no lobby"}), 404

return jsonify({"started": lobby["started"]})

@app.route("/lobby//start", methods=["POST"])

@require_in_lobby

def start_lobby(pin):

name = session.get('name')

with LOBBIES_LOCK:

lobby = LOBBIES.get(pin)

if not lobby or lobby['host'] != name:

return "Forbidden", 403

lobby['started'] = True

# initialize answers dict for all players

for p in lobby['players']:

lobby['answers'].setdefault(p, [])

return redirect(url_for('play', pin=pin))

@app.route("/play/", methods=["GET","POST"])

@require_in_lobby

def play(pin):

name = session['name']

with LOBBIES_LOCK:

lobby = LOBBIES.get(pin)

if not lobby or not lobby['started']:

return redirect(url_for('lobby', pin=pin))

idx = lobby['current_idx']

if request.method == "POST":

choice = request.form.get("choice", "")

lobby['answers'][name].append(choice)

# advance only when *all* players have answered this question

qcount = len(lobby['questions'])

# check if all players have answers length > idx

all_answered = all(len(lobby['answers'].get(p, [])) > idx for p in lobby['players'])

if all_answered:

# move to next question or finish

if idx + 1 >= qcount:

lobby['finished'] = True

return redirect(url_for('scoreboard', pin=pin))

else:

lobby['current_idx'] += 1

# let players fetch next question

return redirect(url_for('play', pin=pin))

else:

# wait page for this player

return render_template_string(T_WAIT, pin=pin)

# GET: show current question to player

if lobby['finished']:

return redirect(url_for('scoreboard', pin=pin))

idx = lobby['current_idx']

question = lobby['questions'][idx]

return render_template_string(T_PLAY, question=question, idx=idx)

@app.route("/player_status/")

@require_in_lobby

def player_status(pin):

with LOBBIES_LOCK:

lobby = LOBBIES.get(pin)

if not lobby:

return jsonify({"error":"no lobby"}), 404

name = session['name']

finished = lobby.get('finished', False)

# detect if next question available for this player

next_available = len(lobby['answers'].get(name, [])) < (lobby['current_idx'] if not finished else len(lobby['questions']))

return jsonify({"finished": finished, "next": next_available})

@app.route("/scoreboard/")

@require_in_lobby

def scoreboard(pin):

with LOBBIES_LOCK:

lobby = LOBBIES.get(pin)

if not lobby:

return redirect(url_for('index'))

# compute scores

scores = []

for p in lobby['players']:

given = lobby['answers'].get(p, [])

score = 0

for i, q in enumerate(lobby['questions']):

if i < len(given) and given[i] == q['answer']:

score += 1

scores.append((p, score))

# sort descending

scores.sort(key=lambda x: x[1], reverse=True)

return render_template_string(T_SCOREBOARD, scoreboard=scores, pin=pin)

# Easy route to list active lobbies (dev only)

@app.route("/_debug/lobbies")

def list_lobbies():

with LOBBIES_LOCK:

return jsonify({k: {"host": v["host"], "players": v["players"], "started": v["started"]} for k,v in LOBBIES.items()})

if __name__ == "__main__":

app.run(debug=True)

More Chapters