Browser-based practice quizzes with six subjects, playable solo or as a
multiplayer match. No build step and no dependencies — open practice.html.
| Subject | Content | Filter by |
|---|---|---|
| APUSH | 232 questions | Period 1–9 |
| AP Gov | 257 questions | The nine required foundational documents |
| AP Gov: 2.3-2.8 | 98 questions over 56 key terms | Institution and difficulty |
| AP Gov: 2.9-2.15 | 165 questions over 90 key terms | Area and difficulty |
| AP Gov: 3.1-3.8 | 162 questions over 78 terms and cases | Area and difficulty |
| Prefix & Suffix Practice | 115 questions over 62 affixes | Difficulty and prefix/suffix focus |
Single player runs at your own pace: answer, read the feedback, continue.
Multiplayer is peer-to-peer over WebRTC — no server, no accounts. One player hosts and shares a five-character room code or an invite link; everyone answers the same question each round, sees who has locked in, and gets a standings board and a powerup shop between rounds. Players can join a match already in progress, and a dropped connection reconnects into the same seat with its score intact.
Scoring is shared across all six subjects. With the timer on, a correct answer is worth 60–100 points depending on speed; with it off, every correct answer is worth 100. Streaks count consecutive correct answers, and your best streak per subject is remembered locally.
practice.html markup and screens
styles.css all styling
app.js screens, solo play, and the multiplayer client
match.js the authoritative match rules: phases, scoring, powerups
net.js transport: room codes, heartbeats, clock sync, reconnects
questions.js APUSH + AP Gov banks
gov-branches-data.js AP Gov 2.3-2.8 key terms and question bank
gov-policy-data.js AP Gov 2.9-2.15 key terms and question bank
gov-liberties-data.js AP Gov 3.1-3.8 key terms and question bank
affix-data.js prefix/suffix vocabulary and question bank
prefix_suffix_vocabulary.csv source of truth for the affix vocabulary
tools/validate-content.js content checker
tools/test-match.js match rule tests
Content and engine stay separate: no question text lives in app.js, and no
game logic lives in the banks. match.js and net.js keep that going — rules
with no DOM, transport with no rules.
The <script> and <link> tags carry a ?v= stamp. There is no build step,
so bump it whenever markup, styling and scripts change together, or a browser
holding one of them from cache will mix old and new.
The host is the only authority. It runs a Match from match.js and is the
single place scores, phase changes and powerups are decided. Everyone —
including the host — then draws from the snapshots it publishes:
client --{answer | buy}--> host --> Match --> {sync | event} --> every client
That shape is what keeps four browsers agreeing. A few consequences worth knowing:
- Nobody scores themselves. A client reports which option it picked and how much time its own clock showed; the host caps that against what it actually observed and works out the points. Two players cannot drift apart.
- The answer key is withheld. A question snapshot carries the options but not the answer or the explanation; both appear only once the round is revealed. Powerups that grey out wrong options are resolved host-side for the same reason.
- Deadlines are absolute and clock-corrected. The host stamps a deadline in its own time; clients estimate the offset from ping round trips (keeping the lowest-latency sample) and re-sync on every snapshot, so a slow link loses latency rather than accumulating drift.
- Nothing waits forever. A round ends when every connected player has answered or the deadline passes. A player who disconnects is dropped from the count within a few seconds, and one who joins mid-question sits that round out instead of holding it open.
- Identity survives a reconnect. Players are keyed by a per-tab id, not by the WebRTC peer id, which changes on every reconnect. Coming back restores the same seat and score. Two tabs of one browser count as two players, which is the easiest way to try a match on your own.
net.js handles the wire: namespaced room codes on the public PeerJS broker,
heartbeats, reconnection with backoff, and a TURN relay fallback — without one,
a fair share of networks (school Wi-Fi especially) cannot establish a peer
connection at all. To use your own relay instead of the public one, replace the
credentials in ICE_SERVERS at the top of that file.
The rules are testable because they have no DOM and no network, and take the clock as an argument:
node tools/test-match.jsThat covers scoring and speed bonuses, timeouts, mid-match joins, disconnects and reconnects, every powerup, the purchase cap, and the guarantee that a question snapshot never contains its own answer.
APUSH / AP Gov — append a row to the matching array in questions.js. The
correct answer is always written first; the engine shuffles the options at
runtime.
[3, "Question text?", "Correct answer", "Wrong 1", "Wrong 2", "Wrong 3"]AP Gov: 2.3-2.8 — append an object to govBranchesBank
in gov-branches-data.js.
{
id: "gb143", term: "gridlock", branch: "congress", topic: "2.3",
questionType: "definition", difficulty: "medium",
question: "As the AMSCO text uses the term, gridlock refers to ...",
options: ["opposing forces congesting the process ...", "...", "...", "..."],
answer: "opposing forces congesting the process ...",
explanation: "Gridlock describes the jam itself rather than any single rule ..."
}branchiscongress,presidency,judiciary, ormixedfor items that turn on two or three institutions at once. It drives the Institution filter, and a session is drawn round-robin across branches so no one institution dominates.difficultyiseasy,medium, orhard; a session ramps easy → hard.questionTypeis one ofdefinition,application,scenario,compare,document,case,process. The engine spreads types out inside each difficulty band so consecutive questions do not feel identical.termmust match an entry ingovBranchesVocab(usemixedfor items that compare several terms), andtopicis the AMSCO topic number.
govBranchesVocab is the key-term list, taken from AMSCO United States
Government & Politics, AP Edition (2022), pages 124–187 — Topic 2.3
(congressional behavior) through Topic 2.8 (the judiciary). Edit that list
first if the source changes.
Question ids are not contiguous. They are identity keys, and the gaps are where Topics 2.1 and 2.2 used to sit; reusing a number would collide with the recently-seen list a returning player already has stored, so keep counting up.
AP Gov: 2.9-2.15 — append an object to govPolicyBank in
gov-policy-data.js. Same shape as the bank above, with area in place of
branch.
{
id: "gp166", term: "rule of four", area: "courts", topic: "2.10",
questionType: "definition", difficulty: "easy",
question: "Under the rule of four, an appeal is added to the docket when ...",
options: ["four of the nine justices agree to hear it", "...", "...", "..."],
answer: "four of the nine justices agree to hear it",
explanation: "Four is deliberately less than a majority, so a bloc that ..."
}areaiscourts(Topics 2.9–2.10, precedent and the Court in action),checks(Topic 2.11, the other branches' limits on the judiciary),bureaucracy(Topics 2.12–2.13, structure and rule making),oversight(Topics 2.14–2.15, accountability), ormixedfor items that turn on the courts and the bureaucracy at once. It drives the Area filter, and a session is drawn round-robin across areas.difficulty,questionTypeandtermwork exactly as in the 2.3-2.8 bank;termmust match an entry ingovPolicyVocab, or bemixedfor items that compare several concepts.
govPolicyVocab is the key-term list, taken from the same AMSCO edition,
pages 188–243 — Chapter 6 Topics 2.9–2.11 (the judiciary's legitimacy) and
Chapter 7 Topics 2.12–2.15 (the bureaucracy). Edit that list first if the
source changes.
Ids run gp001 upward with no gaps so far. Keep counting up rather than
filling any that open later, for the same reason as above.
AP Gov: 3.1-3.8 — append an object to govLibertiesBank in
gov-liberties-data.js. Same shape as the 2.9-2.15 bank.
{
id: "gl163", term: "prior restraint", area: "expression", topic: "3.4",
questionType: "scenario", difficulty: "medium",
question: "A judge orders a newspaper not to print a story until after ...",
options: ["prior restraint, which the Court presumes is invalid", "...", "...", "..."],
answer: "prior restraint, which the Court presumes is invalid",
explanation: "Stopping publication in advance is the one thing the press ..."
}areaisrights(Topic 3.1, the Bill of Rights),religion(Topic 3.2),expression(Topics 3.3–3.4, speech and press),safety(Topics 3.5–3.6, the Second, Fourth and Eighth Amendments),dueprocess(Topics 3.7–3.8, incorporation and the rights of the accused), ormixedfor items that line up cases from several topics. It drives the Area filter, and a session is drawn round-robin across areas.topicmust be one of3.1through3.8;difficulty,questionTypeandtermwork as in the other AP Gov banks.termmust match an entry ingovLibertiesVocab, or bemixed.
govLibertiesVocab is taken from AMSCO United States Government & Politics,
AP Edition (2022), pages 260–328 — Chapter 8 (Topics 3.1–3.4), Chapter 9
(Topics 3.5–3.6) and Chapter 10 (Topics 3.7–3.8). Each entry records the book
page it comes from and whether it is one of AMSCO's own "Key Terms and Names"
(keyTerm: true) or a supporting case from the same pages. The validator fails
if any AMSCO key term has no question. Edit that list first if the source
changes.
Ids run gl001 upward. Keep counting up, as with the other banks.
Prefix & Suffix — append an object to affixBank in affix-data.js.
{
id: "af116", affix: "ante", type: "prefix",
questionType: "meaning", difficulty: "easy",
question: "What does `ante-` most closely mean?",
options: ["before", "after", "against", "around"],
answer: "before",
explanation: "`ante-` means \"before\" ..."
}typeisprefix,suffix, ormixed, and drives the Focus filter.difficultyiseasy,medium, orhard; a session ramps easy → hard.affixmust match an entry inaffixVocab(usemixedfor review items).- Text in
backticksrenders as inline code. Question data holds no HTML.
The vocabulary in affixVocab is generated from
prefix_suffix_vocabulary.csv; edit the CSV first if the word list changes.
After editing any bank:
node tools/validate-content.js(and after touching match.js, node tools/test-match.js.)
It checks that every question has exactly four distinct options, that the answer is among them, that ids and question stems are unique, and that every affix or key term referenced actually exists — then prints coverage by difficulty, question type, affix, institution and area. It also warns if any filter pairing has no questions, since that pairing would hand the setup screen an empty pool, if a bank's correct answer is the longest option often enough to be guessable without reading the question, and if two AP Gov questions share so much wording that one looks like a clone of the other.