feat: add boilerplate
This commit is contained in:
185
app/game.py
Normal file
185
app/game.py
Normal file
@@ -0,0 +1,185 @@
|
||||
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
|
||||
Reference in New Issue
Block a user