Dev.to WebDev 🛠 Dev 👁 0 📖 8 min read

Designing a Fair Scoring Rubric for Maze Game Results

Why a scoring rubric needs more than one number A score is useful only when it answers a specific question. “How many points did this run earn?” describes a result. It does not tell you whether the run was efficient, w

Why a scoring rubric needs more than one number

A score is useful only when it answers a specific question. “How many points did this run earn?” describes a result. It does not tell you whether the run was efficient, whether the controls worked correctly, or whether a wall contact changed the outcome.

That distinction matters when comparing people, builds, or repeated attempts. One run might finish quickly but contain several invalid contacts. Another might take longer while keeping every movement within the permitted boundaries. Without a shared rule table, the comparison becomes an argument about interpretation.

For a practical starting point, Maze Game provides a concrete case for examining how collision rules affect a reported result. Use that observation as input to a larger scoring design, not as the sole definition of quality.

A sound rubric has four parts:

  1. The event being measured — movement, contact, completion, or time.
  2. The condition attached to the event — whether it is valid, penalized, informational, or disqualifying.
  3. The value assigned to the event — points, elapsed time, a count, or a status.
  4. The aggregation rule — how individual values become the final result.

This structure also resembles a database report: collect observations first, define transformations second, and display a summary only after the underlying data is clear.

Build the reference table before calculating anything

Start with a small table that anyone can read without opening the source code. The exact categories depend on the interface, but the shape should remain stable.

Observation Detection rule Treatment Recorded value
Legal advance Destination is open and movement follows the control rules Counted One move
Blocked contact Requested direction is closed by a boundary Classified Contact count
Completion Required destination is reached Marked Finish status
Elapsed duration Timer starts and stops under defined conditions Counted Time value
Invalid input Input is outside the accepted control set Ignored or flagged Input status

The important column is Treatment. A blocked contact might be:

  • recorded but worth zero points;
  • subtracted from the total;
  • allowed without changing the score;
  • allowed only within a limit; or
  • treated as a failure condition.

These are not interchangeable rules. Choose one explicitly and write it down.

A useful format for a team is a plain-language statement followed by a machine-readable equivalent:

blocked_contact = record; no_points
completion = finish
elapsed_time = record_seconds

If the game already defines the behavior, do not invent a second interpretation. Capture the existing rule, test it, and document any ambiguity. If the rules are missing, treat the missing rule as a design defect rather than filling the gap with personal intuition.

The same discipline applies to timing. Specify whether the clock stops at the first valid arrival, whether restarting creates a new record, and whether inactive time is excluded. A duration is not meaningful until its start and end conditions are defined.

Separate observations from derived values

Raw observations should remain available even when the final display shows only a score. For example, a run might contain:

valid_moves: 38
blocked_contacts: 4
completions: 1
elapsed_seconds: 72

A summary could then be calculated from those fields. This separation makes debugging much easier. If the displayed total is unexpectedly low, you can inspect the inputs instead of guessing which arithmetic step failed.

Keep derived values deterministic. Given the same observations and the same rule version, the result should be identical. Avoid calculations that depend on hidden state, random ordering, or an undocumented rounding step unless those factors are part of the recorded result.

If fractions or averages are involved, decide where rounding occurs. Record both the unrounded calculation and the displayed value when precision matters. JavaScript’s number behavior is worth checking rather than assuming; the language reference explains that numbers are represented using the IEEE 754 double format. That makes values such as 0.1 + 0.2 appear as 0.30000000000000004, which can create confusing differences in a ledger or comparison table. See MDN’s number reference.

A practical rule is to compare values with a defined tolerance when the calculation uses floating-point values, or use integer units such as milliseconds and basis points when exact aggregation is more useful.

Decide what counts as a meaningful comparison

Comparisons are only fair when the starting conditions match. Before placing two results side by side, verify:

  • the same board or level version;
  • the same start and finish locations;
  • the same control scheme;
  • the same timer behavior;
  • the same collision policy; and
  • the same scoring version.

A result from an older rule set should not be compared directly with one from a newer set unless the records include enough information to translate them. A simple approach is to append a version identifier to every run:

{
  "level": "room-04",
  "rules_version": "2025-02",
  "valid_moves": 38,
  "blocked_contacts": 4,
  "completed": true,
  "elapsed_seconds": 72
}

The version field does not make the run more interesting by itself. It prevents an otherwise valid record from being interpreted under the wrong assumptions.

For quality checks, compare outcomes in separate columns rather than collapsing everything into one ranking. A completion rate answers whether the objective was met. A contact rate answers whether movement stayed within boundaries. A duration answers how long the attempt took under the specified conditions. These measures can disagree, and that disagreement may reveal the most useful information.

