PlayerCross

Deduction crossword-style vocabulary activity for Moodle — full documentation.

English | Português

View on GitHub Download .zip

Moodle License Status Latest Release PlayerGames Ecosystem Game Activity Author

Moodle Plugin CI Last Commit Open Issues

PlayerCross is a deduction crossword-style vocabulary activity for Moodle. Each round draws a mystery phrase and a set of terms built from related course concepts; resolving a term reveals the letters it shares with every other pending term and with the mystery phrase itself.

👈 Use the sidebar to jump to any section on this page.


✨ Features

🎓 Educational Purpose

PlayerCross is designed to:

Suitable for:

🕹️ PlayerGames Ecosystem

PlayerCross is part of the PlayerGames gamification ecosystem for Moodle. Its main direct integration is with the PlayerHUD block:

See the PlayerGames hub for the full plugin family.

🧩 Optional Integration: AI Hub

PlayerCross’s optional AI Word Generation feature can draw on AI Hub (local_aihub, by the same author, part of the shared PlayerGames ecosystem services). When AI Hub is installed, any personal or site key a teacher or admin has already configured there is automatically available to PlayerCross — no key needs to be re-entered. PlayerCross never contacts an AI provider directly; without AI Hub installed, it falls back to Moodle’s own core_ai subsystem, routing to whatever provider the site administrator has configured there instead.

👉 https://github.com/jeanlucio/moodle-local_aihub

📦 Requirements

Component Version
Moodle 4.5 – 5.2
PHP 8.1+
PlayerHUD (optional) v1.7.1+

🛠️ Installation & Configuration

  1. Download the .zip file or clone this repository.
  2. Extract the folder into your Moodle mod/ directory.
  3. Rename the folder to playercross (if necessary). Final path: your-moodle/mod/playercross/
  4. Visit Site administration > Notifications to complete installation.
  5. Add a PlayerCross activity to any course.

This plugin has no site-level settings for an admin to configure — every setting is configured by the teacher when adding the activity to a course, as covered in Usage below. If block_playerhud isn’t installed on the site, the plugin’s own settings page under Site administration → Plugins → Activity modules → PlayerCross shows an informational notice about it — there’s nothing to configure there either way.

📖 Usage

  1. Add a PlayerCross activity to your course.
  2. Configure:
    • Mystery-phrase length range (independent from the term words’ own length range) and number of terms per round
    • Win condition (both terms and mystery phrase required, or the mystery-phrase guess alone) and whether uncovered mystery-phrase letters are auto-revealed
    • Maximum attempts per term and for the mystery phrase, cooldown between rounds, and round limit
    • Whether hints are allowed at all, and if so, the maximum reveals per round (default 3, or unlimited)
    • Word mode (random or shared sequence)
    • Grading method, grade and ranking scoring mode (Binary or Linear), 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)
  3. Open the Manage words page to add, generate with AI, approve, edit, or delete words.
  4. Students play directly from the activity page — resolving terms, guessing the mystery phrase, 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.
  5. Grades and ranking update automatically after each round.

🧮 Grading & Ranking

PlayerCross 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:

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 mode / Ranking scoring mode settings, both default to Binary). The grade is scored against the activity’s own configured maximum grade; the ranking always uses its own fixed 100-point base, completely independent of the grade — even when the activity has no grade at all (Grade = None, the form’s default), the ranking still works normally:

Mode A won round is worth… A lost round
Binary (default) The full base (the activity’s grade, or a fixed 100 points for ranking) Zero
Linear A share that decreases with every wrong guess made — across every term and the mystery phrase, counted together as a single pool Zero

Terms carry no per-term point value of their own: every wrong guess, whether on a term or on the mystery phrase itself, draws from the same shared error pool, and that pool’s size determines the whole round’s Linear score:

max_errors        = num_terms × (max_attempts_per_term − 1) + (max_attempts_final_guess − 1)
points (grade)    = grade × (max_errors − errors_used + 1) / (max_errors + 1)
points (ranking)  = 100   × (max_errors − errors_used + 1) / (max_errors + 1)

Linear has no grace period: the very first wrong guess already reduces the score. A flawless run (zero wrong guesses anywhere) is still always exactly full credit, and the score never reaches zero for a genuinely completed win — it floors at base / (max_errors + 1) even at the maximum error budget. Because max_errors depends on both attempts settings, choosing Linear for either the grade or the ranking requires Maximum attempts per term and Maximum attempts for the mystery phrase to both be a real number, not unlimited — the settings form blocks saving otherwise.

Worked example with 5 terms, 3 attempts per term, 3 attempts for the mystery phrase (max_errors = 5 × 2 + 2 = 12) — the grade column assumes a 100-point maximum grade, but the ranking column is exactly this in any activity, even one with no grade configured at all:

