Add more tests #395

Merged
LunarTides merged 17 commits from tests into main 2025-05-15 08:48:56 +00:00
LunarTides commented 2025-03-03 10:19:33 +00:00 (Migrated from github.com)
  • Player
  • Event
  • Card
  • Commands
  • Game
  • AI

Summary by CodeRabbit

  • New Features

    • Added new "Hero" location type for cards, enhancing hero assignment and tracking.
    • Added enhanced input translation and pause utilities in game interactions.
  • Bug Fixes

    • Improved quest event comments for clarity on required return values.
    • Fixed typographical errors and clarified descriptions in configuration fun facts.
    • Corrected date format for anniversary event detection and improved hero power method naming.
    • Ensured proper card location updates on discard and active card tracking on cloning.
    • Updated event naming from "KillCard" to "DestroyCard" for consistency across game events and card removal.
    • Replaced calls from kill() to destroy() and introduced removeFromPlay() for more accurate card removal handling.
  • Refactor

    • Consolidated card reloading logic to prevent duplicate reloads and infinite loops.
    • Streamlined placeholder replacement and card cloning logic for better robustness.
    • Extracted and encapsulated "Forgetful" attack logic for maintainability.
    • Simplified and clarified player and card method implementations.
    • Refactored quest removal logic to reduce duplication and improve clarity.
    • Integrated card testing logic inline for streamlined test execution.
  • Documentation

    • Updated command help texts and tool descriptions for clarity and caution.
    • Improved JSDoc comments for card creator utility functions.
    • Updated code statistics and date in documentation.
  • Style

    • Removed unused imports, commented-out code, and improved code comments for readability.
  • Tests

    • Replaced placeholder and failing test stubs with comprehensive tests for card and player behaviors.
    • Cleared failing assertions from unimplemented test placeholders, marking them as pending.
  • Chores

    • Updated command enums for consistency and internal code cleanup.
    • Removed obsolete registration call in deck creator tool.
    • Added timeouts to CI workflow to prevent indefinite execution.
- [ ] Player - [ ] Event - [ ] Card - [ ] Commands - [ ] Game - [ ] AI <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added new "Hero" location type for cards, enhancing hero assignment and tracking. - Added enhanced input translation and pause utilities in game interactions. - **Bug Fixes** - Improved quest event comments for clarity on required return values. - Fixed typographical errors and clarified descriptions in configuration fun facts. - Corrected date format for anniversary event detection and improved hero power method naming. - Ensured proper card location updates on discard and active card tracking on cloning. - Updated event naming from "KillCard" to "DestroyCard" for consistency across game events and card removal. - Replaced calls from `kill()` to `destroy()` and introduced `removeFromPlay()` for more accurate card removal handling. - **Refactor** - Consolidated card reloading logic to prevent duplicate reloads and infinite loops. - Streamlined placeholder replacement and card cloning logic for better robustness. - Extracted and encapsulated "Forgetful" attack logic for maintainability. - Simplified and clarified player and card method implementations. - Refactored quest removal logic to reduce duplication and improve clarity. - Integrated card testing logic inline for streamlined test execution. - **Documentation** - Updated command help texts and tool descriptions for clarity and caution. - Improved JSDoc comments for card creator utility functions. - Updated code statistics and date in documentation. - **Style** - Removed unused imports, commented-out code, and improved code comments for readability. - **Tests** - Replaced placeholder and failing test stubs with comprehensive tests for card and player behaviors. - Cleared failing assertions from unimplemented test placeholders, marking them as pending. - **Chores** - Updated command enums for consistency and internal code cleanup. - Removed obsolete registration call in deck creator tool. - Added timeouts to CI workflow to prevent indefinite execution. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
coderabbitai[bot] commented 2025-05-13 12:10:27 +00:00 (Migrated from github.com)

Walkthrough

This update introduces a broad set of refinements, bug fixes, and test expansions across the codebase. Major changes include improved placeholder handling and keyword logic in the Card class, refactored attack and quest mechanics in Game and eventManager, and significant expansions to test coverage, especially for Card and Player behaviors. Multiple help and command descriptions were clarified, and minor code cleanups, such as removing unused imports and comments, were made throughout.

Changes

File(s) / Path(s) Change Summary
src/card.ts Refactored placeholder replacement logic with enhanced type checks and error logging, improved keyword and stat methods, ensured proper location updates on discard, and tracked clones in active cards.
src/game.ts Extracted "Forgetful" attack logic into a dedicated async method, improved taunt minion filtering to target owner's board, fixed anniversary date format, renamed Heropower to HeroPower, added enhanced input translation and pause methods, and replaced kill calls with destroy in poison and removal logic.
src/event.ts Added runtime check for card initialization in hand, refactored quest removal logic into a helper async function to consolidate quest removal and side effects, and changed event key checks from KillCard to DestroyCard.
src/player.ts Updated weapon destruction to use destroy(), improved drawSpecific to return undefined if card not in deck, cleared old hero location when setting new hero, simplified starting hero assignment, and adjusted quest addition to refund resource on failure.
src/types/card.ts Added new enum member Hero to Location.
src/types/event.ts Renamed enum member KillCard to DestroyCard and updated associated type mappings accordingly.
src/commands.ts Enhanced help command descriptions for clarity and punctuation, consolidated debug card reloading to iterate over game.activeCards with duplicate UUID checks to avoid infinite loops.
src/functions/interact.ts Removed unused import, changed fun fact selection logic to exclude certain seen facts after cycling through all.
src/functions/util.ts Removed commented-out default case line in switch statement.
cards/Classes/Neutral/Collectible/Minions/10-Cost/104-yogg-saron-master-of-fate.ts,
index.ts,
src/functions/util.ts,
tools/cardcreator/vanilla.ts
Removed commented-out // No default lines from switch statements for code clarity.
cards/Examples/4/4-quest.ts Updated comments to reference EventListenerMessage enum instead of boolean return values for quest event listener callbacks.
cards/StartingHeroes/Mage/114-heropower.ts Removed unused import TargetFlag.
config.ts Fixed typo in "existence" and rephrased fun fact descriptions for clarity and conciseness.
loc.md Updated file and line count statistics and date to reflect current codebase metrics.
scripts/cardtest.ts Refactored to inline test and deck setup logic, removed helper functions, disabled AI, replaced decks with 30 "sheep1" cards, improved error handling and reporting, and updated skip/todo counting.
scripts/crashtest.ts Changed deck creation to random selection of 30 cards with imperfect copies, removed player references from game setup, and simplified environment variable handling.
scripts/id/main.ts Replaced command string literals with enum members, fixed capitalization in commented exit call.
scripts/vanilla/generate.ts Added boolean argument false to createGame() call to alter initialization behavior.
tools/cardcreator/class.ts Moved explanatory comment about heropowerId calculation for clarity.
tools/cardcreator/lib.ts Refactored getCardAbility to return directly from cases, updated JSDoc comments to reflect parameter changes and remove unused param descriptions.
tools/cardcreator/vanilla.ts Removed blank line and commented-out // No default line from switch statement.
tools/deckcreator.ts Enhanced help command descriptions with punctuation and clarifications, removed explicit Card.registerAll() call in main function.
test/src/card.test.ts Replaced many test.todo placeholders with detailed tests covering keywords, stats, freezing, attack counts, health modifications, stealth duration, resetting stats, and location management.
test/src/player.test.ts Expanded from placeholders to comprehensive asynchronous tests for player methods including weapon handling, drawing, hero setting, quest progression, and event triggers.
test/src/ai.test.ts,
test/src/commands.test.ts,
test/src/event.test.ts,
test/src/functions/card.test.ts,
test/src/functions/deckcode.test.ts,
test/src/functions/info.test.ts,
test/src/functions/interact.test.ts,
test/src/functions/util.test.ts,
test/src/game.test.ts
Cleared failing assertions from test placeholders, leaving empty async functions as stubs for future implementation.
test/src/functions/util.test.ts Added new assertion testing parseEvalArgs function's support for async method calls on card UUIDs.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Game
    participant Card
    participant Player
    participant EventManager

    User->>Game: Initiates attack
    Game->>Card: Checks attacker keywords (e.g., Forgetful)
    alt Attacker is Forgetful
        Game->>Game: Calls _forgetful(attacker, flags)
        Game->>Card: Performs coin flip
        alt Coin flip fails
            Game->>Game: Attempts random attacks (up to max)
        end
        Game->>Card: Updates forgetful state
    end
    Game->>Card: Proceeds with attack (if not skipped)
    Card->>Player: Applies damage/effects
    Game->>EventManager: Triggers relevant events
sequenceDiagram
    participant Game
    participant Player
    participant EventManager

    Game->>Player: Progresses quest
    Player->>EventManager: Calls questUpdate
    EventManager->>Player: Checks quest progress
    alt Quest complete
        EventManager->>Player: Calls removeQuest helper
        Player->>EventManager: Removes quest, triggers next if present
    end

Poem

In the warren where code bunnies play,
We tidied up comments, swept old lines away.
Cards now shimmer with keywords anew,
Quests and attacks run clever and true.
Tests have multiplied, hopping with cheer—
A springtime of features, the update is here!
🐇

Note

️ AI Code Reviews for VS Code, Cursor, Windsurf

CodeRabbit now has a plugin for VS Code, Cursor and Windsurf. This brings AI code reviews directly in the code editor. Each commit is reviewed immediately, finding bugs before the PR is raised. Seamless context handoff to your AI code agent ensures that you can easily incorporate review feedback.
Learn more here.


Note

️ Faster reviews with caching

CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 16th. To opt out, configure Review - Disable Cache at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the Data Retention setting under your Organization Settings.
Enjoy the performance boost—your workflow just got faster.

Finishing Touches
  • 📝 Generate Docstrings

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.
<!-- This is an auto-generated comment: summarize by coderabbit.ai --> <!-- walkthrough_start --> ## Walkthrough This update introduces a broad set of refinements, bug fixes, and test expansions across the codebase. Major changes include improved placeholder handling and keyword logic in the `Card` class, refactored attack and quest mechanics in `Game` and `eventManager`, and significant expansions to test coverage, especially for `Card` and `Player` behaviors. Multiple help and command descriptions were clarified, and minor code cleanups, such as removing unused imports and comments, were made throughout. ## Changes | File(s) / Path(s) | Change Summary | |-----------------------------------------------------------|---------------| | `src/card.ts` | Refactored placeholder replacement logic with enhanced type checks and error logging, improved keyword and stat methods, ensured proper location updates on discard, and tracked clones in active cards. | | `src/game.ts` | Extracted "Forgetful" attack logic into a dedicated async method, improved taunt minion filtering to target owner's board, fixed anniversary date format, renamed `Heropower` to `HeroPower`, added enhanced input translation and pause methods, and replaced kill calls with destroy in poison and removal logic. | | `src/event.ts` | Added runtime check for card initialization in hand, refactored quest removal logic into a helper async function to consolidate quest removal and side effects, and changed event key checks from KillCard to DestroyCard. | | `src/player.ts` | Updated weapon destruction to use `destroy()`, improved `drawSpecific` to return undefined if card not in deck, cleared old hero location when setting new hero, simplified starting hero assignment, and adjusted quest addition to refund resource on failure. | | `src/types/card.ts` | Added new enum member `Hero` to `Location`. | | `src/types/event.ts` | Renamed enum member `KillCard` to `DestroyCard` and updated associated type mappings accordingly. | | `src/commands.ts` | Enhanced help command descriptions for clarity and punctuation, consolidated debug card reloading to iterate over `game.activeCards` with duplicate UUID checks to avoid infinite loops. | | `src/functions/interact.ts` | Removed unused import, changed fun fact selection logic to exclude certain seen facts after cycling through all. | | `src/functions/util.ts` | Removed commented-out default case line in switch statement. | | `cards/Classes/Neutral/Collectible/Minions/10-Cost/104-yogg-saron-master-of-fate.ts`,<br>`index.ts`,<br>`src/functions/util.ts`,<br>`tools/cardcreator/vanilla.ts` | Removed commented-out `// No default` lines from switch statements for code clarity. | | `cards/Examples/4/4-quest.ts` | Updated comments to reference `EventListenerMessage` enum instead of boolean return values for quest event listener callbacks. | | `cards/StartingHeroes/Mage/114-heropower.ts` | Removed unused import `TargetFlag`. | | `config.ts` | Fixed typo in "existence" and rephrased fun fact descriptions for clarity and conciseness. | | `loc.md` | Updated file and line count statistics and date to reflect current codebase metrics. | | `scripts/cardtest.ts` | Refactored to inline test and deck setup logic, removed helper functions, disabled AI, replaced decks with 30 "sheep1" cards, improved error handling and reporting, and updated skip/todo counting. | | `scripts/crashtest.ts` | Changed deck creation to random selection of 30 cards with imperfect copies, removed player references from game setup, and simplified environment variable handling. | | `scripts/id/main.ts` | Replaced command string literals with enum members, fixed capitalization in commented exit call. | | `scripts/vanilla/generate.ts` | Added boolean argument `false` to `createGame()` call to alter initialization behavior. | | `tools/cardcreator/class.ts` | Moved explanatory comment about `heropowerId` calculation for clarity. | | `tools/cardcreator/lib.ts` | Refactored `getCardAbility` to return directly from cases, updated JSDoc comments to reflect parameter changes and remove unused param descriptions. | | `tools/cardcreator/vanilla.ts` | Removed blank line and commented-out `// No default` line from switch statement. | | `tools/deckcreator.ts` | Enhanced help command descriptions with punctuation and clarifications, removed explicit `Card.registerAll()` call in main function. | | `test/src/card.test.ts` | Replaced many `test.todo` placeholders with detailed tests covering keywords, stats, freezing, attack counts, health modifications, stealth duration, resetting stats, and location management. | | `test/src/player.test.ts` | Expanded from placeholders to comprehensive asynchronous tests for player methods including weapon handling, drawing, hero setting, quest progression, and event triggers. | | `test/src/ai.test.ts`,<br>`test/src/commands.test.ts`,<br>`test/src/event.test.ts`,<br>`test/src/functions/card.test.ts`,<br>`test/src/functions/deckcode.test.ts`,<br>`test/src/functions/info.test.ts`,<br>`test/src/functions/interact.test.ts`,<br>`test/src/functions/util.test.ts`,<br>`test/src/game.test.ts` | Cleared failing assertions from test placeholders, leaving empty async functions as stubs for future implementation. | | `test/src/functions/util.test.ts` | Added new assertion testing `parseEvalArgs` function's support for async method calls on card UUIDs. | ## Sequence Diagram(s) ```mermaid sequenceDiagram participant User participant Game participant Card participant Player participant EventManager User->>Game: Initiates attack Game->>Card: Checks attacker keywords (e.g., Forgetful) alt Attacker is Forgetful Game->>Game: Calls _forgetful(attacker, flags) Game->>Card: Performs coin flip alt Coin flip fails Game->>Game: Attempts random attacks (up to max) end Game->>Card: Updates forgetful state end Game->>Card: Proceeds with attack (if not skipped) Card->>Player: Applies damage/effects Game->>EventManager: Triggers relevant events ``` ```mermaid sequenceDiagram participant Game participant Player participant EventManager Game->>Player: Progresses quest Player->>EventManager: Calls questUpdate EventManager->>Player: Checks quest progress alt Quest complete EventManager->>Player: Calls removeQuest helper Player->>EventManager: Removes quest, triggers next if present end ``` ## Poem > In the warren where code bunnies play, > We tidied up comments, swept old lines away. > Cards now shimmer with keywords anew, > Quests and attacks run clever and true. > Tests have multiplied, hopping with cheer— > A springtime of features, the update is here! > 🐇✨ <!-- walkthrough_end --> <!-- announcements_start --> > [!NOTE] > <details> > <summary>⚡️ AI Code Reviews for VS Code, Cursor, Windsurf</summary> > > CodeRabbit now has a plugin for VS Code, Cursor and Windsurf. This brings AI code reviews directly in the code editor. Each commit is reviewed immediately, finding bugs before the PR is raised. Seamless context handoff to your AI code agent ensures that you can easily incorporate review feedback. > Learn more [here](http://coderabbit.ai/ide). > > </details> --- > [!NOTE] > <details> > <summary>⚡️ Faster reviews with caching</summary> > > CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 16th. To opt out, configure `Review - Disable Cache` at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the `Data Retention` setting under your Organization Settings. > Enjoy the performance boost—your workflow just got faster. > > </details> <!-- announcements_end --> <!-- internal state start --> <!-- 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 --> <!-- internal state end --> <!-- finishing_touch_checkbox_start --> <details open="true"> <summary>✨ Finishing Touches</summary> - [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings </details> <!-- finishing_touch_checkbox_end --> <!-- tips_start --> --- 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. <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> <details> <summary>🪧 Tips</summary> ### Chat There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=LunarTides/Hearthstone.js&utm_content=395): - Review comments: Directly reply to a review comment made by CodeRabbit. Example: - `I pushed a fix in commit <commit_id>, please review it.` - `Explain this complex logic.` - `Open a follow-up GitHub issue for this discussion.` - Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples: - `@coderabbitai explain this code block.` - `@coderabbitai modularize this function.` - PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples: - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.` - `@coderabbitai read src/utils.ts and explain its main purpose.` - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.` - `@coderabbitai help me debug CodeRabbit configuration file.` ### Support Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. ### CodeRabbit Commands (Invoked using PR comments) - `@coderabbitai pause` to pause the reviews on a PR. - `@coderabbitai resume` to resume the paused reviews. - `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository. - `@coderabbitai full review` to do a full review from scratch and review all the files again. - `@coderabbitai summary` to regenerate the summary of the PR. - `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR. - `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR. - `@coderabbitai resolve` resolve all the CodeRabbit review comments. - `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository. - `@coderabbitai help` to get help. ### Other keywords and placeholders - Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed. - Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description. - Add `@coderabbitai` anywhere in the PR title to generate the title automatically. ### CodeRabbit Configuration File (`.coderabbit.yaml`) - You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository. - Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information. - If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json` ### Documentation and Community - Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit. - Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback. - Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements. </details> <!-- tips_end -->
LunarTides commented 2025-05-14 07:35:49 +00:00 (Migrated from github.com)

@coderabbitai review

@coderabbitai review
coderabbitai[bot] commented 2025-05-14 07:35:55 +00:00 (Migrated from github.com)
Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

<!-- This is an auto-generated reply by CodeRabbit --> <details> <summary>✅ Actions performed</summary> Review triggered. > Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused. </details>
coderabbitai[bot] (Migrated from github.com) reviewed 2025-05-14 07:40:10 +00:00
coderabbitai[bot] (Migrated from github.com) left a comment

Actionable comments posted: 12

🔭 Outside diff range comments (1)
src/card.ts (1)

1298-1304: ⚠️ Potential issue

Don’t bypass setLocation()activeCards entry will linger

discard() manually sets this.location = Location.None after removing the card from the player’s hand.
Unlike await this.setLocation(Location.None) it does not:

  1. Remove the card from game.activeCards.
  2. Broadcast the ChangeLocation event.

That leaves a “ghost” card in activeCards, which can break enumeration logic (e.g. reload, killCardsOnBoard) and leak memory.

-			this.location = Location.None;
+			// Use the canonical helper so all side-effects are honoured
+			await this.setLocation(Location.None);
🧹 Nitpick comments (7)
scripts/id/main.ts (1)

10-10: Commented-out code cleanup

The case correction in the commented-out code is good, but consider removing this commented-out code altogether if it's not needed, as it doesn't serve any functional purpose.

- // process.exit(1);
src/event.ts (1)

202-214: Excellent refactoring to eliminate duplicate code.

The extraction of quest removal logic into a reusable helper function removeQuest improves maintainability and reduces duplication. The function properly encapsulates the full quest removal workflow including:

  1. Removing the quest from the player's collection
  2. Showing appropriate messages for secrets
  3. Handling follow-up quests when applicable

This change reduces the risk of inconsistent implementations in the two places where quest removal was previously duplicated.

Consider adding a JSDoc comment to document the helper function's purpose and parameters.

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

13-322: Consider prioritizing remaining tests

While the test suite has significantly improved, there are still many test.todo entries for important methods. Consider prioritizing tests for core functionality like trigger, discard, perfectCopy, and replacePlaceholders next.

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

316-318: Array.prototype.toSorted may not be available in every runtime

Node LTS prior to v20 and some bundlers still lack toSorted.
If you target anything below that, replace with ([...array]).sort() or import a polyfill, otherwise the test suite will crash outside Bun.

src/player.ts (1)

1216-1218: Refund logic can break when costType isn’t “mana”

this[card.costType] += card.cost; assumes the player already has that numeric
property (e.g. corpses, runes are strings).
Guard against non-numeric types or undefined keys to avoid NaN propagation.

src/game.ts (1)

561-637: _forgetful can spin for a long time when no valid targets exist

If every attempt returns an invalid target, the loop may iterate up to
forgetfulRandomTargetFailAmount with no early escape, wasting cycles.

Add a quick bail-out when the target pool is empty:

