Skip to content

Sort performance stats panel by dialect and level#232

Open
Aiuanyu wants to merge 1 commit into
mainfrom
jules-10727692522866028451-4cb0c496
Open

Sort performance stats panel by dialect and level#232
Aiuanyu wants to merge 1 commit into
mainfrom
jules-10727692522866028451-4cb0c496

Conversation

@Aiuanyu

@Aiuanyu Aiuanyu commented Jul 17, 2026

Copy link
Copy Markdown
Owner

Sorted performance stats panel dialect-level distribution by dialect order (四縣 -> 海陸 -> 大埔 -> 饒平 -> 詔安) and level difficulty order (基礎級 -> 初級 -> 中級 -> 中高級 -> 高級 -> 教典). Included cache busting version bump.

Fixes #231


PR created automatically by Jules for task 10727692522866028451 started by @Aiuanyu

Implement custom sorting comparator for dialect-level stats in game statistics panel, ordering dialects by 四縣 -> 海陸 -> 大埔 -> 饒平 -> 詔安, and levels by 基礎級 -> 初級 -> 中級 -> 中高級 -> 高級 -> 教典. Features robust substring-based parsing supporting both short and long names.

Co-authored-by: Aiuanyu <93833368+Aiuanyu@users.noreply.github.com>
@google-labs-jules

Copy link
Copy Markdown
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying hakspring with  Cloudflare Pages  Cloudflare Pages

Latest commit: 3e70283
Status: ✅  Deploy successful!
Preview URL: https://c2678f6f.hakspring.pages.dev
Branch Preview URL: https://jules-10727692522866028451-4.hakspring.pages.dev

