Skip to content

Different approach to refactor - #69

Closed
gundalow wants to merge 2 commits into
candour:mainfrom
gundalow:feature/refactor-stall-enemy-data-constants
Closed

Different approach to refactor#69
gundalow wants to merge 2 commits into
candour:mainfrom
gundalow:feature/refactor-stall-enemy-data-constants

Conversation

@gundalow

@gundalow gundalow commented Apr 21, 2026

Copy link
Copy Markdown
Collaborator

Refactor: Decouple stall/enemy data and logic, centralize constants

This commit finalizes the refactoring effort by:

  • Extracting stall and enemy configuration data into StallData.kt and EnemyData.kt.
  • Moving associated logic into StallActionHandler.kt and EnemyBehaviorHandler.kt.
  • Centralizing global game parameters and type-specific constants into GameConstants, EnemyConstants, StallConstants, and StallUpgradeCategory enum.
  • Removing the obsolete Registry.kt file and unused imports.
  • Updating MainViewModel.kt and logic handlers to utilize the new structures and constants.

These changes improve code organization, readability, and maintainability by separating data from logic and making game balance parameters more accessible for tuning.

Summary by CodeRabbit

  • Refactor
    • Restructured game architecture from registry-based to data-driven system for improved maintainability
    • Centralized game balance parameters and configuration into dedicated data objects
    • Reorganized internal logic handlers for enemy and stall behavior

This commit finalizes the refactoring effort by:
- Extracting stall and enemy configuration data into  and .
- Moving associated logic into  and .
- Centralizing global game parameters and type-specific constants into , , , and  enum.
- Removing the obsolete  file and unused imports.
- Updating  and logic handlers to utilize the new structures and constants.

These changes improve code organization, readability, and maintainability by separating data from logic and making game balance parameters more accessible for tuning.
This commit finalizes the refactoring effort by:
- Extracting stall and enemy configuration data into  and .
- Moving associated logic into  and .
- Centralizing global game parameters and type-specific constants into , , , and  enum.
- Removing the obsolete  file and unused imports.
- Updating  and logic handlers to utilize the new structures and constants.

These changes improve code organization, readability, and maintainability by separating data from logic and making game balance parameters more accessible for tuning.
@coderabbitai

coderabbitai Bot commented Apr 21, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR refactors the game architecture from a registry-based pattern to a data-driven pattern. It removes Registry.kt (containing StallRegistry, EnemyRegistry, and game logic) and replaces it with StallData.kt and EnemyData.kt for configuration, plus StallActionHandler and EnemyBehaviorHandler for logic. Additionally, GameConstants is introduced in MainViewModel.kt to centralize tuning parameters, and gameplay logic throughout is updated to use the new handlers and data structures.

Changes