-			const target = game.functions.util.getRandomTarget(...);
-			// If a target wasn't found, just continue with the attack
-			if (!target) {
-				break;
-			}
+			const target = game.functions.util.getRandomTarget(...);
+			if (!target) {
+				// No possible targets – stop looping
+				break;
+			}
src/card.ts (1)

1588-1631: Minor perf tweak – avoid calling parseTags inside the loop

parseTags runs inside the {key}-replacement while loop, meaning it can be executed dozens of times for a long description. Moving it outside (or deferring until the end) yields the same functional result with noticeably less overhead.

-				text = parseTags(text.replace(reg, replacement));
+				text = text.replace(reg, replacement);
 ...
-		return text.replaceAll("__hsjs__ignore:", "{");
+		return parseTags(
+			text.replaceAll("__hsjs__ignore:", "{"),
+		);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5805b0631e and b2d2a3669a.

📒 Files selected for processing (33)
  • cards/Classes/Neutral/Collectible/Minions/10-Cost/104-yogg-saron-master-of-fate.ts (0 hunks)
  • cards/Examples/4/4-quest.ts (2 hunks)
  • cards/StartingHeroes/Mage/114-heropower.ts (0 hunks)
  • config.ts (2 hunks)
  • index.ts (0 hunks)
  • loc.md (1 hunks)
  • scripts/cardtest.ts (1 hunks)
  • scripts/crashtest.ts (4 hunks)
  • scripts/id/main.ts (4 hunks)
  • scripts/vanilla/generate.ts (1 hunks)
  • src/card.ts (4 hunks)
  • src/commands.ts (3 hunks)
  • src/event.ts (4 hunks)
  • src/functions/interact.ts (1 hunks)
  • src/functions/util.ts (0 hunks)
  • src/game.ts (7 hunks)
  • src/player.ts (7 hunks)
  • src/types/card.ts (1 hunks)
  • test/src/ai.test.ts (1 hunks)
  • test/src/card.test.ts (1 hunks)
  • test/src/commands.test.ts (1 hunks)
  • test/src/event.test.ts (1 hunks)
  • test/src/functions/card.test.ts (3 hunks)
  • test/src/functions/deckcode.test.ts (1 hunks)
  • test/src/functions/info.test.ts (1 hunks)
  • test/src/functions/interact.test.ts (1 hunks)
  • test/src/functions/util.test.ts (5 hunks)
  • test/src/game.test.ts (1 hunks)
  • test/src/player.test.ts (3 hunks)
  • tools/cardcreator/class.ts (1 hunks)
  • tools/cardcreator/lib.ts (2 hunks)
  • tools/cardcreator/vanilla.ts (0 hunks)
  • tools/deckcreator.ts (1 hunks)
💤 Files with no reviewable changes (5)
  • cards/Classes/Neutral/Collectible/Minions/10-Cost/104-yogg-saron-master-of-fate.ts
  • cards/StartingHeroes/Mage/114-heropower.ts
  • src/functions/util.ts
  • index.ts
  • tools/cardcreator/vanilla.ts
🧰 Additional context used
🧬 Code Graph Analysis (7)
scripts/crashtest.ts (2)
src/game.ts (1)
  • createGame (1841-1853)
src/card.ts (1)
  • Card (38-2080)
test/src/functions/util.test.ts (1)
src/functions/util.ts (1)
  • utilFunctions (20-661)
scripts/vanilla/generate.ts (1)
src/game.ts (1)
  • createGame (1841-1853)
src/event.ts (2)
src/types/event.ts (1)
  • QuestObject (69-76)
src/card.ts (1)
  • Card (38-2080)
src/player.ts (3)
src/game.ts (1)
  • Location (790-798)
tools/cardcreator/custom.ts (1)
  • Location (222-239)
src/card.ts (1)
  • Card (38-2080)
test/src/player.test.ts (3)
src/player.ts (1)
  • Player (19-1423)
src/card.ts (2)
  • Card (38-2080)
  • destroy (1177-1180)
src/types/other.ts (1)
  • Target (128-128)
src/card.ts (4)
cards/Examples/3/4-placeholder.ts (1)
  • placeholders (36-59)
cards/Examples/3/last-combined.ts (1)
  • placeholders (62-69)
cards/Examples/Unordered/Card References/131-simple.ts (1)
  • placeholders (33-42)
cards/Examples/Unordered/Card References/132-circular.ts (1)
  • placeholders (27-35)
🔇 Additional comments (40)
test/src/functions/info.test.ts (1)

6-6: Improved test structure by removing failing placeholder

This change aligns with the PR objective of improving tests by replacing a failing assertion with an empty async function, maintaining the test structure for future implementation while preventing test failures.

loc.md (1)

5-5: Updated statistics reflecting codebase changes

The metrics show a healthy evolution of the codebase with increased code lines (+1094) despite having one less file, indicating significant code additions while reducing comment lines. This aligns with the PR's focus on expanding test coverage.

Also applies to: 7-7

tools/cardcreator/class.ts (1)

69-69: Improved code clarity with better comment placement

Moving the comment directly above the heropowerId property enhances readability by keeping the explanation adjacent to the relevant code. This is a good practice for maintaining clear documentation.

src/types/card.ts (1)

194-194: Enhanced game state tracking with new Location enum value

Adding Hero = "Hero" to the Location enum improves the consistency of card location tracking in the game state. This change complements the updates in the player's setHero method mentioned in the summary, creating a more robust system for tracking hero cards.

scripts/id/main.ts (1)

55-92: Improved code maintainability with enums

Good refactoring to use enum values instead of string literals in the switch statement. This improves code maintainability and reduces the risk of typos when referencing commands.

The use of Commands.Increment, Commands.Decrement, Commands.Validate, and Commands.Quit makes the code more readable and type-safe compared to using raw string literals.

test/src/game.test.ts (2)

2-2: Improved import specificity

Good change to import the specific createGame function instead of the entire Game module. This makes the dependencies clearer and potentially improves bundle size.


8-8: Proper test initialization

Enabling the game initialization is a positive change. It ensures the test environment is properly set up before running tests, which will help prevent false positives/negatives when the actual test implementations are added.

test/src/functions/deckcode.test.ts (1)

5-8: Improved test placeholder structure

Converting the failing placeholder assertions to empty test bodies is a good approach. This keeps the test structure in place while preventing the tests from failing until they're properly implemented. Consider adding comments about what each test should verify when implemented.

scripts/vanilla/generate.ts (1)

15-15: Optimization by skipping card registration

Good change - passing false to createGame() skips the Card.registerAll() step, which is unnecessary for this script's purpose of fetching and filtering vanilla cards. This should improve the script's performance since it doesn't need all cards registered.

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

5-16: Improved test placeholder structure

Converting these test placeholders to empty async functions is a good improvement. This maintains the test framework while preventing failures for unimplemented tests. As you implement each test, consider grouping related event tests (e.g., event broadcasting functions, listener management functions) into describe blocks for better organization.

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

5-23: Improved test placeholder structure

Good work converting these placeholder tests to empty functions instead of failing assertions. This keeps the test structure intact without causing failures for unimplemented tests.

Consider organizing these tests into logical groups (like basic commands, debug commands) using nested describe blocks when you implement them, which would improve readability and organization.

test/src/functions/card.test.ts (2)

13-14: Improved test structure for pending tests

Converting failing test placeholders to empty async functions allows the test suite to run without failures while still marking these tests as pending for future implementation.


46-47: Good approach for additional test placeholders

Consistent with the pattern used for other todo tests, using empty async functions for pending test cases is the right approach.

config.ts (2)

344-344: Fixed grammatical issue in fun fact

This minor text improvement enhances the clarity of the fun fact without changing its meaning.


356-357: Improved clarity in project history fun facts

These grammatical improvements make the fun facts about related projects clearer and more readable.

src/functions/interact.ts (1)

938-940: Enhanced fun fact rotation logic

This change improves how fun facts are filtered and displayed to users. When all fun facts have been shown, the system now properly filters out previously seen facts (except those ending with a dash), ensuring a better rotation of content before repeating.

The change aligns well with the typo fixes in the config.ts file, collectively improving the user experience when displaying historical facts about the project.

scripts/crashtest.ts (4)

2-2: Improved card loading strategy

Direct import of the Card class and preloading all cards at startup is more efficient than the previous approach of reading decks from a JSON file.

Also applies to: 9-9


20-20: Proper caching of environment variable check

Using the cached gamesEnv value instead of re-accessing process.env.GAMES is more efficient.


35-35: Simplified game creation

Passing false to createGame prevents redundant card registration since you're using the preloaded cards, which improves performance.


50-57: Enhanced deck construction for testing

Replacing predefined deck codes with randomly sampled cards creates more diverse test scenarios, which is beneficial for crash testing as it increases coverage of different card interactions.

The error handling for empty card arrays is also a good safeguard to prevent runtime issues.

cards/Examples/4/4-quest.ts (2)

67-67: Good update to comment documentation!

The documentation now correctly specifies the use of EventListenerMessage.Skip enum value instead of a boolean false. This clarifies the expected return value and aligns with the implementation at line 75.


82-83: Improved comment clarity for quest event handling.

The updated comment correctly states that returning EventListenerMessage.Success (instead of boolean true) will count this event towards the quest. This matches the implementation at line 86 and provides more descriptive guidance.

tools/deckcreator.ts (1)

748-766: Improved command descriptions with consistent punctuation.

The command descriptions now consistently end with periods and have clearer phrasing. This enhances documentation readability and user experience, particularly for the more cautious warning about the "eval" command.

src/event.ts (3)

11-11: Added import for QuestObject type.

Good addition of the QuestObject type import which is used in the newly extracted removeQuest helper function.


97-102: Added important card initialization validation.

This defensive check ensures cards in players' hands are properly initialized through Card.create() rather than direct instantiation. This prevents potential runtime issues and makes debugging easier by providing a clear error message.


221-224: Consistent use of the new helper function.

The code now uses the extracted removeQuest helper in both locations where quest removal is needed, ensuring consistent behavior and eliminating duplication.

Also applies to: 249-249

test/src/functions/util.test.ts (2)

35-38: Improved test todo placeholders.

The test placeholders now use empty async functions instead of failing assertions. This is a better practice as it marks the tests as pending without causing test failures, allowing for incremental test development.

Also applies to: 50-52, 75-80, 116-118


199-203: Great additional test case for UUID with asynchronous method.

This new test ensures parseEvalArgs correctly handles the combination of await with card UUID references, specifically for asynchronous card methods like readable(). This improves coverage of a real-world use case.

scripts/cardtest.ts (2)

18-43: Improved deck initialization for card testing

The deck creation has been simplified by directly creating sheep cards for both players instead of using deck codes. This approach is more straightforward and removes unnecessary dependencies.


25-31: Good implementation of test environment configuration

Setting game.noOutput = true and disabling AI by setting player1.ai and player2.ai to undefined creates a clean testing environment.

src/commands.ts (2)

22-32: Improved command descriptions

The help text has been standardized with proper punctuation and clearer descriptions, making the command list more professional and consistent.


42-48: Enhanced debug command descriptions

The debug command descriptions have been improved with better wording and consistent punctuation. The description for eval now explicitly warns about security risks, which is a good practice.

test/src/card.test.ts (5)

3-10: Proper test environment initialization

The file now correctly imports necessary types and initializes the game environment with await createGame() before running tests, which is essential for ensuring tests have access to the game state.


27-64: Good coverage for keyword functionality

The tests for hasKeyword and addKeyword thoroughly verify card keyword behavior, including state changes (sleepy, attackTimes, canAttackHero) that should occur when specific keywords are added. This ensures that keyword interactions work correctly.


97-127: Comprehensive testing of attack mechanics

The tests for freeze and decAttack provide good coverage of important game mechanics. The decAttack test is particularly thorough, checking multiple state transitions and verifying that the function returns the correct boolean value based on whether the attack count could be decremented.


235-271: Good coverage of card state management functions

The tests for resetMaxHealth, setStealthDuration, and resetAttackTimes appropriately verify these utility functions that maintain card state. The resetAttackTimes test is particularly valuable as it confirms the correct interaction with Windfury and MegaWindfury keywords.


273-285: Important test for card location tracking

The setLocation test verifies both the location property update and the correct tracking in game.activeCards. This is crucial for ensuring cards are properly managed in the game state.

tools/cardcreator/lib.ts (2)

17-67: Improved JSDoc accuracy

The JSDoc comments for getCardAbility and generateCardPath have been updated to correctly reflect that they now accept a blueprint parameter, which improves documentation accuracy.


20-56: Refactored for better readability and reduced mutable state

The getCardAbility function has been refactored to eliminate the mutable ability variable by directly returning string values in each case. This makes the code clearer and less error-prone.

src/card.ts (1)

1673-1674: Double-insertion check for cloned cards

perfectCopy() now pushes the clone to game.activeCards.
Please verify the clone is not added a second time later (e.g. when a location is assigned) and that old code paths don’t already include it. Duplicate entries corrupt game-wide scans that assume uniqueness.

If needed, wrap the push in an existence check:

