import json import random from sqlalchemy.orm import Session from app.models import Answer, GameSession, Job, Player, Round from app.ral import option_payload WORK_LABELS = { "hybrid": "Hybrid", "remote": "Full remote", "onsite": "In-place only", } def playable_jobs(db: Session) -> list[Job]: return ( db.query(Job) .filter(Job.status.in_(["ready", "needs_salary"])) .filter((Job.ral_exact.isnot(None)) | (Job.ral_min.isnot(None))) .all() ) def start_game(db: Session, session: GameSession) -> Round: jobs = playable_jobs(db) if not jobs: raise ValueError("No playable jobs in the database. Add some from the admin panel.") hard = [j for j in jobs if j.difficulty >= 2] or jobs easy = [j for j in jobs if j.difficulty == 1] or jobs random.shuffle(hard) random.shuffle(easy) selected: list[Job] = [] hi = ei = 0 for i in range(session.num_rounds): bonus = (i + 1) % 3 == 0 pool = hard if bonus else easy idx_source = hi if bonus else ei job = pool[idx_source % len(pool)] if bonus: hi += 1 else: ei += 1 selected.append(job) for old in list(session.rounds): db.delete(old) db.flush() first = None for i, job in enumerate(selected, start=1): payload = json.loads(option_payload(job)) rnd = Round( session_id=session.id, job_id=job.id, round_number=i, is_bonus=(i % 3 == 0), options_json=json.dumps(payload["options"]), correct_option=payload["correct"], status="open" if i == 1 else "pending", ) db.add(rnd) if i == 1: first = rnd session.status = "playing" session.current_round = 1 db.commit() db.refresh(first) return first def current_round(db: Session, session: GameSession) -> Round | None: return ( db.query(Round) .filter(Round.session_id == session.id, Round.round_number == session.current_round) .first() ) def score_answer(rnd: Round, job: Job, choice: str, precise: int | None, work_type: str) -> int: points = 0 mc_ok = choice == rnd.correct_option if rnd.is_bonus: if mc_ok: points += 150 if precise is not None and job.ral_exact: points += 300 if precise == job.ral_exact else -50 if work_type and work_type == job.work_type: points += 50 else: if mc_ok: points += 100 if precise is not None and job.ral_exact: points += 200 if precise == job.ral_exact else -50 return points def submit_answer( db: Session, player: Player, rnd: Round, choice: str, precise: int | None, work_type: str, ) -> Answer: existing = ( db.query(Answer) .filter(Answer.round_id == rnd.id, Answer.player_id == player.id) .first() ) if existing: return existing if rnd.status != "open": raise ValueError("This round is closed") job = rnd.job points = score_answer(rnd, job, choice, precise, work_type) answer = Answer( round_id=rnd.id, player_id=player.id, choice=choice, precise_guess=precise, work_type_guess=work_type or "", points=points, ) db.add(answer) db.commit() db.refresh(answer) return answer def maybe_close_round(db: Session, session: GameSession, rnd: Round) -> bool: db.refresh(rnd) active = [p for p in session.players if p.connected] if not active: return False answers = db.query(Answer).filter(Answer.round_id == rnd.id).all() answered_ids = {a.player_id for a in answers} if not all(p.id in answered_ids for p in active): return False rnd.status = "revealed" for answer in answers: answer.player.score += answer.points db.commit() return True def advance(db: Session, session: GameSession) -> Round | None: nxt = ( db.query(Round) .filter(Round.session_id == session.id, Round.round_number == session.current_round + 1) .first() ) if not nxt: session.status = "finished" db.commit() return None nxt.status = "open" session.current_round += 1 db.commit() db.refresh(nxt) return nxt def public_job(session: GameSession, job: Job, bonus: bool) -> dict: description = job.description_clean if bonus: words = description.split() description = " ".join(words[: max(40, len(words) // 2)]) if len(words) > 40: description += " ..." data = { "title": job.title, "description": description, "has_exact": job.ral_exact is not None, "location": job.location, } if not session.hide_company: data["company"] = job.company if not session.hide_subject: data["subject"] = job.subject if session.extra_guesses: data["extra"] = job.extra_info return data