Handle edge cases as first-class test cases

Most scoring bugs appear at boundaries rather than in ordinary runs. Prepare a small regression list and execute it whenever the rules change.

  1. Immediate blocked contact: The first requested direction is invalid. Does the run record one contact, or does it suppress the event because no movement occurred?
  2. Repeated input while blocked: The same closed direction is requested ten times. Are ten contacts recorded, or is there a cooldown or deduplication rule?
  3. Arrival at the finish: Does reaching the destination end the timer before later input is processed?
  4. Restart during a run: Is the previous attempt retained, replaced, or marked incomplete?
  5. Pause and resume: Does elapsed time exclude paused intervals consistently?
  6. Input after completion: Are extra controls ignored, recorded as invalid, or allowed to alter the final result?
  7. Level change: Does changing a board reset counters, retain history, or create a new record?
  8. Missing timer data: Does the report show “unknown” rather than zero?
  9. Multiple completions: Is only the first valid finish scored?
  10. Rule-version mismatch: Does the interface warn that a comparison is not like-for-like?

Write each expected result as an observable statement. “Blocked input should not add movement points” is testable. “The game should feel fair” is not.

For accessibility and interface behavior, use the W3C Web Content Accessibility Guidelines as a general reference when the result is presented in a browser. The rubric’s data model may be correct while the display still fails to communicate status, timing, or errors clearly. The relevant principle is to make the information perceivable and understandable, not merely present in the DOM.

Choose between a simple score and a detailed record

A single score is convenient for a leaderboard. A detailed record is better for analysis and debugging. You do not always need both, but you should choose based on the user’s task.

Use a single score when:

  • the audience needs a quick ranking;
  • inputs and rules are stable;
  • the result must fit in a compact display;
  • loss of detail is acceptable; and
  • the scoring formula has been tested.

A score such as 400 can represent completion plus efficiency, but it should not hide whether the score came from a valid run. Store the component values beside it.

Use a detailed report when:

  • people are learning the system;
  • disputes need evidence;
  • different objectives are being compared;
  • a rule change may invalidate earlier results; or
  • the result is used for quality assurance.

A table like this communicates more than one number:

Measure Result
Completion Yes
Valid moves 38
Blocked contacts 4
Duration 72 seconds

The final recommendation is simple: publish a summary, but retain the observations and rule version that produced it.

Treat scoring changes like interface changes

Changing a penalty or contact rule can invalidate historical comparisons just as changing a form field can break an old submission. Announce the change, assign a version, and preserve the previous interpretation for existing records.

A migration note should answer:

  1. What changed?
  2. When did it take effect?
  3. Which old records remain valid?
  4. Can old and new results be displayed together?
  5. What must users do differently?

If translation is impossible, label the groups separately. Do not present an old result beside a new one without a visible rule-version distinction.

This is similar to maintaining a schema: field names are not enough if the meaning changes silently. The JSON Data Interchange Format specification is a useful reminder that a document’s values need context and consistent structure; a scoring record should include that context rather than storing an isolated total.

A practical release checklist

Before accepting a scoring implementation:

  • [ ] Every event has one documented treatment.
  • [ ] Collision behavior is defined for repeated attempts.
  • [ ] Timer start, pause, stop, and restart behavior are explicit.
  • [ ] Raw observations are stored separately from derived totals.
  • [ ] Rule versions appear in exported records.
  • [ ] Edge cases have expected results.
  • [ ] Comparisons reject mismatched levels or rule versions.
  • [ ] Missing values are not silently converted to zero.
  • [ ] Rounding and tolerance are defined where applicable.
  • [ ] A user can explain how the final total was produced.

The checklist is deliberately operational. It lets a reviewer reproduce a result from evidence rather than trust the interface.

Frequently asked questions

Does a wall collision always reduce the score?

Not necessarily. It depends on the documented rule. A collision may add a penalty, count only as an observation, or have no effect on points. Test the stated behavior and record the rule version with the result.

Should I compare only the highest scores?

Usually not. Compare completion, elapsed time, contact count, and rule version separately. A high total can conceal a less efficient or rule-incompatible attempt.

How do I debug an incorrect total?

Inspect the raw event record first. Check event classification, aggregation order, timer boundaries, rounding, and any restart or pause behavior. Recalculate from the stored observations and compare that result with the displayed value.

What should be saved for every run?

Save the level identifier, rule version, start and finish status, valid-move count, blocked-contact count, elapsed duration, and relevant timestamps. Preserve enough information to reconstruct the displayed total.

This article was drafted with AI assistance and reviewed for technical accuracy before publishing.

📰 Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.