-		game.activeCards.push(clone);
+		if (!game.activeCards.includes(clone)) {
+			game.activeCards.push(clone);
+		}
**Actionable comments posted: 12** <details> <summary>🔭 Outside diff range comments (1)</summary><blockquote> <details> <summary>src/card.ts (1)</summary><blockquote> `1298-1304`: _⚠️ Potential issue_ **Don’t bypass `setLocation()` – `activeCards` entry will linger** `discard()` manually sets `this.location = Location.None` after removing the card from the player’s hand. Unlike `await this.setLocation(Location.None)` it does **not**: 1. Remove the card from `game.activeCards`. 2. Broadcast the `ChangeLocation` event. That leaves a “ghost” card in `activeCards`, which can break enumeration logic (e.g. `reload`, `killCardsOnBoard`) and leak memory. ```diff - this.location = Location.None; + // Use the canonical helper so all side-effects are honoured + await this.setLocation(Location.None); ``` </blockquote></details> </blockquote></details> <details> <summary>🧹 Nitpick comments (7)</summary><blockquote> <details> <summary>scripts/id/main.ts (1)</summary><blockquote> `10-10`: **Commented-out code cleanup** The case correction in the commented-out code is good, but consider removing this commented-out code altogether if it's not needed, as it doesn't serve any functional purpose. ```diff - // process.exit(1); ``` </blockquote></details> <details> <summary>src/event.ts (1)</summary><blockquote> `202-214`: **Excellent refactoring to eliminate duplicate code.** The extraction of quest removal logic into a reusable helper function `removeQuest` improves maintainability and reduces duplication. The function properly encapsulates the full quest removal workflow including: 1. Removing the quest from the player's collection 2. Showing appropriate messages for secrets 3. Handling follow-up quests when applicable This change reduces the risk of inconsistent implementations in the two places where quest removal was previously duplicated. Consider adding a JSDoc comment to document the helper function's purpose and parameters. </blockquote></details> <details> <summary>test/src/card.test.ts (1)</summary><blockquote> `13-322`: **Consider prioritizing remaining tests** While the test suite has significantly improved, there are still many `test.todo` entries for important methods. Consider prioritizing tests for core functionality like `trigger`, `discard`, `perfectCopy`, and `replacePlaceholders` next. </blockquote></details> <details> <summary>test/src/player.test.ts (1)</summary><blockquote> `316-318`: **`Array.prototype.toSorted` may not be available in every runtime** Node LTS prior to v20 and some bundlers still lack `toSorted`. If you target anything below that, replace with `([...array]).sort()` or import a polyfill, otherwise the test suite will crash outside Bun. </blockquote></details> <details> <summary>src/player.ts (1)</summary><blockquote> `1216-1218`: **Refund logic can break when `costType` isn’t “mana”** `this[card.costType] += card.cost;` assumes the player already has that numeric property (e.g. `corpses`, `runes` are strings). Guard against non-numeric types or undefined keys to avoid `NaN` propagation. </blockquote></details> <details> <summary>src/game.ts (1)</summary><blockquote> `561-637`: **`_forgetful` can spin for a long time when no valid targets exist** If every attempt returns an invalid target, the loop may iterate up to `forgetfulRandomTargetFailAmount` with no early escape, wasting cycles. Add a quick bail-out when the target pool is empty: ```diff - const target = game.functions.util.getRandomTarget(...); - // If a target wasn't found, just continue with the attack - if (!target) { - break; - } + const target = game.functions.util.getRandomTarget(...); + if (!target) { + // No possible targets – stop looping + break; + } ``` </blockquote></details> <details> <summary>src/card.ts (1)</summary><blockquote> `1588-1631`: **Minor perf tweak – avoid calling `parseTags` inside the loop** `parseTags` runs inside the `{key}`-replacement `while` loop, meaning it can be executed dozens of times for a long description. Moving it outside (or deferring until the end) yields the same functional result with noticeably less overhead. ```diff - text = parseTags(text.replace(reg, replacement)); + text = text.replace(reg, replacement); ... - return text.replaceAll("__hsjs__ignore:", "{"); + return parseTags( + text.replaceAll("__hsjs__ignore:", "{"), + ); ``` </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 5805b0631e1b6c39b7208f285b8540c9a3f20253 and b2d2a3669a0e7fd96d9c1875bfa8a13ca1958557. </details> <details> <summary>📒 Files selected for processing (33)</summary> * `cards/Classes/Neutral/Collectible/Minions/10-Cost/104-yogg-saron-master-of-fate.ts` (0 hunks) * `cards/Examples/4/4-quest.ts` (2 hunks) * `cards/StartingHeroes/Mage/114-heropower.ts` (0 hunks) * `config.ts` (2 hunks) * `index.ts` (0 hunks) * `loc.md` (1 hunks) * `scripts/cardtest.ts` (1 hunks) * `scripts/crashtest.ts` (4 hunks) * `scripts/id/main.ts` (4 hunks) * `scripts/vanilla/generate.ts` (1 hunks) * `src/card.ts` (4 hunks) * `src/commands.ts` (3 hunks) * `src/event.ts` (4 hunks) * `src/functions/interact.ts` (1 hunks) * `src/functions/util.ts` (0 hunks) * `src/game.ts` (7 hunks) * `src/player.ts` (7 hunks) * `src/types/card.ts` (1 hunks) * `test/src/ai.test.ts` (1 hunks) * `test/src/card.test.ts` (1 hunks) * `test/src/commands.test.ts` (1 hunks) * `test/src/event.test.ts` (1 hunks) * `test/src/functions/card.test.ts` (3 hunks) * `test/src/functions/deckcode.test.ts` (1 hunks) * `test/src/functions/info.test.ts` (1 hunks) * `test/src/functions/interact.test.ts` (1 hunks) * `test/src/functions/util.test.ts` (5 hunks) * `test/src/game.test.ts` (1 hunks) * `test/src/player.test.ts` (3 hunks) * `tools/cardcreator/class.ts` (1 hunks) * `tools/cardcreator/lib.ts` (2 hunks) * `tools/cardcreator/vanilla.ts` (0 hunks) * `tools/deckcreator.ts` (1 hunks) </details> <details> <summary>💤 Files with no reviewable changes (5)</summary> * cards/Classes/Neutral/Collectible/Minions/10-Cost/104-yogg-saron-master-of-fate.ts * cards/StartingHeroes/Mage/114-heropower.ts * src/functions/util.ts * index.ts * tools/cardcreator/vanilla.ts </details> <details> <summary>🧰 Additional context used</summary> <details> <summary>🧬 Code Graph Analysis (7)</summary> <details> <summary>scripts/crashtest.ts (2)</summary><blockquote> <details> <summary>src/game.ts (1)</summary> * `createGame` (1841-1853) </details> <details> <summary>src/card.ts (1)</summary> * `Card` (38-2080) </details> </blockquote></details> <details> <summary>test/src/functions/util.test.ts (1)</summary><blockquote> <details> <summary>src/functions/util.ts (1)</summary> * `utilFunctions` (20-661) </details> </blockquote></details> <details> <summary>scripts/vanilla/generate.ts (1)</summary><blockquote> <details> <summary>src/game.ts (1)</summary> * `createGame` (1841-1853) </details> </blockquote></details> <details> <summary>src/event.ts (2)</summary><blockquote> <details> <summary>src/types/event.ts (1)</summary> * `QuestObject` (69-76) </details> <details> <summary>src/card.ts (1)</summary> * `Card` (38-2080) </details> </blockquote></details> <details> <summary>src/player.ts (3)</summary><blockquote> <details> <summary>src/game.ts (1)</summary> * `Location` (790-798) </details> <details> <summary>tools/cardcreator/custom.ts (1)</summary> * `Location` (222-239) </details> <details> <summary>src/card.ts (1)</summary> * `Card` (38-2080) </details> </blockquote></details> <details> <summary>test/src/player.test.ts (3)</summary><blockquote> <details> <summary>src/player.ts (1)</summary> * `Player` (19-1423) </details> <details> <summary>src/card.ts (2)</summary> * `Card` (38-2080) * `destroy` (1177-1180) </details> <details> <summary>src/types/other.ts (1)</summary> * `Target` (128-128) </details> </blockquote></details> <details> <summary>src/card.ts (4)</summary><blockquote> <details> <summary>cards/Examples/3/4-placeholder.ts (1)</summary> * `placeholders` (36-59) </details> <details> <summary>cards/Examples/3/last-combined.ts (1)</summary> * `placeholders` (62-69) </details> <details> <summary>cards/Examples/Unordered/Card References/131-simple.ts (1)</summary> * `placeholders` (33-42) </details> <details> <summary>cards/Examples/Unordered/Card References/132-circular.ts (1)</summary> * `placeholders` (27-35) </details> </blockquote></details> </details> </details> <details> <summary>🔇 Additional comments (40)</summary><blockquote> <details> <summary>test/src/functions/info.test.ts (1)</summary> `6-6`: **Improved test structure by removing failing placeholder** This change aligns with the PR objective of improving tests by replacing a failing assertion with an empty async function, maintaining the test structure for future implementation while preventing test failures. </details> <details> <summary>loc.md (1)</summary> `5-5`: **Updated statistics reflecting codebase changes** The metrics show a healthy evolution of the codebase with increased code lines (+1094) despite having one less file, indicating significant code additions while reducing comment lines. This aligns with the PR's focus on expanding test coverage. Also applies to: 7-7 </details> <details> <summary>tools/cardcreator/class.ts (1)</summary> `69-69`: **Improved code clarity with better comment placement** Moving the comment directly above the `heropowerId` property enhances readability by keeping the explanation adjacent to the relevant code. This is a good practice for maintaining clear documentation. </details> <details> <summary>src/types/card.ts (1)</summary> `194-194`: **Enhanced game state tracking with new Location enum value** Adding `Hero = "Hero"` to the Location enum improves the consistency of card location tracking in the game state. This change complements the updates in the player's `setHero` method mentioned in the summary, creating a more robust system for tracking hero cards. </details> <details> <summary>scripts/id/main.ts (1)</summary> `55-92`: **Improved code maintainability with enums** Good refactoring to use enum values instead of string literals in the switch statement. This improves code maintainability and reduces the risk of typos when referencing commands. The use of Commands.Increment, Commands.Decrement, Commands.Validate, and Commands.Quit makes the code more readable and type-safe compared to using raw string literals. </details> <details> <summary>test/src/game.test.ts (2)</summary> `2-2`: **Improved import specificity** Good change to import the specific `createGame` function instead of the entire `Game` module. This makes the dependencies clearer and potentially improves bundle size. --- `8-8`: **Proper test initialization** Enabling the game initialization is a positive change. It ensures the test environment is properly set up before running tests, which will help prevent false positives/negatives when the actual test implementations are added. </details> <details> <summary>test/src/functions/deckcode.test.ts (1)</summary> `5-8`: **Improved test placeholder structure** Converting the failing placeholder assertions to empty test bodies is a good approach. This keeps the test structure in place while preventing the tests from failing until they're properly implemented. Consider adding comments about what each test should verify when implemented. </details> <details> <summary>scripts/vanilla/generate.ts (1)</summary> `15-15`: **Optimization by skipping card registration** Good change - passing `false` to `createGame()` skips the `Card.registerAll()` step, which is unnecessary for this script's purpose of fetching and filtering vanilla cards. This should improve the script's performance since it doesn't need all cards registered. </details> <details> <summary>test/src/event.test.ts (1)</summary> `5-16`: **Improved test placeholder structure** Converting these test placeholders to empty async functions is a good improvement. This maintains the test framework while preventing failures for unimplemented tests. As you implement each test, consider grouping related event tests (e.g., event broadcasting functions, listener management functions) into describe blocks for better organization. </details> <details> <summary>test/src/commands.test.ts (1)</summary> `5-23`: **Improved test placeholder structure** Good work converting these placeholder tests to empty functions instead of failing assertions. This keeps the test structure intact without causing failures for unimplemented tests. Consider organizing these tests into logical groups (like basic commands, debug commands) using nested describe blocks when you implement them, which would improve readability and organization. </details> <details> <summary>test/src/functions/card.test.ts (2)</summary> `13-14`: **Improved test structure for pending tests** Converting failing test placeholders to empty async functions allows the test suite to run without failures while still marking these tests as pending for future implementation. --- `46-47`: **Good approach for additional test placeholders** Consistent with the pattern used for other todo tests, using empty async functions for pending test cases is the right approach. </details> <details> <summary>config.ts (2)</summary> `344-344`: **Fixed grammatical issue in fun fact** This minor text improvement enhances the clarity of the fun fact without changing its meaning. --- `356-357`: **Improved clarity in project history fun facts** These grammatical improvements make the fun facts about related projects clearer and more readable. </details> <details> <summary>src/functions/interact.ts (1)</summary> `938-940`: **Enhanced fun fact rotation logic** This change improves how fun facts are filtered and displayed to users. When all fun facts have been shown, the system now properly filters out previously seen facts (except those ending with a dash), ensuring a better rotation of content before repeating. The change aligns well with the typo fixes in the `config.ts` file, collectively improving the user experience when displaying historical facts about the project. </details> <details> <summary>scripts/crashtest.ts (4)</summary> `2-2`: **Improved card loading strategy** Direct import of the `Card` class and preloading all cards at startup is more efficient than the previous approach of reading decks from a JSON file. Also applies to: 9-9 --- `20-20`: **Proper caching of environment variable check** Using the cached `gamesEnv` value instead of re-accessing `process.env.GAMES` is more efficient. --- `35-35`: **Simplified game creation** Passing `false` to `createGame` prevents redundant card registration since you're using the preloaded cards, which improves performance. --- `50-57`: **Enhanced deck construction for testing** Replacing predefined deck codes with randomly sampled cards creates more diverse test scenarios, which is beneficial for crash testing as it increases coverage of different card interactions. The error handling for empty card arrays is also a good safeguard to prevent runtime issues. </details> <details> <summary>cards/Examples/4/4-quest.ts (2)</summary> `67-67`: **Good update to comment documentation!** The documentation now correctly specifies the use of `EventListenerMessage.Skip` enum value instead of a boolean `false`. This clarifies the expected return value and aligns with the implementation at line 75. --- `82-83`: **Improved comment clarity for quest event handling.** The updated comment correctly states that returning `EventListenerMessage.Success` (instead of boolean `true`) will count this event towards the quest. This matches the implementation at line 86 and provides more descriptive guidance. </details> <details> <summary>tools/deckcreator.ts (1)</summary> `748-766`: **Improved command descriptions with consistent punctuation.** The command descriptions now consistently end with periods and have clearer phrasing. This enhances documentation readability and user experience, particularly for the more cautious warning about the "eval" command. </details> <details> <summary>src/event.ts (3)</summary> `11-11`: **Added import for QuestObject type.** Good addition of the `QuestObject` type import which is used in the newly extracted removeQuest helper function. --- `97-102`: **Added important card initialization validation.** This defensive check ensures cards in players' hands are properly initialized through `Card.create()` rather than direct instantiation. This prevents potential runtime issues and makes debugging easier by providing a clear error message. --- `221-224`: **Consistent use of the new helper function.** The code now uses the extracted `removeQuest` helper in both locations where quest removal is needed, ensuring consistent behavior and eliminating duplication. Also applies to: 249-249 </details> <details> <summary>test/src/functions/util.test.ts (2)</summary> `35-38`: **Improved test todo placeholders.** The test placeholders now use empty async functions instead of failing assertions. This is a better practice as it marks the tests as pending without causing test failures, allowing for incremental test development. Also applies to: 50-52, 75-80, 116-118 --- `199-203`: **Great additional test case for UUID with asynchronous method.** This new test ensures `parseEvalArgs` correctly handles the combination of await with card UUID references, specifically for asynchronous card methods like `readable()`. This improves coverage of a real-world use case. </details> <details> <summary>scripts/cardtest.ts (2)</summary> `18-43`: **Improved deck initialization for card testing** The deck creation has been simplified by directly creating sheep cards for both players instead of using deck codes. This approach is more straightforward and removes unnecessary dependencies. --- `25-31`: **Good implementation of test environment configuration** Setting `game.noOutput = true` and disabling AI by setting `player1.ai` and `player2.ai` to undefined creates a clean testing environment. </details> <details> <summary>src/commands.ts (2)</summary> `22-32`: **Improved command descriptions** The help text has been standardized with proper punctuation and clearer descriptions, making the command list more professional and consistent. --- `42-48`: **Enhanced debug command descriptions** The debug command descriptions have been improved with better wording and consistent punctuation. The description for `eval` now explicitly warns about security risks, which is a good practice. </details> <details> <summary>test/src/card.test.ts (5)</summary> `3-10`: **Proper test environment initialization** The file now correctly imports necessary types and initializes the game environment with `await createGame()` before running tests, which is essential for ensuring tests have access to the game state. --- `27-64`: **Good coverage for keyword functionality** The tests for `hasKeyword` and `addKeyword` thoroughly verify card keyword behavior, including state changes (sleepy, attackTimes, canAttackHero) that should occur when specific keywords are added. This ensures that keyword interactions work correctly. --- `97-127`: **Comprehensive testing of attack mechanics** The tests for `freeze` and `decAttack` provide good coverage of important game mechanics. The `decAttack` test is particularly thorough, checking multiple state transitions and verifying that the function returns the correct boolean value based on whether the attack count could be decremented. --- `235-271`: **Good coverage of card state management functions** The tests for `resetMaxHealth`, `setStealthDuration`, and `resetAttackTimes` appropriately verify these utility functions that maintain card state. The `resetAttackTimes` test is particularly valuable as it confirms the correct interaction with Windfury and MegaWindfury keywords. --- `273-285`: **Important test for card location tracking** The `setLocation` test verifies both the location property update and the correct tracking in `game.activeCards`. This is crucial for ensuring cards are properly managed in the game state. </details> <details> <summary>tools/cardcreator/lib.ts (2)</summary> `17-67`: **Improved JSDoc accuracy** The JSDoc comments for `getCardAbility` and `generateCardPath` have been updated to correctly reflect that they now accept a `blueprint` parameter, which improves documentation accuracy. --- `20-56`: **Refactored for better readability and reduced mutable state** The `getCardAbility` function has been refactored to eliminate the mutable `ability` variable by directly returning string values in each case. This makes the code clearer and less error-prone. </details> <details> <summary>src/card.ts (1)</summary> `1673-1674`: **Double-insertion check for cloned cards** `perfectCopy()` now pushes the clone to `game.activeCards`. Please verify the clone is **not** added a second time later (e.g. when a location is assigned) and that old code paths don’t already include it. Duplicate entries corrupt game-wide scans that assume uniqueness. If needed, wrap the push in an existence check: ```diff - game.activeCards.push(clone); + if (!game.activeCards.includes(clone)) { + game.activeCards.push(clone); + } ``` </details> </blockquote></details> </details> <!-- This is an auto-generated comment by CodeRabbit for review status -->
coderabbitai[bot] (Migrated from github.com) commented 2025-05-14 07:40:08 +00:00

💡 Verification agent

🧩 Analysis chain

Well-structured error handling with good cleanup

The try-catch implementation properly manages test execution, handles errors gracefully, and ensures game.noOutput is reset to false even if an error occurs. The addition of tracking TODOs via EventListenerMessage.Skip is a useful feature for monitoring test progress.

However, note that this function doesn't check whether the card has a test ability before attempting to trigger it.


🏁 Script executed:

#!/bin/bash
# Check if all cards have a Test ability implemented
ast-grep --pattern 'async test(owner, self) {
  $$$
}' | wc -l

Length of output: 66


🏁 Script executed:

#!/bin/bash
# Locate Ability enum to verify if Test is defined
rg -n "export enum Ability" -A10

echo
# Locate the implementation of async trigger on cards
rg -n "async trigger" -A10

Length of output: 1598


🏁 Script executed:

#!/bin/bash
# Show full trigger method implementation to see behavior when ability is missing
sed -n '1210,1260p' src/card.ts

Length of output: 1474


Ensure the “Test” ability is defined and explicitly guard missing tests

While card.trigger(Ability.Test) safely returns false when no handler exists, we should:

  • Add Test = "test" to the Ability enum in src/types/card.ts so Ability.Test is valid.
  • In scripts/cardtest.ts, skip cards without tests instead of silently doing nothing:
    for (const card of cards) {
      // Before triggering:
      const testHandlers = card.abilities?.[Ability.Test];
      if (!testHandlers || testHandlers.length === 0) {
        console.warn(`SKIP: ${card.name} (${card.id}) has no Test ability`);
        continue;
      }
    
      try {
        const returnValue = await card.trigger(Ability.Test);
        if (Array.isArray(returnValue) && returnValue.includes(EventListenerMessage.Skip)) {
          todos++;
        }
      // …
      }
      // game.noOutput reset remains
    }
    
  • Also explicitly handle the false return value if you need to distinguish “no test” from “empty result.”

These changes make missing tests visible and prevent accidental omissions.

_💡 Verification agent_ <details> <summary>🧩 Analysis chain</summary> **Well-structured error handling with good cleanup** The try-catch implementation properly manages test execution, handles errors gracefully, and ensures `game.noOutput` is reset to false even if an error occurs. The addition of tracking TODOs via `EventListenerMessage.Skip` is a useful feature for monitoring test progress. However, note that this function doesn't check whether the card has a test ability before attempting to trigger it. --- 🏁 Script executed: ```shell #!/bin/bash # Check if all cards have a Test ability implemented ast-grep --pattern 'async test(owner, self) { $$$ }' | wc -l ``` Length of output: 66 --- 🏁 Script executed: ```shell #!/bin/bash # Locate Ability enum to verify if Test is defined rg -n "export enum Ability" -A10 echo # Locate the implementation of async trigger on cards rg -n "async trigger" -A10 ``` Length of output: 1598 --- 🏁 Script executed: ```shell #!/bin/bash # Show full trigger method implementation to see behavior when ability is missing sed -n '1210,1260p' src/card.ts ``` Length of output: 1474 --- </details> **Ensure the “Test” ability is defined and explicitly guard missing tests** While `card.trigger(Ability.Test)` safely returns `false` when no handler exists, we should: - Add `Test = "test"` to the `Ability` enum in **src/types/card.ts** so `Ability.Test` is valid. - In **scripts/cardtest.ts**, skip cards without tests instead of silently doing nothing: ```ts for (const card of cards) { // Before triggering: const testHandlers = card.abilities?.[Ability.Test]; if (!testHandlers || testHandlers.length === 0) { console.warn(`SKIP: ${card.name} (${card.id}) has no Test ability`); continue; } try { const returnValue = await card.trigger(Ability.Test); if (Array.isArray(returnValue) && returnValue.includes(EventListenerMessage.Skip)) { todos++; } // … } // game.noOutput reset remains } ``` - Also explicitly handle the `false` return value if you need to distinguish “no test” from “empty result.” These changes make missing tests visible and prevent accidental omissions. <!-- This is an auto-generated reply by CodeRabbit -->
coderabbitai[bot] (Migrated from github.com) commented 2025-05-14 07:40:09 +00:00

⚠️ Potential issue

Spell-damage placeholder scan ignores overrideText

spellDamage is derived from this.text, so callers who pass a custom overrideText containing $<number> placeholders silently skip replacement.

-		const spellDamage = /\$(\d+)/.test(this.text || "");
+		const spellDamage = /\$(\d+)/.test(text);

Using the local text variable ensures both the original description and any supplied override are handled consistently.
Failing to do so produces stale $n tokens in rendered output.

Addressed in commit 5adedb0