View logs

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the version of game-ui.js in index.html and refactors the sorting logic for levels in game-ui.js to sort by dialect and level order instead of total counts. The reviewer suggested optimizing this sorting logic by moving the order arrays and parsing helper function outside of the comparator callback, and caching the parsed results to prevent redundant allocations and string scanning.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread js/game/game-ui.js
Comment on lines +1131 to +1187
const sortedLevels = Object.entries(data.levels).sort((a, b) => {
const lvlA = a[0];
const lvlB = b[0];
const dialectOrder = ['四', '海', '大', '平', '安'];
const levelOrder = ['基', '初', '中', '中高', '高', '教典'];

const parseLevel = (lvl) => {
let dialect = '';
let level = '';

// Find dialect by searching for inclusion of any dialect character
for (const d of dialectOrder) {
if (lvl.includes(d)) {
dialect = d;
break;
}
}

// Find level by searching for inclusion of level keywords
if (lvl.includes('教典')) {
level = '教典';
} else {
// Check '中高' before '中' to avoid partial matching of '中' first
const levelKeywords = ['基', '初', '中高', '中', '高'];
for (const l of levelKeywords) {
if (lvl.includes(l)) {
level = l;
break;
}
}
}
return { dialect, level };
};

const aInfo = parseLevel(lvlA);
const bInfo = parseLevel(lvlB);

let aDialectIdx = dialectOrder.indexOf(aInfo.dialect);
let bDialectIdx = dialectOrder.indexOf(bInfo.dialect);
if (aDialectIdx === -1) aDialectIdx = 999;
if (bDialectIdx === -1) bDialectIdx = 999;

if (aDialectIdx !== bDialectIdx) {
return aDialectIdx - bDialectIdx;
}

let aLevelIdx = levelOrder.indexOf(aInfo.level);
let bLevelIdx = levelOrder.indexOf(bInfo.level);
if (aLevelIdx === -1) aLevelIdx = 999;
if (bLevelIdx === -1) bLevelIdx = 999;

if (aLevelIdx !== bLevelIdx) {
return aLevelIdx - bLevelIdx;
}

return lvlA.localeCompare(lvlB);
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Defining dialectOrder, levelOrder, and the helper function parseLevel inside the sort comparator callback causes them to be re-created on every single comparison. Since sorting is an $O(N \log N)$ operation, this leads to unnecessary memory allocations and garbage collection overhead, which can impact performance on low-end or mobile devices.

Additionally, we can cache the parsed results of parseLevel to avoid redundant string scanning for the same keys during the sort process.

    const dialectOrder = ['四', '海', '大', '平', '安'];
    const levelOrder = ['基', '初', '中', '中高', '高', '教典'];
    const parseCache = {};

    const parseLevel = (lvl) => {
      if (parseCache[lvl]) return parseCache[lvl];

      let dialect = '';
      let level = '';

      // Find dialect by searching for inclusion of any dialect character
      for (const d of dialectOrder) {
        if (lvl.includes(d)) {
          dialect = d;
          break;
        }
      }

      // Find level by searching for inclusion of level keywords
      if (lvl.includes('教典')) {
        level = '教典';
      } else {
        // Check '中高' before '中' to avoid partial matching of '中' first
        const levelKeywords = ['基', '初', '中高', '中', '高'];
        for (const l of levelKeywords) {
          if (lvl.includes(l)) {
            level = l;
            break;
          }
        }
      }
      const result = { dialect, level };
      parseCache[lvl] = result;
      return result;
    };

    const sortedLevels = Object.entries(data.levels).sort((a, b) => {
      const lvlA = a[0];
      const lvlB = b[0];

      const aInfo = parseLevel(lvlA);
      const bInfo = parseLevel(lvlB);

      let aDialectIdx = dialectOrder.indexOf(aInfo.dialect);
      let bDialectIdx = dialectOrder.indexOf(bInfo.dialect);
      if (aDialectIdx === -1) aDialectIdx = 999;
      if (bDialectIdx === -1) bDialectIdx = 999;

      if (aDialectIdx !== bDialectIdx) {
        return aDialectIdx - bDialectIdx;
      }

      let aLevelIdx = levelOrder.indexOf(aInfo.level);
      let bLevelIdx = levelOrder.indexOf(bInfo.level);
      if (aLevelIdx === -1) aLevelIdx = 999;
      if (bLevelIdx === -1) bLevelIdx = 999;

      if (aLevelIdx !== bLevelIdx) {
        return aLevelIdx - bLevelIdx;
      }

      return lvlA.localeCompare(lvlB);
    });

@claude

claude Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review of PR #232 — Sort performance stats panel by dialect and level

Verified the sort logic against getFullLevelName in main.js (dataVarName format: 1-char dialect + level code, or 教典 + dialect char for GIP). The comparator correctly reproduces the requested order (四縣→海陸→大埔→饒平→詔安, 基礎級→初級→中級→中高級→高級→教典), including the 中高 vs collision that's handled by checking the two-char keyword first. Nice catch there.

A few minor, non-blocking notes:

  1. Duplicated ordering constant (js/game/game-ui.js:1134): dialectOrder re-encodes the same canonical dialect order already used in main.js:1306 and main.js:3362 (['四縣', '海陸', '大埔', '饒平', '詔安'], just abbreviated to single chars here). Worth extracting to one shared constant at some point so the two lists can't drift if the order ever changes.

  2. 教典/GIP branch looks currently unreachable: per the comment at game-ui.js:781 ("Assumed cert since PoC focuses on cert levels"), gameActiveDataVarName (and thus hakkaDailyStatsByLevel keys feeding data.levels) currently only ever holds cert-style keys. That makes the 教典/南 handling in parseLevel forward-looking rather than exercised by any current code path — harmless, and matches the issue's ask, but it means this branch is effectively untested today.

  3. Minor efficiency nit: parseLevel, plus the dialectOrder/levelOrder/levelKeywords arrays, are recreated on every single comparator invocation (called twice per comparison during Array.sort). At the current scale (a handful of dialect/level entries) this is a non-issue, but if this pattern gets copied elsewhere for larger lists, precomputing a sort key once per item (Schwartzian transform / map-then-sort) would avoid the repeated parsing.

  4. No test added: the repo has precedent for small dedicated test files for pure logic (js/game/srs.test.js). Since the new sort comparator/parseLevel is a pure function operating only on string keys, it would be easy to cover with a similarly lightweight test — e.g. asserting order for a mixed set of ['安高','四基','海中高','教典四','海初']-style keys.

  5. Cache-busting bump in index.html (4.6.94.6.10) is correctly done per the project's convention in AGENTS.md.

No functional bugs found; the fallback lvlA.localeCompare(lvlB) tie-break at the end is unreachable in practice (stats keys are unique per Object.entries), but it's a harmless defensive default, not a problem.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Game] 戰績面板腔級戰績要排序

1 participant