Skip to content

Refactor Stall Interaction and Optimize Placement Validation - #168

Merged
candour merged 2 commits into
mainfrom
refactor-stall-interaction-11767897202155615896
May 21, 2026
Merged

Refactor Stall Interaction and Optimize Placement Validation#168
candour merged 2 commits into
mainfrom
refactor-stall-interaction-11767897202155615896

Conversation

@candour

@candour candour commented May 20, 2026

Copy link
Copy Markdown
Owner

I have refactored the stall interaction logic in MainViewModel.kt to improve modularity and performance. Specifically:

  1. Modularity: onCellClick has been simplified into a router that delegates to specialized methods like handleBuildStall, handleStallSelection, and mode-specific handlers for pillar removal and outdoor puddles.
  2. Performance: validateStallPlacement now skips expensive A* pathfinding for any enemy whose current path doesn't actually pass through the coordinate of the new stall. This significantly reduces the computational overhead during stall placement.
  3. Consistency and Atomicity: sellStall and undoSell now perform their validation and state modifications entirely within the _gameState.update block, ensuring atomic state transitions and better thread safety.
  4. Idiomatic Code: The Tray Return Uncle placement rule was rewritten using any and none for better readability.

I have also updated fixes.md to reflect these improvements and marked the previous engine optimization (REF-012) as resolved. All relevant unit tests passed successfully.


PR created automatically by Jules for task 11767897202155615896 started by @candour

Summary by CodeRabbit

  • Refactor

    • Optimized stall-placement validation with improved adjacency checking and streamlined neighbor filtering.
    • Enhanced enemy pathfinding to skip redundant calculations during stall placement.
    • Reorganized board interaction handling with restructured stall selection, building, and removal logic.
    • Improved undo-sell functionality with better state validation.
  • Chores

    • Updated development documentation reflecting completed refactoring milestones.

Review Change Stack

- Extracted special mode and stall build/selection logic from onCellClick into specialized handler methods.
- Optimized validateStallPlacement to avoid redundant A* pathfinding for enemies whose paths are not intersected by new stalls.
- Improved atomicity of sellStall and undoSell by moving hex map and validation logic into _gameState.update blocks.
- Streamlined Tray Return Uncle rule check using idiomatic Kotlin.
- Updated fixes.md with REF-013 and marked REF-012 as Resolved.

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

Copy link
Copy Markdown
Contributor

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

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

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

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

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


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

@coderabbitai

coderabbitai Bot commented May 20, 2026

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@candour has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 24 minutes and 56 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d41a7300-a590-4232-94f8-64417ced46e0

📥 Commits

Reviewing files that changed from the base of the PR and between 5d1b4a5 and 48d2216.

📒 Files selected for processing (1)
  • app/src/main/java/com/messark/hawker/MainViewModel.kt
📝 Walkthrough

Walkthrough

MainViewModel stall-placement logic was refactored across validation, user interaction, and transaction flows. The Tray Return Uncle adjacency rule now uses precomputed collections with simplified predicates; enemy re-pathing skips redundant pathfinding when stalls don't intersect remaining paths. onCellClick delegates to extracted helpers. sellStall and undoSell now use early-return guards within state update blocks.

Changes

Stall Placement Validation and Interaction Refactoring

Layer / File(s) Summary
Stall placement validation rule optimization
app/src/main/java/com/messark/hawker/MainViewModel.kt
The Tray Return Uncle adjacency rule now precomputes an unclesToCheck collection and checks neighbors using simplified walkability predicates instead of iterating a trayUncles map. Enemy re-pathing validation during placement optimizes by skipping Pathfinding.findPath checks when the newly placed stall coordinate does not appear in the remaining portion of an enemy's current path.
User interaction refactoring with extracted helpers
app/src/main/java/com/messark/hawker/MainViewModel.kt
onCellClick now delegates to new helper methods—handleRemovePillarClick, handleOutdoorPuddleClick, handleStallSelection, handleBuildStall—for remove-pillar, outdoor-puddle, stall selection, and stall building behaviors, replacing prior inline branching.
Stall selling and undo flow updates
app/src/main/java/com/messark/hawker/MainViewModel.kt
sellStall computes required sale data (coordinate, tile, stall, refund) inside _gameState.update with early-return guards. undoSell is rewritten to operate entirely within _gameState.update, clearing lastSoldStall when the tile is occupied, validating refund affordability and re-placement via validateStallPlacement, and restoring state only on successful validation.
Change log documentation
fixes.md
Updated Hawker Rush change log: REF-012 status changed from In Progress to Resolved; new entry REF-013 documents stall interaction refactoring and validateStallPlacement optimization.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • candour/towerpower#165: undoSell rewrite directly addresses preventing stall restoration when an enemy occupies or approaches the target tile.
  • candour/towerpower#167: Central validateStallPlacement refactoring and undoSell path/tray-uncle rule fixes overlap with the main PR's placement validation and transaction flow changes.
  • candour/towerpower#117: TRAY_RETURN_UNCLE placement validation and enemy re-pathing logic changes are directly connected to this PR's prior placement and enemy-release refactoring.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: refactoring stall interaction logic and optimizing placement validation, which aligns with the key objectives and file-level changes in the PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor-stall-interaction-11767897202155615896

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

❤️ Share

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