_⚠️ Potential issue_ **Spell-damage placeholder scan ignores `overrideText`** `spellDamage` is derived from `this.text`, so callers who pass a custom `overrideText` containing `$<number>` placeholders silently skip replacement. ```diff - const spellDamage = /\$(\d+)/.test(this.text || ""); + const spellDamage = /\$(\d+)/.test(text); ``` Using the local `text` variable ensures both the original description and any supplied override are handled consistently. Failing to do so produces stale `$n` tokens in rendered output. <!-- This is an auto-generated comment by CodeRabbit --> ✅ Addressed in commit 5adedb0
@ -643,23 +643,21 @@ export const debugCommands: CommandList = {
success &&= await game.functions.interact.withStatus(
coderabbitai[bot] (Migrated from github.com) commented 2025-05-14 07:40:08 +00:00

⚠️ Potential issue

Fixed potential infinite loop in card reloading

The refactored card reloading logic now uses the consolidated game.activeCards collection and includes a safeguard against duplicate UUIDs to prevent infinite loops. This is a significant improvement that makes the reload process more robust.

However, the comment mentions a specific issue with "Frozen Test" cards. To fully understand this fix:

#!/bin/bash
# Find references to the Frozen Test card to understand the issue better
rg -A 3 -B 3 "Frozen Test" --glob "*.ts"
_⚠️ Potential issue_ **Fixed potential infinite loop in card reloading** The refactored card reloading logic now uses the consolidated `game.activeCards` collection and includes a safeguard against duplicate UUIDs to prevent infinite loops. This is a significant improvement that makes the reload process more robust. However, the comment mentions a specific issue with "Frozen Test" cards. To fully understand this fix: ```shell #!/bin/bash # Find references to the Frozen Test card to understand the issue better rg -A 3 -B 3 "Frozen Test" --glob "*.ts" ``` <!-- This is an auto-generated comment by CodeRabbit -->
coderabbitai[bot] (Migrated from github.com) commented 2025-05-14 07:40:09 +00:00

⚠️ Potential issue

Weapon destruction no longer silences the weapon’s enchantments

destroyWeapon used weapon.destroy(), which calls silence() before kill().
Changing to kill() alone:

  1. Leaves any auras / enchantments active until the global cleanup runs.
  2. Skips the ChangeLocation event dispatched by setLocation(...) inside kill()behavioural divergence vs. Hearthstone reference.

Unless this is deliberate, revert to destroy() or explicitly call silence() first:

-		await this.weapon.kill();
+		await this.weapon.silence();
+		await this.weapon.kill();
📝 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.

		await this.weapon.silence();
		await this.weapon.kill();
		this.weapon = undefined;

Addressed in commits 3674bcb to 7273995

_⚠️ Potential issue_ **Weapon destruction no longer silences the weapon’s enchantments** `destroyWeapon` used `weapon.destroy()`, which calls `silence()` before `kill()`. Changing to `kill()` alone: 1. Leaves any auras / enchantments active until the global cleanup runs. 2. Skips the `ChangeLocation` event dispatched by `setLocation(...)` inside `kill()` → **behavioural divergence vs. Hearthstone reference**. Unless this is deliberate, revert to `destroy()` or explicitly call `silence()` first: ```diff - await this.weapon.kill(); + await this.weapon.silence(); + await this.weapon.kill(); ``` <!-- 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 await this.weapon.silence(); await this.weapon.kill(); this.weapon = undefined; ``` </details> <!-- suggestion_end --> <!-- This is an auto-generated reply by CodeRabbit --> ✅ Addressed in commits 3674bcb to 7273995 <!-- This is an auto-generated comment by CodeRabbit -->
@ -849,7 +849,14 @@ export class Player {
* @returns Success
coderabbitai[bot] (Migrated from github.com) commented 2025-05-14 07:40:09 +00:00

🛠️ Refactor suggestion

Directly mutating location bypasses bookkeeping & events

Manually assigning:

this.hero.location = Location.Hero;

skips Card.setLocation, so:
game.activeCards is not updated
Event.ChangeLocation is never broadcast

The same applies to the old hero that is set to Location.None.

-			this.hero.location = Location.None;
+			await this.hero.setLocation(Location.None);
...
-		this.hero.location = Location.Hero;
+		await hero.setLocation(Location.Hero);
_🛠️ Refactor suggestion_ **Directly mutating `location` bypasses bookkeeping & events** Manually assigning: ```ts this.hero.location = Location.Hero; ``` skips `Card.setLocation`, so: • `game.activeCards` is not updated • `Event.ChangeLocation` is never broadcast The same applies to the old hero that is set to `Location.None`. ```diff - this.hero.location = Location.None; + await this.hero.setLocation(Location.None); ... - this.hero.location = Location.Hero; + await hero.setLocation(Location.Hero); ``` <!-- This is an auto-generated reply by CodeRabbit -->
coderabbitai[bot] (Migrated from github.com) commented 2025-05-14 07:40:08 +00:00

🛠️ Refactor suggestion

Standardized test placeholders

Similar to the interact test file, this change standardizes the AI test placeholders by removing failing assertions, leaving empty async functions. This creates consistent test placeholders across the codebase.

The PR aims to add tests for AI (among other components), but these changes only prepare the structure without implementing actual tests. To fulfill the PR objective, consider implementing tests for at least some of these AI methods.

Here's an example implementation for the calcMove test:

test("calcMove", async () => {
  const game = await createGame();
  const ai = new Ai(game, game.players[1]);
  const result = ai.calcMove();
  
  expect(result).toBeDefined();
  // Add more specific assertions based on expected behavior
});
_🛠️ Refactor suggestion_ **Standardized test placeholders** Similar to the interact test file, this change standardizes the AI test placeholders by removing failing assertions, leaving empty async functions. This creates consistent test placeholders across the codebase. The PR aims to add tests for AI (among other components), but these changes only prepare the structure without implementing actual tests. To fulfill the PR objective, consider implementing tests for at least some of these AI methods. Here's an example implementation for the `calcMove` test: ```typescript test("calcMove", async () => { const game = await createGame(); const ai = new Ai(game, game.players[1]); const result = ai.calcMove(); expect(result).toBeDefined(); // Add more specific assertions based on expected behavior }); ``` <!-- This is an auto-generated comment by CodeRabbit -->
@ -1,270 +1,322 @@
import { describe, expect, test } from "bun:test";
coderabbitai[bot] (Migrated from github.com) commented 2025-05-14 07:40:08 +00:00

💡 Verification agent

🧩 Analysis chain

Thorough testing of health-related mechanics

The remHealth test is comprehensive, covering interactions with different card types, keywords affecting damage (Stealth, Immune), and weapon destruction. However, there's a discrepancy in the test expectations for Immune cards:

// Immune should not prevent damage, but it returns true.
expect(await card.remHealth(1)).toBe(true);
expect(card.health).toBe(1);

This comment suggests that while remHealth returns true (indicating success), it doesn't actually reduce health when the Immune keyword is present, which seems contradictory.


🏁 Script executed:

#!/bin/bash
# Check the implementation of remHealth to understand how it handles Immune
ast-grep --pattern 'async remHealth($$$) {
  $$$
  if (self.hasKeyword(Keyword.Immune)) {
    $$$
  }
  $$$
}'

Length of output: 112


🏁 Script executed:

#!/bin/bash
set -e

# Locate the implementation of remHealth in TypeScript files
rg -n -C5 "remHealth" -g "*.ts"

# Check for any conditional logic involving the Immune keyword
rg -n -C5 "hasKeyword\s*\(\s*Keyword\.Immune" -g "*.ts"

Length of output: 11509


Clarify Immune behavior in remHealth test

The remHealth test is comprehensive, covering interactions with different card types, keywords (Stealth, Immune), and weapon destruction. However, the Immune case comment is misleading—the implementation and test both show that Immune prevents damage (health stays the same) while still returning true.

Please update the comment in test/src/card.test.ts:

-        // Immune should not prevent damage, but it returns true.
+        // Immune prevents damage: remHealth returns true and health remains unchanged.
📝 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.

		card.addKeyword(Keyword.Immune);
		// Immune prevents damage: remHealth returns true and health remains unchanged.
		expect(await card.remHealth(1)).toBe(true);
		expect(card.health).toBe(1);
		card.remKeyword(Keyword.Immune);

Addressed in commit 5adedb0

_💡 Verification agent_ <details> <summary>🧩 Analysis chain</summary> **Thorough testing of health-related mechanics** The `remHealth` test is comprehensive, covering interactions with different card types, keywords affecting damage (Stealth, Immune), and weapon destruction. However, there's a discrepancy in the test expectations for Immune cards: ``` // Immune should not prevent damage, but it returns true. expect(await card.remHealth(1)).toBe(true); expect(card.health).toBe(1); ``` This comment suggests that while `remHealth` returns true (indicating success), it doesn't actually reduce health when the Immune keyword is present, which seems contradictory. --- 🏁 Script executed: ```shell #!/bin/bash # Check the implementation of remHealth to understand how it handles Immune ast-grep --pattern 'async remHealth($$$) { $$$ if (self.hasKeyword(Keyword.Immune)) { $$$ } $$$ }' ``` Length of output: 112 --- 🏁 Script executed: ```shell #!/bin/bash set -e # Locate the implementation of remHealth in TypeScript files rg -n -C5 "remHealth" -g "*.ts" # Check for any conditional logic involving the Immune keyword rg -n -C5 "hasKeyword\s*\(\s*Keyword\.Immune" -g "*.ts" ``` Length of output: 11509 --- </details> **Clarify Immune behavior in remHealth test** The `remHealth` test is comprehensive, covering interactions with different card types, keywords (Stealth, Immune), and weapon destruction. However, the Immune case comment is misleading—the implementation and test both show that Immune *prevents* damage (health stays the same) while still returning `true`. Please update the comment in `test/src/card.test.ts`: ```diff - // Immune should not prevent damage, but it returns true. + // Immune prevents damage: remHealth returns true and health remains unchanged. ``` <!-- 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 card.addKeyword(Keyword.Immune); // Immune prevents damage: remHealth returns true and health remains unchanged. expect(await card.remHealth(1)).toBe(true); expect(card.health).toBe(1); card.remKeyword(Keyword.Immune); ``` </details> <!-- suggestion_end --> <!-- This is an auto-generated comment by CodeRabbit --> ✅ Addressed in commit 5adedb0
coderabbitai[bot] (Migrated from github.com) commented 2025-05-14 07:40:08 +00:00

💡 Verification agent

🧩 Analysis chain

Clean test placeholders structure

The changes standardize the test placeholders by removing failing assertions from the test.todo blocks, making them empty async functions. This improves code consistency across test files and prepares the structure for future test implementations.

However, given that the PR objective is to "Add more tests," we should aim to implement at least some of these test cases rather than just cleaning up the placeholders.

Let's check if there are any actual test implementations added in other test files:


🏁 Script executed:

#!/bin/bash
# Search for new test implementations in the PR
git diff --name-only HEAD~1 | grep -E "test/.+\.ts" | xargs grep -l "expect\(" 2>/dev/null

Length of output: 80


Let’s broaden the search to the entire test suite for any actual expect(…) calls:


🏁 Script executed:

#!/bin/bash
# Search for any test implementations (i.e. expect calls) in all test files
rg -n "expect\(" --glob "test/**/*.ts" || true

Length of output: 31550


Add concrete tests for interact.test.ts

The file test/src/functions/interact.test.ts currently only has test.todo(…) placeholders (lines 5–34). Since the PR’s goal is to add more tests, please replace at least a few of these with real test blocks and expect assertions to verify key behaviors, for example:

  • prompt functions (deckcode, chooseOne, yesNo, etc.) invoke the correct handlers or return expected values
  • print functions (watermark, license, gameState, etc.) produce the right formatted output
  • input and logging wrappers handle edge cases as intended

Once you’ve added at least one implemented test per category (prompt, print, I/O), we can consider this file ready.

_💡 Verification agent_ <details> <summary>🧩 Analysis chain</summary> **Clean test placeholders structure** The changes standardize the test placeholders by removing failing assertions from the `test.todo` blocks, making them empty async functions. This improves code consistency across test files and prepares the structure for future test implementations. However, given that the PR objective is to "Add more tests," we should aim to implement at least some of these test cases rather than just cleaning up the placeholders. Let's check if there are any actual test implementations added in other test files: --- 🏁 Script executed: ```shell #!/bin/bash # Search for new test implementations in the PR git diff --name-only HEAD~1 | grep -E "test/.+\.ts" | xargs grep -l "expect\(" 2>/dev/null ``` Length of output: 80 --- Let’s broaden the search to the entire test suite for any actual `expect(…)` calls: --- 🏁 Script executed: ```shell #!/bin/bash # Search for any test implementations (i.e. expect calls) in all test files rg -n "expect\(" --glob "test/**/*.ts" || true ``` Length of output: 31550 --- </details> **Add concrete tests for `interact.test.ts`** The file **test/src/functions/interact.test.ts** currently only has `test.todo(…)` placeholders (lines 5–34). Since the PR’s goal is to _add more tests_, please replace at least a few of these with real `test` blocks and `expect` assertions to verify key behaviors, for example: - prompt functions (`deckcode`, `chooseOne`, `yesNo`, etc.) invoke the correct handlers or return expected values - print functions (`watermark`, `license`, `gameState`, etc.) produce the right formatted output - input and logging wrappers handle edge cases as intended Once you’ve added at least one implemented test per category (prompt, print, I/O), we can consider this file ready. <!-- This is an auto-generated comment by CodeRabbit -->
coderabbitai[bot] (Migrated from github.com) commented 2025-05-14 07:40:08 +00:00

🛠️ Refactor suggestion

Standardized test placeholders structure

As with the other test files, these changes standardize the test placeholders by removing failing assertions. This improves code consistency but doesn't fulfill the PR objective of adding tests.

To meet the PR goals, consider implementing actual tests for at least some of these game functions, especially core functionality like attack, play, and summon.

For example:

test("attack", async () => {
  const game = await createGame();
  // Set up game state with attackers and defenders
  
  // Execute an attack
  const result = game.attack(attacker, defender);
  
  // Assert expected outcomes
  expect(defender.health).toBeLessThan(initialHealth);
  expect(attacker.hasAttacked).toBe(true);
});

This would provide real test coverage rather than just placeholders.

_🛠️ Refactor suggestion_ **Standardized test placeholders structure** As with the other test files, these changes standardize the test placeholders by removing failing assertions. This improves code consistency but doesn't fulfill the PR objective of adding tests. To meet the PR goals, consider implementing actual tests for at least some of these game functions, especially core functionality like `attack`, `play`, and `summon`. For example: ```typescript test("attack", async () => { const game = await createGame(); // Set up game state with attackers and defenders // Execute an attack const result = game.attack(attacker, defender); // Assert expected outcomes expect(defender.health).toBeLessThan(initialHealth); expect(attacker.hasAttacked).toBe(true); }); ``` This would provide real test coverage rather than just placeholders. <!-- This is an auto-generated comment by CodeRabbit -->
@ -94,21 +100,78 @@ describe("src/player", () => {
expect(player.emptyMana).toBe(7);
});
coderabbitai[bot] (Migrated from github.com) commented 2025-05-14 07:40:09 +00:00

⚠️ Potential issue

Await the addOverload promise to avoid race-conditions

Player.addOverload is asynchronous (it broadcasts an event).
Without await, the expectation that player.overload has incremented can execute before the promise resolves, making the test flaky.

-		player.addOverload(1);
+		await player.addOverload(1);
📝 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.

	test("addOverload", async () => {
		const player = new Player();

		game.event.addListener(Event.GainOverload, async (value, eventPlayer) => {
			expect(eventPlayer).toBe(player);
			expect(value).toBe(1);
			return EventListenerMessage.Destroy;
		});

		expect(player.overload).toBe(0);
		await player.addOverload(1);

		expect(player.overload).toBe(1);
	});

Addressed in commit 5adedb0

_⚠️ Potential issue_ **Await the `addOverload` promise to avoid race-conditions** `Player.addOverload` is asynchronous (it broadcasts an event). Without `await`, the expectation that `player.overload` has incremented can execute before the promise resolves, making the test flaky. ```diff - player.addOverload(1); + await player.addOverload(1); ``` <!-- prevents intermittent test failures --> <!-- 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 test("addOverload", async () => { const player = new Player(); game.event.addListener(Event.GainOverload, async (value, eventPlayer) => { expect(eventPlayer).toBe(player); expect(value).toBe(1); return EventListenerMessage.Destroy; }); expect(player.overload).toBe(0); await player.addOverload(1); expect(player.overload).toBe(1); }); ``` </details> <!-- suggestion_end --> <!-- This is an auto-generated comment by CodeRabbit --> ✅ Addressed in commit 5adedb0
@ -121,147 +184,602 @@ describe("src/player", () => {
expect(player.armor).toBe(105);
});
coderabbitai[bot] (Migrated from github.com) commented 2025-05-14 07:40:08 +00:00

⚠️ Potential issue

Copy-paste slip: testing the wrong function

The “canUseRunes” test still calls canUseCorpses().
This doesn’t exercise the intended code path and will give a false sense of coverage.

-		expect(player.canUseCorpses()).toBe(false);
+		expect(player.canUseRunes()).toBe(false);
...
-		expect(player.canUseCorpses()).toBe(true);
+		expect(player.canUseRunes()).toBe(true);
📝 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.

	test("canUseRunes", async () => {
		const player = new Player();

		expect(player.canUseRunes()).toBe(false);
		player.heroClass = Class.DeathKnight;
		expect(player.canUseRunes()).toBe(true);
	});

Addressed in commit 5adedb0

_⚠️ Potential issue_ **Copy-paste slip: testing the wrong function** The “canUseRunes” test still calls `canUseCorpses()`. This doesn’t exercise the intended code path and will give a false sense of coverage. ```diff - expect(player.canUseCorpses()).toBe(false); + expect(player.canUseRunes()).toBe(false); ... - expect(player.canUseCorpses()).toBe(true); + expect(player.canUseRunes()).toBe(true); ``` <!-- 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 test("canUseRunes", async () => { const player = new Player(); expect(player.canUseRunes()).toBe(false); player.heroClass = Class.DeathKnight; expect(player.canUseRunes()).toBe(true); }); ``` </details> <!-- suggestion_end --> <!-- This is an auto-generated comment by CodeRabbit --> ✅ Addressed in commit 5adedb0
coderabbitai[bot] (Migrated from github.com) commented 2025-05-14 07:40:09 +00:00

⚠️ Potential issue

Same problem here – don’t forget to await addAttack

addAttack returns a promise for the same reason as addOverload. Add the missing await to guarantee deterministic assertions.

-		player.addAttack(1);
+		await player.addAttack(1);
📝 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 player = new Player();

		game.event.addListener(Event.GainHeroAttack, async (value, eventPlayer) => {
			expect(eventPlayer).toBe(player);
			expect(value).toBe(1);
			return EventListenerMessage.Destroy;
		});

		expect(player.attack).toBe(0);
-		player.addAttack(1);
+		await player.addAttack(1);

		expect(player.attack).toBe(1);
	});

Addressed in commit 5adedb0

_⚠️ Potential issue_ **Same problem here – don’t forget to `await` `addAttack`** `addAttack` returns a promise for the same reason as `addOverload`. Add the missing `await` to guarantee deterministic assertions. ```diff - player.addAttack(1); + await player.addAttack(1); ``` <!-- 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 player = new Player(); game.event.addListener(Event.GainHeroAttack, async (value, eventPlayer) => { expect(eventPlayer).toBe(player); expect(value).toBe(1); return EventListenerMessage.Destroy; }); expect(player.attack).toBe(0); - player.addAttack(1); + await player.addAttack(1); expect(player.attack).toBe(1); }); ``` </details> <!-- suggestion_end --> <!-- This is an auto-generated comment by CodeRabbit --> ✅ Addressed in commit 5adedb0
LunarTides commented 2025-05-14 08:14:07 +00:00 (Migrated from github.com)

@coderabbitai review

@coderabbitai review
coderabbitai[bot] commented 2025-05-14 08:14:12 +00:00 (Migrated from github.com)
Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

<!-- This is an auto-generated reply by CodeRabbit --> <details> <summary>✅ Actions performed</summary> Review triggered. > Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused. </details>
coderabbitai[bot] (Migrated from github.com) reviewed 2025-05-14 08:17:44 +00:00
coderabbitai[bot] (Migrated from github.com) left a comment

Actionable comments posted: 1

♻️ Duplicate comments (1)
test/src/player.test.ts (1)

553-559: Fixed the canUseRunes test implementation.

The implementation now correctly tests canUseRunes() instead of canUseCorpses().

🧹 Nitpick comments (1)
test/src/player.test.ts (1)

774-780: Several todo tests remaining.

These remaining todo tests would complete the test coverage for the Player class.

Would you like me to help implement any of these remaining test cases? I can provide test structure similar to the ones you've already completed.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b2d2a3669a and 5adedb08c7.

📒 Files selected for processing (7)
  • scripts/cardtest.ts (2 hunks)
  • src/card.ts (4 hunks)
  • src/commands.ts (3 hunks)
  • src/game.ts (7 hunks)
  • src/player.ts (7 hunks)
  • test/src/card.test.ts (1 hunks)
  • test/src/player.test.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • scripts/cardtest.ts
  • test/src/card.test.ts
  • src/commands.ts
  • src/player.ts
  • src/game.ts
  • src/card.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
test/src/player.test.ts (3)
src/player.ts (1)
  • Player (19-1423)
src/card.ts (2)
  • Card (38-2080)
  • destroy (1177-1180)
src/types/other.ts (1)
  • Target (128-128)
Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build
🔇 Additional comments (7)
test/src/player.test.ts (7)

2-16: Good job expanding imports to support the new tests.

The imports have been properly organized to support the comprehensive test suite, including Card class, various enums, and types needed for the tests.


104-117: Excellent implementation of the addOverload test including event verification.

The test properly awaits the asynchronous addOverload method and verifies both the state change and event broadcasting.


119-147: Well-structured weapon management test.

This test thoroughly verifies the weapon setting functionality, including:

  • Deathrattle triggering when replacing a weapon
  • Attack value updates
  • State changes

189-202: Good implementation of the addAttack test.

The test properly awaits the asynchronous addAttack method and verifies both the attack value change and event broadcasting.


217-271: Address TODO comments in remHealth test.

There are commented assertions with TODOs that indicate test failures. These should be resolved to ensure complete test coverage.

-		// TODO: This fails for some reason.
-		// expect(times).toBe(1);
+		// FIXME: Event triggering behavior should be investigated
+		expect(times).toBe(1);

Consider debugging the event handling code in Player.remHealth() to understand why the event counts don't match expectations.


724-772: Comprehensive quest progression test.

The test thoroughly verifies:

  1. Quest creation and initial state
  2. Partial and complete progress tracking
  3. Quest completion handling
  4. Event broadcasting

679-692: ⚠️ Potential issue

Missing await for addToHand in discard test.

The addToHand method is asynchronous and should be awaited.

-		await player.addToHand(sheep);
+		await player.addToHand(sheep);

This doesn't appear to be causing issues in the test yet, but it's a potential source of race conditions.

Likely an incorrect or invalid review comment.

**Actionable comments posted: 1** <details> <summary>♻️ Duplicate comments (1)</summary><blockquote> <details> <summary>test/src/player.test.ts (1)</summary><blockquote> `553-559`: **Fixed the canUseRunes test implementation.** The implementation now correctly tests `canUseRunes()` instead of `canUseCorpses()`. </blockquote></details> </blockquote></details> <details> <summary>🧹 Nitpick comments (1)</summary><blockquote> <details> <summary>test/src/player.test.ts (1)</summary><blockquote> `774-780`: **Several todo tests remaining.** These remaining todo tests would complete the test coverage for the Player class. Would you like me to help implement any of these remaining test cases? I can provide test structure similar to the ones you've already completed. </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 b2d2a3669a0e7fd96d9c1875bfa8a13ca1958557 and 5adedb08c7cf4d0f1cf5e9596005b40339eb2e26. </details> <details> <summary>📒 Files selected for processing (7)</summary> * `scripts/cardtest.ts` (2 hunks) * `src/card.ts` (4 hunks) * `src/commands.ts` (3 hunks) * `src/game.ts` (7 hunks) * `src/player.ts` (7 hunks) * `test/src/card.test.ts` (1 hunks) * `test/src/player.test.ts` (3 hunks) </details> <details> <summary>🚧 Files skipped from review as they are similar to previous changes (6)</summary> * scripts/cardtest.ts * test/src/card.test.ts * src/commands.ts * src/player.ts * src/game.ts * src/card.ts </details> <details> <summary>🧰 Additional context used</summary> <details> <summary>🧬 Code Graph Analysis (1)</summary> <details> <summary>test/src/player.test.ts (3)</summary><blockquote> <details> <summary>src/player.ts (1)</summary> * `Player` (19-1423) </details> <details> <summary>src/card.ts (2)</summary> * `Card` (38-2080) * `destroy` (1177-1180) </details> <details> <summary>src/types/other.ts (1)</summary> * `Target` (128-128) </details> </blockquote></details> </details> </details> <details> <summary>⏰ Context from checks skipped due to timeout of 90000ms (1)</summary> * GitHub Check: build </details> <details> <summary>🔇 Additional comments (7)</summary><blockquote> <details> <summary>test/src/player.test.ts (7)</summary> `2-16`: **Good job expanding imports to support the new tests.** The imports have been properly organized to support the comprehensive test suite, including Card class, various enums, and types needed for the tests. --- `104-117`: **Excellent implementation of the addOverload test including event verification.** The test properly awaits the asynchronous addOverload method and verifies both the state change and event broadcasting. --- `119-147`: **Well-structured weapon management test.** This test thoroughly verifies the weapon setting functionality, including: - Deathrattle triggering when replacing a weapon - Attack value updates - State changes --- `189-202`: **Good implementation of the addAttack test.** The test properly awaits the asynchronous addAttack method and verifies both the attack value change and event broadcasting. --- `217-271`: **Address TODO comments in remHealth test.** There are commented assertions with TODOs that indicate test failures. These should be resolved to ensure complete test coverage. ```diff - // TODO: This fails for some reason. - // expect(times).toBe(1); + // FIXME: Event triggering behavior should be investigated + expect(times).toBe(1); ``` Consider debugging the event handling code in `Player.remHealth()` to understand why the event counts don't match expectations. --- `724-772`: **Comprehensive quest progression test.** The test thoroughly verifies: 1. Quest creation and initial state 2. Partial and complete progress tracking 3. Quest completion handling 4. Event broadcasting --- `679-692`: _⚠️ Potential issue_ **Missing await for addToHand in discard test.** The `addToHand` method is asynchronous and should be awaited. ```diff - await player.addToHand(sheep); + await player.addToHand(sheep); ``` This doesn't appear to be causing issues in the test yet, but it's a potential source of race conditions. > Likely an incorrect or invalid review comment. </details> </blockquote></details> </details> <!-- This is an auto-generated comment by CodeRabbit for review status -->
@ -121,147 +184,602 @@ describe("src/player", () => {
expect(player.armor).toBe(105);
});
coderabbitai[bot] (Migrated from github.com) commented 2025-05-14 08:17:43 +00:00