Cohort / File(s) Summary
Configuration Data
app/src/main/java/com/messark/hawkerrush/ui/constants/StallData.kt, app/src/main/java/com/messark/hawkerrush/ui/constants/EnemyData.kt
New files defining StallConstants, StallConfig, StallUpgradeCategory, EnemyConstants, EnemyConfig, and corresponding config maps (StallData.configs, EnemyData.configs) for centralized game balancing and static enemy/stall attributes.
Behavior Handlers
app/src/main/java/com/messark/hawkerrush/logic/StallActionHandler.kt, app/src/main/java/com/messark/hawkerrush/logic/EnemyBehaviorHandler.kt
New files encapsulating gameplay logic: StallActionHandler handles stall firing (fire), damage modifiers, freeze durations, upgrade benefits, and stall instantiation; EnemyBehaviorHandler handles special enemy behavior updates, puddle slow multipliers, HP scaling per wave, and enemy instantiation.
Registry Removal
app/src/main/java/com/messark/hawkerrush/registry/Registry.kt
Deleted file containing StallRegistry, EnemyRegistry, StallDefinition, EnemyDefinition, and FireResult sealed class that encapsulated the same logic now distributed across handlers and data classes.
ViewModel Refactoring
app/src/main/java/com/messark/hawkerrush/MainViewModel.kt
Added GameConstants object with tuning parameters (tick duration, spawn interval, budget multipliers, speed boost, puddle threshold); replaced registry-based stall/enemy lookups with StallData.configs and EnemyData.configs access; updated stall creation to use StallActionHandler.createStallInstance, enemy HP calculation to use EnemyBehaviorHandler.getEnemyHpForWave, stall firing to use StallActionHandler.fire, and projectile targeting to derive from handlers instead of registry definitions.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • Refactor Stall and Enemy Registries #67: Centralizes firing, damage modifiers, HP scaling, and factory helpers into registry objects—this PR achieves the same logic extraction by removing Registry.kt and introducing StallActionHandler/EnemyBehaviorHandler plus data-driven StallData/EnemyData.
  • Implement Legendary Stall Naming System #61: Modifies stall naming/upgrade flow with legendary prefix/suffix and naming category fields in the Stall model and MainViewModel upgrade logic—this PR updates those same Stall instantiation points via the new StallActionHandler.createStallInstance.
  • Add Stall Info Overlay with Signature Moves #64: Updates stall tutorial handling in MainViewModel to lookup from a tutorial data source—this PR shifts those lookups from StallRegistry to StallData.configs while introducing the handler pattern.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 74.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The PR title 'Different approach to refactor' is vague and generic, using non-descriptive language that fails to convey the specific refactoring work being performed. Use a more descriptive title that captures the main refactoring objective, such as 'Extract stall and enemy configuration data into dedicated constants files' or 'Refactor registry-based game logic into data-driven handlers'.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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

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

@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: 6

🧹 Nitpick comments (5)
app/src/main/java/com/messark/hawkerrush/ui/constants/EnemyData.kt (1)

3-4: Unused imports.

Color and IntRect are imported but not used in this file. They may have been carried over from another file during refactoring.

♻️ Remove unused imports
 package com.messark.hawkerrush.ui.constants
 
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.unit.IntRect
 import com.messark.hawkerrush.model.*
app/src/main/java/com/messark/hawkerrush/ui/constants/StallData.kt (1)

68-68: Type consistency: Use Long literal for fireRateMs.

For consistency with other Long values in the file (e.g., line 53: 1000L), consider using 1500L instead of 1500. Same applies to lines 89, 103, and 119.

♻️ Use Long literals consistently
-            fireRateMs = 1500,
+            fireRateMs = 1500L,
app/src/main/java/com/messark/hawkerrush/logic/EnemyBehaviorHandler.kt (2)

57-67: Use explicit locale for String.format to ensure consistent formatting.

String.format("%.1f", ...) uses the device's default locale, which may produce inconsistent decimal separators (e.g., "0.5" vs "0,5") across regions.

♻️ Use explicit Locale
+import java.util.Locale
+
 // In getUpgradeBenefit function:
-                "+${String.format("%.1f", currentRange - stallConfig.range)}"
+                "+${String.format(Locale.US, "%.1f", currentRange - stallConfig.range)}"
 // ...
-                "+${String.format("%.1f", currentRadius - stallConfig.aoeRadius)}"
+                "+${String.format(Locale.US, "%.1f", currentRadius - stallConfig.aoeRadius)}"

277-283: Unused enemyConfig parameter.

The enemyConfig parameter is never used in this function. Since enemyType is passed separately, consider removing the unused parameter or deriving enemyType from enemyConfig.type.

