PlayerWords is a word-guessing vocabulary activity for Moodle. Students guess a hidden word letter-by-letter within a configurable number of attempts, receiving colour-coded and symbol feedback on each guess.
Use the sidebar to jump to any section on this page.
Source code: github.com/jeanlucio/moodle-mod_playerwords
✨ Features
- 🟩 Word-Guessing Gameplay: Colour-coded + symbol feedback per letter (correct position, wrong position, absent); the guess field shakes when a keystroke — physical or on-screen keyboard — is rejected for exceeding the target word’s length, instead of failing silently.
- 📖 Glossary Integration: Import concepts from one or all course glossaries as the word pool, with definitions used as hints.
- 🚧 Configurable Stopwords: A per-activity, comma-separated list of words (e.g. “the, of, and”) to ignore when splitting a multi-word glossary concept into candidates — so a concept like “Kingdom of the Netherlands” can still yield the meaningful term instead of also drawing “of” and “the” as separate one-off words.
- 🤖 AI Word Generation (Optional): Generate candidate words and hints for a given topic via
local_aihub(BYOK) or Moodle’score_aifallback. Generated words are treated as untrusted input — only single-token, purely alphabetic terms within the configured length bounds are saved, and they enter the pool pending teacher approval. - ✍️ Manual Word Pool: Teachers can add, edit, approve, and delete words directly from the management page.
- 🔀 Word Modes: Random word per round (default) or shared sequence mode, where every student receives the same words in the same order.
- 🎲 Word Rotation: In random mode, the same word never repeats on the very next round for a student, unless it is the only word left in the pool.
- 🚫 Duplicate Prevention: The same word text can only exist once per activity pool, regardless of which source added it — a manual entry blocks a colliding glossary import (and vice versa), so random selection is never skewed toward one accidentally-duplicated word.
- 🔤 Manual-Word Validation: A manually added or edited word must be letters only; the form rejects numbers, spaces, and special characters at input time instead of silently accepting a word the game could never actually draw.
- 🧩 Fragmented-Concept Warning: A multi-word glossary concept (e.g. “Second World War”) split into several single-word entries is flagged in the word management table, since each sibling word still carries the full original concept’s definition as its hint.
- 🔍 Live Eligible-Words Count: The settings form shows, live via AJAX, how many words currently fall within the configured length range — against the real pool while editing an existing activity, or a preview of the selected glossary’s content while creating a brand-new one — so a teacher never publishes a range that silently empties the pool.
- ⚠️ Inactive-Words Warning: The game screen tells whoever manages the activity, by name, which approved words are currently excluded from play — outside the configured length range, or containing a character the game cannot use — alongside a standing count of active words. A student never sees this.
- 🎯 Draw-Count Tracking: The word management table shows how many times each word has actually been drawn into a round, counted live from attempt history — no recompute step needed.
- 💡 Hidden Hint System: Hint is hidden by default; students must explicitly reveal it (optionally at an item cost via PlayerHUD).
- 🏳️ Give Up: Students can forfeit the current round at any time — the correct word is revealed immediately.
- ⏱️ Configurable Cooldown: Minimum wait between rounds (minutes, hours, or days), always recomputed from the activity’s current setting — a teacher’s change applies immediately, even to a cooldown already in progress.
- 🔢 Round Limit: Teachers can cap the total number of rounds per student (1–10 or unlimited). Students see a rounds-played counter (e.g. “3 / 10” or “3 / ∞”) both in the lobby and after each round.
- 🛡️ Round-Limit Integrity: A round abandoned mid-play (closed tab, lost session) still counts against the round limit — reserved the moment it starts, not only once it finishes, so it can never grant a free re-roll.
- 🔡 Accent-Insensitive Matching: Diacritics are always stripped before comparing guess and target.
- ⌨️ Accent Picker on the Virtual Keyboard: Long-pressing a vowel key (A, E, I, O, U) on the on-screen keyboard opens a popup with its accented variants, mirroring a mobile keyboard’s own long-press behaviour — sliding the finger picks a variant, releasing confirms it. Purely cosmetic: guess matching stays accent-insensitive throughout.
- ✅ True-Spelling Reveal: When a round ends (win, loss, or forfeit), the word is shown with its real, accented spelling from the word bank, even if the winning guess was typed without accents.
- 📊 Grading Methods: Highest grade, average grade, first attempt, last attempt, or average over all required rounds.
- ⚖️ Configurable Scoring Mode: Choose Binary (all-or-nothing) or Linear (proportional to attempts spared) independently for the grade and for the ranking — see Grading & Ranking. Locked once the activity has recorded a real grade, so every round is guaranteed to be scored under the same rules.
- 🧮 Grading Transparency: Students see the active grading method before playing and their live computed grade after each round, the same way mod_quiz communicates its own grading method.
- 📋 Gradebook Integration: Grades are written automatically on every round completion.
- ✅ Custom Completion Rule: Minimum number of attempts completed, evaluated and applied immediately after each round.
- 🔄 Course Reset Support: “Reset course” clears student attempts and resets grades for the activity, scoped to the target course only.
- 🏆 Top 5 Ranking: Leaderboard scoped to the activity, deliberately capped to the top 5 — never a public ranking of the whole class — with an outsider row so a lower-ranked student still sees their own real position. Respects
SEPARATEGROUPS. - 📋 Attempt History: Students can review every finished round of their own — word, attempts used, time, score, and date — plus their currently computed grade, at any time via the toolbar. Whoever can manage the activity sees every student’s history instead, in one paginated, sortable, per-student-filterable report.
- ❓ In-Game Help: A dedicated help page explains the letter-feedback colours, attempts, hints, timer, and the activity’s grading method.
- 👋 First-Visit Onboarding: The how-to-play modal opens automatically the very first time a user visits any PlayerWords activity on the site — once, ever, site-wide rather than per course or per instance — and never repeats after that; the toolbar help icon always reopens it on demand.
- ♿ Accessibility: WCAG AA contrast on all grid states; non-colour indicators (✓ correct, ~ present);
aria-labelon every cell; a live region announces state changes for screen readers. - ⚡ AJAX-Powered: Every round transition (guess, hint, forfeit, timeout, start, new round) happens without a page reload.
- 🎮 PlayerHUD Integration (Optional): Require inventory items to start a round or to reveal a hint, with atomic FIFO consumption. The student’s current balance against the required quantity is always shown up front, and the action is disabled — not just rejected after the click — when they can’t afford it; a cost pointing at a deleted or another course’s item is waived rather than locking the student out. Can also grant an item for each round won; matching PlayerHUD’s own anti-farming rule, no XP is awarded from that item while the activity allows unlimited rounds — the item is still delivered, just without XP — and the potential win-grant XP is reflected in PlayerHUD’s own “Total XP in the game” ceiling estimate.
- 🛡️ Safe Cross-Course Integration: Every PlayerHUD item reference is validated against the course’s own block instance, never a stale or another course’s item — even after backup/restore or course duplication. Settings preserve a disabled or deleted item as a clearly labelled option instead of silently resetting the field.
- 🔔 PlayerHUD Dependency Awareness: When PlayerHUD is installed on the site but hasn’t been added to the current course yet, the settings form explains that the integration will appear once the block is added. When the block_playerhud plugin isn’t installed anywhere on the site, the site administrator sees an equivalent notice on the plugin’s own settings page — mirroring the disclosure
filter_playerhudandavailability_playerhudalready give, without making PlayerHUD a hard installation requirement. - 📦 Backup & Restore: Full Moodle 2 backup/restore support, including the “Duplicate activity” action, word pool, attempts, user/glossary id remapping, and safe PlayerHUD item remapping (dropped rather than kept pointing at another course’s item when it isn’t part of the same restore).
- 🔐 Privacy API: GDPR/LGPD compliant — complete data export and deletion for all stored personal data.
🎓 Educational Purpose
PlayerWords is designed to:
- Reinforce learning of concepts covered in the course or subject
- Foster playful, game-based learning experiences
- Simplify and make learning and assessment dynamics more intuitive
- Contribute to achieving educational goals across different courses and disciplines
- Promote active learning methodologies, including game-based learning and gamification
- Support retrieval practice — students must recall a concept from memory before seeing any answer, one of the most effective techniques for long-term retention
- Encourage spaced practice — the configurable recharge time between rounds brings students back to the content across multiple sessions, aligning with the principles of spaced repetition
Suitable for:
- Any course that uses concept-based terminology
- Gamified academic courses using the PlayerGames ecosystem
- Formative assessment and self-study reinforcement
- Engagement reinforcement strategies
🕹️ PlayerGames Ecosystem
PlayerWords is part of the PlayerGames gamification ecosystem for Moodle. Its main direct integration is with the PlayerHUD block:
-
PlayerHUD Block (Optional): Configure item costs for starting a round or revealing a hint, and an item grant for each round won. 👉 https://github.com/jeanlucio/moodle-block_playerhud
-
PlayerGroup (Compatible): Standard Moodle groups — created manually or via the PlayerGroup activity — are honoured by the ranking’s
SEPARATEGROUPSfiltering. 👉 https://github.com/jeanlucio/moodle-mod_playergroup
See the author’s Moodle Plugins Directory profile for the full PlayerGames family.
📦 Requirements
| Component | Version |
|---|---|
| Moodle | 4.5+ |
| PHP | 8.1+ |
🛠️ Installation & Configuration
- Download the
.zipfile or clone this repository. - Extract the folder into your Moodle
mod/directory. - Rename the folder to
playerwords(if necessary). Final path:your-moodle/mod/playerwords/ - Visit Site administration > Notifications to complete installation.
- Add a PlayerWords activity to any course.
This plugin has no separate site-level settings to configure after installation — every setting is configured by the teacher when adding the activity to a course, as covered in Usage below.
📖 Usage
- Add a PlayerWords activity to your course.
- Configure:
- Word length range and maximum attempts
- Cooldown between rounds and round limit
- Word mode (random or shared sequence)
- Grading method and gradebook settings
- Word sources (manual, Glossary, AI), Glossary source, and a stopword list to skip when splitting multi-word glossary concepts (all optional)
- PlayerHUD item costs and win grant (optional, when PlayerHUD block is present)
- Open the Manage words page to add, generate with AI, approve, edit, or delete words.
- Students play directly from the activity page — guessing, revealing hints, and forfeiting rounds, with no page reload. The page’s own toolbar gives access to the rules (help), attempt history, and the ranking.
- Grades and ranking update automatically after each round.
🧮 Grading & Ranking
PlayerWords computes a grade and a ranking total from the same finished rounds, but the two are configured completely independently — a teacher can keep the grade simple while still rewarding efficient play in the ranking, or the other way around.
Both are entirely optional, and each is switched on or off on its own:
- Grade: leave the standard
Gradefield set to None to run the activity fully ungraded — no grade is ever computed or written to the gradebook, and theGrading method/Grade scoringsettings disappear from the form. - Ranking: leave
Show rankingset to No to hide the ranking everywhere — in-game, on the dedicated ranking page, and the extra column in the attempt history — and theRanking scoringsetting disappears from the form too.
Turning one off never affects the other: an activity can be graded with no ranking, ranked with no grade, both, or neither.
Per-round scoring decides how much a single round is worth, chosen separately for the grade
and for the ranking (Grade scoring / Ranking scoring settings, both default to Binary):
| Mode | A won round is worth… | A lost, forfeited, or timed-out round |
|---|---|---|
| Binary (default) | The full activity grade | Zero |
| Linear | Full credit on the first two attempts, then a share proportional to attempts spared: grade × (max_attempts − attempts_used + 1) / (max_attempts − 1) |
Zero |
Linear gives full credit on the first two attempts — a confident second guess is not treated as less deserving than a first-try one, since this is a non-punitive educational game rather than a luck-based guessing contest — then scales the remaining attempts down proportionally, never fully zeroing out a win: even winning on the very last allowed attempt still earns a positive share. Example with a 100-point grade and 6 maximum attempts:
| Attempts used | Linear points |
|---|---|
| 1 | 100.00 |
| 2 | 100.00 |
| 3 | 80.00 |
| 4 | 60.00 |
| 5 | 40.00 |
| 6 | 20.00 |
| Not completed | 0.00 |
With Maximum attempts set to 2 or fewer, Linear is numerically identical to Binary — every
allowed attempt already falls within the full-credit plateau.
Combining several rounds into one final grade is a separate setting, Grading method
(highest grade, average grade, first attempt, last attempt, or average over all required rounds).
It works the same regardless of whether the per-round scoring above is Binary or Linear: it only
ever aggregates whatever value each round already recorded.
The ranking is the sum of every finished round’s ranking points for a student (SUM),
ordered highest first; ties are broken by fewer attempts used on average, then less time spent on
average. It only appears when the teacher enables “Show ranking”, and never reveals a round still
in progress.
Only the top 5 are shown — deliberately, not a bug: both the in-game ranking widget and the dedicated ranking page cap the list at 5 rows, to avoid publicly ranking every student in the class. A student ranked lower still sees exactly where they stand: an extra row, separated by “…”, shows their own real position and score, without exposing anyone else’s rank below 5th. Anyone who can manage the activity (editingteacher, manager) never appears in the ranking at all, even if they play the activity themselves — the same way their own attempts are excluded from the attempt report below.
“Show ranking” only controls visibility, not data collection: ranking points are computed and stored for every finished round regardless of whether the setting is on or off at the time. Turning it on after students have already played reveals the full total accumulated since the activity started, not just the points earned from that moment forward — nothing is lost, and nothing needs to be “recovered” by switching it off and back on.
Locked once graded: the moment the activity records a real grade for any student, Maximum
attempts, Grade scoring and Ranking scoring all lock — the same way Moodle already locks a
graded activity’s own “Maximum grade” field once real grades exist. This guarantees every round
ever recorded for that activity was scored under the exact same rules, so the grade and the
ranking total both stay internally consistent for the activity’s whole lifetime.
Attempt history: each student can review their own past rounds — word, attempts used, time, grade score and (when ranking is enabled) ranking points — from the toolbar’s attempt-history page. Whoever can manage the activity sees the same page turn into a report covering every student instead: one table, 30 rows per page, sortable by clicking any column header, and filterable to a single student. Like the ranking, it never includes a manager’s own attempts, even if they played the activity themselves.
🧪 Automated Tests
PlayerWords ships with a PHPUnit test suite covering business logic, repository queries, web services, and Privacy API compliance. Every CI push runs against the full matrix (Moodle 4.5 → 5.x, PostgreSQL & MariaDB).
PHPUnit — Core Tests
| Test file | Cases |
|---|---|
backup_restore_test.php |
5 |
cross_instance_security_test.php |
4 |
lib_grant_potential_test.php |
6 |
lib_reset_userdata_test.php |
4 |
completion/custom_completion_test.php |
7 |
privacy/provider_test.php |
14 |
| Subtotal | 40 |
Local Business-Logic Tests (tests/local/)
| Test file | Cases |
|---|---|
ai_word_generator_test.php |
12 |
attempts_history_service_test.php |
14 |
gameplay_service_test.php |
19 |
hud_service_test.php |
22 |
intro_service_test.php |
5 |
ranking_service_test.php |
6 |
round_presenter_test.php |
35 |
round_service_test.php |
30 |
view_page_service_test.php |
16 |
word_normalizer_test.php |
16 |
words_repository_test.php |
56 |
| Subtotal | 231 |
Web Services Tests (tests/external/)
| Test file | Cases |
|---|---|
count_eligible_words_test.php |
5 |
count_glossary_candidates_test.php |
4 |
end_round_test.php |
4 |
new_round_test.php |
3 |
reveal_hint_test.php |
6 |
start_round_test.php |
5 |
submit_guess_test.php |
7 |
| Subtotal | 34 |
| Grand Total | 305 |
vendor/bin/phpunit --testsuite mod_playerwords
Overall line coverage (moodle-coverage, PHPUnit + Xdebug): 63%.
Full test-by-test breakdown and coverage table →
🔐 Security & Compliance
- Capability-based access control (
mod/playerwords:view,mod/playerwords:addinstance) require_sesskey()protection on all POST actions; AJAX calls are validated by Moodle’score/ajaxdispatcher- Server-side enforcement of round limits and cooldown, always recomputed from current settings
- Round timeout is re-validated against the server’s own deadline (with a small network-latency tolerance) instead of trusting the client’s countdown alone
- Guess charset validation — only Unicode letters accepted
- AI-generated words are treated as untrusted input: only single-token, alphabetic terms within the configured length bounds are saved, and they enter pending teacher approval
- Session round state is isolated per activity instance and per user — a word id or session key from one activity is never accepted by another
- Moodle External API compliant
- Privacy API fully implemented (GDPR/LGPD)
🔒 Third-party Service Disclosure
AI word generation is optional and disabled by default. When a teacher uses it, the
activity topic (never student data or attempt records) is sent through local_aihub — using
that user’s or the site’s own BYOK key, if the plugin is installed — or, as a fallback,
through Moodle’s own core AI subsystem (core_ai), which routes to whatever provider the
site administrator has configured. PlayerWords never contacts an AI provider directly; the
request and its disclosure/consent are entirely owned by local_aihub or by core_ai. If
neither is installed or configured, the AI word source is unavailable and every other feature
keeps working normally.
- Cost: None required by PlayerWords itself. If used, any cost is whatever the underlying
provider charges through a
local_aihubBYOK key, or nothing at all via a free/institutionalcore_aiprovider the site admin may have already configured. - API keys / credentials: Not configured in PlayerWords. Obtain and configure a personal or
site key inside
local_aihub(see its own documentation), or ask the site administrator to configure acore_aiprovider instead. - Demo credentials: Not applicable — no credentials are required to install or use PlayerWords; AI generation is entirely opt-in.
📄 License
This project is licensed under the GNU General Public License v3 (GPLv3).
Copyright: 2026 Jean Lúcio