@github-actions

Copy link
Copy Markdown

Build Successful! 🚀

Download APK

Note: This link will be removed when the PR is closed.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
app/src/main/java/com/messark/hawker/MainViewModel.kt (1)

1111-1134: 💤 Low value

Consider moving validation inside _gameState.update for consistency.

handleBuildStall performs gold validation and validateStallPlacement checks against currentState captured outside the update block, then applies modifications using state inside. This creates a potential TOCTOU window and is inconsistent with the atomic pattern applied to sellStall and undoSell.

On single-threaded Compose UI this is unlikely to cause issues, but aligning all stall operations to the same atomic pattern would improve consistency.

♻️ Suggested refactor to match sellStall/undoSell pattern
 private fun handleBuildStall(coord: AxialCoordinate, tile: HexTile, currentState: GameState) {
-    val stallToPlace = currentState.selectedStallType ?: return
-    if (currentState.gold >= stallToPlace.cost && (tile.type == TileType.FLOOR || tile.type == TileType.DRAIN)) {
-        val blocked = validateStallPlacement(coord, stallToPlace, currentState)
-
-        if (blocked != null) {
-            val newHexes = currentState.hexes.toMutableMap()
-            newHexes[coord] = tile.copy(
-                stall = stallToPlace.copy(id = UUID.randomUUID().toString()),
-                isPermanentlyWet = false
-            )
-
-            _gameState.update { state ->
-                val updatedEnemies = recalculateEnemyPaths(state, blocked, newHexes)
-                state.copy(
-                    hexes = newHexes,
-                    gold = state.gold - stallToPlace.cost,
-                    enemies = updatedEnemies,
-                    lastSoldStall = null
-                )
-            }
-        }
-    }
+    _gameState.update { state ->
+        val stallToPlace = state.selectedStallType ?: return@update state
+        val currentTile = state.hexes[coord] ?: return@update state
+        if (state.gold < stallToPlace.cost) return@update state
+        if (currentTile.type != TileType.FLOOR && currentTile.type != TileType.DRAIN) return@update state
+
+        val blocked = validateStallPlacement(coord, stallToPlace, state) ?: return@update state
+
+        val newHexes = state.hexes.toMutableMap()
+        newHexes[coord] = currentTile.copy(
+            stall = stallToPlace.copy(id = UUID.randomUUID().toString()),
+            isPermanentlyWet = false
+        )
+
+        val updatedEnemies = recalculateEnemyPaths(state, blocked, newHexes)
+        state.copy(
+            hexes = newHexes,
+            gold = state.gold - stallToPlace.cost,
+            enemies = updatedEnemies,
+            lastSoldStall = null
+        )
+    }
 }

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 34b45b13-ac30-47b7-8c2b-05b9a391cc30

📥 Commits

Reviewing files that changed from the base of the PR and between d592660 and 5d1b4a5.

📒 Files selected for processing (2)
  • app/src/main/java/com/messark/hawker/MainViewModel.kt
  • fixes.md

Comment thread fixes.md
@candour
candour enabled auto-merge (squash) May 20, 2026 22:18
@candour

candour commented May 20, 2026

Copy link
Copy Markdown
Owner Author

Date appears to be incorrect.
REF-013 is dated "2026-07-10" which is in the future relative to the PR creation date (May 20, 2026). This should likely be "2026-05-20".

@candour

candour commented May 20, 2026

Copy link
Copy Markdown
Owner Author

Consider moving validation inside _gameState.update for consistency.

handleBuildStall performs gold validation and validateStallPlacement checks against currentState captured outside the update block, then applies modifications using state inside. This creates a potential TOCTOU window and is inconsistent with the atomic pattern applied to sellStall and undoSell.

On single-threaded Compose UI this is unlikely to cause issues, but aligning all stall operations to the same atomic pattern would improve consistency.

@google-labs-jules

Copy link
Copy Markdown
Contributor

Consider moving validation inside _gameState.update for consistency.

handleBuildStall performs gold validation and validateStallPlacement checks against currentState captured outside the update block, then applies modifications using state inside. This creates a potential TOCTOU window and is inconsistent with the atomic pattern applied to sellStall and undoSell.

On single-threaded Compose UI this is unlikely to cause issues, but aligning all stall operations to the same atomic pattern would improve consistency.

Good point! I've refactored handleBuildStall to perform all validation and hex map updates inside the _gameState.update block, ensuring it follows the same atomic pattern as sellStall and undoSell.

- Extracted special mode and stall build/selection logic from onCellClick into specialized handler methods.
- Optimized validateStallPlacement to avoid redundant A* pathfinding for enemies whose paths are not intersected by new stalls.
- Improved atomicity of handleBuildStall, sellStall and undoSell by moving all validation and hex map updates inside _gameState.update blocks.
- Streamlined Tray Return Uncle rule check using idiomatic Kotlin.
- Updated fixes.md with REF-013 and marked REF-012 as Resolved.

Co-authored-by: candour <4670475+candour@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown

Build Successful! 🚀

Download APK

Note: This link will be removed when the PR is closed.

@candour
candour merged commit 5ccf6eb into main May 21, 2026
3 checks passed
@candour
candour deleted the refactor-stall-interaction-11767897202155615896 branch May 21, 2026 01:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant