Files
OcheCompanion/REFACTORING_GAME_MANAGER.md
Alexander Doerflinger 4b8766b304 Refactored GameActivity
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.
2026-02-06 08:09:53 +01:00

9.6 KiB

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:

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:

// 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:

// 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

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

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

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

    // 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.