Improve runes #418

Merged
LunarTides merged 2 commits from runes into main 2025-11-18 10:47:49 +00:00
LunarTides commented 2025-11-18 08:05:02 +00:00 (Migrated from github.com)

Use a new Rune enum rather than a string using characters.

Closes #417

Summary by CodeRabbit

  • New Features

    • Introduced a typed rune system with three rune types (Blood, Frost, Unholy) and array-based rune assignment for cards and decks.
  • Bug Fixes

    • Made rune handling consistent across cards, player logic, deck import/export and creators; added stronger validation and clearer error behavior for invalid rune input.
  • Tests

    • Updated rune-related tests to use the typed rune format and verify new validation/serialization behavior.
Use a new `Rune` enum rather than a string using characters. Closes #417 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Introduced a typed rune system with three rune types (Blood, Frost, Unholy) and array-based rune assignment for cards and decks. * **Bug Fixes** * Made rune handling consistent across cards, player logic, deck import/export and creators; added stronger validation and clearer error behavior for invalid rune input. * **Tests** * Updated rune-related tests to use the typed rune format and verify new validation/serialization behavior. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
coderabbitai[bot] commented 2025-11-18 08:05:21 +00:00 (Migrated from github.com)

Caution

Review failed

The pull request is closed.

Walkthrough

Migrates rune representation from a string (e.g., "FFB") to a typed Rune[] enum (e.g., [Rune.Frost, Rune.Frost, Rune.Blood]). Adds Rune enum and updates types, core classes, deckcode import/export, tools, examples, and tests to use the new Rune[] shape.

Changes

Cohort / File(s) Summary
Types
src/types/card.ts
Add exported enum Rune { Blood, Frost, Unholy }; change BlueprintWithOptional.runes type from string to Rune[].
Core model & logic
src/card.ts, src/player.ts
Change Card.runes and Player.runes from string to Rune[]; update Player.testRunes(runes: Rune[]) and its comparison logic to operate on arrays of Rune.
Deckcode functions
src/functions/deckcode.ts
Update import/export APIs to accept runes: Rune[]; map rune characters ↔ Rune enum on import and export and adjust serialization/validation logic.
Examples
cards/Examples/2/4-runes.ts, cards/Examples/2/lastb-main.ts
Import Rune; change blueprint self.runes assignments from string literals to Rune[] arrays.
Tools / generators
tools/cardcreator/custom.ts, tools/cardcreator/lib.ts, tools/deckcreator.ts
Switch interactive and generated rune handling from string to Rune[]; validate/map input characters to Rune enum; add conditional Rune imports in generated content.
Tests
test/src/player.test.ts
Update tests to pass Rune[] to Player.testRunes() and assert behavior with enum-based inputs.
Manifest / examples
package.json, cards/...
Examples and package references updated to reflect new rune typing where applicable.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant DeckTool as Card/Deck Tool
  participant Deckcode
  participant Model as Player/Card
  Note over User,Deckcode: Import (deckcode string -> internal)
  User->>Deckcode: submit deckcode
  Deckcode->>Deckcode: parse rune chars
  Deckcode->>Model: map chars -> Rune[] (e.g., ['Frost','Frost','Blood'])
  Model-->>DeckTool: create cards/blueprints with Rune[]
  Note over DeckTool,Deckcode: Export (internal -> deckcode string)
  DeckTool->>Model: request cards (with Rune[])
  Model->>Deckcode: provide Rune[] per card
  Deckcode->>Deckcode: serialize Rune[] -> rune initials (r[0]) and build code
  Deckcode-->>User: return deckcode string

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Areas to review carefully:
    • src/functions/deckcode.ts — mapping between rune chars and Rune enum and serialization/round-trip correctness.
    • src/player.tstestRunes logic and counts using arrays instead of string counts.
    • tools/cardcreator/lib.ts & tools/deckcreator.ts — generated templates and CLI input validation for Rune[].
  • LunarTides/Hearthstone.js#418 — Performs the same string-to-Rune[] migration across types, models, deckcode, tools, and tests; likely the closest code-level match.

Poem

🐰 From letters once neat in a line,
I hopped to enums, tidy and fine.
Frost and Blood in triple array,
Typed paws patter and hop away.
Cheers — the runes now dance and play!

Pre-merge checks and finishing touches

Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check Inconclusive The title 'Improve runes' is vague and generic, failing to describe the specific nature of the improvement or the main technical change from converting strings to Rune enums. Consider a more descriptive title like 'Convert runes from string to Rune enum' to clearly convey the breaking change and primary objective.
Passed checks (4 passed)
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check Passed The pull request successfully implements the core objective from issue #417: converting rune representation from string-based to Rune enum across all affected files (types, Player, Card, deckcode functions, examples, and tools).
Out of Scope Changes check Passed All changes are directly related to the rune system improvement objective; no out-of-scope modifications were detected in the implementation.
Docstring Coverage Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b831562550698d267197eb9e3ed76a2acf18947e and d66560d9ab.

📒 Files selected for processing (3)
  • src/functions/deckcode.ts (6 hunks)
  • tools/cardcreator/custom.ts (2 hunks)
  • tools/deckcreator.ts (3 hunks)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