♻️ Remove unused parameter
-    fun getPuddleSlowMultiplier(enemyConfig: EnemyConfig, enemyType: EnemyType): Float {
+    fun getPuddleSlowMultiplier(enemyType: EnemyType): Float {
         return when (enemyType) {
             EnemyType.DELIVERY_RIDER -> 0.2f
             EnemyType.AUNTIE -> 0.8f
             else -> 0.6f
         }
     }

Then update the caller in MainViewModel.kt line 428:

-                    speedMultiplier = EnemyBehaviorHandler.getPuddleSlowMultiplier(enemyConfig, enemy.type)
+                    speedMultiplier = EnemyBehaviorHandler.getPuddleSlowMultiplier(enemy.type)
app/src/main/java/com/messark/hawkerrush/logic/StallActionHandler.kt (1)

6-7: Unused import: EnemyData.

EnemyData is imported but not used in StallActionHandler. This is dead code from the refactoring.

♻️ Remove unused import
 import com.messark.hawkerrush.ui.constants.StallData
-import com.messark.hawkerrush.ui.constants.EnemyData
 import java.util.*

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5dda3dbb-78ed-4786-9c83-9c9e09281cfb

📥 Commits

Reviewing files that changed from the base of the PR and between 003c0f1 and 9b450c6.

📒 Files selected for processing (6)
  • app/src/main/java/com/messark/hawkerrush/MainViewModel.kt
  • app/src/main/java/com/messark/hawkerrush/logic/EnemyBehaviorHandler.kt
  • app/src/main/java/com/messark/hawkerrush/logic/StallActionHandler.kt
  • app/src/main/java/com/messark/hawkerrush/registry/Registry.kt
  • app/src/main/java/com/messark/hawkerrush/ui/constants/EnemyData.kt
  • app/src/main/java/com/messark/hawkerrush/ui/constants/StallData.kt
💤 Files with no reviewable changes (1)
  • app/src/main/java/com/messark/hawkerrush/registry/Registry.kt

Comment on lines +1 to +12
package com.messark.hawkerrush.logic

import androidx.compose.ui.graphics.Color
import com.messark.hawkerrush.model.*
import com.messark.hawkerrush.registry.FireResult
import com.messark.hawkerrush.ui.constants.StallData
import com.messark.hawkerrush.ui.constants.EnemyData
import com.messark.hawkerrush.ui.constants.EnemyConstants
import com.messark.hawkerrush.ui.constants.GameConstants // Import GameConstants
import com.messark.hawkerrush.ui.constants.StallConstants
import com.messark.hawkerrush.ui.constants.StallUpgradeCategory
import java.util.*

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.

⚠️ Potential issue | 🔴 Critical

File contains both handlers but should only have EnemyBehaviorHandler.

This file (EnemyBehaviorHandler.kt) contains both StallActionHandler and EnemyBehaviorHandler. Based on the file list, there's a separate StallActionHandler.kt file. Having duplicate definitions will cause compilation errors due to duplicate class declarations.

Additionally, line 9 imports GameConstants from ui.constants, but GameConstants is defined in MainViewModel.kt (line 21-32). This import path is incorrect and will cause a compilation error.

🐛 Remove StallActionHandler and fix import

Either:

  1. Remove StallActionHandler from this file (keep only EnemyBehaviorHandler), OR
  2. Move GameConstants to ui.constants package
 package com.messark.hawkerrush.logic
 
 import androidx.compose.ui.graphics.Color
 import com.messark.hawkerrush.model.*
 import com.messark.hawkerrush.registry.FireResult
 import com.messark.hawkerrush.ui.constants.StallData
 import com.messark.hawkerrush.ui.constants.EnemyData
 import com.messark.hawkerrush.ui.constants.EnemyConstants
-import com.messark.hawkerrush.ui.constants.GameConstants // Import GameConstants
+import com.messark.hawkerrush.GameConstants // GameConstants is in MainViewModel.kt
 import com.messark.hawkerrush.ui.constants.StallConstants
 import com.messark.hawkerrush.ui.constants.StallUpgradeCategory
 import java.util.*
📝 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.

Suggested change
package com.messark.hawkerrush.logic
import androidx.compose.ui.graphics.Color
import com.messark.hawkerrush.model.*
import com.messark.hawkerrush.registry.FireResult
import com.messark.hawkerrush.ui.constants.StallData
import com.messark.hawkerrush.ui.constants.EnemyData
import com.messark.hawkerrush.ui.constants.EnemyConstants
import com.messark.hawkerrush.ui.constants.GameConstants // Import GameConstants
import com.messark.hawkerrush.ui.constants.StallConstants
import com.messark.hawkerrush.ui.constants.StallUpgradeCategory
import java.util.*
package com.messark.hawkerrush.logic
import androidx.compose.ui.graphics.Color
import com.messark.hawkerrush.model.*
import com.messark.hawkerrush.registry.FireResult
import com.messark.hawkerrush.ui.constants.StallData
import com.messark.hawkerrush.ui.constants.EnemyData
import com.messark.hawkerrush.ui.constants.EnemyConstants
import com.messark.hawkerrush.GameConstants
import com.messark.hawkerrush.ui.constants.StallConstants
import com.messark.hawkerrush.ui.constants.StallUpgradeCategory
import java.util.*

Comment on lines +96 to +98
private fun getUpgradeDamageIncrease(baseDamage: Int): Int {
return (baseDamage * 0.3f).toInt() + 2
}

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.

⚠️ Potential issue | 🔴 Critical

Critical: getUpgradeDamageIncrease is private but called from MainViewModel.

MainViewModel.kt (lines 816, 881) calls StallActionHandler.getUpgradeDamageIncrease(...), but this function is declared private. This will cause a compilation error.

🐛 Change visibility to internal or public
-    private fun getUpgradeDamageIncrease(baseDamage: Int): Int {
+    fun getUpgradeDamageIncrease(baseDamage: Int): Int {
         return (baseDamage * 0.3f).toInt() + 2
     }
📝 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.

Suggested change
private fun getUpgradeDamageIncrease(baseDamage: Int): Int {
return (baseDamage * 0.3f).toInt() + 2
}
fun getUpgradeDamageIncrease(baseDamage: Int): Int {
return (baseDamage * 0.3f).toInt() + 2
}

Comment on lines +244 to +321
/**
* Handles actions and logic related to enemies.
*/
object EnemyBehaviorHandler {

/**
* Updates enemy behavior, including special actions like stopping for tourists.
* This logic was previously in EnemyDefinition.updateSpecialBehavior().
*/
fun updateSpecialBehavior(enemyConfig: EnemyConfig, enemy: Enemy, currentTimeMs: Long): Enemy {
if (enemyConfig.type == EnemyType.TOURIST) {
var isStopped = enemy.isStopped
var stopDurationMs = enemy.stopDurationMs
var lastStopMs = enemy.lastStopMs

if (isStopped) {
stopDurationMs -= 32 // Game tick duration
if (stopDurationMs <= 0) {
isStopped = false
lastStopMs = currentTimeMs
}
} else if (currentTimeMs - lastStopMs > 8000) { // Cooldown before stopping again
isStopped = true
stopDurationMs = 2000L // Duration of the stop
}
return enemy.copy(isStopped = isStopped, stopDurationMs = stopDurationMs, lastStopMs = lastStopMs)
}
return enemy
}

/**
* Calculates the slow multiplier for enemies when they are in a puddle.
* This logic was previously in EnemyDefinition.getPuddleSlowMultiplier().
*/
fun getPuddleSlowMultiplier(enemyConfig: EnemyConfig, enemyType: EnemyType): Float {
return when (enemyType) {
EnemyType.DELIVERY_RIDER -> 0.2f
EnemyType.AUNTIE -> 0.8f
else -> 0.6f
}
}

/**
* Calculates the current HP of an enemy based on the wave number.
* This logic was previously in EnemyDefinition.getHp().
*/
fun getEnemyHpForWave(enemyConfig: EnemyConfig, wave: Int): Int {
return (enemyConfig.baseHp * Math.pow(1.1, (wave - 1).toDouble())).toInt()
}

/**
* Creates an Enemy instance from its configuration.
* This logic was previously in EnemyDefinition.toEnemy().
*/
fun createEnemyInstance(
enemyConfig: EnemyConfig,
id: String = UUID.randomUUID().toString(),
wave: Int,
position: PreciseAxialCoordinate,
path: List<AxialCoordinate>,
isFacingLeft: Boolean
): Enemy {
val hp = getEnemyHpForWave(enemyConfig, wave)
return Enemy(
id = id,
type = enemyConfig.type,
health = hp,
maxHealth = hp,
position = position,
baseSpeed = enemyConfig.baseSpeed,
currentSpeed = enemyConfig.baseSpeed,
path = path,
currentPathIndex = 0,
reward = enemyConfig.reward,
isFacingLeft = isFacingLeft
)
}
}

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.

⚠️ Potential issue | 🔴 Critical

Critical: Duplicate EnemyBehaviorHandler definition.

EnemyBehaviorHandler is defined in both StallActionHandler.kt (lines 244-321) and EnemyBehaviorHandler.kt. This will cause a duplicate class compilation error. Additionally, this version uses hardcoded values (lines 260, 265-267, 291) while EnemyBehaviorHandler.kt uses constants.

🐛 Remove the duplicate EnemyBehaviorHandler from this file

Remove lines 244-321 entirely. EnemyBehaviorHandler should only exist in EnemyBehaviorHandler.kt.

     }
 }
