tilttek / quiz
Quiz games: questions of six kinds, a timer on an injected clock, scoring with a speed bonus, self-paced plays for members and live sessions with pseudonymous guests and teams, standings, a pseudonym filter, live updates through Mercure, and the events and Stimulus hook of a celebration.
Requires
- php: >=8.5
- ext-intl: *
- ext-mbstring: *
- psr/clock: ^1.0
- psr/event-dispatcher: ^1.0
Requires (Dev)
- phpunit/phpunit: ^13.4
- symfony/mercure: ^0.10
- twig/twig: ^3.21
Suggests
- symfony/mercure: To push the moves of a live session to the screens of its host and players, as private updates naming identifiers only
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-11-15 03:41:40 UTC
README
Quiz games: questions of six kinds, a timer on an injected clock, scoring with a speed bonus, self-paced plays for members and live sessions with pseudonymous guests and teams, standings, a pseudonym filter, live updates through Mercure, and the events and Stimulus hook of a celebration.
A quiz is a list of questions, each worth points and maybe timed. A member plays it at their own pace: each question comes when the previous one is answered, and the run ends after the last; the play is the member's alone, its results show once it is over, and only the member's first play of a quiz counts, the later ones being practice. A host plays it live: players join a session by its six-digit PIN, members or guests under a pseudonym, alone or in teams, and the host shows each question to all at once, closes it, shows the standings and moves on, every screen following through Mercure. A guest has no account; the pseudonym passes a word list first, and once the session is over, the application offers the guest to become a member, and their place and score follow them if they do.
Every answer is graded on the server, in thousandths so that partial credit stays exact, and scored by the quiz's
rule: accuracy by default, or speed, where a right answer earns a bonus that falls to nothing at the end of the time.
The time is read from the PSR-20 clock the application gives, never from the browser. The end of a run dispatches
QuizCompleted, then PerfectScore for a perfect one that counts and TeamWon for the winning teams; the result template and its
Stimulus controller turn them into the DOM events quiz:completed, quiz:perfect and quiz:team-won, which the
celebration of the PHIL UX kit plays on.
It is for an application that runs trivia nights, quizzes on a game's lore or rules, or a quick round between talks.
What is asked without a right answer — a poll, a vote — belongs to a poll library; a question bank kept elsewhere plugs
in through Contract\QuestionSource. It is a plain PHP library whose only dependencies are psr/clock and
psr/event-dispatcher, with ext-intl and ext-mbstring to compare typed answers; symfony/mercure is needed only
for the live updates.
Status
| Part | State |
|---|---|
Quiz, Scoring, ScoringMode, Grade, QuestionTimer, AnswerResult |
Implemented and tested |
Questions: SingleChoiceQuestion (true-or-false included), MultipleChoiceQuestion, OrderingQuestion, TypedAnswerQuestion, NumericQuestion |
Implemented and tested |
Answers: ChoiceAnswer, OrderAnswer, TextAnswer, NumberAnswer |
Implemented and tested |
SelfPacedQuizzes and SelfPaced\Play: members at their own pace, plays bound to their member, results after the end, practice replays, erasure |
Implemented and tested |
SelfPaced\Replays, SelfPaced\Feedback, SelfPaced\PerfectScoreRule |
Implemented and tested |
LiveQuizzes, Live\LiveSession, Live\LiveState: live sessions, PIN, guests and their claim by a member |
Implemented and tested |
Pin: the PIN a player types |
Implemented and tested |
Player, Leaderboard, Standing |
Implemented and tested |
Teams: Team\TeamLeaderboard, Team\TeamStanding, Team\TeamScoring |
Implemented and tested |
Pseudonym\WordListFilter with the library's short list, Pseudonym\ChainFilter |
Implemented and tested |
Mercure\LiveSessionPublisher: the moves of a live session pushed through Mercure |
Implemented and tested |
Events: QuizCompleted, PerfectScore, LiveQuizFinished, TeamWon, GuestInvited, PlayerJoined, QuestionShown, QuestionClosed |
Implemented and tested |
View\ResultView, templates/result.html.twig and the Stimulus controller result |
Implemented and tested |
Stores in memory and their contracts, Contract\QuestionSource, Contract\PseudonymFilter |
Implemented and tested |
Documentation
| Document | What it covers |
|---|---|
| Installation | Requirements, installing and wiring. |
| Questions and scoring | The kinds of questions, grades, the timer, accuracy and speed. |
| Playing | Plays at one's own pace, live sessions, guests and their pseudonyms, teams and standings. |
| Live updates | The moves of a live session pushed through Mercure. |
| Celebrations | The events, the result template and its Stimulus controller. |
| Storing | The store contracts, versions and concurrent answers. |
| Extension points | What an application gives the library. |
| Exceptions | QuizException and its exceptions. |
| Testing | The library's tests and benchmarks. |
| Reference | Every type of the library, with its role. |
Quick start
use Tilttek\Quiz\Answer\{ChoiceAnswer, NumberAnswer}; use Tilttek\Quiz\Question\{Choice, NumericQuestion, SingleChoiceQuestion}; use Tilttek\Quiz\Store\{InMemoryPlayStore, InMemoryQuizCatalog}; use Tilttek\Quiz\{Player, Quiz, Scoring, SelfPacedQuizzes}; $quiz = new Quiz('lore', [ new SingleChoiceQuestion('d20', 'How many faces has a d20?', [new Choice('a', 'Twelve'), new Choice('b', 'Twenty')], 'b'), new NumericQuestion('year', 'In which year was the first role-playing game published?', 1974, tolerance: 1), ], Scoring::speed(), timeLimit: 20); $quizzes = new SelfPacedQuizzes(new InMemoryQuizCatalog($quiz), new InMemoryPlayStore(), $dispatcher, $clock); $quizzes->start('play-7', 'lore', Player::member('member:44')); $quizzes->answer('play-7', 'member:44', ChoiceAnswer::of('b')); // null: nothing shows before the end $quizzes->answer('play-7', 'member:44', new NumberAnswer(1974)); // the last: its AnswerResult, QuizCompleted, then PerfectScore $quizzes->results('play-7', 'member:44'); // every AnswerResult: grade, points, bonus
Requirements
PHP 8.5 or later with ext-intl and ext-mbstring; psr/clock and psr/event-dispatcher. The template needs Twig,
and its controller Stimulus, only where the application uses them; the live updates need symfony/mercure.
Contributing
Contributions are welcome on the PHIL monorepo: read the contribution guide and the code of conduct. Report a vulnerability privately, as the security policy explains, never in a public issue.
License
MIT, © Philippe Gamache, doing business as Le Gnome Archiviste; a Tilttek project. See LICENSE.