<!-- This is an auto-generated comment: summarize by coderabbit.ai --> <!-- This is an auto-generated comment: failure by coderabbit.ai --> > [!CAUTION] > ## Review failed > > The pull request is closed. <!-- end of auto-generated comment: failure by coderabbit.ai --> <!-- walkthrough_start --> ## Walkthrough Migrates rune representation from a string (e.g., "FFB") to a typed `Rune[]` enum (e.g., `[Rune.Frost, Rune.Frost, Rune.Blood]`). Adds `Rune` enum and updates types, core classes, deckcode import/export, tools, examples, and tests to use the new `Rune[]` shape. ## Changes | Cohort / File(s) | Summary | |---|---| | **Types** <br> `src/types/card.ts` | Add exported `enum Rune { Blood, Frost, Unholy }`; change `BlueprintWithOptional.runes` type from `string` to `Rune[]`. | | **Core model & logic** <br> `src/card.ts`, `src/player.ts` | Change `Card.runes` and `Player.runes` from `string` to `Rune[]`; update `Player.testRunes(runes: Rune[])` and its comparison logic to operate on arrays of `Rune`. | | **Deckcode functions** <br> `src/functions/deckcode.ts` | Update import/export APIs to accept `runes: Rune[]`; map rune characters ↔ `Rune` enum on import and export and adjust serialization/validation logic. | | **Examples** <br> `cards/Examples/2/4-runes.ts`, `cards/Examples/2/lastb-main.ts` | Import `Rune`; change blueprint `self.runes` assignments from string literals to `Rune[]` arrays. | | **Tools / generators** <br> `tools/cardcreator/custom.ts`, `tools/cardcreator/lib.ts`, `tools/deckcreator.ts` | Switch interactive and generated rune handling from string to `Rune[]`; validate/map input characters to `Rune` enum; add conditional `Rune` imports in generated content. | | **Tests** <br> `test/src/player.test.ts` | Update tests to pass `Rune[]` to `Player.testRunes()` and assert behavior with enum-based inputs. | | **Manifest / examples** <br> `package.json`, `cards/...` | Examples and package references updated to reflect new rune typing where applicable. | ## Sequence Diagram(s) ```mermaid sequenceDiagram participant User participant DeckTool as Card/Deck Tool participant Deckcode participant Model as Player/Card Note over User,Deckcode: Import (deckcode string -> internal) User->>Deckcode: submit deckcode Deckcode->>Deckcode: parse rune chars Deckcode->>Model: map chars -> Rune[] (e.g., ['Frost','Frost','Blood']) Model-->>DeckTool: create cards/blueprints with Rune[] Note over DeckTool,Deckcode: Export (internal -> deckcode string) DeckTool->>Model: request cards (with Rune[]) Model->>Deckcode: provide Rune[] per card Deckcode->>Deckcode: serialize Rune[] -> rune initials (r[0]) and build code Deckcode-->>User: return deckcode string ``` ## Estimated code review effort 🎯 3 (Moderate) | ⏱️ ~25 minutes - Areas to review carefully: - `src/functions/deckcode.ts` — mapping between rune chars and `Rune` enum and serialization/round-trip correctness. - `src/player.ts` — `testRunes` logic and counts using arrays instead of string counts. - `tools/cardcreator/lib.ts` & `tools/deckcreator.ts` — generated templates and CLI input validation for Rune[]. ## Possibly related PRs - LunarTides/Hearthstone.js#418 — Performs the same string-to-`Rune[]` migration across types, models, deckcode, tools, and tests; likely the closest code-level match. ## Poem > 🐰✨ From letters once neat in a line, > I hopped to enums, tidy and fine. > Frost and Blood in triple array, > Typed paws patter and hop away. > Cheers — the runes now dance and play! <!-- walkthrough_end --> <!-- pre_merge_checks_walkthrough_start --> ## Pre-merge checks and finishing touches <details> <summary>❌ Failed checks (1 inconclusive)</summary> | Check name | Status | Explanation | Resolution | | :---------: | :------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------- | | Title check | ❓ Inconclusive | The title 'Improve runes' is vague and generic, failing to describe the specific nature of the improvement or the main technical change from converting strings to Rune enums. | Consider a more descriptive title like 'Convert runes from string to Rune enum' to clearly convey the breaking change and primary objective. | </details> <details> <summary>✅ Passed checks (4 passed)</summary> | Check name | Status | Explanation | | :------------------------: | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Description Check | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled. | | Linked Issues check | ✅ Passed | The pull request successfully implements the core objective from issue #417: converting rune representation from string-based to Rune enum across all affected files (types, Player, Card, deckcode functions, examples, and tools). | | Out of Scope Changes check | ✅ Passed | All changes are directly related to the rune system improvement objective; no out-of-scope modifications were detected in the implementation. | | Docstring Coverage | ✅ Passed | No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. | </details> <!-- pre_merge_checks_walkthrough_end --> --- <details> <summary>📜 Recent review details</summary> **Configuration used**: CodeRabbit UI **Review profile**: CHILL **Plan**: Pro <details> <summary>📥 Commits</summary> Reviewing files that changed from the base of the PR and between b831562550698d267197eb9e3ed76a2acf18947e and d66560d9abc6ebf3b2ba158272692d729695dce0. </details> <details> <summary>📒 Files selected for processing (3)</summary> * `src/functions/deckcode.ts` (6 hunks) * `tools/cardcreator/custom.ts` (2 hunks) * `tools/deckcreator.ts` (3 hunks) </details> </details> <!-- tips_start --> --- Thanks for using [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=LunarTides/Hearthstone.js&utm_content=418)! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. <details> <summary>❤️ Share</summary> - [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai) - [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai) - [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai) - [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code) </details> <sub>Comment `@coderabbitai help` to get the list of available commands and usage tips.</sub> <!-- tips_end --> <!-- internal state start --> <!-- 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 --> <!-- internal state end -->
coderabbitai[bot] (Migrated from github.com) reviewed 2025-11-18 08:10:03 +00:00
coderabbitai[bot] (Migrated from github.com) left a comment

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/functions/deckcode.ts (1)

731-774: Fix array-to-string concatenation that breaks rune parsing at line 739

The bug is confirmed. At line 739, runes += card.runes.map((r) => r[0]) concatenates an array with a string, which produces a comma-separated value (e.g., "B,B,F" instead of "BBF"). This malformed string fails both rune regex patterns at lines 63 and 66, causing the rune header to be misparsed as part of the hero name, which then fails validation and invalidates the deck.

The fix is correct—use .join("") to concatenate the rune characters:

-				runes += card.runes.map((r) => r[0]);
+				runes += card.runes.map((r) => r[0]).join("");
🧹 Nitpick comments (5)
tools/cardcreator/custom.ts (1)

126-143: Remove redundant filter after error throw.

The code throws an error on line 137 if any rune is undefined, making the filter on line 140 unreachable for undefined values. While the filter is defensive, it's redundant given the preceding throw.

Apply this diff to remove the redundant filter:

 		const runesArray = runesString
 			.split("")
 			.map((char) =>
 				Object.values(Rune).find((rune) => rune.startsWith(char)),
 			);
 		if (runesArray.some((rune) => rune === undefined)) {
 			throw new Error("Invalid rune found.");
 		}
 