-
-/**
- * Handles actions and logic related to enemies.
- */
-object EnemyBehaviorHandler {
-    // ... entire object removed ...
-}

Comment on lines 573 to 575
val dq = targetPos.q - proj.position.q
val dr = targetPos.r - proj.position.r
val dr = target.position.r - proj.position.r
val dist = axialDistance(proj.position, targetPos)

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.

⚠️ Potential issue | 🔴 Critical

Critical: Undefined variable target causing compilation error.

Line 574 references target which is not defined in this scope. Based on the context and the pattern on line 573 (targetPos.q), this should be targetPos.r.

🐛 Fix the undefined variable reference
             val dq = targetPos.q - proj.position.q
-            val dr = target.position.r - proj.position.r
+            val dr = targetPos.r - proj.position.r
             val dist = axialDistance(proj.position, targetPos)

Comment on lines +734 to +736
// Use StallActionHandler to create stall instance
val stallConfig = StallData.configs[stallToPlace.stallType] ?: return@update // Should not happen
newHexes[coord] = tile.copy(stall = StallActionHandler.createStallInstance(stallConfig))

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.

⚠️ Potential issue | 🔴 Critical

Critical: Invalid return@update label reference.

Line 735-736 uses return@update but this code is not inside the _gameState.update lambda (which starts at line 738). This will cause a compilation error.