Errors Grade (100-point base) Ranking (100-point base, always) Errors Grade Ranking
0 100.00 100.00 7 46.15 46.15
1 92.31 92.31 8 38.46 38.46
2 84.62 84.62 9 30.77 30.77
3 76.92 76.92 10 23.08 23.08
4 69.23 69.23 11 15.38 15.38
5 61.54 61.54 12 7.69 7.69
6 53.85 53.85 Not completed 0.00 0.00

Early-guess bonus: guessing the mystery phrase correctly before resolving any term adds a flat 10% on top of the base score above — 10% of the activity’s grade, for the grade; 10% of the fixed 100-point base (i.e. always +10 points), for the ranking. For the grade, this is capped at the activity’s nominal maximum — a flawless run already at 100% stays at 100%. For the ranking, it is uncapped — the same flawless run’s ranking total becomes 110, legitimately exceeding the nominal 100-point base, since the ranking rewards efficient early deduction beyond what a gradebook value can represent.

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, Terms per round, Grading method, Maximum attempts per term, Maximum attempts for the mystery phrase and Grade scoring mode all lock — the same way Moodle already locks a graded activity’s own “Maximum grade” field once real grades exist. Since the Linear formula’s error budget is a direct function of the terms count and both attempts settings, changing any of them after real scores exist would make earlier and later rounds worth different things; locking them guarantees every round ever recorded stays internally consistent for the activity’s whole lifetime.

Ranking scoring mode locks separately, the moment any finished attempt exists — it doesn’t wait for a real grade, because ranking points are already computed and stored for every finished round regardless of whether Grade or Show ranking are even on (see above). An entirely ungraded, ranking-only activity already accumulates real history from its very first round; locking the scoring mode once that history exists prevents the same scale inconsistency the lock above prevents for the grade.

Attempt history: each student can review their own past rounds — mystery phrase, terms resolved, attempts used, time, grade score and (when ranking is enabled) ranking points — on a dedicated toolbar page. Whoever can manage the activity sees that same page too, including their own attempts if they have played the activity. The all-students report lives on a separate page, visible only to whoever can manage the activity: one table with every student’s attempts, sortable by clicking any column header, and filterable to a single student. Like the ranking, that report never includes a manager’s own attempts.

Deleting attempts reverses these locks when they no longer apply. From the all-students report, whoever can manage the activity can delete a student’s attempts individually or in bulk. Deleting genuinely clears that student’s grade rather than leaving a stale value behind, so if every attempt for every student is removed, Terms per round, Grading method, both attempt limits and Grade scoring mode unlock again exactly as grade_item::has_grades() expects — the same way they would on an activity that was never played at all. Ranking scoring mode follows the same logic against the attempts table directly, independent of whether grading is even on.

🧪 Automated Tests

PlayerCross ships with a PHPUnit test suite covering business logic, repository queries, web services, and Privacy API compliance, plus a Behat suite covering gameplay, PlayerHUD integration, and reports end-to-end in a real browser. 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 8
cross_instance_security_test.php 4
lib_grant_potential_test.php 6
lib_reset_userdata_test.php 4
lib_supports_test.php 2
completion/custom_completion_test.php 6
privacy/provider_test.php 21
lib_update_grades_test.php 2
mod_form_test.php 4
Subtotal 57

Local Business-Logic Tests (tests/local/)

Test file Cases
ai_word_generator_test.php 19
attempts_history_service_test.php 21
gameplay_service_test.php 16
hud_service_test.php 27
intro_service_test.php 5
puzzle_builder_test.php 9
ranking_service_test.php 9
round_presenter_test.php 60
round_service_test.php 67
view_page_service_test.php 40
word_normalizer_test.php 38
words_repository_test.php 67
Subtotal 378

Web Services Tests (tests/external/)

Test file Cases
count_eligible_theme_words_test.php 5
count_eligible_words_test.php 5
count_glossary_candidates_test.php 5
end_round_test.php 6
new_round_test.php 5
reveal_hint_test.php 8
start_round_test.php 7
submit_term_guess_test.php 8
submit_final_guess_test.php 7
Subtotal 56
Grand Total 491
vendor/bin/phpunit --testsuite mod_playercross

Overall line coverage (moodle-coverage, PHPUnit + Xdebug): 90%.

Behat — End-to-End Tests

Feature file Scenarios
mod_playercross_smoke.feature 1
mod_playercross_gameplay.feature 15
mod_playercross_playerhud.feature 4
mod_playercross_reports.feature 5
mod_playercross_settings.feature 5
mod_playercross_toolbar.feature 9
Subtotal 39

Full test-by-test breakdown and coverage table →

🔐 Security & Compliance

🔒 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 AI Hub (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. PlayerCross 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.

📄 License

This project is licensed under the GNU General Public License v3 (GPLv3).

Copyright: 2026 Jean Lúcio