🛠️ Refactor suggestion

Potential infinite loop in shuffleIntoDeck test.

The while loop could run indefinitely if the shuffling algorithm consistently places the card at index 0 or at the end.

Add a safety counter to prevent infinite loops:

+		let attempts = 0;
+		const MAX_ATTEMPTS = 10;
 		while (
 			player.deck.indexOf(sheep) <= 0 ||
 			player.deck.indexOf(sheep) >= player.deck.length
-		) {
+		&& attempts < MAX_ATTEMPTS) {
+			attempts++;
 			game.functions.util.remove(player.deck, sheep);
 			expect(await player.shuffleIntoDeck(sheep)).toBe(true);
 		}
+
+		// Assert that we succeeded in placing it somewhere in the middle
+		expect(attempts < MAX_ATTEMPTS).toBe(true);
📝 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.

test("shuffleIntoDeck", async () => {
    const player = new Player();

    expect(player.deck.length).toBe(0);

    const sheep = await Card.create(game.cardIds.sheep1, player);
    for (let i = 0; i < 10; i++) {
        const sheep_copy = await sheep.imperfectCopy();
        sheep_copy.name = i.toString();

        await player.addToDeck(sheep_copy);
    }

    expect(player.deck.length).toBe(10);
    expect(await player.shuffleIntoDeck(sheep)).toBe(true);

    // Prevent an infinite loop if shuffle keeps placing it at the ends
    let attempts = 0;
    const MAX_ATTEMPTS = 10;
    while (
        player.deck.indexOf(sheep) <= 0 ||
        player.deck.indexOf(sheep) >= player.deck.length
        && attempts < MAX_ATTEMPTS
    ) {
        attempts++;
        game.functions.util.remove(player.deck, sheep);
        expect(await player.shuffleIntoDeck(sheep)).toBe(true);
    }

    // Assert we succeeded within our attempt budget
    expect(attempts < MAX_ATTEMPTS).toBe(true);

    expect(player.deck.indexOf(sheep)).toBeGreaterThan(0);
    expect(player.deck.indexOf(sheep)).toBeLessThan(player.deck.length);
});

Addressed in commits 3674bcb to 7273995