🐛 Fix the return statement
                         // Use StallActionHandler to create stall instance
-                        val stallConfig = StallData.configs[stallToPlace.stallType] ?: return@update // Should not happen
+                        val stallConfig = StallData.configs[stallToPlace.stallType] ?: return
                         newHexes[coord] = tile.copy(stall = StallActionHandler.createStallInstance(stallConfig))
📝 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.

Suggested change
// Use StallActionHandler to create stall instance
val stallConfig = StallData.configs[stallToPlace.stallType] ?: return@update // Should not happen
newHexes[coord] = tile.copy(stall = StallActionHandler.createStallInstance(stallConfig))
// Use StallActionHandler to create stall instance
val stallConfig = StallData.configs[stallToPlace.stallType] ?: return
newHexes[coord] = tile.copy(stall = StallActionHandler.createStallInstance(stallConfig))

Comment on lines +75 to +80
reward = 100,
spriteRow = 3
)
)
}

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.

⚠️ Potential issue | 🔴 Critical

Critical: Duplicate code causing syntax error.

Lines 75-80 are an exact duplicate of lines 69-74. This will cause a compilation error due to duplicate closing braces and repeated map entries.

🐛 Remove the duplicate lines
         )
     )
 }
-           reward = 100,
-            spriteRow = 3
-        )
-    )
-}
 
📝 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.

Suggested change
reward = 100,
spriteRow = 3
)
)
}
reward = 100,
spriteRow = 3
)
)
}

@candour candour closed this May 2, 2026
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.

2 participants