Skip to content

Commit cd77f67

Browse files
quiz: split bank into two host-selectable 15-question pools
The competition runs as two back-to-back sittings with different students, so the bank is now two independent pools of matched shape: 5 astronomy (2 image IDs) + 10 Indian space technology = 15 each. Pools are host-side only. Players register and wait exactly as before; the reveal payload carries a per-pool index and a total of 15 either way, so nothing about pool selection reaches the client. Switching is refused while a question is open, clears every answer, and drops the room back to lobby. Mongo snapshots are keyed per pool so the first sitting's leaderboard survives the second. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 8c12d25 commit cd77f67

6 files changed

Lines changed: 364 additions & 123 deletions

File tree

‎sast-backend/models/QuizSession.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const mongoose = require('mongoose');
1111
const QuizSessionSchema = new mongoose.Schema(
1212
{
1313
sessionId: { type: String, required: true, unique: true, index: true },
14+
poolId: { type: Number, default: 1 },
1415
phase: { type: String, enum: ['lobby', 'live', 'ended'], default: 'lobby' },
1516
questionCount: Number,
1617
lastQuestionIndex: Number,

‎sast-backend/quiz/engine.js‎

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
const crypto = require('crypto');
2-
const { QUESTIONS } = require('./questions');
2+
const { getPool, DEFAULT_POOL } = require('./questions');
33

44
/**
55
* Live quiz state machine.
@@ -29,8 +29,11 @@ const norm = (s) => String(s ?? '').trim().replace(/\s+/g, ' ');
2929
const rollKey = (s) => norm(s).toUpperCase();
3030

3131
class QuizEngine {
32-
constructor({ questions = QUESTIONS, now = () => Date.now() } = {}) {
33-
this.questions = questions;
32+
constructor({ questions, pool = DEFAULT_POOL, now = () => Date.now() } = {}) {
33+
// An explicit `questions` bank wins (tests use it); otherwise the engine
34+
// is backed by one of the two competition pools.
35+
this.poolId = questions ? null : Number(pool);
36+
this.questions = questions || getPool(pool);
3437
this.now = now;
3538
this.reset();
3639
}
@@ -47,6 +50,35 @@ class QuizEngine {
4750
return this.questions.length;
4851
}
4952

53+
/**
54+
* Load one of the competition pools.
55+
*
56+
* Refused while a question is open — swapping the bank out from under a
57+
* live round would invalidate the answers already in flight. Registered
58+
* players are kept (the host may have a room already waiting) but their
59+
* answers are dropped, since indices mean something different in the new
60+
* pool. Use reset() to clear the roster too.
61+
*/
62+
setPool(id) {
63+
if (this.round && !this.round.closed) {
64+
return { error: 'Close the current question before switching pools.' };
65+
}
66+
const next = Number(id);
67+
const questions = getPool(next);
68+
this.poolId = next;
69+
this.questions = questions;
70+
71+
this.phase = 'lobby';
72+
this.round = null;
73+
this.startedAt = null;
74+
this.endedAt = null;
75+
for (const p of this.players.values()) {
76+
p.answers.clear();
77+
p.ready = false;
78+
}
79+
return { poolId: this.poolId, total: this.total };
80+
}
81+
5082
/* ── players ─────────────────────────────────────────────── */
5183

5284
join({ name, roll }) {
@@ -274,6 +306,7 @@ class QuizEngine {
274306
}
275307
return {
276308
phase: this.phase,
309+
poolId: this.poolId,
277310
total: this.total,
278311
players: this.players.size,
279312
connected,

0 commit comments

Comments
 (0)