-		runes = runesArray.filter((rune) => rune !== undefined);
+		runes = runesArray as Rune[];

Since we've already verified no undefined values exist, we can safely cast the array to Rune[].

tools/deckcreator.ts (1)

97-119: Simplify class assignment - unnecessary enum lookup.

On line 112, the expression Class[cl as Class] is redundant. For string enums like Class, when cl is "Mage", Class["Mage"] simply returns "Mage" again. Since cl is already validated as a member of the classes array (which contains Class values), you can assign it directly.

Apply this diff to simplify:

-	if (!cl) {
-		await game.pause("\n<red>Invalid class.</red>");
-		return askClass();
-	}
-
-	heroClass = Class[cl as Class];
+	if (!cl) {
+		await game.pause("\n<red>Invalid class.</red>");
+		return askClass();
+	}
+
+	heroClass = cl;

The subsequent undefined check (lines 115-119) ensures type safety, so this simplification is safe.

src/player.ts (1)

15-18: Rune[] integration is consistent; consider empty‑requirement semantics and a helper for counts

The switch to a strongly typed Rune import plus runes: Rune[] = [] and the new testRunes(runes: Rune[]) implementation looks aligned with the enum‑based design and preserves the old “count runes by type” behavior.

Two small follow‑ups to consider:

  • If testRunes([]) is ever called for cards with no rune requirement, the guard if (runes.length <= 0 || this.runes.length <= 0) forces it to return false. If call sites don’t already skip rune‑less cards, this could incorrectly reject otherwise valid cards. A safer pattern is e.g.:

    testRunes(runes: Rune[]): boolean {
    	if (runes.length === 0) {
    		return true;
    	}
    	if (this.runes.length === 0) {
    		return false;
    	}
    	// existing counting logic...
    }
    
  • You recompute counts via filter six times. A small helper like countRunes(runes: Rune[]) returning { blood, frost, unholy } would keep the logic DRY and make future rune additions less error‑prone.

Also applies to: 275-277, 981-983, 1031-1052

src/functions/deckcode.ts (2)

1-12: Make Rune import + char→Rune mapping type‑safe

Importing Rune and mapping from characters to enum values is the right direction, but this block has a couple of type/robustness sharp edges:

const actualRunes = runes
	.split("")
	.map((char) =>
		Object.values(Rune).find((rune) => rune.startsWith(char)),
	)
	// TODO: Maybe throw an error if this fails?
	.filter((r) => r !== undefined);

player.runes = actualRunes;
  • Object.values(Rune) will include non‑string members if Rune is a numeric enum; calling .startsWith on those will throw at runtime. This is safe only if Rune is a string enum.
  • filter((r) => r !== undefined) does not narrow the type for TypeScript, so actualRunes is still seen as (Rune | undefined)[], which doesn’t strictly match player.runes: Rune[].

A more robust version that keeps runtime behavior and tightens types:

const actualRunes = runes
	.split("")
	.map((char) =>
		Object.values(Rune)
			.filter((v): v is string => typeof v === "string")
			.find((rune) => rune.startsWith(char)),
	)
	.filter((r): r is Rune => r !== undefined);

player.runes = actualRunes;

This both guards against non‑string enum members and gives you a clean Rune[] type.

Also applies to: 90-101


321-323: Exporting runes from Rune[] – tighten handling of empty arrays and update docs

You’ve updated the export API to accept Rune[]:

/**
 * @param runes The runes of the deck. Example: "BFU"
 */
