Refactored GameActivity to make use of the newly introduced GameManager to centralize the Game logic into one class and shrink the GameActivity to the relevant parts.
271 lines
9.6 KiB
Markdown
271 lines
9.6 KiB
Markdown
# GameActivity Refactoring Summary
|
|
|
|
## Overview
|
|
Successfully refactored the `GameActivity` to extract all business logic into a new singleton class called `GameManager`. This refactoring significantly improves code organization, maintainability, and testability.
|
|
|
|
## What Was Changed
|
|
|
|
### 1. Created New `GameManager` Singleton Class
|
|
**Location:** `app/src/main/java/com/aldo/apps/ochecompanion/game/GameManager.java`
|
|
|
|
The `GameManager` class now handles:
|
|
- **Match Initialization & Loading**: Automatically loads ongoing matches from the database or creates new ones
|
|
- **Game State Management**: Tracks all player states, scores, current turn, darts thrown, etc.
|
|
- **Game Rules & Logic**: Implements bust detection, double-out enforcement, win conditions
|
|
- **Database Operations**: All save/load operations for match progress and statistics
|
|
- **Statistics Tracking**: Player stats, double attempts, dart hit distributions
|
|
- **Match Persistence**: Replaces `onSaveInstanceState` - the singleton persists across configuration changes
|
|
|
|
### 2. Defined `GameStateCallback` Interface
|
|
The callback interface enables clean separation between business logic and UI:
|
|
```java
|
|
public interface GameStateCallback {
|
|
void onGameStateChanged(); // General UI refresh needed
|
|
void onTurnIndicatorsChanged(); // Update dart pills display
|
|
void onMultiplierChanged(int m); // Update multiplier buttons
|
|
void onBust(); // Bust animation/sound
|
|
void onPlayerWin(PlayerState w, int checkout); // Win celebration
|
|
void onOneEightyScored(); // 180 celebration
|
|
void onResetVisuals(); // Clear bust/error visuals
|
|
}
|
|
```
|
|
|
|
### 3. Refactored `GameActivity`
|
|
**Before:** 1298 lines
|
|
**After:** 680 lines
|
|
**Reduction:** 47.6% fewer lines!
|
|
|
|
#### What Was Removed:
|
|
- ❌ All game state variables (`mPlayerStates`, `mActivePlayerIndex`, `mMultiplier`, etc.)
|
|
- ❌ `onSaveInstanceState()` and `onRestoreInstanceState()` methods
|
|
- ❌ `setupGame()` method
|
|
- ❌ `loadMatchProgress()` method
|
|
- ❌ `saveMatchProgress()` method
|
|
- ❌ `saveCompletedMatch()` method
|
|
- ❌ `updatePlayerStats()` methods
|
|
- ❌ `trackDoubleAttempt()` method
|
|
- ❌ `incrementMatchesPlayed()` method
|
|
- ❌ `recordTurnHitsToStatistics()` method
|
|
- ❌ Inner `X01State` class
|
|
- ❌ Inner `DartHit` class
|
|
- ❌ Complex match loading logic in `onCreate()`
|
|
|
|
#### What Remains (UI Only):
|
|
- ✅ UI component initialization and references
|
|
- ✅ View setup and event listeners
|
|
- ✅ Visual effects (animations, vibrations, sounds)
|
|
- ✅ UI update methods that read from `GameManager`
|
|
- ✅ Callback method implementations
|
|
- ✅ Checkout suggestion display
|
|
- ✅ Turn indicator (dart pills) updates
|
|
|
|
### 4. Key Architectural Improvements
|
|
|
|
#### Before:
|
|
```java
|
|
// GameActivity handled everything
|
|
public class GameActivity extends BaseActivity {
|
|
private int mActivePlayerIndex;
|
|
private List<X01State> mPlayerStates;
|
|
private List<Integer> mCurrentTurnDarts;
|
|
// ... dozens more state variables
|
|
|
|
@Override
|
|
protected void onCreate(Bundle savedInstanceState) {
|
|
// 100+ lines of complex loading logic
|
|
if (savedInstanceState == null) {
|
|
new Thread(() -> {
|
|
// Load players
|
|
// Check if match exists
|
|
// Load progress
|
|
// Create new match
|
|
// etc...
|
|
}).start();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onSaveInstanceState(Bundle outState) {
|
|
// Manually save all game state
|
|
outState.putInt("activePlayerIndex", ...);
|
|
outState.putIntArray("currentTurnDarts", ...);
|
|
// ... many more lines
|
|
}
|
|
}
|
|
```
|
|
|
|
#### After:
|
|
```java
|
|
// GameActivity is now just a UI controller
|
|
public class GameActivity extends BaseActivity
|
|
implements GameManager.GameStateCallback {
|
|
|
|
private GameManager mGameManager;
|
|
|
|
@Override
|
|
protected void onCreate(Bundle savedInstanceState) {
|
|
// Simple initialization
|
|
mGameManager = GameManager.getInstance(this);
|
|
mGameManager.setCallback(this);
|
|
|
|
// One line to initialize/load match
|
|
mGameManager.initializeMatch(matchId, startingScore, () -> {
|
|
runOnUiThread(() -> updateUI());
|
|
});
|
|
}
|
|
|
|
// No onSaveInstanceState needed!
|
|
// GameManager singleton persists across config changes
|
|
|
|
@Override
|
|
public void onGameStateChanged() {
|
|
updateUI(); // Just update the display
|
|
}
|
|
}
|
|
```
|
|
|
|
## Benefits of This Refactoring
|
|
|
|
### 1. **Separation of Concerns**
|
|
- **GameManager**: Pure business logic, no Android dependencies beyond Context
|
|
- **GameActivity**: Pure UI controller, no game logic
|
|
|
|
### 2. **Easier Testing**
|
|
- Business logic can now be unit tested independently
|
|
- Mock the callback interface to test GameManager
|
|
- Test UI separately without complex game state setup
|
|
|
|
### 3. **No More Configuration Change Issues**
|
|
- Singleton pattern means state survives screen rotations automatically
|
|
- No need for `onSaveInstanceState` / `onRestoreInstanceState`
|
|
- Match progress is always saved to database, never lost
|
|
|
|
### 4. **Improved Maintainability**
|
|
- Clear boundaries between layers
|
|
- Single Responsibility Principle enforced
|
|
- Easier to find and fix bugs
|
|
- New features can be added to GameManager without touching UI
|
|
|
|
### 5. **Better Code Reusability**
|
|
- GameManager can be used from other Activities/Fragments
|
|
- Game logic is centralized in one place
|
|
- Future features (e.g., match history viewer) can read from GameManager
|
|
|
|
### 6. **Cleaner Data Flow**
|
|
```
|
|
User Input → GameActivity → GameManager.onNumberTap()
|
|
↓
|
|
[Process Game Logic]
|
|
↓
|
|
GameStateCallback.onGameStateChanged()
|
|
↓
|
|
GameActivity.updateUI()
|
|
```
|
|
|
|
## API Overview
|
|
|
|
### GameManager Public Methods
|
|
|
|
#### Initialization
|
|
```java
|
|
GameManager.getInstance(Context) // Get singleton instance
|
|
void setCallback(GameStateCallback) // Register UI callback
|
|
void initializeMatch(int matchId, int startScore, Runnable onComplete)
|
|
void resetGame() // Clear state for new match
|
|
```
|
|
|
|
#### Game Actions
|
|
```java
|
|
void onNumberTap(int baseValue) // Process dart throw
|
|
void submitTurn() // End turn, advance player
|
|
void undoLastDart() // Remove last dart
|
|
void setMultiplier(int multiplier) // Set 1x/2x/3x
|
|
```
|
|
|
|
#### State Getters
|
|
```java
|
|
PlayerState getActivePlayer() // Current player
|
|
List<PlayerState> getPlayerStates() // All players
|
|
List<Integer> getCurrentTurnDarts() // Darts in current turn
|
|
int getCurrentTarget() // Score after current darts
|
|
int getDartsRemainingInTurn() // 0-3 darts left
|
|
boolean isTurnOver() // Turn complete?
|
|
boolean isBustedTurn() // Current turn bust?
|
|
boolean isMatchCompleted() // Match finished?
|
|
```
|
|
|
|
## Migration Guide (For Future Reference)
|
|
|
|
If you need to add new game features:
|
|
|
|
1. **Add business logic to GameManager**
|
|
- Add new methods to handle the logic
|
|
- Update game state
|
|
- Call appropriate callback methods
|
|
|
|
2. **Add UI response in GameActivity**
|
|
- Implement any new callback methods
|
|
- Update UI based on GameManager getters
|
|
|
|
3. **Example: Adding "Undo Turn" Feature**
|
|
```java
|
|
// In GameManager:
|
|
public void undoTurn() {
|
|
if (turnHistory.isEmpty()) return;
|
|
// Restore previous state
|
|
notifyGameStateChanged();
|
|
}
|
|
|
|
// In GameActivity:
|
|
findViewById(R.id.btnUndoTurn)
|
|
.setOnClickListener(v -> mGameManager.undoTurn());
|
|
```
|
|
|
|
## Files Changed
|
|
|
|
### Created:
|
|
- ✨ `app/src/main/java/com/aldo/apps/ochecompanion/game/GameManager.java` (665 lines)
|
|
|
|
### Modified:
|
|
- 🔧 `app/src/main/java/com/aldo/apps/ochecompanion/GameActivity.java` (1298 → 680 lines, -47.6%)
|
|
|
|
### Total Impact:
|
|
- **Lines of Code**: Net addition of 47 lines (665 new - 618 removed)
|
|
- **Maintainability**: Significantly improved
|
|
- **Testability**: Dramatically improved
|
|
- **Architecture**: Clean separation of concerns achieved
|
|
|
|
## Testing Recommendations
|
|
|
|
### Unit Tests for GameManager
|
|
- Test match initialization with/without existing data
|
|
- Test dart scoring logic (bust, double-out, valid throws)
|
|
- Test turn submission and player rotation
|
|
- Test win condition detection
|
|
- Test statistics tracking
|
|
- Test state persistence
|
|
|
|
### Integration Tests for GameActivity
|
|
- Test UI updates on game state changes
|
|
- Test callback invocations
|
|
- Test animations and visual feedback
|
|
- Test multiplier UI updates
|
|
- Test checkout suggestions
|
|
|
|
### Manual Testing Checklist
|
|
- [ ] Start new match - verify it creates in database
|
|
- [ ] Resume existing match - verify state restored
|
|
- [ ] Rotate screen - verify no state loss
|
|
- [ ] Throw darts - verify UI updates
|
|
- [ ] Hit bust - verify visual feedback
|
|
- [ ] Score 180 - verify celebration
|
|
- [ ] Win match - verify win screen
|
|
- [ ] Check statistics - verify accurate tracking
|
|
- [ ] Use undo - verify state restored correctly
|
|
|
|
## Conclusion
|
|
|
|
This refactoring successfully decouples business logic from UI presentation in the GameActivity. The new `GameManager` singleton provides a clean, testable, and maintainable architecture that follows SOLID principles and Android best practices. The 47.6% reduction in GameActivity size demonstrates the effectiveness of this separation of concerns.
|
|
|
|
The singleton pattern eliminates the need for `onSaveInstanceState` while maintaining state across configuration changes. All match persistence is now handled transparently by GameManager through database operations, making the code more robust and reliable.
|