⚠️ This plugin is under active development. It is not yet published on the Moodle Plugin Directory. Some features described below are planned and not yet implemented — see Features for exactly which is which.
PlayerPuzzle (mod_playerpuzzle) is a turn-based Match-3 RPG gamified activity for Moodle:
the student combines pieces on an 8×8 board to attack a boss controlled by a simple AI, answering
questions pulled from the course’s own Moodle Question Bank to land critical hits. The teacher
chooses between a self-contained Single Match or a Campaign of up to 10 levels (10 phases
each); optional permanent progression (coins, sword/shield level) is handled entirely by the
companion block_playerhud when it’s present in the course — PlayerPuzzle keeps no
economy of its own.
👈 Use the sidebar to jump to any section on this page.
🖼️ Screenshots
Browse with the arrows, the dots, or the left/right keys. Click the image to zoom.
The Lobby: current Campaign progress, the minimum-questions notice and the Play button, before the student ever enters the match
✨ Features
✅ Implemented
- ⚔️ Match-3 Turn-Based Combat: An 8×8 board (Phaser 3) where the student swaps pieces to attack a boss. Combining Question Orb pieces charges a question challenge; a correct answer deals extra damage. See How Combat Works.
- 🔄 Symmetric Boss AI: All seven pieces resolve exactly the same way on the boss’s own turn as they do on the student’s — the boss deals damage, stacks a critical-hit multiplier, poisons the student, shields itself, heals, and banks its own gold to compensate the student’s final balance, instead of most pieces being inert whenever the AI happens to match them.
- 🎮 Two Game Modes: Campaign (a continuous run through up to 10 levels × 10 phases each,
where winning never costs a new attempt — only losing does) and Single Match (a self-
contained round reusing the
grademethodpattern from the PlayerGames ecosystem). See Game Modes. - 📈 Linear HP Scaling: Boss and student HP scale with the current level/phase using a fixed
formula (
base × (1 + rate × (level−1) + rate × (phase−1))), computed once server-side and never recalculated in the client. - 🔄 Campaign Resume: Closing the browser mid-campaign and coming back resumes the same level/phase instead of restarting at Level 1 — the server rotates the anti-replay token and preserves progress transparently.
- 🎯 Real Question Bank Integration: Questions are pulled from Moodle’s own Question Bank
(
core_question), reusing categories the teacher already has in the course — see Question Engine. - 🛡️ Three-Pillar Anti-Cheat: Blind JSON (the correct answer never reaches the client until the server validates it), server-side damage clamping against the phase-scaled boss HP, and single-use anti-replay tokens rotated on every state-changing call.
- 🗨️ Accessible Question Modal: The question challenge renders as a native HTML
<dialog>— focus trapping, ESC handling and centering all come from the browser itself, with focus saved and restored to whatever triggered it. - 📊 HUD Progress Indicator: “Level X — Phase Y of 10” shown during Campaign combat.
- 💰 PlayerHUD Economy Integration: Fully optional. When block_playerhud is present in the course, the teacher picks which of the block’s own items represent coins, sword level and shield level — winning a match credits coins, and a configurable item can be granted on every victory (with XP suppressed once the attempt limit is set to unlimited, to prevent farming). Without PlayerHUD, the plugin still works standalone with no permanent progression.
- 🏛️ Lobby: Shows the student’s PlayerHUD balance (only the items the teacher configured), current Campaign progress, and the configured minimum-questions notice before the match starts.
- 🔐 Privacy (GDPR): Complete Privacy Provider — metadata declaration, export and deletion of all stored personal data.
- 🧪 Automated Tests: 104-case PHPUnit suite, green across the CI matrix (Moodle 4.5 → 5.2) — see the Automated Tests section.
⏳ In Development / Planned
- 🧮 PlayerHUD Combat Multiplier:
hud_service::get_upgrade_level()already reads the student’s sword/shield level, butcombat.jsdoes not yet apply it as a damage/defense multiplier during a match. - 🎬 Post-Match Debrief: A non-automatic review screen after winning a phase — every question
answered, correct vs. chosen, damage dealt, coins earned — with “Play Next Phase” / “Exit”
buttons. The
advance_phaseendpoint that powers the “next phase” transition already exists; the screen itself does not yet. - 🛍️ In-Combat Consumables: Shield, Quick Magic, Hint and Potion, purchasable mid-match with a session-local coin balance (Potion will also be consumable from a permanent PlayerHUD stock).
- ❤️🩹 Boss Revive / Minimum Questions Enforcement: The “minimum questions per match” setting already exists in the activity form and shows a notice in the Lobby, but the actual gameplay rule (reviving the boss at 50% HP if the minimum hasn’t been reached yet) is not wired up yet.
- 🎓 Gradebook Integration:
grade_item_update()/update_grades(), scaled by the configured Maximum Grade — the two grading formulas (progress-based for Campaign,grademethod-based for Single Match) are designed but not yet pushed to the Moodle gradebook. - ✅ Custom Completion Rules:
FEATURE_COMPLETION_HAS_RULESis currently declaredfalse(honestly, rather than advertising a broken feature) until the real implementation lands. - 💾 Backup & Restore:
FEATURE_BACKUP_MOODLE2is currentlyfalsefor the same reason. - 📋 Teacher Report: A dedicated report page summarizing class performance.
- ♿ Full Accessibility Layer: A parallel, always-present HTML layer (a visually-hidden
<table role="grid">mirroring the board,aria-liveturn-by-turn announcements, number-key input1–9) for screen-reader play without touching the Canvas. The question dialog already follows this design; the rest of the combat loop does not yet. - 📚 Own Question Bank + AI Generation: A second, PlayerPuzzle-owned question source
(manual entry or AI-assisted generation via
local_aihub), coexisting with the real Question Bank rather than replacing it. - 🏆 Ranking: A separate leaderboard (not tied to the configured grade), with its own aggregation per game mode.
- 🥊 Dispute Mode (V2): A future head-to-head mode — architecture not finalized yet.
The plugin is Alpha-stage software: everything in "Implemented" above works today and is covered by the automated test suite; everything in "Planned" is designed (see the project's internal roadmap) but not yet built.
⚔️ How Combat Works
Combat runs on an 8×8 Match-3 board (Phaser 3), portrait-first (540×960), adapting to a landscape layout on wider screens.
Turn Structure
- The student makes a move on the board.
- Combos and cascades keep the student’s turn going.
- Once the board settles, it becomes the Boss’s turn: a simple AI scans the board and executes one valid swap. Whatever it matches resolves through the exact same seven pieces and effects as the student’s own turn (see below) — the boss can deal damage, build up a multiplier, poison the student, shield itself, heal, and bank gold, exactly like the student does on their own turn.
The Seven Pieces
Every effect is symmetric: a piece does the same thing no matter which side is playing it — only who benefits changes (whoever’s turn it currently is).
| Piece | Effect |
|---|---|
| ⭐ Star | Adds +0.1 to a damage multiplier, stacking across turns; consumed on that side’s critical hit and reset to 1 when that side answers a question wrong |
| 📖 Spellbook | Fills a 0–100 poison meter of its own (+10 per piece); on reaching 100, arms 3 damage rounds against the opponent (one per their own turn) and resets, carrying over any overshoot |
| ❓ Question Orb | Fills a 100-point energy bar of its own for each side; reaching 100 pauses combat and opens the Question Challenge — the student answers for real, the boss “answers” by picking an option at random, validated the same way server-side |
| ⚔️ Swords | Direct damage to the opponent — no question needed. Scales with level/phase, and a 4- or 5-piece match deals 1.5x/2x the damage of a 3-piece match |
| 🛡️ Shield | Fills a 0–100 meter of its own (+10 per piece); on reaching 100, negates the next hit that side takes in full, no matter how large, then resets |
| 🧪 Potion | Heals that side’s own HP — scales the same way Sword damage does, at half the rate of a same-size Sword match |
| 🪙 Coin | Adds to that side’s own gold count for the match — see the combo formula and net compensation in PlayerHUD integration |
The Question Challenge
When either side’s Question Orb bar reaches 100%, the match pauses and opens the question dialog:
- Correct answer — a critical hit: triple base damage, multiplied by whatever Star bonus that side has stacked up.
- Wrong answer — that side takes damage, and its Star multiplier resets to 1.
This is the moment the plugin ties Match-3 skill to actual content knowledge: matching pieces well fills the bar and builds up the multiplier, but the real payoff only lands if the student also answers correctly.
Server-Side Truth
Every number that governs combat — the boss’s and student’s maximum HP, and the base damage figure Sword/Star/Spellbook/Potion all scale from — is computed once on the server for the current level and phase (see Game Modes for how these scale) and handed to the client as configuration; the client never recalculates any of it. Damage the client reports back is clamped server-side against that same phase-scaled boss HP before it is accepted — see Security & Anti-Cheat.
🎯 Question Engine
Source
PlayerPuzzle reads questions from Moodle’s own Question Bank (core_question) — the
teacher picks an existing category in the activity’s settings, and any Multiple Choice or
True/False question in it can be served during combat. This means a teacher who already
maintains a question bank for Quiz can point PlayerPuzzle at the same category and reuse it
immediately, with no content migration.
A second, PlayerPuzzle-owned question source (manual entry plus AI-assisted generation) is planned to coexist alongside the real Question Bank — see Features.
Blind JSON — the Answer Never Leaks
The question text and every answer option are sent to the browser, but the correct answer id
is not. question_fetcher.php strips it server-side before the question ever reaches the
client. When the student submits a choice, mod_playerpuzzle_validate_answer checks it against
the real record in the database and returns only a boolean plus (on a wrong answer) the correct
option — so nothing in the page source or network traffic ever reveals the answer in advance.
This is the first of the plugin’s three anti-cheat pillars.
Minimum Questions Per Match
The activity settings let a teacher require a minimum number of questions to be answered before a match can end in victory, and the Lobby shows this requirement to the student before they start. The gameplay rule that enforces it — reviving the boss at half HP if the minimum hasn’t been reached yet — is not wired up yet; see Features.
🎮 Game Modes
The teacher picks one mode per activity instance.
Campaign
A continuous run through up to 10 levels, each split into 10 phases. Winning a phase never costs a new attempt — only losing (or timing out) does, and the configured Max Attempts limit only counts finished (lost/timeout/abandoned) attempts, never a phase won along the way.
- Resuming: closing the browser mid-campaign and coming back resumes the exact level/phase the student was on — the server rotates the anti-replay token and picks up the most recent in-progress attempt, instead of restarting at Level 1.
-
HP Scaling: both the boss’s and the student’s maximum HP scale up with the current level/phase, using a fixed linear formula computed once server-side:
Formula Boss HP base × (1 + 0.5 × (level − 1) + 0.1 × (phase − 1))Student HP base × (1 + 0.3 × (level − 1) + 0.05 × (phase − 1))The boss scales faster than the student — later phases are meant to feel harder, not just longer.
- Advancing: after winning a phase, the server verifies the reported damage actually cleared that phase’s boss HP before allowing the advance, and rotates the token again — the same request can’t be replayed to skip several phases off one real victory.
Single Match
A single, self-contained round — no levels or phases. Grading reuses the grademethod pattern
already established by the sibling mod_playerwords/mod_playercross activities: the teacher
sets a Max Matches limit (or leaves it unlimited) via a 0–10 selector, matching the scale
of a short, repeatable round rather than a long campaign.
Why Two Modes Instead of One
A Campaign “attempt” and a Single Match “attempt” are structurally different — a continuous run
across many phases versus independent, repeatable rounds — so the plugin models them separately
rather than forcing one grading/attempt-counting scheme to fit both. question_fetcher.php,
save_progress.php and the Lobby all read the configured gamemode to know which rules apply.
💰 PlayerHUD Integration
PlayerPuzzle has no permanent economy of its own. Coins and upgrade levels live entirely in block_playerhud, and PlayerPuzzle only reads/writes to it when the block is present in the course — the plugin works standalone with zero configuration if it isn’t.
Teacher-Picked Items, Not Auto-Detection
For each course, the teacher explicitly picks which of the block’s own items represent:
- Coins — credited when a match is won (the gold collected during combat is banked here).
- Sword level — read as an upgrade tier for the student’s damage.
- Shield level — read as an upgrade tier for the student’s defense.
- Potion — optional; can also be consumed from a permanent PlayerHUD stock instead of (or alongside) the session-local coin purchase.
- Retry cost — optional; charged starting from the student’s 2nd attempt, without ever raising the configured attempt limit. Intended to be earned through reinforcement content outside the game (a video, another activity), not through playing PlayerPuzzle itself — so a student who struggles most isn’t also the one least able to afford another try.
- Win grant — optional; a fixed item awarded on every victory, on top of the coins.
Every one of these is a plain dropdown of the block’s own configured items — the same pattern
already used by the sibling mod_playerwords/mod_playercross activities — so adding this
integration never required a change to block_playerhud itself.
Coin Gain and the Boss’s Compensation
Matching the Coin piece (see How Combat Works) earns coins on a combo curve: a 3-piece match earns the configured “Coin gain” base value, a 4-piece match earns 1.5x that, and a 5-piece match earns 2x — the same curve Sword damage uses. Unlike every other combat value, Coin gain deliberately does not scale by level or phase, so a teacher can set fixed consumable prices (Potion, Shield, Quick Magic, Hint) that stay balanced for the whole campaign instead of getting proportionally cheaper as it progresses.
The boss also banks its own Coin total when it matches the piece on its own turn — it has no shop
and never spends it. Its only purpose is to net against the student’s balance at the end of the
match: the student’s final coin count is max(0, student's coins − boss's coins), banked via
PlayerHUD only on victory. This gives the boss’s AI a reason to “compete” for the same piece the
student wants, instead of it being inert whenever the AI happens to match it.
What’s Actually Wired Up Today
- ✅ Crediting coins on victory.
- ✅ Reading the student’s sword/shield upgrade level (
hud_service::get_upgrade_level()). - ⏳ Applying that upgrade level as an actual damage/defense multiplier during combat — the
reader exists and is tested;
combat.jsdoesn’t consume it yet. - ⏳ Potion/retry-cost/win-grant items — configurable today, not yet consumed by gameplay.
See Features for the full implemented/planned breakdown.
♿ Accessibility
The Design
Match-3 gameplay lives on a Canvas, which is opaque to screen readers by design. PlayerPuzzle’s accessibility approach is a parallel, always-present HTML layer rather than an afterthought bolt- on:
- A visually-hidden
<table role="grid">mirrors the board state, navigable cell by cell. - Turn-by-turn events (available moves, damage dealt, boss HP remaining, energy bar full, a
question answered) are announced through an
aria-live="polite"region, so a screen reader user gets the same information a sighted player reads from the HUD. - Number keys
1–9execute the correspondingly-announced move;Spacere-reads the list of available moves. aria-live="assertive"is reserved for events that interrupt the turn (e.g. taking damage from a poison tick).
What’s Implemented Today
- ✅ The question dialog — the moment gameplay most needs to be accessible — is a native
HTML
<dialog>, not a Canvas overlay. Opening it traps focus and centers it using the browser’s own behavior; closing it restores focus to whatever element triggered it.
What’s Planned
- ⏳ The parallel HTML board (
<table role="grid">), thearia-liveturn announcements, and the number-key input scheme described above are designed but not built yet. - ⏳ A phase-advance announcement (“You advanced to Level 5, Phase 2”) once the post-match debrief screen exists — see Features.
speechSynthesis is never used as the default announcement channel, to avoid conflicting with
whatever screen reader the student already has running.
🔐 Security & Anti-Cheat
Every combat number a student sees is a claim the client makes about itself. PlayerPuzzle treats none of it as trustworthy until the server confirms it, through three pillars:
1. Server as the Source of Truth
Boss and student HP for the current level/phase are computed once, server-side
(combat::calculate_boss_hp()/calculate_student_hp()), and handed to the client as read-only
configuration. The client never recalculates them.
2. Sanity-Checked Damage
Whatever damage the client reports at the end of a match is clamped against the real, phase-
scaled boss HP before it’s accepted — not the base HP, and not whatever the client claims the
cap should be. Advancing a phase (advance_phase) applies the same rule: the server requires the
reported damage to actually clear that phase’s boss HP before allowing the transition.
A fuller sanity check — accounting for PlayerHUD upgrade multipliers and elapsed play time — is designed but not implemented yet; see Features.
3. Single-Use Anti-Replay Tokens
Every state-changing call (starting a match, resuming a campaign attempt, advancing a phase) issues a fresh token and invalidates the previous one. A captured request can’t be replayed to re-trigger the same reward twice — resuming a campaign mid-run rotates the token exactly like finishing one does, so the guarantee holds across a session that spans multiple page loads, not just within a single match.
Blind JSON
The correct answer id is never sent to the client — see Question Engine for how this is enforced at the data layer, not just the UI layer.
🎓 Educational Purpose
PlayerPuzzle is designed to:
- Turn correctly answering a content question into an immediate, visually rewarding combat event (a critical hit), rather than just a checkbox toward a grade
- Reuse a course’s existing Question Bank, so content-fixation activities a teacher already maintains for Quiz can be repurposed without any migration
- Keep the rigor of assessment intact underneath the game — attempts are limited, answers are validated server-side, and the grade is designed to reflect genuine progress/performance (see Game Modes), never client-reported numbers
- Reward sustained engagement in a course-scoped way through the optional PlayerHUD integration, without requiring any permanent economy for a course that doesn’t want one
Suitable for:
- Any course already using
block_playerhudfor gamified progression - Teachers who already maintain a Question Bank category for the same content and want a second, game-based way to reinforce it
- Institutions exploring gamified content-fixation as a complement to (not a replacement for) formal quizzes
🕹️ PlayerGames Ecosystem
PlayerPuzzle is part of the PlayerGames gamification ecosystem for Moodle. None of the plugins below are required — PlayerPuzzle works standalone — but installing the ones relevant to a course unlocks the integrations documented throughout this page.
-
PlayerGames: Central hub of the ecosystem — site-wide XP, seasons, daily mini-games, and the Ecosystem Dashboard that ties every installed Player plugin together. 👉 github.com/jeanlucio/moodle-local_playergames
-
PlayerHUD Block: XP, levels, inventory, drops, quests, RPG classes and ranking inside each course. PlayerPuzzle reads/writes to it for its entire optional economy — see PlayerHUD Integration. 👉 github.com/jeanlucio/moodle-block_playerhud
-
PlayerHUD Filter: Enables item drops via shortcodes inside course content. 👉 github.com/jeanlucio/moodle-filter_playerhud
-
PlayerHUD Availability Restriction: Restricts access to course activities based on the student’s current level or collected items. 👉 github.com/jeanlucio/moodle-availability_playerhud
-
PlayerWords: A sibling activity module (word-guessing) that established the
grademethod/attempt-counting pattern PlayerPuzzle’s Single Match mode reuses. 👉 github.com/jeanlucio/moodle-mod_playerwords -
PlayerCross: A sibling activity module (crossword) sharing the same architectural patterns (
local\*_page_serviceentry-point classes, PlayerHUD integration conventions). 👉 github.com/jeanlucio/moodle-mod_playercross -
PlayerLand: Another Phaser-based activity module in the ecosystem, sharing the same dynamic-script-loading pattern for its own bundled game engine. 👉 github.com/jeanlucio/moodle-mod_playerland
-
PlayerGroup: Lets students autonomously form their own groups directly from the activity page — no teacher intervention needed. 👉 github.com/jeanlucio/moodle-mod_playergroup
-
AI Hub: Shared BYOK (bring your own key) broker for AI features across the ecosystem. PlayerPuzzle’s planned question-generation feature will consume it as a soft dependency, falling back to Moodle’s own
core_ai— see Features. 👉 github.com/jeanlucio/moodle-local_aihub
📦 Requirements
| Component | Version |
|---|---|
| Moodle | 4.5 – 5.2 |
| PHP | 8.2+ |
block_playerhud is an optional dependency — see PlayerHUD Integration.
🛠️ Installation & Configuration
⚠️ This plugin is not yet published on the Moodle Plugin Directory. Install manually from this repository.
- Download the
.zipfile or clone this repository. - Extract the folder into your Moodle
mod/directory. - Rename the folder to
playerpuzzle(if necessary). Final path:your-moodle/mod/playerpuzzle/ - Visit Site administration → Notifications to complete installation.
- Optional: install block_playerhud if you want the optional coin/upgrade economy — see PlayerHUD Integration.
- Add a PlayerPuzzle activity to any course, pick a Game Mode, a Question Bank category, and (optionally) which PlayerHUD items to use.
📖 Usage
For Teachers
- Add a PlayerPuzzle activity to a course.
- Choose a Game Mode — Campaign or Single Match — see Game Modes.
- Pick the Question Bank category the match will draw from.
- Optionally set a Minimum Questions per Match and whether wrong answers should count against the grade.
- Optionally configure the PlayerHUD Integration items, if
block_playerhudis installed in the course. - Set the Maximum Grade — both grading formulas scale to it, never to a fixed
100.
For Students
- Open the activity to reach the Lobby — it shows your current progress (Campaign mode), your PlayerHUD balance if configured, and any minimum-questions requirement.
- Select Play Game to start (or resume) a match.
- Match pieces to attack the boss, fill the energy bar to trigger a Question Challenge, and answer correctly for a critical hit.
- In Campaign mode, winning a phase never costs a new attempt — only losing does.
🧪 Automated Tests
PlayerPuzzle ships with an extensive PHPUnit suite covering the combat engine, the question pipeline, anti-cheat security, PlayerHUD integration, backup/restore, completion and privacy. Every CI push runs the full matrix (Moodle 4.5 → 5.3, PHP 8.1 → 8.4, PostgreSQL & MariaDB).
PHPUnit — Unit & Integration Tests (tests/)
| Test file | Cases |
|---|---|
access_test.php |
1 |
backup_restore_test.php |
13 |
generator_test.php |
1 |
lib_completion_info_test.php |
6 |
lib_crud_test.php |
8 |
lib_grade_item_update_test.php |
4 |
lib_pluginfile_test.php |
5 |
lib_supports_test.php |
2 |
lib_update_grades_test.php |
4 |
mod_form_test.php |
11 |
phaser_loading_test.php |
2 |
uninstall_test.php |
2 |
| Subtotal | 59 |
Completion Tests (tests/completion/)
| Test file | Cases |
|---|---|
custom_completion_test.php |
11 |
| Subtotal | 11 |
Event Tests (tests/event/)
| Test file | Cases |
|---|---|
game_completed_test.php |
3 |
game_started_test.php |
2 |
| Subtotal | 5 |
Web Services Tests (tests/external/)
| Test file | Cases |
|---|---|
advance_phase_test.php |
26 |
buy_stock_test.php |
10 |
draw_question_test.php |
8 |
generate_questions_test.php |
2 |
get_phase_questionlog_test.php |
5 |
save_combat_state_test.php |
14 |
save_generated_questions_test.php |
4 |
save_progress_test.php |
29 |
set_sound_preference_test.php |
5 |
transfer_hud_coins_test.php |
8 |
use_stock_test.php |
15 |
validate_answer_test.php |
19 |
| Subtotal | 145 |
Form Tests (tests/form/)
| Test file | Cases |
|---|---|
import_form_test.php |
3 |
question_form_test.php |
11 |
| Subtotal | 14 |
Local Business-Logic Tests (tests/local/)
| Test file | Cases |
|---|---|
ai_question_generator_test.php |
19 |
attempt_consumables_test.php |
7 |
attempt_questions_test.php |
1 |
coin_ledger_test.php |
4 |
combat_state_test.php |
5 |
game_page_service_test.php |
27 |
grade_calculator_test.php |
21 |
hud_service_test.php |
18 |
lobby_page_service_test.php |
22 |
move_log_test.php |
20 |
question_bank_sync_test.php |
13 |
question_editor_files_test.php |
3 |
question_list_service_test.php |
3 |
questions_repository_test.php |
21 |
ranking_service_test.php |
9 |
replay_credit_test.php |
8 |
report_service_test.php |
17 |
sound_preferences_test.php |
7 |
user_stock_test.php |
10 |
| Subtotal | 235 |
Combat Engine Tests (tests/local/engine/)
| Test file | Cases |
|---|---|
board_engine_test.php |
25 |
combat_engine_test.php |
29 |
combat_test.php |
9 |
golden_vectors_test.php |
5 |
prng_test.php |
5 |
question_fetcher_test.php |
20 |
replay_test.php |
28 |
security_test.php |
43 |
| Subtotal | 164 |
Privacy Tests (tests/privacy/)
| Test file | Cases |
|---|---|
provider_test.php |
36 |
| Subtotal | 36 |
| Grand Total | 669 |
vendor/bin/phpunit --testsuite mod_playerpuzzle_testsuite
Coverage (moodle-coverage, PHPUnit + Xdebug): 86.79% lines, 74.03% methods
(21/47 classes fully covered).
Full test-by-test breakdown and coverage table →
JavaScript — Unit Tests (tests/js/engine/)
The deterministic match-3/combat/PRNG math, run headless via Node’s own test runner (node
--test) — no Jest, no browser. First plugin in the ecosystem with a JS test layer of its own.
| Test file | Cases |
|---|---|
board_rules.test.js |
25 |
combat_rules.test.js |
29 |
prng.test.js |
5 |
| Subtotal | 59 |
node --test tests/js/engine/board_rules.test.js tests/js/engine/combat_rules.test.js tests/js/engine/prng.test.js
Behat — Acceptance Tests
| Feature file | Scenarios |
|---|---|
mod_playerpuzzle_accessibility.feature |
4 |
mod_playerpuzzle_gameplay.feature |
4 |
mod_playerpuzzle_match.feature |
3 |
mod_playerpuzzle_ranking.feature |
3 |
mod_playerpuzzle_settings.feature |
1 |
mod_playerpuzzle_smoke.feature |
1 |
| Subtotal | 16 |
php admin/tool/behat/cli/init.php
vendor/bin/behat --tags=@mod_playerpuzzle --profile=chrome
🔎 Third-party Service Disclosure
Not applicable today. PlayerPuzzle does not currently call any external service — every question comes from Moodle’s own Question Bank (see Question Engine), and no network request leaves the server as part of gameplay.
Planned
A second question source — manual entry plus optional AI-assisted generation via the
companion local_aihub plugin, falling back to
Moodle’s own core_ai — is designed for a future phase (see Features). When it
ships, this section will disclose the exact providers, what data is transmitted, and any cost
implications, following the same pattern already documented for
local_playergames and
local_aihub itself. As with those plugins, AI
generation will be entirely opt-in and will never include student data in a prompt.
📄 License
This project is licensed under the GNU General Public License v3 (GPLv3).
Copyright: 2026 Jean Lúcio
Support
Found a bug or have a question? Open an issue on the issue tracker.
Maintainer
Maintained by Jean Lúcio.