export(
	deck: CardLike[],
	heroClass: string,
	runes: Rune[],
): { code: string; error: FunctionsExportDeckError } {
	
	if (runes) {
		deckcode +=
			new Set(runes).size === 1
				? `[3${runes[0][0]}] `
				: `[${runes.map((r) => r[0]).join("")}] `;
	}

Two things to clean up:

  • The JSDoc still shows runes as the old "BFU" string representation. It should now show a Rune[] example, e.g. [Rune.Blood, Rune.Frost, Rune.Unholy], and maybe note that the deckcode still uses initials.
  • if (runes) is always true for an array, even when it’s empty. If any call site passes [] for non‑Death Knight decks, this will render [] into the deckcode, which won’t match the import regex and will yield an invalid header.

A small adjustment makes the behavior safer and clearer:

/**
 * @param runes The runes of the deck. Example: [Rune.Blood, Rune.Frost, Rune.Unholy]
 */
export(
	deck: CardLike[],
	heroClass: string,
	runes: Rune[],
): { code: string; error: FunctionsExportDeckError } {
	
	if (runes.length) {
		const runeChars = runes.map((r) => r[0]);
		deckcode +=
			new Set(runeChars).size === 1
				? `[3${runeChars[0]}] `
				: `[${runeChars.join("")}] `;
	}

This preserves the existing encoding (single‑type [3X] or mixed [XYZ]) while avoiding invalid [] output.

Also applies to: 325-329, 355-363

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 99b306e0b3 and a105ec6aa2.

📒 Files selected for processing (10)
  • cards/Examples/2/4-runes.ts (2 hunks)
  • cards/Examples/2/lastb-main.ts (2 hunks)
  • src/card.ts (2 hunks)
  • src/functions/deckcode.ts (6 hunks)
  • src/player.ts (3 hunks)
  • src/types/card.ts (2 hunks)
  • test/src/player.test.ts (2 hunks)
  • tools/cardcreator/custom.ts (2 hunks)
  • tools/cardcreator/lib.ts (2 hunks)
  • tools/deckcreator.ts (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
tools/deckcreator.ts (1)
src/functions/interact.ts (1)
  • watermark (858-922)
tools/cardcreator/custom.ts (2)
src/game.ts (1)
  • input (1252-1262)
src/functions/interact.ts (1)
  • input (1203-1250)
tools/cardcreator/lib.ts (1)
cards/Examples/2/4-runes.ts (1)
  • blueprint (13-37)
🔇 Additional comments (8)
src/types/card.ts (2)

188-195: LGTM! Clean enum definition.

The new Rune enum is well-defined with clear, self-documenting values. The string enum pattern is consistent with other enums in the file (e.g., Class, Rarity, SpellSchool).


420-420: Type change verification complete—all rune usages properly updated.

Codebase search confirms zero string-based rune assignments. All usages across the codebase (deckcreator, deckcode, player logic, card types, tests, and examples) consistently use the Rune enum, confirming the breaking change has been fully migrated.

cards/Examples/2/lastb-main.ts (1)

10-10: LGTM! Example updated correctly.

The import of Rune is properly added, and the rune assignment correctly uses the new array-based syntax with enum values.

Also applies to: 30-30

src/card.ts (1)

20-20: LGTM! Core type change correctly implemented.

The Rune type import and the field type change from string to Rune[] are correctly implemented. This is the fundamental change that enables typed rune handling throughout the codebase.

Also applies to: 304-304

test/src/player.test.ts (1)

13-13: Excellent test coverage for the Rune[] migration.

The tests comprehensively validate the new Rune[] behavior:

  • Order independence (e.g., [Rune.Blood, Rune.Unholy] vs [Rune.Unholy, Rune.Blood])
  • Repeated rune handling (e.g., [Rune.Blood, Rune.Blood, Rune.Blood])
  • Subset validation (ensuring cards requiring 2 Frost runes fail when player has only 1 Frost)

This thorough testing gives confidence that the migration is correct.

Also applies to: 638-684

cards/Examples/2/4-runes.ts (1)

9-9: LGTM! Rune assignment correctly updated.

The import and array-based rune assignment correctly reflect the requirement stated in the comment (2 Frost runes and 1 Blood rune).

Also applies to: 30-30

tools/cardcreator/lib.ts (1)

192-194: LGTM! Card generation correctly updated for Rune[].

The rune serialization correctly generates array syntax with enum values ([Rune.Blood, Rune.Frost]), and the conditional import ensures Rune is only imported when needed.

Also applies to: 261-263

tools/deckcreator.ts (1)

123-148: LGTM! Rune collection logic correctly implemented.

The rune collection loop properly:

  • Collects up to 3 runes from user input
  • Maps single-character input to Rune enum values by matching the first character
  • Validates and skips invalid input
  • Assigns the final array to player1.runes
**Actionable comments posted: 0** > [!CAUTION] > Some comments are outside the diff and can’t be posted inline due to platform limitations. > > > > <details> > <summary>⚠️ Outside diff range comments (1)</summary><blockquote> > > <details> > <summary>src/functions/deckcode.ts (1)</summary><blockquote> > > `731-774`: **Fix array-to-string concatenation that breaks rune parsing at line 739** > > The bug is confirmed. At line 739, `runes += card.runes.map((r) => r[0])` concatenates an array with a string, which produces a comma-separated value (e.g., `"B,B,F"` instead of `"BBF"`). This malformed string fails both rune regex patterns at lines 63 and 66, causing the rune header to be misparsed as part of the hero name, which then fails validation and invalidates the deck. > > The fix is correct—use `.join("")` to concatenate the rune characters: > > ```diff > - runes += card.runes.map((r) => r[0]); > + runes += card.runes.map((r) => r[0]).join(""); > ``` > > </blockquote></details> > > </blockquote></details> <details> <summary>🧹 Nitpick comments (5)</summary><blockquote> <details> <summary>tools/cardcreator/custom.ts (1)</summary><blockquote> `126-143`: **Remove redundant filter after error throw.** The code throws an error on line 137 if any rune is undefined, making the filter on line 140 unreachable for undefined values. While the filter is defensive, it's redundant given the preceding throw. Apply this diff to remove the redundant filter: ```diff const runesArray = runesString .split("") .map((char) => Object.values(Rune).find((rune) => rune.startsWith(char)), ); if (runesArray.some((rune) => rune === undefined)) { throw new Error("Invalid rune found."); } - runes = runesArray.filter((rune) => rune !== undefined); + runes = runesArray as Rune[]; ``` Since we've already verified no undefined values exist, we can safely cast the array to `Rune[]`. </blockquote></details> <details> <summary>tools/deckcreator.ts (1)</summary><blockquote> `97-119`: **Simplify class assignment - unnecessary enum lookup.** On line 112, the expression `Class[cl as Class]` is redundant. For string enums like `Class`, when `cl` is `"Mage"`, `Class["Mage"]` simply returns `"Mage"` again. Since `cl` is already validated as a member of the `classes` array (which contains `Class` values), you can assign it directly. Apply this diff to simplify: ```diff - if (!cl) { - await game.pause("\n<red>Invalid class.</red>"); - return askClass(); - } - - heroClass = Class[cl as Class]; + if (!cl) { + await game.pause("\n<red>Invalid class.</red>"); + return askClass(); + } + + heroClass = cl; ``` The subsequent undefined check (lines 115-119) ensures type safety, so this simplification is safe. </blockquote></details> <details> <summary>src/player.ts (1)</summary><blockquote> `15-18`: **Rune[] integration is consistent; consider empty‑requirement semantics and a helper for counts** The switch to a strongly typed `Rune` import plus `runes: Rune[] = []` and the new `testRunes(runes: Rune[])` implementation looks aligned with the enum‑based design and preserves the old “count runes by type” behavior. Two small follow‑ups to consider: - If `testRunes([])` is ever called for cards with no rune requirement, the guard `if (runes.length <= 0 || this.runes.length <= 0)` forces it to return `false`. If call sites don’t already skip rune‑less cards, this could incorrectly reject otherwise valid cards. A safer pattern is e.g.: ```ts testRunes(runes: Rune[]): boolean { if (runes.length === 0) { return true; } if (this.runes.length === 0) { return false; } // existing counting logic... } ``` - You recompute counts via `filter` six times. A small helper like `countRunes(runes: Rune[])` returning `{ blood, frost, unholy }` would keep the logic DRY and make future rune additions less error‑prone. Also applies to: 275-277, 981-983, 1031-1052 </blockquote></details> <details> <summary>src/functions/deckcode.ts (2)</summary><blockquote> `1-12`: **Make Rune import + char→Rune mapping type‑safe** Importing `Rune` and mapping from characters to enum values is the right direction, but this block has a couple of type/robustness sharp edges: ```ts const actualRunes = runes .split("") .map((char) => Object.values(Rune).find((rune) => rune.startsWith(char)), ) // TODO: Maybe throw an error if this fails? .filter((r) => r !== undefined); player.runes = actualRunes; ``` - `Object.values(Rune)` will include non‑string members if `Rune` is a numeric enum; calling `.startsWith` on those will throw at runtime. This is safe only if `Rune` is a string enum. - `filter((r) => r !== undefined)` does not narrow the type for TypeScript, so `actualRunes` is still seen as `(Rune | undefined)[]`, which doesn’t strictly match `player.runes: Rune[]`. A more robust version that keeps runtime behavior and tightens types: ```ts const actualRunes = runes .split("") .map((char) => Object.values(Rune) .filter((v): v is string => typeof v === "string") .find((rune) => rune.startsWith(char)), ) .filter((r): r is Rune => r !== undefined); player.runes = actualRunes; ``` This both guards against non‑string enum members and gives you a clean `Rune[]` type. Also applies to: 90-101 --- `321-323`: **Exporting runes from Rune[] – tighten handling of empty arrays and update docs** You’ve updated the export API to accept `Rune[]`: ```ts /** * @param runes The runes of the deck. Example: "BFU" */ export( deck: CardLike[], heroClass: string, runes: Rune[], ): { code: string; error: FunctionsExportDeckError } { … if (runes) { deckcode += new Set(runes).size === 1 ? `[3${runes[0][0]}] ` : `[${runes.map((r) => r[0]).join("")}] `; } ``` Two things to clean up: - The JSDoc still shows `runes` as the old `"BFU"` string representation. It should now show a Rune[] example, e.g. `[Rune.Blood, Rune.Frost, Rune.Unholy]`, and maybe note that the deckcode still uses initials. - `if (runes)` is always true for an array, even when it’s empty. If any call site passes `[]` for non‑Death Knight decks, this will render `[] ` into the deckcode, which won’t match the import regex and will yield an invalid header. A small adjustment makes the behavior safer and clearer: ```ts /** * @param runes The runes of the deck. Example: [Rune.Blood, Rune.Frost, Rune.Unholy] */ export( deck: CardLike[], heroClass: string, runes: Rune[], ): { code: string; error: FunctionsExportDeckError } { … if (runes.length) { const runeChars = runes.map((r) => r[0]); deckcode += new Set(runeChars).size === 1 ? `[3${runeChars[0]}] ` : `[${runeChars.join("")}] `; } ``` This preserves the existing encoding (single‑type `[3X]` or mixed `[XYZ]`) while avoiding invalid `[]` output. Also applies to: 325-329, 355-363 </blockquote></details> </blockquote></details> <details> <summary>📜 Review details</summary> **Configuration used**: CodeRabbit UI **Review profile**: CHILL **Plan**: Pro <details> <summary>📥 Commits</summary> Reviewing files that changed from the base of the PR and between 99b306e0b3cae9e2be076ee7430dfecd44e8f753 and a105ec6aa2ebd7d2c65b3a78bde0c4d903902f40. </details> <details> <summary>📒 Files selected for processing (10)</summary> * `cards/Examples/2/4-runes.ts` (2 hunks) * `cards/Examples/2/lastb-main.ts` (2 hunks) * `src/card.ts` (2 hunks) * `src/functions/deckcode.ts` (6 hunks) * `src/player.ts` (3 hunks) * `src/types/card.ts` (2 hunks) * `test/src/player.test.ts` (2 hunks) * `tools/cardcreator/custom.ts` (2 hunks) * `tools/cardcreator/lib.ts` (2 hunks) * `tools/deckcreator.ts` (3 hunks) </details> <details> <summary>🧰 Additional context used</summary> <details> <summary>🧬 Code graph analysis (3)</summary> <details> <summary>tools/deckcreator.ts (1)</summary><blockquote> <details> <summary>src/functions/interact.ts (1)</summary> * `watermark` (858-922) </details> </blockquote></details> <details> <summary>tools/cardcreator/custom.ts (2)</summary><blockquote> <details> <summary>src/game.ts (1)</summary> * `input` (1252-1262) </details> <details> <summary>src/functions/interact.ts (1)</summary> * `input` (1203-1250) </details> </blockquote></details> <details> <summary>tools/cardcreator/lib.ts (1)</summary><blockquote> <details> <summary>cards/Examples/2/4-runes.ts (1)</summary> * `blueprint` (13-37) </details> </blockquote></details> </details> </details> <details> <summary>🔇 Additional comments (8)</summary><blockquote> <details> <summary>src/types/card.ts (2)</summary><blockquote> `188-195`: **LGTM! Clean enum definition.** The new `Rune` enum is well-defined with clear, self-documenting values. The string enum pattern is consistent with other enums in the file (e.g., `Class`, `Rarity`, `SpellSchool`). --- `420-420`: **Type change verification complete—all rune usages properly updated.** Codebase search confirms zero string-based rune assignments. All usages across the codebase (deckcreator, deckcode, player logic, card types, tests, and examples) consistently use the Rune enum, confirming the breaking change has been fully migrated. </blockquote></details> <details> <summary>cards/Examples/2/lastb-main.ts (1)</summary><blockquote> `10-10`: **LGTM! Example updated correctly.** The import of `Rune` is properly added, and the rune assignment correctly uses the new array-based syntax with enum values. Also applies to: 30-30 </blockquote></details> <details> <summary>src/card.ts (1)</summary><blockquote> `20-20`: **LGTM! Core type change correctly implemented.** The `Rune` type import and the field type change from `string` to `Rune[]` are correctly implemented. This is the fundamental change that enables typed rune handling throughout the codebase. Also applies to: 304-304 </blockquote></details> <details> <summary>test/src/player.test.ts (1)</summary><blockquote> `13-13`: **Excellent test coverage for the Rune[] migration.** The tests comprehensively validate the new Rune[] behavior: - Order independence (e.g., `[Rune.Blood, Rune.Unholy]` vs `[Rune.Unholy, Rune.Blood]`) - Repeated rune handling (e.g., `[Rune.Blood, Rune.Blood, Rune.Blood]`) - Subset validation (ensuring cards requiring 2 Frost runes fail when player has only 1 Frost) This thorough testing gives confidence that the migration is correct. Also applies to: 638-684 </blockquote></details> <details> <summary>cards/Examples/2/4-runes.ts (1)</summary><blockquote> `9-9`: **LGTM! Rune assignment correctly updated.** The import and array-based rune assignment correctly reflect the requirement stated in the comment (2 Frost runes and 1 Blood rune). Also applies to: 30-30 </blockquote></details> <details> <summary>tools/cardcreator/lib.ts (1)</summary><blockquote> `192-194`: **LGTM! Card generation correctly updated for Rune[].** The rune serialization correctly generates array syntax with enum values (`[Rune.Blood, Rune.Frost]`), and the conditional import ensures `Rune` is only imported when needed. Also applies to: 261-263 </blockquote></details> <details> <summary>tools/deckcreator.ts (1)</summary><blockquote> `123-148`: **LGTM! Rune collection logic correctly implemented.** The rune collection loop properly: - Collects up to 3 runes from user input - Maps single-character input to Rune enum values by matching the first character - Validates and skips invalid input - Assigns the final array to `player1.runes` </blockquote></details> </blockquote></details> </details> <!-- This is an auto-generated comment by CodeRabbit for review status -->
coderabbitai[bot] (Migrated from github.com) reviewed 2025-11-18 10:36:09 +00:00
coderabbitai[bot] (Migrated from github.com) left a comment

Actionable comments posted: 1

🧹 Nitpick comments (3)
src/functions/deckcode.ts (3)

90-101: Make addRunes char→Rune mapping type‑safe and explicit

The new mapping from characters to Rune has a couple of rough edges:

  • Object.values(Rune).find(...) returns Rune | undefined, and .filter((r) => r !== undefined) does not narrow the type, so actualRunes is still typed as (Rune | undefined)[] and may not assign cleanly to player.runes under strict TypeScript settings.
  • Any unexpected character silently drops out of actualRunes, potentially leaving the player with fewer than 3 runes without a clear error (the TODO hints at this).

You can address both in a small, local tweak:

const actualRunes = runes
  .split("")
  .map((char) =>
    Object.values(Rune).find((rune) => rune.startsWith(char)),
  )
-  // TODO: Maybe throw an error if this fails?
-  .filter((r) => r !== undefined);
+  // TODO: Maybe throw an error if this fails?
+  .filter((r): r is Rune => r !== undefined);

If you do want to fail hard on bad input, you could also add a length check right after this and reuse panic("INVALIDRUNES") or throw.


321-363: Clarify and constrain Rune[] usage in export to avoid malformed codes

The new export signature and serialization logic look reasonable, but there are a couple of edge cases worth tightening up:

  • new Set(runes).size === 1 will encode [Rune.Blood] or [Rune.Blood, Rune.Blood] as [3B] even though the rune array is not length 3. If you expect exactly three runes whenever the array is non‑empty, it’s safer to enforce that invariant:
-    if (runes.length > 0) {
+    if (runes.length > 0) {
       // If the runes is 3 of one type, write, for example, 3B instead of BBB
-      deckcode +=
-        new Set(runes).size === 1
+      deckcode +=
+        runes.length === 3 && new Set(runes).size === 1
          ? `[3${runes[0][0]}] `
          : `[${runes.map((r) => r[0]).join("")}] `;
    }
  • Since this function is a public API and now requires runes: Rune[], you might also consider giving runes a default of [] (or an overload) so non‑rune classes don’t have to plumb an array everywhere, unless you explicitly want this to remain a strict breaking change.

731-741: Avoid duplicating Rune→character logic and relying on r[0]

fromVanilla now turns card.runes: Rune[] into a string via:

runes += card.runes.map((r) => r[0]).join("");

This implicitly relies on Rune’s string representation always starting with the desired deckcode character (B/F/U). The same assumption appears in export (runes[0][0], runes.map((r) => r[0])).

To keep this robust if the enum ever changes, consider centralizing the mapping:

const runeToChar = (r: Rune): string => r[0]; // or a switch/map if enum values change

and then use runeToChar both here and in export. That removes the magic indexing and keeps all rune encoding logic in one place.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a105ec6aa2 and b831562550698d267197eb9e3ed76a2acf18947e.

📒 Files selected for processing (3)
  • src/functions/deckcode.ts (6 hunks)
  • tools/cardcreator/custom.ts (2 hunks)
  • tools/deckcreator.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • tools/deckcreator.ts
🧰 Additional context used
🧬 Code graph analysis (2)
tools/cardcreator/custom.ts (2)
src/game.ts (1)
  • input (1252-1262)
src/functions/interact.ts (1)
  • input (1203-1250)
src/functions/deckcode.ts (3)
tools/deckcreator.ts (2)
  • deck (1142-1147)
  • deckcode (1104-1115)
src/types/other.ts (1)
  • CardLike (104-104)
src/functions/interact.ts (1)
  • deckcode (62-116)
🔇 Additional comments (2)
tools/cardcreator/custom.ts (2)

9-9: LGTM!

The Rune import is correctly added to support the new typed rune system.


141-141: Verify the early-break behavior for multi-class cards.

The loop breaks after capturing runes for the first rune-capable class. If a card belongs to multiple classes that support runes, it will only prompt once and use those runes for all classes.

This appears intentional, but please confirm this aligns with Hearthstone's rune rules—specifically, whether multi-class cards should have a single set of runes or separate runes per class.

**Actionable comments posted: 1** <details> <summary>🧹 Nitpick comments (3)</summary><blockquote> <details> <summary>src/functions/deckcode.ts (3)</summary><blockquote> `90-101`: **Make `addRunes` char→Rune mapping type‑safe and explicit** The new mapping from characters to `Rune` has a couple of rough edges: - `Object.values(Rune).find(...)` returns `Rune | undefined`, and `.filter((r) => r !== undefined)` does not narrow the type, so `actualRunes` is still typed as `(Rune | undefined)[]` and may not assign cleanly to `player.runes` under strict TypeScript settings. - Any unexpected character silently drops out of `actualRunes`, potentially leaving the player with fewer than 3 runes without a clear error (the TODO hints at this). You can address both in a small, local tweak: ```diff const actualRunes = runes .split("") .map((char) => Object.values(Rune).find((rune) => rune.startsWith(char)), ) - // TODO: Maybe throw an error if this fails? - .filter((r) => r !== undefined); + // TODO: Maybe throw an error if this fails? + .filter((r): r is Rune => r !== undefined); ``` If you do want to fail hard on bad input, you could also add a length check right after this and reuse `panic("INVALIDRUNES")` or throw. --- `321-363`: **Clarify and constrain Rune[] usage in `export` to avoid malformed codes** The new `export` signature and serialization logic look reasonable, but there are a couple of edge cases worth tightening up: - `new Set(runes).size === 1` will encode `[Rune.Blood]` or `[Rune.Blood, Rune.Blood]` as `[3B]` even though the rune array is not length 3. If you expect exactly three runes whenever the array is non‑empty, it’s safer to enforce that invariant: ```diff - if (runes.length > 0) { + if (runes.length > 0) { // If the runes is 3 of one type, write, for example, 3B instead of BBB - deckcode += - new Set(runes).size === 1 + deckcode += + runes.length === 3 && new Set(runes).size === 1 ? `[3${runes[0][0]}] ` : `[${runes.map((r) => r[0]).join("")}] `; } ``` - Since this function is a public API and now requires `runes: Rune[]`, you might also consider giving `runes` a default of `[]` (or an overload) so non‑rune classes don’t have to plumb an array everywhere, unless you explicitly want this to remain a strict breaking change. --- `731-741`: **Avoid duplicating Rune→character logic and relying on `r[0]`** `fromVanilla` now turns `card.runes: Rune[]` into a string via: ```ts runes += card.runes.map((r) => r[0]).join(""); ``` This implicitly relies on `Rune`’s string representation always starting with the desired deckcode character (B/F/U). The same assumption appears in `export` (`runes[0][0]`, `runes.map((r) => r[0])`). To keep this robust if the enum ever changes, consider centralizing the mapping: ```ts const runeToChar = (r: Rune): string => r[0]; // or a switch/map if enum values change ``` and then use `runeToChar` both here and in `export`. That removes the magic indexing and keeps all rune encoding logic in one place. </blockquote></details> </blockquote></details> <details> <summary>📜 Review details</summary> **Configuration used**: CodeRabbit UI **Review profile**: CHILL **Plan**: Pro <details> <summary>📥 Commits</summary> Reviewing files that changed from the base of the PR and between a105ec6aa2ebd7d2c65b3a78bde0c4d903902f40 and b831562550698d267197eb9e3ed76a2acf18947e. </details> <details> <summary>📒 Files selected for processing (3)</summary> * `src/functions/deckcode.ts` (6 hunks) * `tools/cardcreator/custom.ts` (2 hunks) * `tools/deckcreator.ts` (3 hunks) </details> <details> <summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary> * tools/deckcreator.ts </details> <details> <summary>🧰 Additional context used</summary> <details> <summary>🧬 Code graph analysis (2)</summary> <details> <summary>tools/cardcreator/custom.ts (2)</summary><blockquote> <details> <summary>src/game.ts (1)</summary> * `input` (1252-1262) </details> <details> <summary>src/functions/interact.ts (1)</summary> * `input` (1203-1250) </details> </blockquote></details> <details> <summary>src/functions/deckcode.ts (3)</summary><blockquote> <details> <summary>tools/deckcreator.ts (2)</summary> * `deck` (1142-1147) * `deckcode` (1104-1115) </details> <details> <summary>src/types/other.ts (1)</summary> * `CardLike` (104-104) </details> <details> <summary>src/functions/interact.ts (1)</summary> * `deckcode` (62-116) </details> </blockquote></details> </details> </details> <details> <summary>🔇 Additional comments (2)</summary><blockquote> <details> <summary>tools/cardcreator/custom.ts (2)</summary><blockquote> `9-9`: **LGTM!** The `Rune` import is correctly added to support the new typed rune system. --- `141-141`: **Verify the early-break behavior for multi-class cards.** The loop breaks after capturing runes for the first rune-capable class. If a card belongs to multiple classes that support runes, it will only prompt once and use those runes for all classes. This appears intentional, but please confirm this aligns with Hearthstone's rune rules—specifically, whether multi-class cards should have a single set of runes or separate runes per class. </blockquote></details> </blockquote></details> </details> <!-- This is an auto-generated comment by CodeRabbit for review status -->
@ -117,12 +118,27 @@ async function common(): Promise<BlueprintWithOptional> {
.map((k) => game.lodash.startCase(k) as Keyword);
coderabbitai[bot] (Migrated from github.com) commented 2025-11-18 10:36:08 +00:00

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

Fix case-insensitive matching and potential ambiguity in rune selection.

The current implementation has two critical issues:

  1. Case sensitivity: rune.startsWith(char) is case-sensitive. If users enter lowercase letters (e.g., "f" for Frost) but the enum values are capitalized, the match will fail and throw "Invalid rune found."

  2. Ambiguous matching: Using .find() with .startsWith() returns only the first matching rune. If multiple runes start with the same letter (e.g., "Frost" and "Fire" both start with "F"), users cannot select the second one.

Apply this diff to fix both issues by matching case-insensitively and using a map for unambiguous lookup:

-		const runesString = await input(
-			`Runes (${Object.values(Rune)
-				.map((rune) => rune[0])
-				.join("")}): `,
-		);
-		const runesArray = runesString
-			.split("")
-			.map((char) =>
-				Object.values(Rune).find((rune) => rune.startsWith(char)),
-			);
+		const runeInitials = Object.values(Rune).map((rune) => rune[0].toUpperCase());
+		const runesString = await input(
+			`Runes (${runeInitials.join("")}): `,
+		);
+		const runeMap = new Map(
+			Object.values(Rune).map((rune) => [rune[0].toUpperCase(), rune])
+		);
+		const runesArray = runesString
+			.toUpperCase()
+			.split("")
+			.map((char) => runeMap.get(char));
 		if (runesArray.some((rune) => rune === undefined)) {
-			throw new Error("Invalid rune found.");
+			const validInitials = Array.from(runeMap.keys()).join(", ");
+			throw new Error(`Invalid rune character(s) in input. Valid initials: ${validInitials}`);
 		}
 
 		runes = runesArray as Rune[];

This change:

  • Normalizes input and enum values to uppercase for case-insensitive comparison
  • Uses a Map for O(1) lookup and unambiguous matching
  • Improves the error message to show valid options

Run the following script to verify the Rune enum definition and check for initial collisions:


🏁 Script executed:

#!/bin/bash
# Description: Verify Rune enum values and check for collisions in first characters

# Find the Rune enum definition
ast-grep --pattern 'enum Rune {
  $$$
}'

# Also search for any Rune type definitions
rg -nP --type=ts -A 10 'enum Rune'

Length of output: 654


Fix case-sensitivity in rune selection logic.

The case-sensitivity issue is valid—users entering lowercase letters will be rejected even though the prompt only shows uppercase initials. However, verification shows the Rune enum has unique first characters (B, F, U), so there is no collision/ambiguity concern.

A simpler fix addresses only the actual issue:

-		const runesArray = runesString
-			.split("")
-			.map((char) =>
-				Object.values(Rune).find((rune) => rune.startsWith(char)),
-			);
+		const runesArray = runesString
+			.toUpperCase()
+			.split("")
+			.map((char) =>
+				Object.values(Rune).find((rune) => rune.startsWith(char)),
+			);

This normalizes input to uppercase, making matching case-insensitive without overcomplicating the logic.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

			const runesString = await input(
				`Runes (${Object.values(Rune)
					.map((rune) => rune[0])
					.join("")}): `,
			);
			const runesArray = runesString
				.toUpperCase()
				.split("")
				.map((char) =>
					Object.values(Rune).find((rune) => rune.startsWith(char)),
				);
			if (runesArray.some((rune) => rune === undefined)) {
				throw new Error("Invalid rune found.");
			}

			runes = runesArray as Rune[];
🤖 Prompt for AI Agents
In tools/cardcreator/custom.ts around lines 126 to 140, the rune-matching logic
is case-sensitive causing lowercase user input to be rejected; convert the user
input string to uppercase before splitting and matching (i.e., normalize
runesString to uppercase), then perform the same .split("").map(...) using
Object.values(Rune).find(rune => rune.startsWith(char)) and keep the existing
undefined check and cast to Rune[].
_⚠️ Potential issue_ | _🟠 Major_ <details> <summary>🧩 Analysis chain</summary> **Fix case-insensitive matching and potential ambiguity in rune selection.** The current implementation has two critical issues: 1. **Case sensitivity**: `rune.startsWith(char)` is case-sensitive. If users enter lowercase letters (e.g., "f" for Frost) but the enum values are capitalized, the match will fail and throw "Invalid rune found." 2. **Ambiguous matching**: Using `.find()` with `.startsWith()` returns only the first matching rune. If multiple runes start with the same letter (e.g., "Frost" and "Fire" both start with "F"), users cannot select the second one. Apply this diff to fix both issues by matching case-insensitively and using a map for unambiguous lookup: ```diff - const runesString = await input( - `Runes (${Object.values(Rune) - .map((rune) => rune[0]) - .join("")}): `, - ); - const runesArray = runesString - .split("") - .map((char) => - Object.values(Rune).find((rune) => rune.startsWith(char)), - ); + const runeInitials = Object.values(Rune).map((rune) => rune[0].toUpperCase()); + const runesString = await input( + `Runes (${runeInitials.join("")}): `, + ); + const runeMap = new Map( + Object.values(Rune).map((rune) => [rune[0].toUpperCase(), rune]) + ); + const runesArray = runesString + .toUpperCase() + .split("") + .map((char) => runeMap.get(char)); if (runesArray.some((rune) => rune === undefined)) { - throw new Error("Invalid rune found."); + const validInitials = Array.from(runeMap.keys()).join(", "); + throw new Error(`Invalid rune character(s) in input. Valid initials: ${validInitials}`); } runes = runesArray as Rune[]; ``` This change: - Normalizes input and enum values to uppercase for case-insensitive comparison - Uses a `Map` for O(1) lookup and unambiguous matching - Improves the error message to show valid options Run the following script to verify the Rune enum definition and check for initial collisions: --- 🏁 Script executed: ```shell #!/bin/bash # Description: Verify Rune enum values and check for collisions in first characters # Find the Rune enum definition ast-grep --pattern 'enum Rune { $$$ }' # Also search for any Rune type definitions rg -nP --type=ts -A 10 'enum Rune' ``` Length of output: 654 --- </details> **Fix case-sensitivity in rune selection logic.** The case-sensitivity issue is valid—users entering lowercase letters will be rejected even though the prompt only shows uppercase initials. However, verification shows the Rune enum has unique first characters (B, F, U), so there is no collision/ambiguity concern. A simpler fix addresses only the actual issue: ```diff - const runesArray = runesString - .split("") - .map((char) => - Object.values(Rune).find((rune) => rune.startsWith(char)), - ); + const runesArray = runesString + .toUpperCase() + .split("") + .map((char) => + Object.values(Rune).find((rune) => rune.startsWith(char)), + ); ``` This normalizes input to uppercase, making matching case-insensitive without overcomplicating the logic. <!-- suggestion_start --> <details> <summary>📝 Committable suggestion</summary> > ‼️ **IMPORTANT** > Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements. ```suggestion const runesString = await input( `Runes (${Object.values(Rune) .map((rune) => rune[0]) .join("")}): `, ); const runesArray = runesString .toUpperCase() .split("") .map((char) => Object.values(Rune).find((rune) => rune.startsWith(char)), ); if (runesArray.some((rune) => rune === undefined)) { throw new Error("Invalid rune found."); } runes = runesArray as Rune[]; ``` </details> <!-- suggestion_end --> <details> <summary>🤖 Prompt for AI Agents</summary> ``` In tools/cardcreator/custom.ts around lines 126 to 140, the rune-matching logic is case-sensitive causing lowercase user input to be rejected; convert the user input string to uppercase before splitting and matching (i.e., normalize runesString to uppercase), then perform the same .split("").map(...) using Object.values(Rune).find(rune => rune.startsWith(char)) and keep the existing undefined check and cast to Rune[]. ``` </details> <!-- This is an auto-generated comment by CodeRabbit -->
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
LunarTides/Hearthstone.js!418
No description provided.