_🛠️ Refactor suggestion_ **Potential infinite loop in shuffleIntoDeck test.** The while loop could run indefinitely if the shuffling algorithm consistently places the card at index 0 or at the end. Add a safety counter to prevent infinite loops: ```diff + let attempts = 0; + const MAX_ATTEMPTS = 10; while ( player.deck.indexOf(sheep) <= 0 || player.deck.indexOf(sheep) >= player.deck.length - ) { + && attempts < MAX_ATTEMPTS) { + attempts++; game.functions.util.remove(player.deck, sheep); expect(await player.shuffleIntoDeck(sheep)).toBe(true); } + + // Assert that we succeeded in placing it somewhere in the middle + expect(attempts < MAX_ATTEMPTS).toBe(true); ``` <!-- 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 test("shuffleIntoDeck", async () => { const player = new Player(); expect(player.deck.length).toBe(0); const sheep = await Card.create(game.cardIds.sheep1, player); for (let i = 0; i < 10; i++) { const sheep_copy = await sheep.imperfectCopy(); sheep_copy.name = i.toString(); await player.addToDeck(sheep_copy); } expect(player.deck.length).toBe(10); expect(await player.shuffleIntoDeck(sheep)).toBe(true); // Prevent an infinite loop if shuffle keeps placing it at the ends let attempts = 0; const MAX_ATTEMPTS = 10; while ( player.deck.indexOf(sheep) <= 0 || player.deck.indexOf(sheep) >= player.deck.length && attempts < MAX_ATTEMPTS ) { attempts++; game.functions.util.remove(player.deck, sheep); expect(await player.shuffleIntoDeck(sheep)).toBe(true); } // Assert we succeeded within our attempt budget expect(attempts < MAX_ATTEMPTS).toBe(true); expect(player.deck.indexOf(sheep)).toBeGreaterThan(0); expect(player.deck.indexOf(sheep)).toBeLessThan(player.deck.length); }); ``` </details> <!-- suggestion_end --> <!-- This is an auto-generated comment by CodeRabbit --> ✅ Addressed in commits 3674bcb to 7273995
LunarTides (Migrated from github.com) reviewed 2025-05-14 08:23:41 +00:00
@ -849,7 +849,14 @@ export class Player {
* @returns Success
LunarTides (Migrated from github.com) commented 2025-05-14 08:23:41 +00:00

This has been fixed in the newest commit.

This has been fixed in the newest commit.
coderabbitai[bot] (Migrated from github.com) reviewed 2025-05-14 08:24:03 +00:00
@ -849,7 +849,14 @@ export class Player {
* @returns Success
coderabbitai[bot] (Migrated from github.com) commented 2025-05-14 08:24:03 +00:00

@LunarTides Thanks for the update! I'm glad to hear you've fixed the issue with the proper location tracking. Using setLocation() ensures all the card bookkeeping stays in sync.


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

@LunarTides Thanks for the update! I'm glad to hear you've fixed the issue with the proper location tracking. Using `setLocation()` ensures all the card bookkeeping stays in sync. --- <!-- This is an auto-generated comment: tweet message by coderabbit.ai --> If you found this review helpful, would you consider giving us a shout-out on [X](https://twitter.com/intent/tweet?related=CodeRabbitAI&text=Avoid%20direct%20property%20mutation%20to%20keep%20state%20%26%20events%20in%20sync!%20Use%20setter%20methods%20like%20%60setLocation%60%20to%20update%20game%20state%20and%20trigger%20events%20properly.%20Try%20%40coderabbitai%20for%20smart%20code%20reviews!%20%F0%9F%9B%A0%EF%B8%8F%E2%9C%A8&url=https%3A%2F%2Fgithub.com%2FLunarTides%2FHearthstone.js%2Fpull%2F395%23discussion_r2088282655)? Thank you for using CodeRabbit! <!-- end of auto-generated comment: tweet message by coderabbit.ai --> <!-- <agentComponentType>chat-answer</agentComponentType> --> <!-- <rawResChunk><answer>@LunarTides Thanks for the update! I'm glad to hear you've fixed the issue with the proper location tracking. Using `setLocation()` ensures all the card bookkeeping stays in sync. --- &lt;!-- This is an auto-generated comment: tweet message by coderabbit.ai --&gt; If you found this review helpful, would you consider giving us a shout-out on [X](https://twitter.com/intent/tweet?related=CodeRabbitAI&text=Avoid%20direct%20property%20mutation%20to%20keep%20state%20%26%20events%20in%20sync!%20Use%20setter%20methods%20like%20%60setLocation%60%20to%20update%20game%20state%20and%20trigger%20events%20properly.%20Try%20%40coderabbitai%20for%20smart%20code%20reviews!%20%F0%9F%9B%A0%EF%B8%8F%E2%9C%A8&url=https%3A%2F%2Fgithub.com%2FLunarTides%2FHearthstone.js%2Fpull%2F395%23discussion_r2088282655)? Thank you for using CodeRabbit! &lt;!-- end of auto-generated comment: tweet message by coderabbit.ai --&gt; </answer></rawResChunk> --> <!-- This is an auto-generated reply by CodeRabbit -->
LunarTides (Migrated from github.com) reviewed 2025-05-15 07:45:59 +00:00
LunarTides (Migrated from github.com) commented 2025-05-15 07:45:59 +00:00

Mostly added in the newest commit (5adedb0).

Mostly added in the newest commit (5adedb0).
coderabbitai[bot] (Migrated from github.com) reviewed 2025-05-15 07:47:34 +00:00
coderabbitai[bot] (Migrated from github.com) commented 2025-05-15 07:47:34 +00:00
🧩 Analysis chain

Let me check the changes in the commit the user referenced and the exact implementation of the card test functionality.

Let me check how the Ability.Test is actually used in the code:

@LunarTides I see you've implemented most of the suggested changes in the latest code. Your implementation now properly checks if (!card.abilities.test) before attempting to trigger the test, which addresses the main concern about skipping cards without test abilities.

The updated code is more streamlined - it eliminated separate helper functions, simplified deck setup, and improved error messaging. I also notice you're keeping track of skipped cards, which is a nice addition for reporting purposes.

One small thing to consider: is Ability.Test formally defined in the enum? The verification agent suggested adding Test = "test" to the Ability enum, but I didn't see confirmation that this was addressed. If it's already defined, that's great - if not, it might be worth adding for better type safety and code clarity.


🏁 Script executed:

#!/bin/bash
# Check if Test is defined in the Ability enum
rg -n "export enum Ability" -A 30 --type ts

Length of output: 1441


🏁 Script executed:

#!/bin/bash
# Get details about the commit 5adedb0
git show --name-only 5adedb0

# Check the current implementation of cardtest.ts
cat scripts/cardtest.ts | grep -A 5 -B 5 "if (!card.abilities.test)"

Length of output: 615


🏁 Script executed:

#!/bin/bash
# Check how Ability.Test is used in the script
rg "Ability\.Test" scripts/cardtest.ts

Length of output: 97


@LunarTides I can confirm you've fully implemented all the suggestions! Looking at the codebase:

  1. Test = "test" is properly defined in the Ability enum in src/types/card.ts (line 181)
  2. Your implementation properly checks if (!card.abilities.test) before triggering tests
  3. You've added tracking for skipped cards with skips++

The refactored implementation is clean and handles all the important edge cases. Great work on improving the testing setup!


🧬 Code Graph Analysis Results

Relevant snippets for understanding scripts/cardtest.ts:


File: src/game.ts (lines 1836-1848)

export async function createGame(registerCards = true) {
	const player1 = new Player();
	const player2 = new Player();
	const game = new Game(player1, player2);
	await game.functions.util.importConfig();
	await game.functions.util.importLanguageMap();
	game.doConfigAi();
	if (registerCards) {
		await Card.registerAll();
	}

	return { game, player1, player2 };
}
  • Creates a new game instance with two players.
  • Imports configuration and language mappings.
  • Configures AI settings.
  • Optionally registers all cards (default true).
  • Returns the game instance and the two players.

File: src/card.ts (lines 38-2080) — Summary of Card class and relevant methods

The Card class represents a card in the game with properties such as name, text, cost, classes, type, rarity, abilities, owner, and many others.

Key relevant methods and properties used in cardtest.ts:

  • static async all(include_uncollectible = false): Promise<Card[]>

    • Returns all cards, optionally including uncollectible ones.
    • Uses cached cards or creates them from blueprints if not loaded.
  • static async create(id: number, player: Player, suppressEvent = false): Promise<Card>

    • Creates a new card instance with the given id and owner.
    • Calls setup to initialize the card.
  • abilities: { [key in Ability]?: AbilityCallback[] }

    • Stores the card's abilities keyed by ability name.
  • async trigger<E extends Event>(name: Ability, key?, value?, eventPlayer?): Promise<unknown[] | typeof Card.REFUND | false>

    • Triggers the specified ability on the card.
    • Returns an array of results, or Card.REFUND or false.
  • owner: Player

    • The player who owns the card.
  • name: string

    • The card's name.
  • id: number

    • The card's blueprint id.
  • constructor(id: number, owner: Player)

    • Initializes the card from blueprint and sets owner.
  • async setup(suppressEvent = false): Promise<void>

    • Async initialization after construction.
    • Calls doBlueprint and triggers Ability.Create.
    • Broadcasts CreateCard event unless suppressed.
    • Creates a backup of the card's initial state.
  • async doBlueprint(trigger = true): Promise<void>

    • Applies blueprint properties to the card.
    • Converts functions in blueprint to abilities.
    • Parses text tags.
    • Optionally triggers Ability.Create.
  • static async registerAll(): Promise<boolean>

    • Imports and registers all card blueprints from the cards folder.
    • Validates blueprints.
  • async perfectCopy(): this

    • Returns a perfect clone of the card with a new UUID.

Summary of how these relate to scripts/cardtest.ts:

  • cardtest.ts imports createGame to create game instances and players.
  • It uses Card.all(true) to get all cards including uncollectible ones.
  • For each card with a test ability, it creates a new game, sets up decks, and triggers the test ability.
  • The trigger method on Card is used to run the test ability.
  • Errors during the test ability trigger are caught and logged.
  • The Card.create method is used to create cards like "sheep1" for decks.
  • The createGame function optionally registers cards, which is relevant for loading blueprints used in tests.

[End of relevant snippets.]

<details> <summary>🧩 Analysis chain</summary> Let me check the changes in the commit the user referenced and the exact implementation of the card test functionality. Let me check how the `Ability.Test` is actually used in the code: @LunarTides I see you've implemented most of the suggested changes in the latest code. Your implementation now properly checks `if (!card.abilities.test)` before attempting to trigger the test, which addresses the main concern about skipping cards without test abilities. The updated code is more streamlined - it eliminated separate helper functions, simplified deck setup, and improved error messaging. I also notice you're keeping track of skipped cards, which is a nice addition for reporting purposes. One small thing to consider: is `Ability.Test` formally defined in the enum? The verification agent suggested adding `Test = "test"` to the `Ability` enum, but I didn't see confirmation that this was addressed. If it's already defined, that's great - if not, it might be worth adding for better type safety and code clarity. --- 🏁 Script executed: ``` #!/bin/bash # Check if Test is defined in the Ability enum rg -n "export enum Ability" -A 30 --type ts ``` Length of output: 1441 --- 🏁 Script executed: ``` #!/bin/bash # Get details about the commit 5adedb0 git show --name-only 5adedb0 # Check the current implementation of cardtest.ts cat scripts/cardtest.ts | grep -A 5 -B 5 "if (!card.abilities.test)" ``` Length of output: 615 --- 🏁 Script executed: ``` #!/bin/bash # Check how Ability.Test is used in the script rg "Ability\.Test" scripts/cardtest.ts ``` Length of output: 97 --- </details> @LunarTides I can confirm you've fully implemented all the suggestions! Looking at the codebase: 1. `Test = "test"` is properly defined in the `Ability` enum in src/types/card.ts (line 181) 2. Your implementation properly checks `if (!card.abilities.test)` before triggering tests 3. You've added tracking for skipped cards with `skips++` The refactored implementation is clean and handles all the important edge cases. Great work on improving the testing setup! --- <details> <summary>🧬 Code Graph Analysis Results</summary> ### Relevant snippets for understanding `scripts/cardtest.ts`: --- #### File: `src/game.ts` (lines 1836-1848) ```ts export async function createGame(registerCards = true) { const player1 = new Player(); const player2 = new Player(); const game = new Game(player1, player2); await game.functions.util.importConfig(); await game.functions.util.importLanguageMap(); game.doConfigAi(); if (registerCards) { await Card.registerAll(); } return { game, player1, player2 }; } ``` - Creates a new game instance with two players. - Imports configuration and language mappings. - Configures AI settings. - Optionally registers all cards (default true). - Returns the game instance and the two players. --- #### File: `src/card.ts` (lines 38-2080) — Summary of `Card` class and relevant methods The `Card` class represents a card in the game with properties such as `name`, `text`, `cost`, `classes`, `type`, `rarity`, `abilities`, `owner`, and many others. Key relevant methods and properties used in `cardtest.ts`: - `static async all(include_uncollectible = false): Promise<Card[]>` - Returns all cards, optionally including uncollectible ones. - Uses cached cards or creates them from blueprints if not loaded. - `static async create(id: number, player: Player, suppressEvent = false): Promise<Card>` - Creates a new card instance with the given id and owner. - Calls `setup` to initialize the card. - `abilities: { [key in Ability]?: AbilityCallback[] }` - Stores the card's abilities keyed by ability name. - `async trigger<E extends Event>(name: Ability, key?, value?, eventPlayer?): Promise<unknown[] | typeof Card.REFUND | false>` - Triggers the specified ability on the card. - Returns an array of results, or `Card.REFUND` or `false`. - `owner: Player` - The player who owns the card. - `name: string` - The card's name. - `id: number` - The card's blueprint id. - `constructor(id: number, owner: Player)` - Initializes the card from blueprint and sets owner. - `async setup(suppressEvent = false): Promise<void>` - Async initialization after construction. - Calls `doBlueprint` and triggers `Ability.Create`. - Broadcasts `CreateCard` event unless suppressed. - Creates a backup of the card's initial state. - `async doBlueprint(trigger = true): Promise<void>` - Applies blueprint properties to the card. - Converts functions in blueprint to abilities. - Parses text tags. - Optionally triggers `Ability.Create`. - `static async registerAll(): Promise<boolean>` - Imports and registers all card blueprints from the cards folder. - Validates blueprints. - `async perfectCopy(): this` - Returns a perfect clone of the card with a new UUID. --- ### Summary of how these relate to `scripts/cardtest.ts`: - `cardtest.ts` imports `createGame` to create game instances and players. - It uses `Card.all(true)` to get all cards including uncollectible ones. - For each card with a `test` ability, it creates a new game, sets up decks, and triggers the `test` ability. - The `trigger` method on `Card` is used to run the test ability. - Errors during the test ability trigger are caught and logged. - The `Card.create` method is used to create cards like "sheep1" for decks. - The `createGame` function optionally registers cards, which is relevant for loading blueprints used in tests. --- [End of relevant snippets.] </details> <!-- <agentComponentType>chat-answer</agentComponentType> --> <!-- <rawResChunk><answer><details> <summary>🧩 Analysis chain</summary> Let me check the changes in the commit the user referenced and the exact implementation of the card test functionality. Let me check how the `Ability.Test` is actually used in the code: @LunarTides I see you've implemented most of the suggested changes in the latest code. Your implementation now properly checks `if (!card.abilities.test)` before attempting to trigger the test, which addresses the main concern about skipping cards without test abilities. The updated code is more streamlined - it eliminated separate helper functions, simplified deck setup, and improved error messaging. I also notice you're keeping track of skipped cards, which is a nice addition for reporting purposes. One small thing to consider: is `Ability.Test` formally defined in the enum? The verification agent suggested adding `Test = "test"` to the `Ability` enum, but I didn't see confirmation that this was addressed. If it's already defined, that's great - if not, it might be worth adding for better type safety and code clarity. --- 🏁 Script executed: ``` #!/bin/bash # Check if Test is defined in the Ability enum rg -n "export enum Ability" -A 30 --type ts ``` Length of output: 1441 --- 🏁 Script executed: ``` #!/bin/bash # Get details about the commit 5adedb0 git show --name-only 5adedb0 # Check the current implementation of cardtest.ts cat scripts/cardtest.ts | grep -A 5 -B 5 "if (!card.abilities.test)" ``` Length of output: 615 --- 🏁 Script executed: ``` #!/bin/bash # Check how Ability.Test is used in the script rg "Ability\.Test" scripts/cardtest.ts ``` Length of output: 97 --- </details> @LunarTides I can confirm you've fully implemented all the suggestions! Looking at the codebase: 1. `Test = "test"` is properly defined in the `Ability` enum in src/types/card.ts (line 181) 2. Your implementation properly checks `if (!card.abilities.test)` before triggering tests 3. You've added tracking for skipped cards with `skips++` The refactored implementation is clean and handles all the important edge cases. Great work on improving the testing setup! --- <details> <summary>🧬 Code Graph Analysis Results</summary> ### Relevant snippets for understanding `scripts/cardtest.ts`: --- #### File: `src/game.ts` (lines 1836-1848) ```ts export async function createGame(registerCards = true) { const player1 = new Player(); const player2 = new Player(); const game = new Game(player1, player2); await game.functions.util.importConfig(); await game.functions.util.importLanguageMap(); game.doConfigAi(); if (registerCards) { await Card.registerAll(); } return { game, player1, player2 }; } ``` - Creates a new game instance with two players. - Imports configuration and language mappings. - Configures AI settings. - Optionally registers all cards (default true). - Returns the game instance and the two players. --- #### File: `src/card.ts` (lines 38-2080) — Summary of `Card` class and relevant methods The `Card` class represents a card in the game with properties such as `name`, `text`, `cost`, `classes`, `type`, `rarity`, `abilities`, `owner`, and many others. Key relevant methods and properties used in `cardtest.ts`: - `static async all(include_uncollectible = false): Promise<Card[]>` - Returns all cards, optionally including uncollectible ones. - Uses cached cards or creates them from blueprints if not loaded. - `static async create(id: number, player: Player, suppressEvent = false): Promise<Card>` - Creates a new card instance with the given id and owner. - Calls `setup` to initialize the card. - `abilities: { [key in Ability]?: AbilityCallback[] }` - Stores the card's abilities keyed by ability name. - `async trigger<E extends Event>(name: Ability, key?, value?, eventPlayer?): Promise<unknown[] | typeof Card.REFUND | false>` - Triggers the specified ability on the card. - Returns an array of results, or `Card.REFUND` or `false`. - `owner: Player` - The player who owns the card. - `name: string` - The card's name. - `id: number` - The card's blueprint id. - `constructor(id: number, owner: Player)` - Initializes the card from blueprint and sets owner. - `async setup(suppressEvent = false): Promise<void>` - Async initialization after construction. - Calls `doBlueprint` and triggers `Ability.Create`. - Broadcasts `CreateCard` event unless suppressed. - Creates a backup of the card's initial state. - `async doBlueprint(trigger = true): Promise<void>` - Applies blueprint properties to the card. - Converts functions in blueprint to abilities. - Parses text tags. - Optionally triggers `Ability.Create`. - `static async registerAll(): Promise<boolean>` - Imports and registers all card blueprints from the cards folder. - Validates blueprints. - `async perfectCopy(): this` - Returns a perfect clone of the card with a new UUID. --- ### Summary of how these relate to `scripts/cardtest.ts`: - `cardtest.ts` imports `createGame` to create game instances and players. - It uses `Card.all(true)` to get all cards including uncollectible ones. - For each card with a `test` ability, it creates a new game, sets up decks, and triggers the `test` ability. - The `trigger` method on `Card` is used to run the test ability. - Errors during the test ability trigger are caught and logged. - The `Card.create` method is used to create cards like "sheep1" for decks. - The `createGame` function optionally registers cards, which is relevant for loading blueprints used in tests. --- [End of relevant snippets.] </details> </answer></rawResChunk> --> <!-- This is an auto-generated reply by CodeRabbit -->
LunarTides (Migrated from github.com) reviewed 2025-05-15 08:08:56 +00:00
LunarTides (Migrated from github.com) commented 2025-05-15 08:08:56 +00:00

This is incorrect. destroy only calls silence before kill. Calling kill manually does not skip the event broadcast. "Silencing" the card before destroying it, as was the previous behavior, was incorrect and does not match Hearthstone. The terminology is messy here, since destroyWeapon implies that destroy is called on the weapon, but it actually means "killing" it.

Since the terminology causes confusion for people unfamiliar to the codebase, the newest commit (3674bcb) has renamed these methods. kill has been renamed to destroy and destroy has been renamed to removeFromPlay. This should make the purpose of these methods clearer.

This is incorrect. `destroy` only calls `silence` before `kill`. Calling `kill` manually does not skip the event broadcast. "Silencing" the card before destroying it, as was the previous behavior, was incorrect and does not match Hearthstone. The terminology is messy here, since `destroyWeapon` implies that `destroy` is called on the weapon, but it actually means "killing" it. Since the terminology causes confusion for people unfamiliar to the codebase, the newest commit (3674bcb) has renamed these methods. `kill` has been renamed to `destroy` and `destroy` has been renamed to `removeFromPlay`. This should make the purpose of these methods clearer.
coderabbitai[bot] (Migrated from github.com) reviewed 2025-05-15 08:10:52 +00:00
coderabbitai[bot] (Migrated from github.com) commented 2025-05-15 08:10:51 +00:00
🧩 Analysis chain

I need to check if I can see the latest changes with the renamed methods.

Let's examine the actual changes in that commit:

Let's look at the complete player.ts file to see the destroyWeapon method:

Let's look for tests related to these methods to understand the expected behavior:

Let's check for any card instances that might be affected by this change in the codebase:

Without seeing the revised implementations, I'll need to trust the user's explanation and provide a response acknowledging their correction:

@LunarTides Thank you for the clarification about the weapon destruction methods. I see that you've renamed the methods in commit 3674bcb to make their purposes clearer: kill()destroy() and destroy()removeFromPlay(). This should indeed help avoid confusion for those unfamiliar with the codebase.

The change from weapon.destroy() to weapon.kill() (now weapon.destroy() after renaming) was correct as it aligns with Hearthstone's behavior by not silencing enchantments before destruction. Sorry for the confusion in my previous comment.


🏁 Script executed:

#!/bin/bash
# Check the Card class implementation for the updated method names
grep -A 10 "async destroy\|async removeFromPlay\|async kill" src/card.ts

Length of output: 484


🏁 Script executed:

#!/bin/bash
# Let's check the git history to see the commit that renamed these methods
git show 3674bcb --name-only

Length of output: 912


🏁 Script executed:

#!/bin/bash
# Check the specific changes in the Card class methods
git show 3674bcb -- src/card.ts | grep -A 10 -B 2 "async destroy\|async removeFromPlay\|async kill"

Length of output: 681


🏁 Script executed:

#!/bin/bash
# Check the current destroyWeapon implementation
grep -A 10 "async destroyWeapon" src/player.ts

Length of output: 287


🏁 Script executed:

#!/bin/bash
# Check for tests related to weapon destruction
grep -r "destroyWeapon\|weapon.*destroy\|weapon.*kill" test/

Length of output: 274


🏁 Script executed:

#!/bin/bash
# Check for cards that call these methods
grep -r "\.destroy()\|\.kill()\|\.removeFromPlay()" --include="*.ts" cards/

Length of output: 760


@LunarTides Thank you for the clarification on the method naming. Looking at the commit 3674bcb, I can now see that you've renamed the methods to better reflect their purpose:

  • kill()destroy() - Sets health to 0 and triggers board cleanup
  • destroy()removeFromPlay() - Silences, destroys, and sets location to None

You're absolutely right - calling weapon.kill() (now weapon.destroy()) was the correct approach as it doesn't silence the weapon before destroying it, which aligns with Hearthstone's actual behavior.

The terminology change makes the API much clearer: "destroy" better describes what the method actually does, and "removeFromPlay" more accurately describes the combined silence+destroy operation. Sorry for the confusion in my previous comment!


🧬 Code Graph Analysis Results

Relevant snippets for understanding src/player.ts and the user comment about destroy, silence, and kill terminology:


/**
 * Destroys the card.
 *
 * This sets the card's health to 0, then forces the game to remove dead cards from the board.
 *
 * @returns Success
 */
async destroy(): Promise<boolean> {
	await this.setStats(this.attack, 0);
	await game.killCardsOnBoard();
	return true;
}

/**
 * Silences the card.
 *
 * @returns Success
 */
async silence(): Promise<boolean> {
	/*
	 * Tell the minion to undo it's passive.
	 * The false tells the minion that this is the last time it will call remove
	 * so it should finish whatever it is doing.
	 */
	const removeReturn = await this.trigger(Ability.Remove, "SilenceCard");

	// If the remove function returned false, then we should not silence.
	if (Array.isArray(removeReturn) && removeReturn[0] === false) {
		return false;
	}

	// Remove abilities from the card.
	for (const ability of Object.keys(this.abilities)) {
		this.abilities[ability as Ability] = [];
	}

	for (const key of Object.keys(this)) {
		if (
			key === "health" &&
			(this.health ?? 0) < (this.backups.init.health ?? 0)
		) {
			continue;
		}

		if (key === "sleepy" || key === "attackTimes") {
			continue;
		}

		/*
		 * Check if a backup exists for the attribute. If it does; restore it.
		 * HACK: Never usage
		 */
		if (this.backups.init[key as never]) {
			this[key as never] = this.backups.init[key as never];
		} else if (this.blueprint[key as never]) {
			/*
			 * Check if the attribute if defined in the blueprint. If it is; restore it.
			 * HACK: Never usage
			 */
			this[key as never] = this.blueprint[key as never];
		}
	}

	// Don't strikethrough the text if the card doesn't have text (e.g. Sheep)
	if (this.text) {
		this.text = `<strikethrough>${this.text}</strikethrough>`;
	}

	this.keywords = {};

	// Remove active enchantments.
	this.applyEnchantments();

	await game.event.broadcast(Event.SilenceCard, this, this.owner);

	await game.killCardsOnBoard();
	return true;
}

/**
 * Removes the card from play.
 *
 * This will silence the card and destroy it.
 */
async removeFromPlay(): Promise<void> {
	await this.silence();
	await this.destroy();

	await this.setLocation(Location.None);
}
  • destroy() sets health to 0 and triggers board cleanup.
  • silence() removes abilities, resets stats to backup/blueprint values, applies strikethrough to text, clears keywords and enchantments, broadcasts silence event, and triggers board cleanup.
  • removeFromPlay() calls silence() then destroy(), then sets location to None.

/**
 * Destroys this player's weapon
 *
 * # Examples
 * ```
 * // Assume the player has a weapon with 5 attack and the player hasn't attacked this turn.
 * assert.equal(player.weapon.attack, 5);
 * assert.equal(player.attack, 5);
 *
 * await player.destroyWeapon();
 *
 * assert.equal(player.weapon, null);
 * assert.equal(player.attack, 0);
 * ```
 *
 * @returns Success
 */
async destroyWeapon(): Promise<boolean> {
	if (!this.weapon) {
		return false;
	}

	await this.weapon.trigger(Ability.Deathrattle);
	this.attack -= this.weapon.attack ?? 0;

	await this.weapon.destroy();
	this.weapon = undefined;

	return true;
}
  • destroyWeapon() triggers the weapon's deathrattle, reduces player's attack by weapon attack, calls weapon.destroy(), and clears the weapon reference.

From src/types/other.ts (line 128):

export type Target = Card | Player;
  • Defines Target as either a Card or a Player.

Summary of relevance:

  • The user comment discusses confusion about the methods destroy, kill, and silence on cards and weapons.
  • The Card class methods destroy(), silence(), and removeFromPlay() are directly relevant, showing that destroy() only sets health to 0 and triggers cleanup, while silence() removes abilities and resets stats.
  • The Player class method destroyWeapon() calls weapon.destroy() but does not call silence(), matching the user's note that "destroy" only calls silence before kill (now renamed).
  • The user mentions renaming kill to destroy and destroy to removeFromPlay to clarify semantics, which aligns with the Card class methods.
  • The Target type is relevant as it shows the types involved in attacks and destruction.

No other snippets from the provided code are directly relevant to the user's comment about the destroy/silence/kill terminology and behavior.


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

<details> <summary>🧩 Analysis chain</summary> I need to check if I can see the latest changes with the renamed methods. Let's examine the actual changes in that commit: Let's look at the complete player.ts file to see the destroyWeapon method: Let's look for tests related to these methods to understand the expected behavior: Let's check for any card instances that might be affected by this change in the codebase: Without seeing the revised implementations, I'll need to trust the user's explanation and provide a response acknowledging their correction: @LunarTides Thank you for the clarification about the weapon destruction methods. I see that you've renamed the methods in commit 3674bcb to make their purposes clearer: `kill()` → `destroy()` and `destroy()` → `removeFromPlay()`. This should indeed help avoid confusion for those unfamiliar with the codebase. The change from `weapon.destroy()` to `weapon.kill()` (now `weapon.destroy()` after renaming) was correct as it aligns with Hearthstone's behavior by not silencing enchantments before destruction. Sorry for the confusion in my previous comment. --- 🏁 Script executed: ``` #!/bin/bash # Check the Card class implementation for the updated method names grep -A 10 "async destroy\|async removeFromPlay\|async kill" src/card.ts ``` Length of output: 484 --- 🏁 Script executed: ``` #!/bin/bash # Let's check the git history to see the commit that renamed these methods git show 3674bcb --name-only ``` Length of output: 912 --- 🏁 Script executed: ``` #!/bin/bash # Check the specific changes in the Card class methods git show 3674bcb -- src/card.ts | grep -A 10 -B 2 "async destroy\|async removeFromPlay\|async kill" ``` Length of output: 681 --- 🏁 Script executed: ``` #!/bin/bash # Check the current destroyWeapon implementation grep -A 10 "async destroyWeapon" src/player.ts ``` Length of output: 287 --- 🏁 Script executed: ``` #!/bin/bash # Check for tests related to weapon destruction grep -r "destroyWeapon\|weapon.*destroy\|weapon.*kill" test/ ``` Length of output: 274 --- 🏁 Script executed: ``` #!/bin/bash # Check for cards that call these methods grep -r "\.destroy()\|\.kill()\|\.removeFromPlay()" --include="*.ts" cards/ ``` Length of output: 760 --- </details> @LunarTides Thank you for the clarification on the method naming. Looking at the commit 3674bcb, I can now see that you've renamed the methods to better reflect their purpose: - `kill()` → `destroy()` - Sets health to 0 and triggers board cleanup - `destroy()` → `removeFromPlay()` - Silences, destroys, and sets location to None You're absolutely right - calling `weapon.kill()` (now `weapon.destroy()`) was the correct approach as it doesn't silence the weapon before destroying it, which aligns with Hearthstone's actual behavior. The terminology change makes the API much clearer: "destroy" better describes what the method actually does, and "removeFromPlay" more accurately describes the combined silence+destroy operation. Sorry for the confusion in my previous comment! --- <details> <summary>🧬 Code Graph Analysis Results</summary> ### Relevant snippets for understanding `src/player.ts` and the user comment about `destroy`, `silence`, and `kill` terminology: --- #### From `src/card.ts` (lines 38-2086, summarized key methods related to destroy/silence): ```ts /** * Destroys the card. * * This sets the card's health to 0, then forces the game to remove dead cards from the board. * * @returns Success */ async destroy(): Promise<boolean> { await this.setStats(this.attack, 0); await game.killCardsOnBoard(); return true; } /** * Silences the card. * * @returns Success */ async silence(): Promise<boolean> { /* * Tell the minion to undo it's passive. * The false tells the minion that this is the last time it will call remove * so it should finish whatever it is doing. */ const removeReturn = await this.trigger(Ability.Remove, "SilenceCard"); // If the remove function returned false, then we should not silence. if (Array.isArray(removeReturn) && removeReturn[0] === false) { return false; } // Remove abilities from the card. for (const ability of Object.keys(this.abilities)) { this.abilities[ability as Ability] = []; } for (const key of Object.keys(this)) { if ( key === "health" && (this.health ?? 0) < (this.backups.init.health ?? 0) ) { continue; } if (key === "sleepy" || key === "attackTimes") { continue; } /* * Check if a backup exists for the attribute. If it does; restore it. * HACK: Never usage */ if (this.backups.init[key as never]) { this[key as never] = this.backups.init[key as never]; } else if (this.blueprint[key as never]) { /* * Check if the attribute if defined in the blueprint. If it is; restore it. * HACK: Never usage */ this[key as never] = this.blueprint[key as never]; } } // Don't strikethrough the text if the card doesn't have text (e.g. Sheep) if (this.text) { this.text = `<strikethrough>${this.text}</strikethrough>`; } this.keywords = {}; // Remove active enchantments. this.applyEnchantments(); await game.event.broadcast(Event.SilenceCard, this, this.owner); await game.killCardsOnBoard(); return true; } /** * Removes the card from play. * * This will silence the card and destroy it. */ async removeFromPlay(): Promise<void> { await this.silence(); await this.destroy(); await this.setLocation(Location.None); } ``` - `destroy()` sets health to 0 and triggers board cleanup. - `silence()` removes abilities, resets stats to backup/blueprint values, applies strikethrough to text, clears keywords and enchantments, broadcasts silence event, and triggers board cleanup. - `removeFromPlay()` calls `silence()` then `destroy()`, then sets location to None. --- #### From `src/player.ts` (lines related to weapon destruction): ```ts /** * Destroys this player's weapon * * # Examples * ``` * // Assume the player has a weapon with 5 attack and the player hasn't attacked this turn. * assert.equal(player.weapon.attack, 5); * assert.equal(player.attack, 5); * * await player.destroyWeapon(); * * assert.equal(player.weapon, null); * assert.equal(player.attack, 0); * ``` * * @returns Success */ async destroyWeapon(): Promise<boolean> { if (!this.weapon) { return false; } await this.weapon.trigger(Ability.Deathrattle); this.attack -= this.weapon.attack ?? 0; await this.weapon.destroy(); this.weapon = undefined; return true; } ``` - `destroyWeapon()` triggers the weapon's deathrattle, reduces player's attack by weapon attack, calls `weapon.destroy()`, and clears the weapon reference. --- #### From `src/types/other.ts` (line 128): ```ts export type Target = Card | Player; ``` - Defines `Target` as either a `Card` or a `Player`. --- ### Summary of relevance: - The user comment discusses confusion about the methods `destroy`, `kill`, and `silence` on cards and weapons. - The `Card` class methods `destroy()`, `silence()`, and `removeFromPlay()` are directly relevant, showing that `destroy()` only sets health to 0 and triggers cleanup, while `silence()` removes abilities and resets stats. - The `Player` class method `destroyWeapon()` calls `weapon.destroy()` but does not call `silence()`, matching the user's note that "destroy" only calls `silence` before `kill` (now renamed). - The user mentions renaming `kill` to `destroy` and `destroy` to `removeFromPlay` to clarify semantics, which aligns with the `Card` class methods. - The `Target` type is relevant as it shows the types involved in attacks and destruction. --- ### No other snippets from the provided code are directly relevant to the user's comment about the destroy/silence/kill terminology and behavior. </details> --- <!-- This is an auto-generated comment: tweet message by coderabbit.ai --> If you found this review helpful, would you consider giving us a shout-out on [X](https://twitter.com/intent/tweet?related=CodeRabbitAI&text=%40coderabbitai%20highlights%20that%20weapon%20destruction%20must%20silence%20enchantments%20first%20to%20avoid%20lingering%20effects%20and%20match%20Hearthstone%20behavior.%20Clear%20method%20renaming%20now%20prevents%20confusion!%20%F0%9F%8E%AF%F0%9F%9B%A1%EF%B8%8F%20Try%20it!&url=https%3A%2F%2Fgithub.com%2FLunarTides%2FHearthstone.js%2Fpull%2F395%23discussion_r2088282658)? Thank you for using CodeRabbit! <!-- end of auto-generated comment: tweet message by coderabbit.ai --> <!-- <agentComponentType>chat-answer</agentComponentType> --> <!-- <rawResChunk><answer><details> <summary>🧩 Analysis chain</summary> I need to check if I can see the latest changes with the renamed methods. Let's examine the actual changes in that commit: Let's look at the complete player.ts file to see the destroyWeapon method: Let's look for tests related to these methods to understand the expected behavior: Let's check for any card instances that might be affected by this change in the codebase: Without seeing the revised implementations, I'll need to trust the user's explanation and provide a response acknowledging their correction: @LunarTides Thank you for the clarification about the weapon destruction methods. I see that you've renamed the methods in commit 3674bcb to make their purposes clearer: `kill()` → `destroy()` and `destroy()` → `removeFromPlay()`. This should indeed help avoid confusion for those unfamiliar with the codebase. The change from `weapon.destroy()` to `weapon.kill()` (now `weapon.destroy()` after renaming) was correct as it aligns with Hearthstone's behavior by not silencing enchantments before destruction. Sorry for the confusion in my previous comment. --- 🏁 Script executed: ``` #!/bin/bash # Check the Card class implementation for the updated method names grep -A 10 "async destroy\|async removeFromPlay\|async kill" src/card.ts ``` Length of output: 484 --- 🏁 Script executed: ``` #!/bin/bash # Let's check the git history to see the commit that renamed these methods git show 3674bcb --name-only ``` Length of output: 912 --- 🏁 Script executed: ``` #!/bin/bash # Check the specific changes in the Card class methods git show 3674bcb -- src/card.ts | grep -A 10 -B 2 "async destroy\|async removeFromPlay\|async kill" ``` Length of output: 681 --- 🏁 Script executed: ``` #!/bin/bash # Check the current destroyWeapon implementation grep -A 10 "async destroyWeapon" src/player.ts ``` Length of output: 287 --- 🏁 Script executed: ``` #!/bin/bash # Check for tests related to weapon destruction grep -r "destroyWeapon\|weapon.*destroy\|weapon.*kill" test/ ``` Length of output: 274 --- 🏁 Script executed: ``` #!/bin/bash # Check for cards that call these methods grep -r "\.destroy()\|\.kill()\|\.removeFromPlay()" --include="*.ts" cards/ ``` Length of output: 760 --- </details> @LunarTides Thank you for the clarification on the method naming. Looking at the commit 3674bcb, I can now see that you've renamed the methods to better reflect their purpose: - `kill()` → `destroy()` - Sets health to 0 and triggers board cleanup - `destroy()` → `removeFromPlay()` - Silences, destroys, and sets location to None You're absolutely right - calling `weapon.kill()` (now `weapon.destroy()`) was the correct approach as it doesn't silence the weapon before destroying it, which aligns with Hearthstone's actual behavior. The terminology change makes the API much clearer: "destroy" better describes what the method actually does, and "removeFromPlay" more accurately describes the combined silence+destroy operation. Sorry for the confusion in my previous comment! --- <details> <summary>🧬 Code Graph Analysis Results</summary> ### Relevant snippets for understanding `src/player.ts` and the user comment about `destroy`, `silence`, and `kill` terminology: --- #### From `src/card.ts` (lines 38-2086, summarized key methods related to destroy/silence): ```ts /** * Destroys the card. * * This sets the card's health to 0, then forces the game to remove dead cards from the board. * * @returns Success */ async destroy(): Promise<boolean> { await this.setStats(this.attack, 0); await game.killCardsOnBoard(); return true; } /** * Silences the card. * * @returns Success */ async silence(): Promise<boolean> { /* * Tell the minion to undo it's passive. * The false tells the minion that this is the last time it will call remove * so it should finish whatever it is doing. */ const removeReturn = await this.trigger(Ability.Remove, "SilenceCard"); // If the remove function returned false, then we should not silence. if (Array.isArray(removeReturn) && removeReturn[0] === false) { return false; } // Remove abilities from the card. for (const ability of Object.keys(this.abilities)) { this.abilities[ability as Ability] = []; } for (const key of Object.keys(this)) { if ( key === "health" && (this.health ?? 0) < (this.backups.init.health ?? 0) ) { continue; } if (key === "sleepy" || key === "attackTimes") { continue; } /* * Check if a backup exists for the attribute. If it does; restore it. * HACK: Never usage */ if (this.backups.init[key as never]) { this[key as never] = this.backups.init[key as never]; } else if (this.blueprint[key as never]) { /* * Check if the attribute if defined in the blueprint. If it is; restore it. * HACK: Never usage */ this[key as never] = this.blueprint[key as never]; } } // Don't strikethrough the text if the card doesn't have text (e.g. Sheep) if (this.text) { this.text = `<strikethrough>${this.text}</strikethrough>`; } this.keywords = {}; // Remove active enchantments. this.applyEnchantments(); await game.event.broadcast(Event.SilenceCard, this, this.owner); await game.killCardsOnBoard(); return true; } /** * Removes the card from play. * * This will silence the card and destroy it. */ async removeFromPlay(): Promise<void> { await this.silence(); await this.destroy(); await this.setLocation(Location.None); } ``` - `destroy()` sets health to 0 and triggers board cleanup. - `silence()` removes abilities, resets stats to backup/blueprint values, applies strikethrough to text, clears keywords and enchantments, broadcasts silence event, and triggers board cleanup. - `removeFromPlay()` calls `silence()` then `destroy()`, then sets location to None. --- #### From `src/player.ts` (lines related to weapon destruction): ```ts /** * Destroys this player's weapon * * # Examples * ``` * // Assume the player has a weapon with 5 attack and the player hasn't attacked this turn. * assert.equal(player.weapon.attack, 5); * assert.equal(player.attack, 5); * * await player.destroyWeapon(); * * assert.equal(player.weapon, null); * assert.equal(player.attack, 0); * ``` * * @returns Success */ async destroyWeapon(): Promise<boolean> { if (!this.weapon) { return false; } await this.weapon.trigger(Ability.Deathrattle); this.attack -= this.weapon.attack ?? 0; await this.weapon.destroy(); this.weapon = undefined; return true; } ``` - `destroyWeapon()` triggers the weapon's deathrattle, reduces player's attack by weapon attack, calls `weapon.destroy()`, and clears the weapon reference. --- #### From `src/types/other.ts` (line 128): ```ts export type Target = Card | Player; ``` - Defines `Target` as either a `Card` or a `Player`. --- ### Summary of relevance: - The user comment discusses confusion about the methods `destroy`, `kill`, and `silence` on cards and weapons. - The `Card` class methods `destroy()`, `silence()`, and `removeFromPlay()` are directly relevant, showing that `destroy()` only sets health to 0 and triggers cleanup, while `silence()` removes abilities and resets stats. - The `Player` class method `destroyWeapon()` calls `weapon.destroy()` but does not call `silence()`, matching the user's note that "destroy" only calls `silence` before `kill` (now renamed). - The user mentions renaming `kill` to `destroy` and `destroy` to `removeFromPlay` to clarify semantics, which aligns with the `Card` class methods. - The `Target` type is relevant as it shows the types involved in attacks and destruction. --- ### No other snippets from the provided code are directly relevant to the user's comment about the destroy/silence/kill terminology and behavior. </details> --- &lt;!-- This is an auto-generated comment: tweet message by coderabbit.ai --&gt; If you found this review helpful, would you consider giving us a shout-out on [X](https://twitter.com/intent/tweet?related=CodeRabbitAI&text=%40coderabbitai%20highlights%20that%20weapon%20destruction%20must%20silence%20enchantments%20first%20to%20avoid%20lingering%20effects%20and%20match%20Hearthstone%20behavior.%20Clear%20method%20renaming%20now%20prevents%20confusion!%20%F0%9F%8E%AF%F0%9F%9B%A1%EF%B8%8F%20Try%20it!&url=https%3A%2F%2Fgithub.com%2FLunarTides%2FHearthstone.js%2Fpull%2F395%23discussion_r2088282658)? Thank you for using CodeRabbit! &lt;!-- end of auto-generated comment: tweet message by coderabbit.ai --&gt; </answer></rawResChunk> --> <!-- This is an auto-generated reply by CodeRabbit -->
coderabbitai[bot] (Migrated from github.com) reviewed 2025-05-15 08:23:16 +00:00
coderabbitai[bot] (Migrated from github.com) left a comment

Actionable comments posted: 1

🧹 Nitpick comments (3)
.github/workflows/bun.yml (1)

37-38: Refactor repeated Crashtest invocations for maintainability
Running the same script five times inline increases YAML verbosity and maintenance effort. Consider using a loop or parameter to DRY up this step and make it easier to adjust the repeat count in the future.

Proposed diff:

-    - name: Crashtest
-      run: |
-        bun ./scripts/crashtest.ts &&
-        bun ./scripts/crashtest.ts &&
-        bun ./scripts/crashtest.ts &&
-        bun ./scripts/crashtest.ts &&
-        bun ./scripts/crashtest.ts
-      # If it takes more than 10 minutes, it's probably stuck in an infinite loop.
-      timeout-minutes: 10
+    - name: Crashtest
+      run: |
+        for i in {1..5}; do
+          bun ./scripts/crashtest.ts
+        done
+      # Fail if Crashtest runs longer than 10 minutes to catch potential infinite loops
+      timeout-minutes: 10
test/src/card.test.ts (1)

75-95: getKeyword and setKeyword tests need separation

The tests for getKeyword and setKeyword contain nearly identical code and are testing the same functionality. Consider refactoring to better separate concerns.

 test("getKeyword", async () => {
 	const card = await Card.create(game.cardIds.sheep1, game.player);
-	expect(card.setKeyword(Keyword.Dormant, 3)).toBe(false);
+	expect(card.getKeyword(Keyword.Dormant)).toBe(false);

 	card.addKeyword(Keyword.Dormant);
 	expect(card.hasKeyword(Keyword.Dormant)).toBe(true);

 	expect(card.setKeyword(Keyword.Dormant, 3)).toBe(true);
 	expect(card.getKeyword(Keyword.Dormant)).toBe(3);
 });
src/card.ts (1)

1177-1186: Added comprehensive removeFromPlay method.

This new method provides a more complete way to remove a card from play by:

  1. Silencing it (removing abilities)
  2. Destroying it (setting health to 0)
  3. Setting its location to None

Consider adding more detailed documentation about the method's purpose and the sequence of operations.

/**
 * Removes the card from play.
 *
 * This will silence the card and destroy it.
+ *
+ * Order of operations:
+ * 1. Silence the card (remove abilities)
+ * 2. Destroy the card (set health to 0)
+ * 3. Set location to None (remove from tracking)
 */
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between 5adedb08c7 and 727399509a.

📒 Files selected for processing (20)
  • .github/workflows/bun.yml (2 hunks)
  • cards/Classes/Druid/Collectible/Spells/1-Cost/101-floops-glorious-gloop.ts (1 hunks)
  • cards/Classes/Druid/Collectible/Spells/4-Cost/82-poison-seeds.ts (1 hunks)
  • cards/Classes/Neutral/Collectible/Minions/10-Cost/104-yogg-saron-master-of-fate.ts (1 hunks)
  • cards/Examples/2/7-Titan/7-ability1.ts (1 hunks)
  • cards/Examples/DIY/1.ts (1 hunks)
  • cards/Examples/DIY/4.ts (2 hunks)
  • cards/Examples/Test/disable-heropower.ts (1 hunks)
  • cards/Examples/Test/immortal.ts (2 hunks)
  • cards/Galakrond/Priest/70-hero.ts (1 hunks)
  • cards/Lackeys/28-witchy.ts (1 hunks)
  • cards/StartingHeroes/Death Knight/23-frail-ghoul.ts (1 hunks)
  • config.ts (3 hunks)
  • src/card.ts (6 hunks)
  • src/event.ts (5 hunks)
  • src/game.ts (10 hunks)
  • src/player.ts (7 hunks)
  • src/types/event.ts (2 hunks)
  • test/src/card.test.ts (1 hunks)
  • test/src/player.test.ts (3 hunks)
Files skipped from review due to trivial changes (1)
  • config.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • cards/Classes/Neutral/Collectible/Minions/10-Cost/104-yogg-saron-master-of-fate.ts
  • src/player.ts
  • src/event.ts
  • test/src/player.test.ts
🧰 Additional context used
🧬 Code Graph Analysis (2)
test/src/card.test.ts (2)
src/game.ts (2)
  • createGame (1836-1848)
  • Location (785-793)
src/card.ts (1)
  • Card (38-2086)
src/card.ts (4)
cards/Examples/3/4-placeholder.ts (1)
  • placeholders (36-59)
cards/Examples/3/last-combined.ts (1)
  • placeholders (62-69)
cards/Examples/Unordered/Card References/132-circular.ts (1)
  • placeholders (27-35)
cards/Examples/Unordered/Card References/131-simple.ts (1)
  • placeholders (33-42)
🔇 Additional comments (47)
.github/workflows/bun.yml (1)

12-12: Add global timeout to the build job
A 30-minute job timeout helps guard against runaway workflows and aligns with CI best practices for bounded execution.

cards/Lackeys/28-witchy.ts (1)

66-66: Updated to use removeFromPlay() instead of destroy()

This change aligns with the semantic meaning of transforming a minion, where the card is silently removed without triggering death effects before the new minion is summoned. The removeFromPlay() method provides the appropriate behavior for transformation effects.

cards/Examples/DIY/1.ts (1)

48-48: Method renamed from kill() to destroy()

This change is part of a codebase-wide standardization of card removal semantics, replacing calls to kill() with destroy() for consistency.

cards/Classes/Druid/Collectible/Spells/1-Cost/101-floops-glorious-gloop.ts (1)

30-30: Event updated from KillCard to DestroyCard

This change aligns the event name with the standardized card removal terminology. The spell text "Whenever a minion dies this turn" now consistently maps to the DestroyCard event.

cards/Classes/Druid/Collectible/Spells/4-Cost/82-poison-seeds.ts (1)

31-31: Method renamed from kill() to destroy()

This change makes the code more closely match the card text "Destroy all minions..." and maintains consistency with the standardized card removal semantics throughout the codebase.

cards/Examples/Test/disable-heropower.ts (1)

48-48: Method renamed from kill() to destroy()

This change is part of a broader refactoring across the codebase that standardizes card removal terminology. The functionality remains the same - the minion is removed from play, allowing the hero power to be used again.

cards/Examples/DIY/4.ts (2)

32-32: Event name updated from KillCard to DestroyCard

This change aligns with the standardized card removal terminology across the codebase. The event check now properly looks for the renamed destroy event, maintaining the same functionality.


75-75: Method renamed from kill() to destroy()

The card removal method has been updated to use destroy() instead of kill(), consistent with the terminology standardization throughout the codebase.

cards/Examples/2/7-Titan/7-ability1.ts (1)

47-47: Method renamed from kill() to destroy()

This change is part of the codebase refactoring that standardizes card removal terminology. The functionality remains unchanged - the target enemy minion is still removed from play.

cards/StartingHeroes/Death Knight/23-frail-ghoul.ts (1)

41-42: Updated comment and method from kill() to destroy()

Both the comment and implementation have been updated to use "destroy" terminology instead of "kill", consistent with the standardized card removal approach across the codebase. The functionality remains the same - the minion dies at the end of the owner's turn.

cards/Galakrond/Priest/70-hero.ts (1)

42-43: Method rename from kill() to destroy() looks good

The change from minion.kill() to minion.destroy() aligns with the broader refactoring across the codebase to standardize the terminology around card removal mechanics.

src/types/event.ts (2)

93-93: Event renaming is consistent with method renaming

The renaming of KillCard to DestroyCard in the Event enum aligns properly with the method renaming pattern (from kill() to destroy()) across the codebase.


180-182: Comment updated to match renamed event

The comment has been appropriately updated to reference "destroyed" instead of "killed" to maintain consistency with the renamed event.

cards/Examples/Test/immortal.ts (2)

34-36: Comment and condition updated to use DestroyCard

The comments and condition have been correctly updated to use "DestroyCard" instead of "KillCard" to match the renamed event type, maintaining consistency across the codebase.


50-50: Method call changed to destroy() for consistency

The test method now properly calls self.destroy() instead of self.kill(), ensuring the test uses the renamed method.

test/src/card.test.ts (17)

3-4: Appropriate imports added for test implementation

The imports for createGame and additional types are necessary for the test implementations that were added.


10-10: Game initialization for tests looks good

Awaiting createGame() at the start of the tests ensures that the game environment is properly initialized before running any tests, which is necessary for testing card behaviors that rely on the game state.


27-33: Good test implementation for hasKeyword

The test correctly verifies that hasKeyword returns false for keywords not present on the card and true after adding the keyword.


35-64: Comprehensive test for addKeyword with side effects

The test thoroughly verifies various aspects of the addKeyword method, including:

  1. Basic keyword addition
  2. Effects on sleepy status when adding Charge, Rush, CantAttack, or UnlimitedAttacks
  3. Effects on attackTimes for UnlimitedAttacks
  4. Effects on canAttackHero for Rush vs Charge

This comprehensive coverage helps ensure that the side effects of adding keywords are correctly implemented.


66-73: Good basic test for remKeyword

The test verifies that remKeyword correctly removes a keyword from a card.


97-103: Simple and effective test for freeze method

The test correctly verifies that calling freeze() on a card adds the Frozen keyword.


105-127: Thorough test for decAttack with multiple scenarios

The test covers multiple scenarios for decAttack:

  1. Decrementing attack times from 1 to 0 and checking sleepy status
  2. Attempting to decrement from 0 (expecting false return)
  3. Decrementing from 2 to 1 without affecting sleepy
  4. Decrementing from 1 to 0 and setting sleepy

This comprehensive coverage helps ensure the method behaves correctly in all cases.


129-141: Good test for ready method with CantAttack interaction

The test properly verifies that ready() clears the sleepy flag, except when the card has the CantAttack keyword.


143-157: Good test for setStats with maxHealth option

The test correctly verifies the behavior of setStats with and without changing maxHealth.


159-168: Good test for addStats

The test verifies that addStats correctly increases both attack and health values, including maxHealth.


170-180: Good test for remStats

The test verifies that remStats correctly decreases both attack and health values, including maxHealth.


182-192: Good test for addHealth with restore option

The test verifies the behavior of addHealth with and without the restore option:

  1. With restore=true (default), it doesn't increase health beyond maxHealth
  2. With restore=false, it increases both health and maxHealth

194-233: Comprehensive test for remHealth with keyword interactions

The test thoroughly verifies remHealth behavior with different card types and keywords:

  1. Basic health reduction
  2. Immunity for Location type cards
  3. Immunity due to Stealth keyword
  4. Interaction with Immune keyword (preventing damage while still returning true)
  5. Weapon destruction when health is reduced to zero

The comment on line 222 correctly reflects the behavior: "Immune prevents damage. remHealth returns true, and health remains unchanged."


235-242: Good test for resetMaxHealth

The test verifies that resetMaxHealth correctly sets the maxHealth value to match the current health.


244-252: Good test for setStealthDuration

The test verifies that setStealthDuration correctly calculates the stealth duration based on the current game turn.


254-271: Thorough test for resetAttackTimes with different keywords

The test verifies that resetAttackTimes correctly sets the attackTimes based on the presence of Windfury and MegaWindfury keywords:

  1. Basic case: 1 attack time
  2. With Windfury: 2 attack times
  3. With MegaWindfury: 4 attack times
  4. After removing both keywords: back to 1 attack time

273-285: Good test for setLocation with game state checking

The test verifies that setLocation correctly:

  1. Updates the card's location
  2. Adds the card to game.activeCards when location is Board
  3. Removes the card from game.activeCards when location is None
src/game.ts (9)

56-58: Improved taunt detection logic.

The updated code now properly filters taunt minions from the target's owner's board (or the target itself if it's a player), rather than always filtering from the opponent's board. This correctly handles all attack scenarios.


281-283: Good refactoring of forgetful attack logic.

Extracting the forgetful logic into a separate method improves code organization and modularity. This makes the code more maintainable by isolating this complex behavior.


497-497: Consistent use of destroy() instead of kill().

This change aligns with the broader codebase refactoring of renaming kill() to destroy() for better semantic clarity.


559-632: Well-structured implementation of the forgetful mechanic.

The _forgetful method is well-implemented with:

  • Clear state management for the keyword
  • Proper random target selection logic
  • Configurable failure attempts via game config
  • Thorough documentation

This implementation properly handles the complex "Forgetful" mechanic while maintaining readable code.


770-771: Event name consistency with DestroyCard.

Event broadcasting and ability triggers now consistently use "DestroyCard" instead of "KillCard", matching the event name changes in types/event.ts and the method rename in the Card class.

Also applies to: 785-785


795-795: Fixed method name to follow proper casing convention.

The method name was corrected from Heropower to HeroPower, improving naming consistency in the codebase.


1328-1349: Added flexible input translation with additional controls.

This new method enhances the translation system by allowing developers to override the noInput flag and control the input queue usage. This provides more flexibility for user interactions.


1351-1360: Added convenience method for translated pauses.

The new pauseTranslate method simplifies the common pattern of translating text and then pausing the game. This improves code readability for this frequent operation.


1229-1229:

Verification successful

Changed anniversary date format - verify behavior.

The date format for the anniversary event was changed from "MM-DD" to "DD/MM" with the value "14/02". This changes when the anniversary event would trigger.

Please verify this is the intended behavior for the anniversary date format change:


🏁 Script executed:

#!/bin/bash
# Check if there are any other references to the anniversary date format in the codebase
rg -A 2 -B 2 "anniversary" --glob "*.ts"

Length of output: 1506


Anniversary event date format update is correct

I confirmed that in src/game.ts the anniversary check now uses date.format(currentDate, "DD/MM") === "14/02" and there are no other legacy “MM-DD” checks elsewhere in the codebase. The event will correctly fire on February 14th as intended.

src/card.ts (6)

850-850: Clarified addHealth parameter documentation.

The documentation now correctly explains that the restore parameter controls whether health is capped at max health, rather than resetting health to max. This clarification helps prevent misuse of the method.


1091-1091: Updated bounce to use removeFromPlay.

The bounce method now calls removeFromPlay() instead of destroy(), which provides a more complete cleanup of the card by also silencing it and setting its location to None.


1097-1108: Renamed kill() to destroy() for consistency.

This method rename contributes to a more consistent API terminology across the codebase. The implementation logic remains the same - setting the card's health to 0 and then removing it from the board.


1308-1308: Updated discard to set location to None.

The discard method now properly updates the card's location state to Location.None upon successful discard, which ensures consistent state management.


1573-1638: Improved placeholder replacement robustness.

The replacePlaceholders method has been significantly enhanced with:

  • Proper spell damage placeholder detection using the local text variable
  • Type checking and clear error logging with card name context
  • Special markers to prevent infinite recursion
  • Better conditional formatting based on configuration
  • More consistent regex processing

These changes make the placeholder system more robust against edge cases and invalid inputs.


1679-1679: Fixed card lifecycle tracking in perfectCopy.

The perfectCopy method now properly adds the cloned card to game.activeCards, ensuring it's tracked correctly for lifecycle management and operations like reloading cards.

**Actionable comments posted: 1** <details> <summary>🧹 Nitpick comments (3)</summary><blockquote> <details> <summary>.github/workflows/bun.yml (1)</summary><blockquote> `37-38`: **Refactor repeated Crashtest invocations for maintainability** Running the same script five times inline increases YAML verbosity and maintenance effort. Consider using a loop or parameter to DRY up this step and make it easier to adjust the repeat count in the future. Proposed diff: ```diff - - name: Crashtest - run: | - bun ./scripts/crashtest.ts && - bun ./scripts/crashtest.ts && - bun ./scripts/crashtest.ts && - bun ./scripts/crashtest.ts && - bun ./scripts/crashtest.ts - # If it takes more than 10 minutes, it's probably stuck in an infinite loop. - timeout-minutes: 10 + - name: Crashtest + run: | + for i in {1..5}; do + bun ./scripts/crashtest.ts + done + # Fail if Crashtest runs longer than 10 minutes to catch potential infinite loops + timeout-minutes: 10 ``` </blockquote></details> <details> <summary>test/src/card.test.ts (1)</summary><blockquote> `75-95`: **getKeyword and setKeyword tests need separation** The tests for `getKeyword` and `setKeyword` contain nearly identical code and are testing the same functionality. Consider refactoring to better separate concerns. ```diff test("getKeyword", async () => { const card = await Card.create(game.cardIds.sheep1, game.player); - expect(card.setKeyword(Keyword.Dormant, 3)).toBe(false); + expect(card.getKeyword(Keyword.Dormant)).toBe(false); card.addKeyword(Keyword.Dormant); expect(card.hasKeyword(Keyword.Dormant)).toBe(true); expect(card.setKeyword(Keyword.Dormant, 3)).toBe(true); expect(card.getKeyword(Keyword.Dormant)).toBe(3); }); ``` </blockquote></details> <details> <summary>src/card.ts (1)</summary><blockquote> `1177-1186`: **Added comprehensive removeFromPlay method.** This new method provides a more complete way to remove a card from play by: 1. Silencing it (removing abilities) 2. Destroying it (setting health to 0) 3. Setting its location to None Consider adding more detailed documentation about the method's purpose and the sequence of operations. ```diff /** * Removes the card from play. * * This will silence the card and destroy it. + * + * Order of operations: + * 1. Silence the card (remove abilities) + * 2. Destroy the card (set health to 0) + * 3. Set location to None (remove from tracking) */ ``` </blockquote></details> </blockquote></details> <details> <summary>📜 Review details</summary> **Configuration used: CodeRabbit UI** **Review profile: CHILL** **Plan: Pro** **Cache: Disabled due to data retention organization setting** **Knowledge Base: Disabled due to data retention organization setting** <details> <summary>📥 Commits</summary> Reviewing files that changed from the base of the PR and between 5adedb08c7cf4d0f1cf5e9596005b40339eb2e26 and 727399509a46f21259370df37a81e5d10686e10c. </details> <details> <summary>📒 Files selected for processing (20)</summary> * `.github/workflows/bun.yml` (2 hunks) * `cards/Classes/Druid/Collectible/Spells/1-Cost/101-floops-glorious-gloop.ts` (1 hunks) * `cards/Classes/Druid/Collectible/Spells/4-Cost/82-poison-seeds.ts` (1 hunks) * `cards/Classes/Neutral/Collectible/Minions/10-Cost/104-yogg-saron-master-of-fate.ts` (1 hunks) * `cards/Examples/2/7-Titan/7-ability1.ts` (1 hunks) * `cards/Examples/DIY/1.ts` (1 hunks) * `cards/Examples/DIY/4.ts` (2 hunks) * `cards/Examples/Test/disable-heropower.ts` (1 hunks) * `cards/Examples/Test/immortal.ts` (2 hunks) * `cards/Galakrond/Priest/70-hero.ts` (1 hunks) * `cards/Lackeys/28-witchy.ts` (1 hunks) * `cards/StartingHeroes/Death Knight/23-frail-ghoul.ts` (1 hunks) * `config.ts` (3 hunks) * `src/card.ts` (6 hunks) * `src/event.ts` (5 hunks) * `src/game.ts` (10 hunks) * `src/player.ts` (7 hunks) * `src/types/event.ts` (2 hunks) * `test/src/card.test.ts` (1 hunks) * `test/src/player.test.ts` (3 hunks) </details> <details> <summary>✅ Files skipped from review due to trivial changes (1)</summary> * config.ts </details> <details> <summary>🚧 Files skipped from review as they are similar to previous changes (4)</summary> * cards/Classes/Neutral/Collectible/Minions/10-Cost/104-yogg-saron-master-of-fate.ts * src/player.ts * src/event.ts * test/src/player.test.ts </details> <details> <summary>🧰 Additional context used</summary> <details> <summary>🧬 Code Graph Analysis (2)</summary> <details> <summary>test/src/card.test.ts (2)</summary><blockquote> <details> <summary>src/game.ts (2)</summary> * `createGame` (1836-1848) * `Location` (785-793) </details> <details> <summary>src/card.ts (1)</summary> * `Card` (38-2086) </details> </blockquote></details> <details> <summary>src/card.ts (4)</summary><blockquote> <details> <summary>cards/Examples/3/4-placeholder.ts (1)</summary> * `placeholders` (36-59) </details> <details> <summary>cards/Examples/3/last-combined.ts (1)</summary> * `placeholders` (62-69) </details> <details> <summary>cards/Examples/Unordered/Card References/132-circular.ts (1)</summary> * `placeholders` (27-35) </details> <details> <summary>cards/Examples/Unordered/Card References/131-simple.ts (1)</summary> * `placeholders` (33-42) </details> </blockquote></details> </details> </details> <details> <summary>🔇 Additional comments (47)</summary><blockquote> <details> <summary>.github/workflows/bun.yml (1)</summary> `12-12`: **Add global timeout to the build job** A 30-minute job timeout helps guard against runaway workflows and aligns with CI best practices for bounded execution. </details> <details> <summary>cards/Lackeys/28-witchy.ts (1)</summary> `66-66`: **Updated to use `removeFromPlay()` instead of `destroy()`** This change aligns with the semantic meaning of transforming a minion, where the card is silently removed without triggering death effects before the new minion is summoned. The `removeFromPlay()` method provides the appropriate behavior for transformation effects. </details> <details> <summary>cards/Examples/DIY/1.ts (1)</summary> `48-48`: **Method renamed from `kill()` to `destroy()`** This change is part of a codebase-wide standardization of card removal semantics, replacing calls to `kill()` with `destroy()` for consistency. </details> <details> <summary>cards/Classes/Druid/Collectible/Spells/1-Cost/101-floops-glorious-gloop.ts (1)</summary> `30-30`: **Event updated from `KillCard` to `DestroyCard`** This change aligns the event name with the standardized card removal terminology. The spell text "Whenever a minion dies this turn" now consistently maps to the `DestroyCard` event. </details> <details> <summary>cards/Classes/Druid/Collectible/Spells/4-Cost/82-poison-seeds.ts (1)</summary> `31-31`: **Method renamed from `kill()` to `destroy()`** This change makes the code more closely match the card text "Destroy all minions..." and maintains consistency with the standardized card removal semantics throughout the codebase. </details> <details> <summary>cards/Examples/Test/disable-heropower.ts (1)</summary> `48-48`: **Method renamed from `kill()` to `destroy()`** This change is part of a broader refactoring across the codebase that standardizes card removal terminology. The functionality remains the same - the minion is removed from play, allowing the hero power to be used again. </details> <details> <summary>cards/Examples/DIY/4.ts (2)</summary> `32-32`: **Event name updated from `KillCard` to `DestroyCard`** This change aligns with the standardized card removal terminology across the codebase. The event check now properly looks for the renamed destroy event, maintaining the same functionality. --- `75-75`: **Method renamed from `kill()` to `destroy()`** The card removal method has been updated to use `destroy()` instead of `kill()`, consistent with the terminology standardization throughout the codebase. </details> <details> <summary>cards/Examples/2/7-Titan/7-ability1.ts (1)</summary> `47-47`: **Method renamed from `kill()` to `destroy()`** This change is part of the codebase refactoring that standardizes card removal terminology. The functionality remains unchanged - the target enemy minion is still removed from play. </details> <details> <summary>cards/StartingHeroes/Death Knight/23-frail-ghoul.ts (1)</summary> `41-42`: **Updated comment and method from `kill()` to `destroy()`** Both the comment and implementation have been updated to use "destroy" terminology instead of "kill", consistent with the standardized card removal approach across the codebase. The functionality remains the same - the minion dies at the end of the owner's turn. </details> <details> <summary>cards/Galakrond/Priest/70-hero.ts (1)</summary> `42-43`: **Method rename from kill() to destroy() looks good** The change from `minion.kill()` to `minion.destroy()` aligns with the broader refactoring across the codebase to standardize the terminology around card removal mechanics. </details> <details> <summary>src/types/event.ts (2)</summary> `93-93`: **Event renaming is consistent with method renaming** The renaming of `KillCard` to `DestroyCard` in the Event enum aligns properly with the method renaming pattern (from `kill()` to `destroy()`) across the codebase. --- `180-182`: **Comment updated to match renamed event** The comment has been appropriately updated to reference "destroyed" instead of "killed" to maintain consistency with the renamed event. </details> <details> <summary>cards/Examples/Test/immortal.ts (2)</summary> `34-36`: **Comment and condition updated to use DestroyCard** The comments and condition have been correctly updated to use "DestroyCard" instead of "KillCard" to match the renamed event type, maintaining consistency across the codebase. --- `50-50`: **Method call changed to destroy() for consistency** The test method now properly calls `self.destroy()` instead of `self.kill()`, ensuring the test uses the renamed method. </details> <details> <summary>test/src/card.test.ts (17)</summary> `3-4`: **Appropriate imports added for test implementation** The imports for `createGame` and additional types are necessary for the test implementations that were added. --- `10-10`: **Game initialization for tests looks good** Awaiting `createGame()` at the start of the tests ensures that the game environment is properly initialized before running any tests, which is necessary for testing card behaviors that rely on the game state. --- `27-33`: **Good test implementation for hasKeyword** The test correctly verifies that `hasKeyword` returns false for keywords not present on the card and true after adding the keyword. --- `35-64`: **Comprehensive test for addKeyword with side effects** The test thoroughly verifies various aspects of the `addKeyword` method, including: 1. Basic keyword addition 2. Effects on `sleepy` status when adding Charge, Rush, CantAttack, or UnlimitedAttacks 3. Effects on `attackTimes` for UnlimitedAttacks 4. Effects on `canAttackHero` for Rush vs Charge This comprehensive coverage helps ensure that the side effects of adding keywords are correctly implemented. --- `66-73`: **Good basic test for remKeyword** The test verifies that `remKeyword` correctly removes a keyword from a card. --- `97-103`: **Simple and effective test for freeze method** The test correctly verifies that calling `freeze()` on a card adds the Frozen keyword. --- `105-127`: **Thorough test for decAttack with multiple scenarios** The test covers multiple scenarios for `decAttack`: 1. Decrementing attack times from 1 to 0 and checking sleepy status 2. Attempting to decrement from 0 (expecting false return) 3. Decrementing from 2 to 1 without affecting sleepy 4. Decrementing from 1 to 0 and setting sleepy This comprehensive coverage helps ensure the method behaves correctly in all cases. --- `129-141`: **Good test for ready method with CantAttack interaction** The test properly verifies that `ready()` clears the sleepy flag, except when the card has the CantAttack keyword. --- `143-157`: **Good test for setStats with maxHealth option** The test correctly verifies the behavior of `setStats` with and without changing maxHealth. --- `159-168`: **Good test for addStats** The test verifies that `addStats` correctly increases both attack and health values, including maxHealth. --- `170-180`: **Good test for remStats** The test verifies that `remStats` correctly decreases both attack and health values, including maxHealth. --- `182-192`: **Good test for addHealth with restore option** The test verifies the behavior of `addHealth` with and without the restore option: 1. With restore=true (default), it doesn't increase health beyond maxHealth 2. With restore=false, it increases both health and maxHealth --- `194-233`: **Comprehensive test for remHealth with keyword interactions** The test thoroughly verifies `remHealth` behavior with different card types and keywords: 1. Basic health reduction 2. Immunity for Location type cards 3. Immunity due to Stealth keyword 4. Interaction with Immune keyword (preventing damage while still returning true) 5. Weapon destruction when health is reduced to zero The comment on line 222 correctly reflects the behavior: "Immune prevents damage. remHealth returns true, and health remains unchanged." --- `235-242`: **Good test for resetMaxHealth** The test verifies that `resetMaxHealth` correctly sets the maxHealth value to match the current health. --- `244-252`: **Good test for setStealthDuration** The test verifies that `setStealthDuration` correctly calculates the stealth duration based on the current game turn. --- `254-271`: **Thorough test for resetAttackTimes with different keywords** The test verifies that `resetAttackTimes` correctly sets the attackTimes based on the presence of Windfury and MegaWindfury keywords: 1. Basic case: 1 attack time 2. With Windfury: 2 attack times 3. With MegaWindfury: 4 attack times 4. After removing both keywords: back to 1 attack time --- `273-285`: **Good test for setLocation with game state checking** The test verifies that `setLocation` correctly: 1. Updates the card's location 2. Adds the card to game.activeCards when location is Board 3. Removes the card from game.activeCards when location is None </details> <details> <summary>src/game.ts (9)</summary> `56-58`: **Improved taunt detection logic.** The updated code now properly filters taunt minions from the target's owner's board (or the target itself if it's a player), rather than always filtering from the opponent's board. This correctly handles all attack scenarios. --- `281-283`: **Good refactoring of forgetful attack logic.** Extracting the forgetful logic into a separate method improves code organization and modularity. This makes the code more maintainable by isolating this complex behavior. --- `497-497`: **Consistent use of destroy() instead of kill().** This change aligns with the broader codebase refactoring of renaming kill() to destroy() for better semantic clarity. --- `559-632`: **Well-structured implementation of the forgetful mechanic.** The `_forgetful` method is well-implemented with: - Clear state management for the keyword - Proper random target selection logic - Configurable failure attempts via game config - Thorough documentation This implementation properly handles the complex "Forgetful" mechanic while maintaining readable code. --- `770-771`: **Event name consistency with DestroyCard.** Event broadcasting and ability triggers now consistently use "DestroyCard" instead of "KillCard", matching the event name changes in types/event.ts and the method rename in the Card class. Also applies to: 785-785 --- `795-795`: **Fixed method name to follow proper casing convention.** The method name was corrected from `Heropower` to `HeroPower`, improving naming consistency in the codebase. --- `1328-1349`: **Added flexible input translation with additional controls.** This new method enhances the translation system by allowing developers to override the noInput flag and control the input queue usage. This provides more flexibility for user interactions. --- `1351-1360`: **Added convenience method for translated pauses.** The new `pauseTranslate` method simplifies the common pattern of translating text and then pausing the game. This improves code readability for this frequent operation. --- `1229-1229`: <details> <summary>✅ Verification successful</summary> **Changed anniversary date format - verify behavior.** The date format for the anniversary event was changed from "MM-DD" to "DD/MM" with the value "14/02". This changes when the anniversary event would trigger. Please verify this is the intended behavior for the anniversary date format change: --- 🏁 Script executed: ```shell #!/bin/bash # Check if there are any other references to the anniversary date format in the codebase rg -A 2 -B 2 "anniversary" --glob "*.ts" ``` Length of output: 1506 --- </details> **Anniversary event date format update is correct** I confirmed that in `src/game.ts` the anniversary check now uses `date.format(currentDate, "DD/MM") === "14/02"` and there are no other legacy “MM-DD” checks elsewhere in the codebase. The event will correctly fire on February 14th as intended. </details> <details> <summary>src/card.ts (6)</summary> `850-850`: **Clarified addHealth parameter documentation.** The documentation now correctly explains that the `restore` parameter controls whether health is capped at max health, rather than resetting health to max. This clarification helps prevent misuse of the method. --- `1091-1091`: **Updated bounce to use removeFromPlay.** The `bounce` method now calls `removeFromPlay()` instead of `destroy()`, which provides a more complete cleanup of the card by also silencing it and setting its location to None. --- `1097-1108`: **Renamed kill() to destroy() for consistency.** This method rename contributes to a more consistent API terminology across the codebase. The implementation logic remains the same - setting the card's health to 0 and then removing it from the board. --- `1308-1308`: **Updated discard to set location to None.** The `discard` method now properly updates the card's location state to `Location.None` upon successful discard, which ensures consistent state management. --- `1573-1638`: **Improved placeholder replacement robustness.** The `replacePlaceholders` method has been significantly enhanced with: - Proper spell damage placeholder detection using the local text variable - Type checking and clear error logging with card name context - Special markers to prevent infinite recursion - Better conditional formatting based on configuration - More consistent regex processing These changes make the placeholder system more robust against edge cases and invalid inputs. --- `1679-1679`: **Fixed card lifecycle tracking in perfectCopy.** The `perfectCopy` method now properly adds the cloned card to `game.activeCards`, ensuring it's tracked correctly for lifecycle management and operations like reloading cards. </details> </blockquote></details> </details> <!-- This is an auto-generated comment by CodeRabbit for review status -->
coderabbitai[bot] (Migrated from github.com) commented 2025-05-15 08:23:15 +00:00

💡 Verification agent

🧩 Analysis chain

Add tests for destroy() and other key methods

While the tests added are excellent, there are still several critical methods that remain as test.todo, including destroy() (the renamed kill() method) that's central to the refactoring in this PR. Consider prioritizing tests for these methods in a future PR.


🏁 Script executed:

#!/bin/bash
# Find any files that still use kill() instead of destroy()
echo "Checking for any remaining references to kill() method in card scripts:"
rg "\.kill\(\)" --type ts -i -g "cards/**" -g "src/**"

echo "Checking for any remaining references to KillCard event in card scripts:"
rg "KillCard" --type ts -i -g "cards/**" -g "src/**"

Length of output: 654


Add tests for destroy() and other key Card methods

Several critical methods in test/src/card.test.ts are still marked as test.todo, most notably the renamed destroy() (formerly kill()) and other core behaviors. Please prioritize adding specs for at least the following in your next PR:

  • destroy()
  • Attack-related methods: canAttack(), canBeAttacked(), attackTarget()
  • Board state changes: silence(), removeFromPlay(), reset(), reload()
  • Backup/restore: createBackup(), restoreBackup()
  • Enchantment handling: addEnchantment(), removeEnchantment(), applyEnchantments(), enchantmentExists()
  • Other behaviors: trigger(), manathirst(), discard(), condition(), perfectCopy(), imperfectCopy(), isAlive(), validateForDeck(), takeControl(), readable(), view()

Having focused tests for these will ensure the refactoring around destroy() and related game-state changes remains robust.

🤖 Prompt for AI Agents
In test/src/card.test.ts between lines 287 and 321, many critical methods
including destroy() and other key card behaviors are still marked as test.todo.
To fix this, prioritize writing focused unit tests for destroy() and the related
core methods such as canAttack(), canBeAttacked(), attackTarget(), silence(),
removeFromPlay(), reset(), reload(), createBackup(), restoreBackup(),
addEnchantment(), removeEnchantment(), applyEnchantments(), enchantmentExists(),
trigger(), manathirst(), discard(), condition(), perfectCopy(), imperfectCopy(),
isAlive(), validateForDeck(), takeControl(), readable(), and view() in your next
PR to ensure robust coverage of the refactored functionality.
_💡 Verification agent_ <details> <summary>🧩 Analysis chain</summary> **Add tests for destroy() and other key methods** While the tests added are excellent, there are still several critical methods that remain as `test.todo`, including `destroy()` (the renamed `kill()` method) that's central to the refactoring in this PR. Consider prioritizing tests for these methods in a future PR. --- 🏁 Script executed: ```shell #!/bin/bash # Find any files that still use kill() instead of destroy() echo "Checking for any remaining references to kill() method in card scripts:" rg "\.kill\(\)" --type ts -i -g "cards/**" -g "src/**" echo "Checking for any remaining references to KillCard event in card scripts:" rg "KillCard" --type ts -i -g "cards/**" -g "src/**" ``` Length of output: 654 --- </details> **Add tests for `destroy()` and other key Card methods** Several critical methods in `test/src/card.test.ts` are still marked as `test.todo`, most notably the renamed `destroy()` (formerly `kill()`) and other core behaviors. Please prioritize adding specs for at least the following in your next PR: - `destroy()` - Attack-related methods: `canAttack()`, `canBeAttacked()`, `attackTarget()` - Board state changes: `silence()`, `removeFromPlay()`, `reset()`, `reload()` - Backup/restore: `createBackup()`, `restoreBackup()` - Enchantment handling: `addEnchantment()`, `removeEnchantment()`, `applyEnchantments()`, `enchantmentExists()` - Other behaviors: `trigger()`, `manathirst()`, `discard()`, `condition()`, `perfectCopy()`, `imperfectCopy()`, `isAlive()`, `validateForDeck()`, `takeControl()`, `readable()`, `view()` Having focused tests for these will ensure the refactoring around `destroy()` and related game-state changes remains robust. <details> <summary>🤖 Prompt for AI Agents</summary> ``` In test/src/card.test.ts between lines 287 and 321, many critical methods including destroy() and other key card behaviors are still marked as test.todo. To fix this, prioritize writing focused unit tests for destroy() and the related core methods such as canAttack(), canBeAttacked(), attackTarget(), silence(), removeFromPlay(), reset(), reload(), createBackup(), restoreBackup(), addEnchantment(), removeEnchantment(), applyEnchantments(), enchantmentExists(), trigger(), manathirst(), discard(), condition(), perfectCopy(), imperfectCopy(), isAlive(), validateForDeck(), takeControl(), readable(), and view() in your next PR to ensure robust coverage of the refactored functionality. ``` </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!395
No description provided.