Fixed the Group Standing view
The group standing view had an issue with not being able to display participant data correctly due to multiple issue and data not being available, not it is available and working properly
This commit is contained in:
@@ -23,13 +23,14 @@ import com.aldo.apps.ochecompanion.ui.MatchRecapView;
|
|||||||
import com.aldo.apps.ochecompanion.ui.QuickStartButton;
|
import com.aldo.apps.ochecompanion.ui.QuickStartButton;
|
||||||
import com.aldo.apps.ochecompanion.ui.adapter.MainMenuPlayerAdapter;
|
import com.aldo.apps.ochecompanion.ui.adapter.MainMenuPlayerAdapter;
|
||||||
import com.aldo.apps.ochecompanion.utils.DartsConstants;
|
import com.aldo.apps.ochecompanion.utils.DartsConstants;
|
||||||
import com.aldo.apps.ochecompanion.utils.UIConstants;
|
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main entry point and home screen of the Oche Companion application.
|
* Main entry point and home screen of the Oche Companion application.
|
||||||
* Displays the squad of players, allows adding new players, and shows match recap with test data.
|
* Displays the squad of players, allows adding new players, and shows match
|
||||||
|
* recap.
|
||||||
*/
|
*/
|
||||||
public class MainMenuActivity extends BaseActivity {
|
public class MainMenuActivity extends BaseActivity {
|
||||||
|
|
||||||
@@ -40,67 +41,61 @@ public class MainMenuActivity extends BaseActivity {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom view component that displays a match summary.
|
* Custom view component that displays a match summary.
|
||||||
* Can be clicked to cycle through different test data states.
|
|
||||||
*/
|
*/
|
||||||
private MatchRecapView mMatchRecap;
|
private MatchRecapView mMatchRecap;
|
||||||
|
|
||||||
/**
|
|
||||||
* Counter for cycling through different test data scenarios.
|
|
||||||
* Increments on each click to cycle through null match, 1v1 match, and group match states.
|
|
||||||
*/
|
|
||||||
private int mTestCounter = 0;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link SharedPreferences} containing the currently selected settings.
|
* The {@link SharedPreferences} containing the currently selected settings.
|
||||||
*/
|
*/
|
||||||
private SharedPreferences mSettingsPref;
|
private SharedPreferences mSettingsPref;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Centralized database helper that manages all database operations with proper synchronization.
|
* Centralized database helper that manages all database operations with proper
|
||||||
|
* synchronization.
|
||||||
*/
|
*/
|
||||||
private DatabaseHelper mDatabaseHelper;
|
private DatabaseHelper mDatabaseHelper;
|
||||||
|
|
||||||
/** The ongoing match retrieved from the database, if any. Used for quick start functionality. */
|
/**
|
||||||
|
* The ongoing match retrieved from the database, if any. Used for quick start
|
||||||
|
* functionality.
|
||||||
|
*/
|
||||||
private Match mOngoingMatch;
|
private Match mOngoingMatch;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the activity: enables edge-to-edge display, configures window insets,
|
* Initializes the activity: enables edge-to-edge display, configures window
|
||||||
|
* insets,
|
||||||
* and sets up the match recap view with test data click listener.
|
* and sets up the match recap view with test data click listener.
|
||||||
*
|
*
|
||||||
* @param savedInstanceState Bundle containing saved state, or null if none exists.
|
* @param savedInstanceState Bundle containing saved state, or null if none
|
||||||
|
* exists.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(final Bundle savedInstanceState) {
|
protected void onCreate(final Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
// Enable edge-to-edge display for immersive UI experience
|
// Enable edge-to-edge display for immersive UI experience
|
||||||
EdgeToEdge.enable(this);
|
EdgeToEdge.enable(this);
|
||||||
setContentView(R.layout.activity_main);
|
setContentView(R.layout.activity_main);
|
||||||
mSettingsPref = PreferenceManager.getDefaultSharedPreferences(this);
|
mSettingsPref = PreferenceManager.getDefaultSharedPreferences(this);
|
||||||
mDatabaseHelper = DatabaseHelper.getInstance(this);
|
mDatabaseHelper = DatabaseHelper.getInstance(this);
|
||||||
|
|
||||||
// Configure window insets to properly handle system bars (status bar, navigation bar)
|
// Configure window insets to properly handle system bars (status bar,
|
||||||
|
// navigation bar)
|
||||||
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
|
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
|
||||||
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
|
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
|
||||||
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
|
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
|
||||||
return insets;
|
return insets;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
findViewById(R.id.btnSettings).setOnClickListener(v -> launchSettings());
|
findViewById(R.id.btnSettings).setOnClickListener(v -> launchSettings());
|
||||||
|
|
||||||
// Set up match recap view with test data functionality
|
// Set up match recap view
|
||||||
mMatchRecap = findViewById(R.id.match_recap);
|
mMatchRecap = findViewById(R.id.match_recap);
|
||||||
mMatchRecap.setOnClickListener(v -> {
|
|
||||||
// Cycle through test data scenarios on each click
|
|
||||||
applyTestData(mTestCounter);
|
|
||||||
mTestCounter++;
|
|
||||||
new Thread(() -> mDatabaseHelper.printAllMatches()).start();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Refreshes the squad view with latest player data from the database when activity resumes.
|
* Refreshes the squad view with latest player data from the database when
|
||||||
|
* activity resumes.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected void onResume() {
|
protected void onResume() {
|
||||||
@@ -132,7 +127,8 @@ public class MainMenuActivity extends BaseActivity {
|
|||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**game.
|
/**
|
||||||
|
* game.
|
||||||
* Checks for any ongoing matches and resumes them if found,
|
* Checks for any ongoing matches and resumes them if found,
|
||||||
* otherwise starts a new game with the default score.
|
* otherwise starts a new game with the default score.
|
||||||
*/
|
*/
|
||||||
@@ -159,20 +155,21 @@ public class MainMenuActivity extends BaseActivity {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the squad view: sets up the RecyclerView with adapter,
|
* Initializes the squad view: sets up the RecyclerView with adapter,
|
||||||
* configures the add player button, and loads all players from the database on a background thread.
|
* configures the add player button, and loads all players from the database on
|
||||||
|
* a background thread.
|
||||||
*/
|
*/
|
||||||
private void initSquadView() {
|
private void initSquadView() {
|
||||||
// Get references to UI components
|
// Get references to UI components
|
||||||
final TextView addPlayerBtn = findViewById(R.id.btnAddPlayer);
|
final TextView addPlayerBtn = findViewById(R.id.btnAddPlayer);
|
||||||
final RecyclerView squadView = findViewById(R.id.rvSquad);
|
final RecyclerView squadView = findViewById(R.id.rvSquad);
|
||||||
|
|
||||||
// Configure RecyclerView with linear layout
|
// Configure RecyclerView with linear layout
|
||||||
squadView.setLayoutManager(new LinearLayoutManager(MainMenuActivity.this));
|
squadView.setLayoutManager(new LinearLayoutManager(MainMenuActivity.this));
|
||||||
|
|
||||||
// Create and attach adapter
|
// Create and attach adapter
|
||||||
final MainMenuPlayerAdapter adapter = new MainMenuPlayerAdapter();
|
final MainMenuPlayerAdapter adapter = new MainMenuPlayerAdapter();
|
||||||
squadView.setAdapter(adapter);
|
squadView.setAdapter(adapter);
|
||||||
|
|
||||||
// Set up button to launch AddPlayerActivity
|
// Set up button to launch AddPlayerActivity
|
||||||
addPlayerBtn.setOnClickListener(v -> {
|
addPlayerBtn.setOnClickListener(v -> {
|
||||||
final Intent intent = new Intent(MainMenuActivity.this, AddPlayerActivity.class);
|
final Intent intent = new Intent(MainMenuActivity.this, AddPlayerActivity.class);
|
||||||
@@ -181,73 +178,66 @@ public class MainMenuActivity extends BaseActivity {
|
|||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
final List<Player> allPlayers = mDatabaseHelper.getAllPlayers();
|
final List<Player> allPlayers = mDatabaseHelper.getAllPlayers();
|
||||||
runOnUiThread(() -> adapter.updatePlayers(allPlayers));
|
runOnUiThread(() -> adapter.updatePlayers(allPlayers));
|
||||||
|
|
||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies the last completed match from the database to the match recap view.
|
* Applies the last completed match from the database to the match recap view.
|
||||||
|
* Enriches participant data with profile pictures from the database using batch
|
||||||
|
* fetch.
|
||||||
*/
|
*/
|
||||||
private void applyLastMatch() {
|
private void applyLastMatch() {
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
|
final List<Match.ParticipantData> participants = new ArrayList<>();
|
||||||
final Match lastCompleted = mDatabaseHelper.getLastCompletedMatch();
|
final Match lastCompleted = mDatabaseHelper.getLastCompletedMatch();
|
||||||
if (lastCompleted != null) {
|
if (lastCompleted != null) {
|
||||||
Log.d(TAG, "applyLastMatch: Applying last completed match [" + lastCompleted + "]");
|
Log.d(TAG, "applyLastMatch: Applying last completed match [" + lastCompleted + "]");
|
||||||
runOnUiThread(() -> mMatchRecap.setMatch(lastCompleted));
|
|
||||||
|
// Enrich participant data with profile pictures from database (batch fetch)
|
||||||
|
participants.addAll(lastCompleted.getAllParticipants());
|
||||||
|
|
||||||
|
// Collect all player IDs that need enrichment
|
||||||
|
final List<Long> playerIds = new java.util.ArrayList<>();
|
||||||
|
for (final Match.ParticipantData participant : participants) {
|
||||||
|
if (participant.player.id > 0) {
|
||||||
|
playerIds.add(participant.player.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Batch fetch all players in one query
|
||||||
|
if (!playerIds.isEmpty()) {
|
||||||
|
Log.d(TAG, "applyLastMatch: Fetching " + playerIds.size() + " players: " + playerIds);
|
||||||
|
final List<Player> fullPlayers = mDatabaseHelper.getPlayersByIds(playerIds);
|
||||||
|
Log.d(TAG, "applyLastMatch: Retrieved " + fullPlayers.size() + " players from database");
|
||||||
|
|
||||||
|
// Create a map for quick lookup
|
||||||
|
final java.util.Map<Long, Player> playerMap = new java.util.HashMap<>();
|
||||||
|
for (final Player player : fullPlayers) {
|
||||||
|
playerMap.put(player.id, player);
|
||||||
|
Log.d(TAG, "applyLastMatch: Player " + player.id + " has profilePictureUri: "
|
||||||
|
+ player.profilePictureUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enrich each participant with full player data
|
||||||
|
for (final Match.ParticipantData participant : participants) {
|
||||||
|
final Player fullPlayer = playerMap.get(participant.player.id);
|
||||||
|
if (fullPlayer != null) {
|
||||||
|
Log.d(TAG, "applyLastMatch: Enriching participant " + participant.player.username
|
||||||
|
+ " (id=" + participant.player.id + ") with profilePictureUri: "
|
||||||
|
+ fullPlayer.profilePictureUri);
|
||||||
|
participant.player.profilePictureUri = fullPlayer.profilePictureUri;
|
||||||
|
participant.player.careerAverage = fullPlayer.careerAverage;
|
||||||
|
Log.d(TAG, "applyLastMatch: After enrichment, participant.player.profilePictureUri = "
|
||||||
|
+ participant.player.profilePictureUri);
|
||||||
|
} else {
|
||||||
|
// Player was deleted - participant will show with name only (no profile
|
||||||
|
// picture)
|
||||||
|
Log.d(TAG, "applyLastMatch: Player " + participant.player.id + " not found (deleted?)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
runOnUiThread(() -> mMatchRecap.setMatchWithParticipants(lastCompleted, participants));
|
||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Applies test data to the match recap view for development and testing.
|
|
||||||
* Cycles through null match (counter % 3 == 0), 1v1 match (counter % 3 == 1),
|
|
||||||
* and group match (counter % 3 == 2) based on the counter value.
|
|
||||||
*
|
|
||||||
* @param counter Counter value used to determine which test scenario to display.
|
|
||||||
*/
|
|
||||||
private void applyTestData(final int counter) {
|
|
||||||
Log.d(TAG, "applyTestData: Applying Test Data [" + counter + "]");
|
|
||||||
// Create test player objects
|
|
||||||
final Player playerOne = new Player(DartsConstants.TEST_PLAYER_1, null);
|
|
||||||
playerOne.id = 1;
|
|
||||||
final Player playerTwo = new Player(DartsConstants.TEST_PLAYER_2, null);
|
|
||||||
playerTwo.id = 2;
|
|
||||||
final Player playerThree = new Player(DartsConstants.TEST_PLAYER_3, null);
|
|
||||||
playerThree.id = 3;
|
|
||||||
final Player playerFour = new Player(DartsConstants.TEST_PLAYER_4, null);
|
|
||||||
playerFour.id = 4;
|
|
||||||
|
|
||||||
// Create score maps for test matches
|
|
||||||
final java.util.Map<Integer, Integer> scores1v1 = new java.util.HashMap<>();
|
|
||||||
scores1v1.put(1, 0); // Player 1 won (reached 0 from 501)
|
|
||||||
scores1v1.put(2, 157); // Player 2 remaining score
|
|
||||||
|
|
||||||
final java.util.Map<Integer, Integer> scoresGroup = new java.util.HashMap<>();
|
|
||||||
scoresGroup.put(1, 0); // Player 1 won (reached 0 from 501)
|
|
||||||
scoresGroup.put(2, 89); // Player 2 remaining score
|
|
||||||
scoresGroup.put(3, 234); // Player 3 remaining score
|
|
||||||
scoresGroup.put(4, 312); // Player 4 remaining score
|
|
||||||
|
|
||||||
// Create test match objects with different player configurations and scores
|
|
||||||
final Match match1on1 = new Match("501", java.util.Arrays.asList(playerOne, playerTwo), scores1v1);
|
|
||||||
match1on1.markCompleted(); // Mark as completed for test data
|
|
||||||
|
|
||||||
final Match matchGroup = new Match("501", java.util.Arrays.asList(playerOne, playerTwo, playerThree, playerFour), scoresGroup);
|
|
||||||
matchGroup.markCompleted(); // Mark as completed for test data
|
|
||||||
|
|
||||||
// Cycle through different test scenarios based on counter value
|
|
||||||
if (counter % UIConstants.TEST_CYCLE_MODULO == 0) {
|
|
||||||
Log.d(TAG, "applyTestData: No recent match selected.");
|
|
||||||
// Scenario 1: No match (null state)
|
|
||||||
mMatchRecap.setMatch(null);
|
|
||||||
} else if (counter % UIConstants.TEST_CYCLE_MODULO == 1) {
|
|
||||||
Log.d(TAG, "applyTestData: 1 on 1 Match");
|
|
||||||
// Scenario 2: 1v1 match (two players)
|
|
||||||
mMatchRecap.setMatch(match1on1);
|
|
||||||
} else {
|
|
||||||
Log.d(TAG, "applyTestData: Group Match.");
|
|
||||||
// Scenario 3: Group match (four players)
|
|
||||||
mMatchRecap.setMatch(matchGroup);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -629,6 +629,28 @@ public class DatabaseHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves multiple players by their IDs in a single batch query.
|
||||||
|
* More efficient than calling getPlayerById() multiple times.
|
||||||
|
* Blocks until the operation completes to ensure consistency with any pending
|
||||||
|
* writes.
|
||||||
|
*
|
||||||
|
* @param playerIds List of player IDs to retrieve
|
||||||
|
* @return List of players found (may be fewer than requested if some IDs don't
|
||||||
|
* exist)
|
||||||
|
*/
|
||||||
|
public List<Player> getPlayersByIds(final List<Long> playerIds) {
|
||||||
|
if (playerIds == null || playerIds.isEmpty()) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return mExecutor.submit(() -> mDatabase.playerDao().getPlayersByIds(playerIds)).get();
|
||||||
|
} catch (final Exception e) {
|
||||||
|
Log.e(TAG, "getPlayersByIds: Failed to submit task", e);
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inserts a PlayerStats entity into the database.
|
* Inserts a PlayerStats entity into the database.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -76,6 +76,17 @@ public interface PlayerDao {
|
|||||||
@Query("SELECT * FROM players WHERE isPrimaryUser = 1 LIMIT 1")
|
@Query("SELECT * FROM players WHERE isPrimaryUser = 1 LIMIT 1")
|
||||||
Player getPrimaryUser();
|
Player getPrimaryUser();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves multiple players by their IDs in a single query.
|
||||||
|
* Must be called on a background thread.
|
||||||
|
*
|
||||||
|
* @param ids List of player IDs to retrieve
|
||||||
|
* @return List of players found (may be fewer than requested if some IDs don't
|
||||||
|
* exist)
|
||||||
|
*/
|
||||||
|
@Query("SELECT * FROM players WHERE id IN (:ids)")
|
||||||
|
List<Player> getPlayersByIds(final List<Long> ids);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears the primary user flag from all players.
|
* Clears the primary user flag from all players.
|
||||||
* Used before setting a new primary user to ensure only one exists.
|
* Used before setting a new primary user to ensure only one exists.
|
||||||
|
|||||||
@@ -295,7 +295,8 @@ public class Match implements Serializable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Data class representing a participant in a match with their score.
|
* Data class representing a participant in a match with their score and game
|
||||||
|
* average.
|
||||||
*/
|
*/
|
||||||
public static class ParticipantData {
|
public static class ParticipantData {
|
||||||
/** The Player object */
|
/** The Player object */
|
||||||
@@ -304,19 +305,48 @@ public class Match implements Serializable {
|
|||||||
/** The player's score in this match */
|
/** The player's score in this match */
|
||||||
public final int score;
|
public final int score;
|
||||||
|
|
||||||
public ParticipantData(final Player player, final int score) {
|
/** The player's average for this specific game (points per 3 darts) */
|
||||||
|
public final double gameAverage;
|
||||||
|
|
||||||
|
public ParticipantData(final Player player, final int score, final double gameAverage) {
|
||||||
this.player = player;
|
this.player = player;
|
||||||
this.score = score;
|
this.score = score;
|
||||||
|
this.gameAverage = gameAverage;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all participants with their match scores.
|
* Returns all participants with their match scores and game averages.
|
||||||
|
* Handles both legacy JSONArray format and MatchProgress JSONObject format.
|
||||||
*
|
*
|
||||||
* @return List of ParticipantData objects containing player info and scores
|
* @return List of ParticipantData objects containing player info, scores, and
|
||||||
|
* game averages
|
||||||
*/
|
*/
|
||||||
public List<ParticipantData> getAllParticipants() {
|
public List<ParticipantData> getAllParticipants() {
|
||||||
final List<ParticipantData> participants = new ArrayList<>();
|
final List<ParticipantData> participants = new ArrayList<>();
|
||||||
|
|
||||||
|
// First, try to parse as MatchProgress format (JSONObject with "players" array)
|
||||||
|
final MatchProgress progress = MatchProgressConverter.fromString(participantData);
|
||||||
|
if (progress != null && progress.players != null) {
|
||||||
|
// New format: MatchProgress object
|
||||||
|
for (final MatchProgress.PlayerStateSnapshot playerProgress : progress.players) {
|
||||||
|
final Player player = new Player(playerProgress.name, null);
|
||||||
|
player.id = playerProgress.playerId;
|
||||||
|
|
||||||
|
final int score = playerProgress.remainingScore;
|
||||||
|
|
||||||
|
// Calculate game average: (scored points / darts thrown) * 3
|
||||||
|
final int scoredPoints = progress.startingScore - playerProgress.remainingScore;
|
||||||
|
final double gameAverage = playerProgress.dartsThrown > 0
|
||||||
|
? (double) scoredPoints / playerProgress.dartsThrown * 3
|
||||||
|
: 0.0;
|
||||||
|
|
||||||
|
participants.add(new ParticipantData(player, score, gameAverage));
|
||||||
|
}
|
||||||
|
return participants;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to legacy JSONArray format
|
||||||
try {
|
try {
|
||||||
final JSONArray participantArray = new JSONArray(participantData);
|
final JSONArray participantArray = new JSONArray(participantData);
|
||||||
for (int i = 0; i < participantArray.length(); i++) {
|
for (int i = 0; i < participantArray.length(); i++) {
|
||||||
@@ -327,7 +357,8 @@ public class Match implements Serializable {
|
|||||||
player.id = participant.optInt("id", 0);
|
player.id = participant.optInt("id", 0);
|
||||||
player.careerAverage = participant.optDouble("careerAverage", 0.0);
|
player.careerAverage = participant.optDouble("careerAverage", 0.0);
|
||||||
final int score = participant.optInt("score", 0);
|
final int score = participant.optInt("score", 0);
|
||||||
participants.add(new ParticipantData(player, score));
|
// Legacy format doesn't have game average data, use 0.0
|
||||||
|
participants.add(new ParticipantData(player, score, 0.0));
|
||||||
}
|
}
|
||||||
} catch (JSONException e) {
|
} catch (JSONException e) {
|
||||||
// Return empty list if JSON parsing fails
|
// Return empty list if JSON parsing fails
|
||||||
|
|||||||
@@ -16,14 +16,16 @@ import java.util.List;
|
|||||||
/**
|
/**
|
||||||
* GameManager: Singleton manager for handling all X01 game business logic.
|
* GameManager: Singleton manager for handling all X01 game business logic.
|
||||||
* <p>
|
* <p>
|
||||||
* This class serves as the central data pool and business logic handler for an active darts match.
|
* This class serves as the central data pool and business logic handler for an
|
||||||
|
* active darts match.
|
||||||
* It manages:
|
* It manages:
|
||||||
* - Match state (scores, active player, dart throws)
|
* - Match state (scores, active player, dart throws)
|
||||||
* - Database operations (loading/saving match progress)
|
* - Database operations (loading/saving match progress)
|
||||||
* - Game rules (bust detection, double-out, win conditions)
|
* - Game rules (bust detection, double-out, win conditions)
|
||||||
* - Statistics tracking
|
* - Statistics tracking
|
||||||
* <p>
|
* <p>
|
||||||
* The GameManager decouples business logic from UI, making GameActivity a simple view controller
|
* The GameManager decouples business logic from UI, making GameActivity a
|
||||||
|
* simple view controller
|
||||||
* that only handles UI updates via the GameStateCallback interface.
|
* that only handles UI updates via the GameStateCallback interface.
|
||||||
*/
|
*/
|
||||||
public class GameManager {
|
public class GameManager {
|
||||||
@@ -31,94 +33,94 @@ public class GameManager {
|
|||||||
* Tag for logging purposes.
|
* Tag for logging purposes.
|
||||||
*/
|
*/
|
||||||
private static final String TAG = "GameManager";
|
private static final String TAG = "GameManager";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Singleton instance of GameManager.
|
* Singleton instance of GameManager.
|
||||||
* Volatile to ensure thread-safe lazy initialization.
|
* Volatile to ensure thread-safe lazy initialization.
|
||||||
*/
|
*/
|
||||||
private static volatile GameManager sInstance;
|
private static volatile GameManager sInstance;
|
||||||
|
|
||||||
// ========================================================================================
|
// ========================================================================================
|
||||||
// Dependencies
|
// Dependencies
|
||||||
// ========================================================================================
|
// ========================================================================================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reference to the centralized database helper for all database operations.
|
* Reference to the centralized database helper for all database operations.
|
||||||
* Initialized once in constructor and never changed.
|
* Initialized once in constructor and never changed.
|
||||||
*/
|
*/
|
||||||
private final DatabaseHelper mDatabaseHelper;
|
private final DatabaseHelper mDatabaseHelper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback interface for notifying the UI layer of game state changes.
|
* Callback interface for notifying the UI layer of game state changes.
|
||||||
* Set by the UI controller (GameActivity) when it's ready to receive updates.
|
* Set by the UI controller (GameActivity) when it's ready to receive updates.
|
||||||
*/
|
*/
|
||||||
private GameStateCallback mCallback;
|
private GameStateCallback mCallback;
|
||||||
|
|
||||||
// ========================================================================================
|
// ========================================================================================
|
||||||
// Game State
|
// Game State
|
||||||
// ========================================================================================
|
// ========================================================================================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The database ID of the current match.
|
* The database ID of the current match.
|
||||||
* -1 indicates no match is loaded.
|
* -1 indicates no match is loaded.
|
||||||
*/
|
*/
|
||||||
private int mMatchId = -1;
|
private int mMatchId = -1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The starting score for this X01 game (typically 501, 301, or 701).
|
* The starting score for this X01 game (typically 501, 301, or 701).
|
||||||
*/
|
*/
|
||||||
private int mStartingScore = DartsConstants.DEFAULT_GAME_SCORE;
|
private int mStartingScore = DartsConstants.DEFAULT_GAME_SCORE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Index of the currently active player (0 to playerCount-1).
|
* Index of the currently active player (0 to playerCount-1).
|
||||||
* Cycles through players as turns complete.
|
* Cycles through players as turns complete.
|
||||||
*/
|
*/
|
||||||
private int mActivePlayerIndex = 0;
|
private int mActivePlayerIndex = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Current multiplier for the next dart (1=Single, 2=Double, 3=Triple).
|
* Current multiplier for the next dart (1=Single, 2=Double, 3=Triple).
|
||||||
* Resets to 1 after each dart throw for safety.
|
* Resets to 1 after each dart throw for safety.
|
||||||
*/
|
*/
|
||||||
private int mMultiplier = 1;
|
private int mMultiplier = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of all player states in this match.
|
* List of all player states in this match.
|
||||||
* Order determines turn order.
|
* Order determines turn order.
|
||||||
* Thread-safe operations through GameManager's single-threaded nature.
|
* Thread-safe operations through GameManager's single-threaded nature.
|
||||||
*/
|
*/
|
||||||
private final List<PlayerState> mPlayerStates = new ArrayList<>();
|
private final List<PlayerState> mPlayerStates = new ArrayList<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Point values of darts thrown in the current turn (up to 3).
|
* Point values of darts thrown in the current turn (up to 3).
|
||||||
* Cleared when turn is submitted or reset.
|
* Cleared when turn is submitted or reset.
|
||||||
*/
|
*/
|
||||||
private final List<Integer> mCurrentTurnDarts = new ArrayList<>();
|
private final List<Integer> mCurrentTurnDarts = new ArrayList<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detailed dart hit information (base value and multiplier) for current turn.
|
* Detailed dart hit information (base value and multiplier) for current turn.
|
||||||
* Parallel to mCurrentTurnDarts, used for statistics and heat map tracking.
|
* Parallel to mCurrentTurnDarts, used for statistics and heat map tracking.
|
||||||
* Cleared when turn is submitted or reset.
|
* Cleared when turn is submitted or reset.
|
||||||
*/
|
*/
|
||||||
private final List<DartHit> mCurrentTurnDartHits = new ArrayList<>();
|
private final List<DartHit> mCurrentTurnDartHits = new ArrayList<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flag indicating the current turn has ended (bust, win, or 3 darts thrown).
|
* Flag indicating the current turn has ended (bust, win, or 3 darts thrown).
|
||||||
* Prevents additional dart entry until turn is submitted.
|
* Prevents additional dart entry until turn is submitted.
|
||||||
*/
|
*/
|
||||||
private boolean mIsTurnOver = false;
|
private boolean mIsTurnOver = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flag indicating the current turn resulted in a bust.
|
* Flag indicating the current turn resulted in a bust.
|
||||||
* Used to prevent UI from subtracting bust darts from score display.
|
* Used to prevent UI from subtracting bust darts from score display.
|
||||||
*/
|
*/
|
||||||
private boolean mIsBustedTurn = false;
|
private boolean mIsBustedTurn = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flag indicating the match has been completed (a player has won).
|
* Flag indicating the match has been completed (a player has won).
|
||||||
* When true, game state is frozen and match is marked as COMPLETED in database.
|
* When true, game state is frozen and match is marked as COMPLETED in database.
|
||||||
*/
|
*/
|
||||||
private boolean mIsMatchCompleted = false;
|
private boolean mIsMatchCompleted = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback interface for communicating game state changes to the UI layer.
|
* Callback interface for communicating game state changes to the UI layer.
|
||||||
*/
|
*/
|
||||||
@@ -127,41 +129,43 @@ public class GameManager {
|
|||||||
* Called when the game state has changed and UI should be refreshed.
|
* Called when the game state has changed and UI should be refreshed.
|
||||||
*/
|
*/
|
||||||
void onGameStateChanged();
|
void onGameStateChanged();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the turn indicators (dart pills) should be updated.
|
* Called when the turn indicators (dart pills) should be updated.
|
||||||
*/
|
*/
|
||||||
void onTurnIndicatorsChanged();
|
void onTurnIndicatorsChanged();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the multiplier has changed.
|
* Called when the multiplier has changed.
|
||||||
|
*
|
||||||
* @param multiplier The new multiplier value (1=Single, 2=Double, 3=Triple)
|
* @param multiplier The new multiplier value (1=Single, 2=Double, 3=Triple)
|
||||||
*/
|
*/
|
||||||
void onMultiplierChanged(int multiplier);
|
void onMultiplierChanged(int multiplier);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a bust occurs.
|
* Called when a bust occurs.
|
||||||
*/
|
*/
|
||||||
void onBust();
|
void onBust();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a player wins the match.
|
* Called when a player wins the match.
|
||||||
* @param winner The winning player's state
|
*
|
||||||
|
* @param winner The winning player's state
|
||||||
* @param checkoutValue The final dart value that won the game
|
* @param checkoutValue The final dart value that won the game
|
||||||
*/
|
*/
|
||||||
void onPlayerWin(PlayerState winner, int checkoutValue);
|
void onPlayerWin(PlayerState winner, int checkoutValue);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a perfect 180 is scored.
|
* Called when a perfect 180 is scored.
|
||||||
*/
|
*/
|
||||||
void onOneEightyScored();
|
void onOneEightyScored();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called to reset visual effects after a bust.
|
* Called to reset visual effects after a bust.
|
||||||
*/
|
*/
|
||||||
void onResetVisuals();
|
void onResetVisuals();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a single dart hit with its base value and multiplier.
|
* Represents a single dart hit with its base value and multiplier.
|
||||||
* Immutable data class used for tracking individual dart throws.
|
* Immutable data class used for tracking individual dart throws.
|
||||||
@@ -171,16 +175,16 @@ public class GameManager {
|
|||||||
* The dartboard number hit (1-20 or 25 for bull).
|
* The dartboard number hit (1-20 or 25 for bull).
|
||||||
*/
|
*/
|
||||||
public final int baseValue;
|
public final int baseValue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The multiplier applied to the base value (1=single, 2=double, 3=triple).
|
* The multiplier applied to the base value (1=single, 2=double, 3=triple).
|
||||||
*/
|
*/
|
||||||
public final int multiplier;
|
public final int multiplier;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a DartHit with the specified base value and multiplier.
|
* Constructs a DartHit with the specified base value and multiplier.
|
||||||
*
|
*
|
||||||
* @param baseValue The dartboard number (1-20 or 25 for bull)
|
* @param baseValue The dartboard number (1-20 or 25 for bull)
|
||||||
* @param multiplier The multiplier (1=single, 2=double, 3=triple)
|
* @param multiplier The multiplier (1=single, 2=double, 3=triple)
|
||||||
*/
|
*/
|
||||||
public DartHit(final int baseValue, final int multiplier) {
|
public DartHit(final int baseValue, final int multiplier) {
|
||||||
@@ -188,7 +192,7 @@ public class GameManager {
|
|||||||
this.multiplier = multiplier;
|
this.multiplier = multiplier;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* State holder for a single player's X01 game progress.
|
* State holder for a single player's X01 game progress.
|
||||||
* Tracks current match state for an individual player.
|
* Tracks current match state for an individual player.
|
||||||
@@ -199,35 +203,35 @@ public class GameManager {
|
|||||||
* Used for statistics updates and player information.
|
* Used for statistics updates and player information.
|
||||||
*/
|
*/
|
||||||
public final Player player;
|
public final Player player;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Player's database ID for quick lookups.
|
* Player's database ID for quick lookups.
|
||||||
* 0 for guest players who don't have database entries.
|
* 0 for guest players who don't have database entries.
|
||||||
*/
|
*/
|
||||||
public final long playerId;
|
public final long playerId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Player's display name cached from the Player entity.
|
* Player's display name cached from the Player entity.
|
||||||
* Stored for convenience to avoid repeated lookups.
|
* Stored for convenience to avoid repeated lookups.
|
||||||
*/
|
*/
|
||||||
public final String name;
|
public final String name;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Player's current remaining score in this match.
|
* Player's current remaining score in this match.
|
||||||
* Decreases with valid throws, resets to previous value on bust.
|
* Decreases with valid throws, resets to previous value on bust.
|
||||||
*/
|
*/
|
||||||
public int remainingScore;
|
public int remainingScore;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Total number of darts thrown by this player in the current match.
|
* Total number of darts thrown by this player in the current match.
|
||||||
* Used for calculating averages and statistics.
|
* Used for calculating averages and statistics.
|
||||||
*/
|
*/
|
||||||
public int dartsThrown = 0;
|
public int dartsThrown = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a PlayerState for a player with the specified starting score.
|
* Constructs a PlayerState for a player with the specified starting score.
|
||||||
*
|
*
|
||||||
* @param player The Player entity from the database
|
* @param player The Player entity from the database
|
||||||
* @param startScore The starting score for this X01 game (e.g., 501)
|
* @param startScore The starting score for this X01 game (e.g., 501)
|
||||||
*/
|
*/
|
||||||
public PlayerState(final Player player, final int startScore) {
|
public PlayerState(final Player player, final int startScore) {
|
||||||
@@ -237,17 +241,18 @@ public class GameManager {
|
|||||||
this.remainingScore = startScore;
|
this.remainingScore = startScore;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Private constructor to enforce singleton pattern.
|
* Private constructor to enforce singleton pattern.
|
||||||
* Initializes the database helper with application context.
|
* Initializes the database helper with application context.
|
||||||
*
|
*
|
||||||
* @param context Application or Activity context (will be converted to application context)
|
* @param context Application or Activity context (will be converted to
|
||||||
|
* application context)
|
||||||
*/
|
*/
|
||||||
private GameManager(final Context context) {
|
private GameManager(final Context context) {
|
||||||
mDatabaseHelper = DatabaseHelper.getInstance(context);
|
mDatabaseHelper = DatabaseHelper.getInstance(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the singleton instance of GameManager.
|
* Gets the singleton instance of GameManager.
|
||||||
*
|
*
|
||||||
@@ -260,7 +265,7 @@ public class GameManager {
|
|||||||
}
|
}
|
||||||
return sInstance;
|
return sInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers a callback to receive game state updates.
|
* Registers a callback to receive game state updates.
|
||||||
* Immediately triggers an initial state change callback to synchronize the UI.
|
* Immediately triggers an initial state change callback to synchronize the UI.
|
||||||
@@ -269,20 +274,24 @@ public class GameManager {
|
|||||||
*/
|
*/
|
||||||
public void setCallback(final GameStateCallback callback) {
|
public void setCallback(final GameStateCallback callback) {
|
||||||
mCallback = callback;
|
mCallback = callback;
|
||||||
//Send one initial callback to sync UI with current state
|
// Send one initial callback to sync UI with current state
|
||||||
notifyGameStateChanged();
|
notifyGameStateChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes a new game or loads an existing match from the database with explicit player list.
|
* Initializes a new game or loads an existing match from the database with
|
||||||
* This overload allows passing a pre-loaded player list instead of loading from database.
|
* explicit player list.
|
||||||
|
* This overload allows passing a pre-loaded player list instead of loading from
|
||||||
|
* database.
|
||||||
*
|
*
|
||||||
* @param matchId The match ID to load, or -1 to create a new match
|
* @param matchId The match ID to load, or -1 to create a new match
|
||||||
* @param startingScore The starting score (501, 301, etc.)
|
* @param startingScore The starting score (501, 301, etc.)
|
||||||
* @param players Pre-loaded list of players to use for this match
|
* @param players Pre-loaded list of players to use for this match
|
||||||
* @param onComplete Callback executed on the calling thread when initialization is complete
|
* @param onComplete Callback executed on the calling thread when
|
||||||
|
* initialization is complete
|
||||||
*/
|
*/
|
||||||
public void initializeMatch(final int matchId, final int startingScore, final List<Player> players, final Runnable onComplete) {
|
public void initializeMatch(final int matchId, final int startingScore, final List<Player> players,
|
||||||
|
final Runnable onComplete) {
|
||||||
mStartingScore = startingScore;
|
mStartingScore = startingScore;
|
||||||
mMatchId = matchId;
|
mMatchId = matchId;
|
||||||
|
|
||||||
@@ -293,13 +302,13 @@ public class GameManager {
|
|||||||
match = mDatabaseHelper.getMatchById(matchId);
|
match = mDatabaseHelper.getMatchById(matchId);
|
||||||
Log.d(TAG, "initializeMatch: Loaded match from DB: " + match);
|
Log.d(TAG, "initializeMatch: Loaded match from DB: " + match);
|
||||||
|
|
||||||
|
|
||||||
if (match != null && match.participantData != null && !match.participantData.isEmpty()) {
|
if (match != null && match.participantData != null && !match.participantData.isEmpty()) {
|
||||||
// Load match progress from database
|
// Load match progress from database
|
||||||
try {
|
try {
|
||||||
final MatchProgress progress = MatchProgressConverter.fromString(match.participantData);
|
final MatchProgress progress = MatchProgressConverter.fromString(match.participantData);
|
||||||
if (progress != null) {
|
if (progress != null) {
|
||||||
Log.d(TAG, "initializeMatch: Found saved progress with " + progress.players.size() + " players");
|
Log.d(TAG, "initializeMatch: Found saved progress with " + progress.players.size()
|
||||||
|
+ " players");
|
||||||
// Initialize player states
|
// Initialize player states
|
||||||
initializePlayerStates(players);
|
initializePlayerStates(players);
|
||||||
loadMatchProgress(progress);
|
loadMatchProgress(progress);
|
||||||
@@ -341,33 +350,33 @@ public class GameManager {
|
|||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes a new game or loads an existing match from the database.
|
* Initializes a new game or loads an existing match from the database.
|
||||||
* This should be called when starting/resuming a match.
|
* This should be called when starting/resuming a match.
|
||||||
*
|
*
|
||||||
* @param matchId The match ID to load, or -1 to create a new match
|
* @param matchId The match ID to load, or -1 to create a new match
|
||||||
* @param startingScore The starting score (501, 301, etc.)
|
* @param startingScore The starting score (501, 301, etc.)
|
||||||
* @param onComplete Callback when initialization is complete
|
* @param onComplete Callback when initialization is complete
|
||||||
*/
|
*/
|
||||||
public void initializeMatch(final int matchId, final int startingScore, final Runnable onComplete) {
|
public void initializeMatch(final int matchId, final int startingScore, final Runnable onComplete) {
|
||||||
mStartingScore = startingScore;
|
mStartingScore = startingScore;
|
||||||
mMatchId = matchId;
|
mMatchId = matchId;
|
||||||
|
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
final List<Player> allPlayers = mDatabaseHelper.getAllPlayers();
|
final List<Player> allPlayers = mDatabaseHelper.getAllPlayers();
|
||||||
Log.d(TAG, "initializeMatch: Loading players, count = " + (allPlayers != null ? allPlayers.size() : 0));
|
Log.d(TAG, "initializeMatch: Loading players, count = " + (allPlayers != null ? allPlayers.size() : 0));
|
||||||
initializeMatch(matchId, startingScore, allPlayers, onComplete);
|
initializeMatch(matchId, startingScore, allPlayers, onComplete);
|
||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes player states from the provided player list.
|
* Initializes player states from the provided player list.
|
||||||
* Clears any existing player states and creates new PlayerState objects
|
* Clears any existing player states and creates new PlayerState objects
|
||||||
* for each player with the current starting score.
|
* for each player with the current starting score.
|
||||||
* If no players are provided, creates a single guest player.
|
* If no players are provided, creates a single guest player.
|
||||||
*
|
*
|
||||||
* @param players List of Player entities to initialize, or null/empty for guest player
|
* @param players List of Player entities to initialize, or null/empty for guest
|
||||||
|
* player
|
||||||
*/
|
*/
|
||||||
private void initializePlayerStates(final List<Player> players) {
|
private void initializePlayerStates(final List<Player> players) {
|
||||||
mPlayerStates.clear();
|
mPlayerStates.clear();
|
||||||
@@ -381,7 +390,7 @@ public class GameManager {
|
|||||||
mPlayerStates.add(new PlayerState(guest, mStartingScore));
|
mPlayerStates.add(new PlayerState(guest, mStartingScore));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads match progress from a saved state.
|
* Loads match progress from a saved state.
|
||||||
* Restores player scores, darts thrown, and active player index from
|
* Restores player scores, darts thrown, and active player index from
|
||||||
@@ -390,13 +399,14 @@ public class GameManager {
|
|||||||
* @param progress The MatchProgress snapshot to restore from
|
* @param progress The MatchProgress snapshot to restore from
|
||||||
*/
|
*/
|
||||||
private void loadMatchProgress(final MatchProgress progress) {
|
private void loadMatchProgress(final MatchProgress progress) {
|
||||||
if (progress == null || mPlayerStates.isEmpty()) return;
|
if (progress == null || mPlayerStates.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
Log.d(TAG, "loadMatchProgress: Loading saved match progress");
|
Log.d(TAG, "loadMatchProgress: Loading saved match progress");
|
||||||
|
|
||||||
// Restore active player index
|
// Restore active player index
|
||||||
mActivePlayerIndex = progress.activePlayerIndex;
|
mActivePlayerIndex = progress.activePlayerIndex;
|
||||||
|
|
||||||
// Restore player scores and darts thrown
|
// Restore player scores and darts thrown
|
||||||
for (int i = 0; i < progress.players.size() && i < mPlayerStates.size(); i++) {
|
for (int i = 0; i < progress.players.size() && i < mPlayerStates.size(); i++) {
|
||||||
MatchProgress.PlayerStateSnapshot snapshot = progress.players.get(i);
|
MatchProgress.PlayerStateSnapshot snapshot = progress.players.get(i);
|
||||||
@@ -404,74 +414,77 @@ public class GameManager {
|
|||||||
state.remainingScore = snapshot.remainingScore;
|
state.remainingScore = snapshot.remainingScore;
|
||||||
state.dartsThrown = snapshot.dartsThrown;
|
state.dartsThrown = snapshot.dartsThrown;
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.d(TAG, "loadMatchProgress: Match progress loaded successfully");
|
Log.d(TAG, "loadMatchProgress: Match progress loaded successfully");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes a dart throw when a keyboard number is tapped.
|
* Processes a dart throw when a keyboard number is tapped.
|
||||||
*
|
*
|
||||||
* @param baseValue Face value of the number hit (1-20 or 25 for Bull)
|
* @param baseValue Face value of the number hit (1-20 or 25 for Bull)
|
||||||
*/
|
*/
|
||||||
public void onNumberTap(final int baseValue) {
|
public void onNumberTap(final int baseValue) {
|
||||||
if (mCurrentTurnDarts.size() >= 3 || mIsTurnOver) return;
|
if (mCurrentTurnDarts.size() >= 3 || mIsTurnOver)
|
||||||
|
return;
|
||||||
|
|
||||||
int points = baseValue * mMultiplier;
|
int points = baseValue * mMultiplier;
|
||||||
if (baseValue == DartsConstants.BULL_VALUE && mMultiplier == DartsConstants.MULTIPLIER_TRIPLE) {
|
if (baseValue == DartsConstants.BULL_VALUE && mMultiplier == DartsConstants.MULTIPLIER_TRIPLE) {
|
||||||
points = DartsConstants.DOUBLE_BULL_VALUE; // Triple Bull is Double Bull
|
points = DartsConstants.DOUBLE_BULL_VALUE; // Triple Bull is Double Bull
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayerState active = mPlayerStates.get(mActivePlayerIndex);
|
PlayerState active = mPlayerStates.get(mActivePlayerIndex);
|
||||||
int scoreBeforeDart = active.remainingScore;
|
int scoreBeforeDart = active.remainingScore;
|
||||||
for (int d : mCurrentTurnDarts) scoreBeforeDart -= d;
|
for (int d : mCurrentTurnDarts)
|
||||||
|
scoreBeforeDart -= d;
|
||||||
|
|
||||||
int scoreAfterDart = scoreBeforeDart - points;
|
int scoreAfterDart = scoreBeforeDart - points;
|
||||||
boolean isDouble = (mMultiplier == DartsConstants.MULTIPLIER_DOUBLE) || (points == DartsConstants.DOUBLE_BULL_VALUE);
|
boolean isDouble = (mMultiplier == DartsConstants.MULTIPLIER_DOUBLE)
|
||||||
|
|| (points == DartsConstants.DOUBLE_BULL_VALUE);
|
||||||
|
|
||||||
// --- DOUBLE OUT LOGIC CHECK ---
|
// --- DOUBLE OUT LOGIC CHECK ---
|
||||||
if (scoreAfterDart < 0 || scoreAfterDart == DartsConstants.BUST_SCORE || (scoreAfterDart == 0 && !isDouble)) {
|
if (scoreAfterDart < 0 || scoreAfterDart == DartsConstants.BUST_SCORE || (scoreAfterDart == 0 && !isDouble)) {
|
||||||
// BUST CONDITION
|
// BUST CONDITION
|
||||||
mCurrentTurnDarts.add(points);
|
mCurrentTurnDarts.add(points);
|
||||||
mCurrentTurnDartHits.add(new DartHit(baseValue, mMultiplier));
|
mCurrentTurnDartHits.add(new DartHit(baseValue, mMultiplier));
|
||||||
|
|
||||||
// Track double-out miss if trying to finish but failed
|
// Track double-out miss if trying to finish but failed
|
||||||
if (scoreBeforeDart <= 40 && isDouble) {
|
if (scoreBeforeDart <= 40 && isDouble) {
|
||||||
trackDoubleAttempt(active, true);
|
trackDoubleAttempt(active, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
mIsTurnOver = true;
|
mIsTurnOver = true;
|
||||||
mIsBustedTurn = true;
|
mIsBustedTurn = true;
|
||||||
|
|
||||||
notifyTurnIndicatorsChanged();
|
notifyTurnIndicatorsChanged();
|
||||||
notifyBust();
|
notifyBust();
|
||||||
} else if (scoreAfterDart == 0 && isDouble) {
|
} else if (scoreAfterDart == 0 && isDouble) {
|
||||||
// VICTORY CONDITION
|
// VICTORY CONDITION
|
||||||
mCurrentTurnDarts.add(points);
|
mCurrentTurnDarts.add(points);
|
||||||
mCurrentTurnDartHits.add(new DartHit(baseValue, mMultiplier));
|
mCurrentTurnDartHits.add(new DartHit(baseValue, mMultiplier));
|
||||||
|
|
||||||
// Track successful double-out
|
// Track successful double-out
|
||||||
trackDoubleAttempt(active, false);
|
trackDoubleAttempt(active, false);
|
||||||
|
|
||||||
mIsTurnOver = true;
|
mIsTurnOver = true;
|
||||||
|
|
||||||
notifyTurnIndicatorsChanged();
|
notifyTurnIndicatorsChanged();
|
||||||
handleWin(active);
|
handleWin(active);
|
||||||
} else {
|
} else {
|
||||||
// VALID THROW
|
// VALID THROW
|
||||||
mCurrentTurnDarts.add(points);
|
mCurrentTurnDarts.add(points);
|
||||||
mCurrentTurnDartHits.add(new DartHit(baseValue, mMultiplier));
|
mCurrentTurnDartHits.add(new DartHit(baseValue, mMultiplier));
|
||||||
|
|
||||||
notifyTurnIndicatorsChanged();
|
notifyTurnIndicatorsChanged();
|
||||||
notifyGameStateChanged();
|
notifyGameStateChanged();
|
||||||
|
|
||||||
if (mCurrentTurnDarts.size() == DartsConstants.MAX_DARTS_PER_TURN) {
|
if (mCurrentTurnDarts.size() == DartsConstants.MAX_DARTS_PER_TURN) {
|
||||||
mIsTurnOver = true;
|
mIsTurnOver = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setMultiplier(DartsConstants.MULTIPLIER_SINGLE);
|
setMultiplier(DartsConstants.MULTIPLIER_SINGLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the win condition when a player finishes on zero with a double.
|
* Handles the win condition when a player finishes on zero with a double.
|
||||||
* Updates statistics, records dart hits, increments match counters, saves the
|
* Updates statistics, records dart hits, increments match counters, saves the
|
||||||
@@ -482,86 +495,93 @@ public class GameManager {
|
|||||||
private void handleWin(final PlayerState winner) {
|
private void handleWin(final PlayerState winner) {
|
||||||
final int dartsThrown = mCurrentTurnDarts.size();
|
final int dartsThrown = mCurrentTurnDarts.size();
|
||||||
int pointsMade = 0;
|
int pointsMade = 0;
|
||||||
for (int d : mCurrentTurnDarts) pointsMade += d;
|
for (int d : mCurrentTurnDarts)
|
||||||
|
pointsMade += d;
|
||||||
final int checkoutValue = mCurrentTurnDarts.get(mCurrentTurnDarts.size() - 1);
|
final int checkoutValue = mCurrentTurnDarts.get(mCurrentTurnDarts.size() - 1);
|
||||||
|
|
||||||
|
// Update winner's score to 0 and increment darts thrown
|
||||||
|
winner.remainingScore = 0;
|
||||||
|
winner.dartsThrown += dartsThrown;
|
||||||
|
|
||||||
// Update statistics
|
// Update statistics
|
||||||
updatePlayerStats(winner, dartsThrown, pointsMade, false, checkoutValue);
|
updatePlayerStats(winner, dartsThrown, pointsMade, false, checkoutValue);
|
||||||
|
|
||||||
// Record dart hits
|
// Record dart hits
|
||||||
recordTurnHitsToStatistics(winner, new ArrayList<>(mCurrentTurnDartHits));
|
recordTurnHitsToStatistics(winner, new ArrayList<>(mCurrentTurnDartHits));
|
||||||
|
|
||||||
// Increment matches played for all players
|
// Increment matches played for all players
|
||||||
incrementMatchesPlayed();
|
incrementMatchesPlayed();
|
||||||
|
|
||||||
// Clear turn state
|
// Clear turn state
|
||||||
mCurrentTurnDarts.clear();
|
mCurrentTurnDarts.clear();
|
||||||
mCurrentTurnDartHits.clear();
|
mCurrentTurnDartHits.clear();
|
||||||
|
|
||||||
// Save completed match
|
// Save completed match
|
||||||
saveCompletedMatch(winner);
|
saveCompletedMatch(winner);
|
||||||
|
|
||||||
// Mark match as completed
|
// Mark match as completed
|
||||||
mIsMatchCompleted = true;
|
mIsMatchCompleted = true;
|
||||||
|
|
||||||
// Notify UI
|
// Notify UI
|
||||||
if (mCallback != null) {
|
if (mCallback != null) {
|
||||||
mCallback.onPlayerWin(winner, checkoutValue);
|
mCallback.onPlayerWin(winner, checkoutValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Submits the current turn and advances to the next player.
|
* Submits the current turn and advances to the next player.
|
||||||
*/
|
*/
|
||||||
public void submitTurn() {
|
public void submitTurn() {
|
||||||
// Don't submit if no darts thrown
|
// Don't submit if no darts thrown
|
||||||
if (mCurrentTurnDarts.isEmpty()) return;
|
if (mCurrentTurnDarts.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
// Calculate turn total
|
// Calculate turn total
|
||||||
int turnTotal = 0;
|
int turnTotal = 0;
|
||||||
for (int d : mCurrentTurnDarts) turnTotal += d;
|
for (int d : mCurrentTurnDarts)
|
||||||
|
turnTotal += d;
|
||||||
|
|
||||||
PlayerState active = mPlayerStates.get(mActivePlayerIndex);
|
PlayerState active = mPlayerStates.get(mActivePlayerIndex);
|
||||||
|
|
||||||
// Calculate final score
|
// Calculate final score
|
||||||
int finalScore = active.remainingScore - turnTotal;
|
int finalScore = active.remainingScore - turnTotal;
|
||||||
|
|
||||||
// Check for 180
|
// Check for 180
|
||||||
if (finalScore > 0 && turnTotal == 180) {
|
if (finalScore > 0 && turnTotal == 180) {
|
||||||
notifyOneEighty();
|
notifyOneEighty();
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isBust = mIsBustedTurn;
|
boolean isBust = mIsBustedTurn;
|
||||||
|
|
||||||
// Update score only if not bust
|
// Update score only if not bust
|
||||||
if (!isBust) {
|
if (!isBust) {
|
||||||
active.remainingScore = finalScore;
|
active.remainingScore = finalScore;
|
||||||
active.dartsThrown += mCurrentTurnDarts.size();
|
active.dartsThrown += mCurrentTurnDarts.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePlayerStats(active, mCurrentTurnDarts.size(), turnTotal, isBust);
|
updatePlayerStats(active, mCurrentTurnDarts.size(), turnTotal, isBust);
|
||||||
|
|
||||||
// Record dart hits
|
// Record dart hits
|
||||||
recordTurnHitsToStatistics(active, new ArrayList<>(mCurrentTurnDartHits));
|
recordTurnHitsToStatistics(active, new ArrayList<>(mCurrentTurnDartHits));
|
||||||
|
|
||||||
// Rotate to next player
|
// Rotate to next player
|
||||||
mActivePlayerIndex = (mActivePlayerIndex + 1) % mPlayerStates.size();
|
mActivePlayerIndex = (mActivePlayerIndex + 1) % mPlayerStates.size();
|
||||||
|
|
||||||
// Reset turn state
|
// Reset turn state
|
||||||
mCurrentTurnDarts.clear();
|
mCurrentTurnDarts.clear();
|
||||||
mCurrentTurnDartHits.clear();
|
mCurrentTurnDartHits.clear();
|
||||||
mIsTurnOver = false;
|
mIsTurnOver = false;
|
||||||
mIsBustedTurn = false;
|
mIsBustedTurn = false;
|
||||||
|
|
||||||
// Save progress
|
// Save progress
|
||||||
saveMatchProgress();
|
saveMatchProgress();
|
||||||
|
|
||||||
// Notify UI
|
// Notify UI
|
||||||
notifyResetVisuals();
|
notifyResetVisuals();
|
||||||
notifyGameStateChanged();
|
notifyGameStateChanged();
|
||||||
notifyTurnIndicatorsChanged();
|
notifyTurnIndicatorsChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the most recently thrown dart from current turn.
|
* Removes the most recently thrown dart from current turn.
|
||||||
*/
|
*/
|
||||||
@@ -569,16 +589,16 @@ public class GameManager {
|
|||||||
if (!mCurrentTurnDarts.isEmpty()) {
|
if (!mCurrentTurnDarts.isEmpty()) {
|
||||||
mCurrentTurnDarts.remove(mCurrentTurnDarts.size() - 1);
|
mCurrentTurnDarts.remove(mCurrentTurnDarts.size() - 1);
|
||||||
mCurrentTurnDartHits.remove(mCurrentTurnDartHits.size() - 1);
|
mCurrentTurnDartHits.remove(mCurrentTurnDartHits.size() - 1);
|
||||||
|
|
||||||
mIsTurnOver = false;
|
mIsTurnOver = false;
|
||||||
mIsBustedTurn = false;
|
mIsBustedTurn = false;
|
||||||
|
|
||||||
notifyResetVisuals();
|
notifyResetVisuals();
|
||||||
notifyTurnIndicatorsChanged();
|
notifyTurnIndicatorsChanged();
|
||||||
notifyGameStateChanged();
|
notifyGameStateChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the current multiplier.
|
* Sets the current multiplier.
|
||||||
*
|
*
|
||||||
@@ -590,7 +610,7 @@ public class GameManager {
|
|||||||
mCallback.onMultiplierChanged(multiplier);
|
mCallback.onMultiplierChanged(multiplier);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves the current match progress to the database.
|
* Saves the current match progress to the database.
|
||||||
* Creates a MatchProgress snapshot of the current game state (player scores,
|
* Creates a MatchProgress snapshot of the current game state (player scores,
|
||||||
@@ -602,18 +622,17 @@ public class GameManager {
|
|||||||
progress.activePlayerIndex = mActivePlayerIndex;
|
progress.activePlayerIndex = mActivePlayerIndex;
|
||||||
progress.startingScore = mStartingScore;
|
progress.startingScore = mStartingScore;
|
||||||
progress.players = new ArrayList<>();
|
progress.players = new ArrayList<>();
|
||||||
|
|
||||||
for (PlayerState state : mPlayerStates) {
|
for (PlayerState state : mPlayerStates) {
|
||||||
progress.players.add(new MatchProgress.PlayerStateSnapshot(
|
progress.players.add(new MatchProgress.PlayerStateSnapshot(
|
||||||
state.playerId,
|
state.playerId,
|
||||||
state.name,
|
state.name,
|
||||||
state.remainingScore,
|
state.remainingScore,
|
||||||
state.dartsThrown
|
state.dartsThrown));
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String progressJson = MatchProgressConverter.fromProgress(progress);
|
String progressJson = MatchProgressConverter.fromProgress(progress);
|
||||||
|
|
||||||
if (mMatchId > 0) {
|
if (mMatchId > 0) {
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
try {
|
try {
|
||||||
@@ -632,7 +651,7 @@ public class GameManager {
|
|||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves the completed match to the database.
|
* Saves the completed match to the database.
|
||||||
* Marks the match as COMPLETED, saves the final game state, and updates
|
* Marks the match as COMPLETED, saves the final game state, and updates
|
||||||
@@ -641,32 +660,32 @@ public class GameManager {
|
|||||||
* @param winner The PlayerState of the winning player (used for logging)
|
* @param winner The PlayerState of the winning player (used for logging)
|
||||||
*/
|
*/
|
||||||
private void saveCompletedMatch(final PlayerState winner) {
|
private void saveCompletedMatch(final PlayerState winner) {
|
||||||
if (mMatchId <= 0) return;
|
if (mMatchId <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
try {
|
try {
|
||||||
Match match = mDatabaseHelper.getMatchById(mMatchId);
|
Match match = mDatabaseHelper.getMatchById(mMatchId);
|
||||||
if (match != null) {
|
if (match != null) {
|
||||||
match.state = Match.MatchState.COMPLETED;
|
match.state = Match.MatchState.COMPLETED;
|
||||||
match.timestamp = System.currentTimeMillis();
|
match.timestamp = System.currentTimeMillis();
|
||||||
|
|
||||||
final MatchProgress finalProgress = new MatchProgress();
|
final MatchProgress finalProgress = new MatchProgress();
|
||||||
finalProgress.activePlayerIndex = mActivePlayerIndex;
|
finalProgress.activePlayerIndex = mActivePlayerIndex;
|
||||||
finalProgress.startingScore = mStartingScore;
|
finalProgress.startingScore = mStartingScore;
|
||||||
finalProgress.players = new ArrayList<>();
|
finalProgress.players = new ArrayList<>();
|
||||||
|
|
||||||
for (PlayerState state : mPlayerStates) {
|
for (PlayerState state : mPlayerStates) {
|
||||||
finalProgress.players.add(new MatchProgress.PlayerStateSnapshot(
|
finalProgress.players.add(new MatchProgress.PlayerStateSnapshot(
|
||||||
state.playerId,
|
state.playerId,
|
||||||
state.name,
|
state.name,
|
||||||
state.remainingScore,
|
state.remainingScore,
|
||||||
state.dartsThrown
|
state.dartsThrown));
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
match.participantData = MatchProgressConverter.fromProgress(finalProgress);
|
match.participantData = MatchProgressConverter.fromProgress(finalProgress);
|
||||||
mDatabaseHelper.updateMatch(match);
|
mDatabaseHelper.updateMatch(match);
|
||||||
|
|
||||||
Log.d(TAG, "saveCompletedMatch: Match " + mMatchId + " marked as completed");
|
Log.d(TAG, "saveCompletedMatch: Match " + mMatchId + " marked as completed");
|
||||||
} else {
|
} else {
|
||||||
Log.e(TAG, "saveCompletedMatch: Match not found with ID: " + mMatchId);
|
Log.e(TAG, "saveCompletedMatch: Match not found with ID: " + mMatchId);
|
||||||
@@ -676,34 +695,35 @@ public class GameManager {
|
|||||||
}
|
}
|
||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates player statistics in the database after a turn.
|
* Updates player statistics in the database after a turn.
|
||||||
* Convenience method that delegates to the overloaded version with checkoutValue=0.
|
* Convenience method that delegates to the overloaded version with
|
||||||
|
* checkoutValue=0.
|
||||||
*
|
*
|
||||||
* @param active The PlayerState whose statistics should be updated
|
* @param active The PlayerState whose statistics should be updated
|
||||||
* @param dartsThrown Number of darts thrown in this turn
|
* @param dartsThrown Number of darts thrown in this turn
|
||||||
* @param pointsMade Total points scored in this turn
|
* @param pointsMade Total points scored in this turn
|
||||||
* @param wasBust Whether this turn resulted in a bust
|
* @param wasBust Whether this turn resulted in a bust
|
||||||
*/
|
*/
|
||||||
private void updatePlayerStats(final PlayerState active, final int dartsThrown, final int pointsMade,
|
private void updatePlayerStats(final PlayerState active, final int dartsThrown, final int pointsMade,
|
||||||
final boolean wasBust) {
|
final boolean wasBust) {
|
||||||
updatePlayerStats(active, dartsThrown, pointsMade, wasBust, 0);
|
updatePlayerStats(active, dartsThrown, pointsMade, wasBust, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates player statistics in the database with optional checkout value.
|
* Updates player statistics in the database with optional checkout value.
|
||||||
* Tracks darts thrown, points made, bust count, and successful checkouts.
|
* Tracks darts thrown, points made, bust count, and successful checkouts.
|
||||||
* Executes asynchronously on a background thread.
|
* Executes asynchronously on a background thread.
|
||||||
*
|
*
|
||||||
* @param active The PlayerState whose statistics should be updated
|
* @param active The PlayerState whose statistics should be updated
|
||||||
* @param dartsThrown Number of darts thrown in this turn
|
* @param dartsThrown Number of darts thrown in this turn
|
||||||
* @param pointsMade Total points scored in this turn
|
* @param pointsMade Total points scored in this turn
|
||||||
* @param wasBust Whether this turn resulted in a bust
|
* @param wasBust Whether this turn resulted in a bust
|
||||||
* @param checkoutValue The checkout score if this was a winning turn (0 if not)
|
* @param checkoutValue The checkout score if this was a winning turn (0 if not)
|
||||||
*/
|
*/
|
||||||
private void updatePlayerStats(final PlayerState active, final int dartsThrown, final int pointsMade,
|
private void updatePlayerStats(final PlayerState active, final int dartsThrown, final int pointsMade,
|
||||||
final boolean wasBust, final int checkoutValue) {
|
final boolean wasBust, final int checkoutValue) {
|
||||||
if (active.player != null && active.player.id != 0) {
|
if (active.player != null && active.player.id != 0) {
|
||||||
new Thread(() -> mDatabaseHelper.updatePlayerStatistics(
|
new Thread(() -> mDatabaseHelper.updatePlayerStatistics(
|
||||||
active.player.id,
|
active.player.id,
|
||||||
@@ -711,23 +731,23 @@ public class GameManager {
|
|||||||
pointsMade,
|
pointsMade,
|
||||||
wasBust,
|
wasBust,
|
||||||
checkoutValue,
|
checkoutValue,
|
||||||
active.dartsThrown
|
active.dartsThrown)).start();
|
||||||
)).start();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tracks a double-out attempt in player statistics.
|
* Tracks a double-out attempt in player statistics.
|
||||||
* Records whether a player attempted to finish on a double and whether
|
* Records whether a player attempted to finish on a double and whether
|
||||||
* they succeeded or missed. Executes asynchronously on a background thread.
|
* they succeeded or missed. Executes asynchronously on a background thread.
|
||||||
*
|
*
|
||||||
* @param playerState The PlayerState of the player who attempted the double
|
* @param playerState The PlayerState of the player who attempted the double
|
||||||
* @param isMissed true if the double-out attempt was missed, false if successful
|
* @param isMissed true if the double-out attempt was missed, false if
|
||||||
|
* successful
|
||||||
*/
|
*/
|
||||||
private void trackDoubleAttempt(final PlayerState playerState, final boolean isMissed) {
|
private void trackDoubleAttempt(final PlayerState playerState, final boolean isMissed) {
|
||||||
new Thread(() -> mDatabaseHelper.trackDoubleAttempt(playerState.playerId, isMissed)).start();
|
new Thread(() -> mDatabaseHelper.trackDoubleAttempt(playerState.playerId, isMissed)).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Increments matches played counter for all players in the current match.
|
* Increments matches played counter for all players in the current match.
|
||||||
* Called when a match is completed to update the match count for all
|
* Called when a match is completed to update the match count for all
|
||||||
@@ -740,7 +760,7 @@ public class GameManager {
|
|||||||
}
|
}
|
||||||
new Thread(() -> mDatabaseHelper.incrementMatchesPlayed(playerIds)).start();
|
new Thread(() -> mDatabaseHelper.incrementMatchesPlayed(playerIds)).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Records all dart hits from a confirmed turn to player statistics.
|
* Records all dart hits from a confirmed turn to player statistics.
|
||||||
* Updates the hit distribution map for heat map visualization.
|
* Updates the hit distribution map for heat map visualization.
|
||||||
@@ -748,19 +768,21 @@ public class GameManager {
|
|||||||
* Executes asynchronously on a background thread.
|
* Executes asynchronously on a background thread.
|
||||||
*
|
*
|
||||||
* @param playerState The PlayerState whose statistics should be updated
|
* @param playerState The PlayerState whose statistics should be updated
|
||||||
* @param dartHits List of DartHit objects representing the darts thrown in this turn
|
* @param dartHits List of DartHit objects representing the darts thrown in
|
||||||
|
* this turn
|
||||||
*/
|
*/
|
||||||
private void recordTurnHitsToStatistics(final PlayerState playerState, final List<DartHit> dartHits) {
|
private void recordTurnHitsToStatistics(final PlayerState playerState, final List<DartHit> dartHits) {
|
||||||
if (dartHits.isEmpty()) return;
|
if (dartHits.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
final List<DatabaseHelper.DartHit> dbDartHits = new ArrayList<>();
|
final List<DatabaseHelper.DartHit> dbDartHits = new ArrayList<>();
|
||||||
for (DartHit hit : dartHits) {
|
for (DartHit hit : dartHits) {
|
||||||
dbDartHits.add(new DatabaseHelper.DartHit(hit.baseValue, hit.multiplier));
|
dbDartHits.add(new DatabaseHelper.DartHit(hit.baseValue, hit.multiplier));
|
||||||
}
|
}
|
||||||
|
|
||||||
new Thread(() -> mDatabaseHelper.recordDartHits(playerState.playerId, dbDartHits)).start();
|
new Thread(() -> mDatabaseHelper.recordDartHits(playerState.playerId, dbDartHits)).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resets the game state for a new match.
|
* Resets the game state for a new match.
|
||||||
* This clears all current match data but keeps the singleton instance alive.
|
* This clears all current match data but keeps the singleton instance alive.
|
||||||
@@ -776,11 +798,11 @@ public class GameManager {
|
|||||||
mIsBustedTurn = false;
|
mIsBustedTurn = false;
|
||||||
mIsMatchCompleted = false;
|
mIsMatchCompleted = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========================================================================================
|
// ========================================================================================
|
||||||
// Getters for Game State
|
// Getters for Game State
|
||||||
// ========================================================================================
|
// ========================================================================================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the database ID of the current match.
|
* Gets the database ID of the current match.
|
||||||
*
|
*
|
||||||
@@ -789,7 +811,7 @@ public class GameManager {
|
|||||||
public int getMatchId() {
|
public int getMatchId() {
|
||||||
return mMatchId;
|
return mMatchId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the starting score for this X01 game.
|
* Gets the starting score for this X01 game.
|
||||||
*
|
*
|
||||||
@@ -798,7 +820,7 @@ public class GameManager {
|
|||||||
public int getStartingScore() {
|
public int getStartingScore() {
|
||||||
return mStartingScore;
|
return mStartingScore;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the index of the currently active player.
|
* Gets the index of the currently active player.
|
||||||
*
|
*
|
||||||
@@ -807,7 +829,7 @@ public class GameManager {
|
|||||||
public int getActivePlayerIndex() {
|
public int getActivePlayerIndex() {
|
||||||
return mActivePlayerIndex;
|
return mActivePlayerIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the current dart multiplier setting.
|
* Gets the current dart multiplier setting.
|
||||||
*
|
*
|
||||||
@@ -816,7 +838,7 @@ public class GameManager {
|
|||||||
public int getMultiplier() {
|
public int getMultiplier() {
|
||||||
return mMultiplier;
|
return mMultiplier;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a copy of all player states in this match.
|
* Gets a copy of all player states in this match.
|
||||||
* Returns a new list to prevent external modification of the internal state.
|
* Returns a new list to prevent external modification of the internal state.
|
||||||
@@ -826,17 +848,18 @@ public class GameManager {
|
|||||||
public List<PlayerState> getPlayerStates() {
|
public List<PlayerState> getPlayerStates() {
|
||||||
return new ArrayList<>(mPlayerStates);
|
return new ArrayList<>(mPlayerStates);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the currently active player's state.
|
* Gets the currently active player's state.
|
||||||
*
|
*
|
||||||
* @return The active PlayerState, or null if no players are loaded
|
* @return The active PlayerState, or null if no players are loaded
|
||||||
*/
|
*/
|
||||||
public PlayerState getActivePlayer() {
|
public PlayerState getActivePlayer() {
|
||||||
if (mPlayerStates.isEmpty()) return null;
|
if (mPlayerStates.isEmpty())
|
||||||
|
return null;
|
||||||
return mPlayerStates.get(mActivePlayerIndex);
|
return mPlayerStates.get(mActivePlayerIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a copy of the darts thrown in the current turn.
|
* Gets a copy of the darts thrown in the current turn.
|
||||||
* Returns a new list to prevent external modification.
|
* Returns a new list to prevent external modification.
|
||||||
@@ -846,7 +869,7 @@ public class GameManager {
|
|||||||
public List<Integer> getCurrentTurnDarts() {
|
public List<Integer> getCurrentTurnDarts() {
|
||||||
return new ArrayList<>(mCurrentTurnDarts);
|
return new ArrayList<>(mCurrentTurnDarts);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a copy of the detailed dart hits in the current turn.
|
* Gets a copy of the detailed dart hits in the current turn.
|
||||||
* Returns a new list to prevent external modification.
|
* Returns a new list to prevent external modification.
|
||||||
@@ -856,7 +879,7 @@ public class GameManager {
|
|||||||
public List<DartHit> getCurrentTurnDartHits() {
|
public List<DartHit> getCurrentTurnDartHits() {
|
||||||
return new ArrayList<>(mCurrentTurnDartHits);
|
return new ArrayList<>(mCurrentTurnDartHits);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the current turn is over (bust, win, or 3 darts thrown).
|
* Checks if the current turn is over (bust, win, or 3 darts thrown).
|
||||||
*
|
*
|
||||||
@@ -865,7 +888,7 @@ public class GameManager {
|
|||||||
public boolean isTurnOver() {
|
public boolean isTurnOver() {
|
||||||
return mIsTurnOver;
|
return mIsTurnOver;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the current turn resulted in a bust.
|
* Checks if the current turn resulted in a bust.
|
||||||
*
|
*
|
||||||
@@ -874,7 +897,7 @@ public class GameManager {
|
|||||||
public boolean isBustedTurn() {
|
public boolean isBustedTurn() {
|
||||||
return mIsBustedTurn;
|
return mIsBustedTurn;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the match has been completed (someone won).
|
* Checks if the match has been completed (someone won).
|
||||||
*
|
*
|
||||||
@@ -883,27 +906,31 @@ public class GameManager {
|
|||||||
public boolean isMatchCompleted() {
|
public boolean isMatchCompleted() {
|
||||||
return mIsMatchCompleted;
|
return mIsMatchCompleted;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates the current target score (remaining score minus current turn darts).
|
* Calculates the current target score (remaining score minus current turn
|
||||||
* If the turn is busted, returns the remaining score without subtracting bust darts
|
* darts).
|
||||||
|
* If the turn is busted, returns the remaining score without subtracting bust
|
||||||
|
* darts
|
||||||
* since those darts don't count.
|
* since those darts don't count.
|
||||||
*
|
*
|
||||||
* @return The effective remaining score after considering current turn darts
|
* @return The effective remaining score after considering current turn darts
|
||||||
*/
|
*/
|
||||||
public int getCurrentTarget() {
|
public int getCurrentTarget() {
|
||||||
PlayerState active = getActivePlayer();
|
PlayerState active = getActivePlayer();
|
||||||
if (active == null) return 0;
|
if (active == null)
|
||||||
|
return 0;
|
||||||
|
|
||||||
if (mIsBustedTurn) {
|
if (mIsBustedTurn) {
|
||||||
return active.remainingScore;
|
return active.remainingScore;
|
||||||
}
|
}
|
||||||
|
|
||||||
int turnPointsSoFar = 0;
|
int turnPointsSoFar = 0;
|
||||||
for (int d : mCurrentTurnDarts) turnPointsSoFar += d;
|
for (int d : mCurrentTurnDarts)
|
||||||
|
turnPointsSoFar += d;
|
||||||
return active.remainingScore - turnPointsSoFar;
|
return active.remainingScore - turnPointsSoFar;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the number of darts remaining in the current turn.
|
* Gets the number of darts remaining in the current turn.
|
||||||
* A turn consists of up to 3 darts.
|
* A turn consists of up to 3 darts.
|
||||||
@@ -913,11 +940,11 @@ public class GameManager {
|
|||||||
public int getDartsRemainingInTurn() {
|
public int getDartsRemainingInTurn() {
|
||||||
return 3 - mCurrentTurnDarts.size();
|
return 3 - mCurrentTurnDarts.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========================================================================================
|
// ========================================================================================
|
||||||
// Callback Notification Methods
|
// Callback Notification Methods
|
||||||
// ========================================================================================
|
// ========================================================================================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notifies the UI callback that the general game state has changed.
|
* Notifies the UI callback that the general game state has changed.
|
||||||
* Used for updating scores, player names, averages, and checkout suggestions.
|
* Used for updating scores, player names, averages, and checkout suggestions.
|
||||||
@@ -928,9 +955,10 @@ public class GameManager {
|
|||||||
mCallback.onGameStateChanged();
|
mCallback.onGameStateChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notifies the UI callback that the turn indicators (dart pills) should be updated.
|
* Notifies the UI callback that the turn indicators (dart pills) should be
|
||||||
|
* updated.
|
||||||
* Called whenever darts are thrown or undone.
|
* Called whenever darts are thrown or undone.
|
||||||
* Null-safe - does nothing if no callback is registered.
|
* Null-safe - does nothing if no callback is registered.
|
||||||
*/
|
*/
|
||||||
@@ -939,7 +967,7 @@ public class GameManager {
|
|||||||
mCallback.onTurnIndicatorsChanged();
|
mCallback.onTurnIndicatorsChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notifies the UI callback that a bust has occurred.
|
* Notifies the UI callback that a bust has occurred.
|
||||||
* Triggers bust animations, sounds, and visual feedback.
|
* Triggers bust animations, sounds, and visual feedback.
|
||||||
@@ -950,7 +978,7 @@ public class GameManager {
|
|||||||
mCallback.onBust();
|
mCallback.onBust();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notifies the UI callback that a perfect 180 was scored.
|
* Notifies the UI callback that a perfect 180 was scored.
|
||||||
* Triggers celebration animations, sounds, and vibrations.
|
* Triggers celebration animations, sounds, and vibrations.
|
||||||
@@ -961,7 +989,7 @@ public class GameManager {
|
|||||||
mCallback.onOneEightyScored();
|
mCallback.onOneEightyScored();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notifies the UI callback to reset visual effects (bust overlays, colors).
|
* Notifies the UI callback to reset visual effects (bust overlays, colors).
|
||||||
* Called when starting a new turn or undoing darts.
|
* Called when starting a new turn or undoing darts.
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import com.aldo.apps.ochecompanion.R;
|
|||||||
import com.aldo.apps.ochecompanion.database.objects.Match;
|
import com.aldo.apps.ochecompanion.database.objects.Match;
|
||||||
import com.aldo.apps.ochecompanion.ui.adapter.MainMenuGroupMatchAdapter;
|
import com.aldo.apps.ochecompanion.ui.adapter.MainMenuGroupMatchAdapter;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays summary of most recent match. Adapts display based on match type:
|
* Displays summary of most recent match. Adapts display based on match type:
|
||||||
* empty state (no matches), 1v1 state (2 players), or group state (3+ players).
|
* empty state (no matches), 1v1 state (2 players), or group state (3+ players).
|
||||||
@@ -35,24 +37,27 @@ public class MatchRecapView extends FrameLayout {
|
|||||||
private View mStateGroup;
|
private View mStateGroup;
|
||||||
|
|
||||||
// ========== 1v1 View References ==========
|
// ========== 1v1 View References ==========
|
||||||
|
|
||||||
/** Player 1 name in 1v1 match. */
|
/** Player 1 name in 1v1 match. */
|
||||||
private TextView mTvP1Name;
|
private TextView mTvP1Name;
|
||||||
|
|
||||||
/** Player 2 name in 1v1 match. */
|
/** Player 2 name in 1v1 match. */
|
||||||
private TextView mTvP2Name;
|
private TextView mTvP2Name;
|
||||||
|
|
||||||
/** Player 1 score in 1v1 match. */
|
/** Player 1 score in 1v1 match. */
|
||||||
private TextView mTvP1Score;
|
private TextView mTvP1Score;
|
||||||
|
|
||||||
/** Player 2 score in 1v1 match. */
|
/** Player 2 score in 1v1 match. */
|
||||||
private TextView mTvP2Score;
|
private TextView mTvP2Score;
|
||||||
|
|
||||||
// ========== Group View References ==========
|
// ========== Group View References ==========
|
||||||
|
|
||||||
/** RecyclerView displaying leaderboard for group matches. */
|
/** RecyclerView displaying leaderboard for group matches. */
|
||||||
private RecyclerView mRvLeaderboard;
|
private RecyclerView mRvLeaderboard;
|
||||||
|
|
||||||
|
/** Adapter for group match leaderboard, reused across updates. */
|
||||||
|
private MainMenuGroupMatchAdapter mGroupMatchAdapter;
|
||||||
|
|
||||||
/** Constructor for programmatic instantiation. */
|
/** Constructor for programmatic instantiation. */
|
||||||
public MatchRecapView(@NonNull final Context context) {
|
public MatchRecapView(@NonNull final Context context) {
|
||||||
this(context, null);
|
this(context, null);
|
||||||
@@ -75,7 +80,7 @@ public class MatchRecapView extends FrameLayout {
|
|||||||
mTvP1Score = findViewById(R.id.tvP1Score);
|
mTvP1Score = findViewById(R.id.tvP1Score);
|
||||||
mTvP2Name = findViewById(R.id.tvP2Name);
|
mTvP2Name = findViewById(R.id.tvP2Name);
|
||||||
mTvP2Score = findViewById(R.id.tvP2Score);
|
mTvP2Score = findViewById(R.id.tvP2Score);
|
||||||
|
|
||||||
mRvLeaderboard = findViewById(R.id.rvLeaderboard);
|
mRvLeaderboard = findViewById(R.id.rvLeaderboard);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,13 +99,34 @@ public class MatchRecapView extends FrameLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds match with pre-enriched participants (e.g., with profile pictures from
|
||||||
|
* database).
|
||||||
|
* Use this when participants have already been enriched to avoid re-parsing
|
||||||
|
* JSON.
|
||||||
|
*/
|
||||||
|
public void setMatchWithParticipants(@Nullable final Match match,
|
||||||
|
@Nullable final List<Match.ParticipantData> enrichedParticipants) {
|
||||||
|
Log.d(TAG, "setMatchWithParticipants() called with: match = [" + match + "]");
|
||||||
|
if (match == null) {
|
||||||
|
updateVisibility(mStateEmpty);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match.getParticipantCount() > 2) {
|
||||||
|
setupGroupStateWithParticipants(enrichedParticipants);
|
||||||
|
} else {
|
||||||
|
setup1v1State(match);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Configures 1v1 state with player names and scores. */
|
/** Configures 1v1 state with player names and scores. */
|
||||||
private void setup1v1State(final Match match) {
|
private void setup1v1State(final Match match) {
|
||||||
updateVisibility(mState1v1);
|
updateVisibility(mState1v1);
|
||||||
|
|
||||||
mTvP1Name.setText(match.getPlayerNameByPosition(0));
|
mTvP1Name.setText(match.getPlayerNameByPosition(0));
|
||||||
mTvP1Score.setText(String.valueOf(match.getPlayerScoreByPosition(0)));
|
mTvP1Score.setText(String.valueOf(match.getPlayerScoreByPosition(0)));
|
||||||
|
|
||||||
mTvP2Name.setText(match.getPlayerNameByPosition(1));
|
mTvP2Name.setText(match.getPlayerNameByPosition(1));
|
||||||
mTvP2Score.setText(String.valueOf(match.getPlayerScoreByPosition(1)));
|
mTvP2Score.setText(String.valueOf(match.getPlayerScoreByPosition(1)));
|
||||||
}
|
}
|
||||||
@@ -108,12 +134,31 @@ public class MatchRecapView extends FrameLayout {
|
|||||||
/** Configures group state with leaderboard RecyclerView. */
|
/** Configures group state with leaderboard RecyclerView. */
|
||||||
private void setupGroupState(final Match match) {
|
private void setupGroupState(final Match match) {
|
||||||
updateVisibility(mStateGroup);
|
updateVisibility(mStateGroup);
|
||||||
|
|
||||||
mRvLeaderboard.setLayoutManager(new LinearLayoutManager(getContext()));
|
// Initialize adapter and layout manager only once
|
||||||
|
if (mGroupMatchAdapter == null) {
|
||||||
final MainMenuGroupMatchAdapter adapter = new MainMenuGroupMatchAdapter();
|
mGroupMatchAdapter = new MainMenuGroupMatchAdapter();
|
||||||
mRvLeaderboard.setAdapter(adapter);
|
mRvLeaderboard.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
adapter.updateMatch(match);
|
mRvLeaderboard.setAdapter(mGroupMatchAdapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the adapter with new match data
|
||||||
|
mGroupMatchAdapter.updateMatch(match);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Configures group state with pre-enriched participants. */
|
||||||
|
private void setupGroupStateWithParticipants(final List<Match.ParticipantData> enrichedParticipants) {
|
||||||
|
updateVisibility(mStateGroup);
|
||||||
|
|
||||||
|
// Initialize adapter and layout manager only once
|
||||||
|
if (mGroupMatchAdapter == null) {
|
||||||
|
mGroupMatchAdapter = new MainMenuGroupMatchAdapter();
|
||||||
|
mRvLeaderboard.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
|
mRvLeaderboard.setAdapter(mGroupMatchAdapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the adapter with enriched participant data
|
||||||
|
mGroupMatchAdapter.updateParticipants(enrichedParticipants);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Shows only the specified state container, hides others. */
|
/** Shows only the specified state container, hides others. */
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.aldo.apps.ochecompanion.ui;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
import android.util.Log;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
@@ -13,11 +14,18 @@ import com.google.android.material.imageview.ShapeableImageView;
|
|||||||
import com.aldo.apps.ochecompanion.R;
|
import com.aldo.apps.ochecompanion.R;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reusable MaterialCardView for displaying player info: profile picture, username,
|
* Reusable MaterialCardView for displaying player info: profile picture,
|
||||||
* and career statistics. Uses Glide for image loading with fallback to default icon.
|
* username,
|
||||||
|
* and career statistics. Uses Glide for image loading with fallback to default
|
||||||
|
* icon.
|
||||||
*/
|
*/
|
||||||
public class PlayerItemView extends MaterialCardView {
|
public class PlayerItemView extends MaterialCardView {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tag for debugging purposes.
|
||||||
|
*/
|
||||||
|
private static final String TAG = "PlayerItemView";
|
||||||
|
|
||||||
/** Player profile picture (circular, loaded via Glide). */
|
/** Player profile picture (circular, loaded via Glide). */
|
||||||
private ShapeableImageView mIvAvatar;
|
private ShapeableImageView mIvAvatar;
|
||||||
|
|
||||||
@@ -50,32 +58,35 @@ public class PlayerItemView extends MaterialCardView {
|
|||||||
mTvStats = findViewById(R.id.tvPlayerAvg);
|
mTvStats = findViewById(R.id.tvPlayerAvg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Binds player data to view components (username, stats, avatar).
|
* Binds player data to view components (username, stats, avatar).
|
||||||
|
*
|
||||||
* @param player The Player object containing data to display
|
* @param player The Player object containing data to display
|
||||||
*/
|
*/
|
||||||
public void bind(@NonNull final Player player) {
|
public void bind(@NonNull final Player player) {
|
||||||
mTvUsername.setText(player.username);
|
mTvUsername.setText(player.username);
|
||||||
mTvStats.setText(String.format(
|
mTvStats.setText(String.format(
|
||||||
getContext().getString(R.string.txt_player_average_base),
|
getContext().getString(R.string.txt_player_average_base),
|
||||||
player.careerAverage));
|
player.careerAverage));
|
||||||
|
|
||||||
if (player.profilePictureUri != null) {
|
if (player.profilePictureUri != null) {
|
||||||
Glide.with(getContext())
|
Glide.with(getContext())
|
||||||
.load(player.profilePictureUri)
|
.load(player.profilePictureUri)
|
||||||
.into(mIvAvatar);
|
.into(mIvAvatar);
|
||||||
} else {
|
} else {
|
||||||
mIvAvatar.setImageResource(R.drawable.ic_users);
|
mIvAvatar.setImageResource(R.drawable.ic_users);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Binds player data along with a specific score (e.g., match score) instead of career average.
|
* Binds player data along with a specific score (e.g., match score) instead of
|
||||||
|
* career average.
|
||||||
|
*
|
||||||
* @param player The Player object containing data to display
|
* @param player The Player object containing data to display
|
||||||
* @param score The specific score to display (e.g., current match score)
|
* @param score The specific score to display (e.g., current match score)
|
||||||
*/
|
*/
|
||||||
public void bindWithScore(@NonNull final Player player, final int score) {
|
public void bindWithScore(@NonNull final Player player, final int score) {
|
||||||
|
Log.d(TAG, "bindWithScore() called with: player = [" + player + "], score = [" + score + "]");
|
||||||
mTvUsername.setText(player.username);
|
mTvUsername.setText(player.username);
|
||||||
// Display match score instead of career average
|
// Display match score instead of career average
|
||||||
mTvStats.setText(String.valueOf(score));
|
mTvStats.setText(String.valueOf(score));
|
||||||
@@ -89,4 +100,71 @@ public class PlayerItemView extends MaterialCardView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void bindWithGameAverage(@NonNull final Player player, final double gameAverage) {
|
||||||
|
Log.d(TAG, "bindWithGameAverage() called with: player = [" + player
|
||||||
|
+ "], gameAverage = [" + gameAverage + "]");
|
||||||
|
mTvUsername.setText(player.username);
|
||||||
|
// Display game average
|
||||||
|
mTvStats.setText(String.format(
|
||||||
|
getContext().getString(R.string.txt_player_average_base), gameAverage));
|
||||||
|
|
||||||
|
if (player.profilePictureUri != null) {
|
||||||
|
Glide.with(getContext())
|
||||||
|
.load(player.profilePictureUri)
|
||||||
|
.placeholder(R.drawable.ic_users)
|
||||||
|
.into(mIvAvatar);
|
||||||
|
} else {
|
||||||
|
mIvAvatar.setImageResource(R.drawable.ic_users);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds player data with game average and position indicator.
|
||||||
|
*
|
||||||
|
* @param player The Player object containing data to display
|
||||||
|
* @param gameAverage The game average to display (points per 3 darts)
|
||||||
|
* @param position The player's position (1 = 1st place, 2 = 2nd place, etc.)
|
||||||
|
* @param isWinner Whether this player won (remaining score = 0)
|
||||||
|
*/
|
||||||
|
public void bindWithGameAverageAndPosition(@NonNull final Player player, final double gameAverage,
|
||||||
|
final int position, final boolean isWinner) {
|
||||||
|
Log.d(TAG, "bindWithGameAverageAndPosition() called with: player = [" + player
|
||||||
|
+ "], gameAverage = [" + gameAverage + "], position = [" + position + "], isWinner = [" + isWinner
|
||||||
|
+ "]");
|
||||||
|
|
||||||
|
// Build username with position indicator
|
||||||
|
final String positionSuffix = getPositionSuffix(position);
|
||||||
|
final String displayName = isWinner
|
||||||
|
? "🏆 " + player.username + " (" + positionSuffix + ")"
|
||||||
|
: player.username + " (" + positionSuffix + ")";
|
||||||
|
|
||||||
|
mTvUsername.setText(displayName);
|
||||||
|
|
||||||
|
// Display game average
|
||||||
|
mTvStats.setText(String.format(
|
||||||
|
getContext().getString(R.string.txt_player_average_base), gameAverage));
|
||||||
|
|
||||||
|
if (player.profilePictureUri != null) {
|
||||||
|
Glide.with(getContext())
|
||||||
|
.load(player.profilePictureUri)
|
||||||
|
.placeholder(R.drawable.ic_users)
|
||||||
|
.into(mIvAvatar);
|
||||||
|
} else {
|
||||||
|
mIvAvatar.setImageResource(R.drawable.ic_users);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the position suffix (1st, 2nd, 3rd, 4th, etc.)
|
||||||
|
*/
|
||||||
|
private String getPositionSuffix(final int position) {
|
||||||
|
if (position == 1)
|
||||||
|
return "1st";
|
||||||
|
if (position == 2)
|
||||||
|
return "2nd";
|
||||||
|
if (position == 3)
|
||||||
|
return "3rd";
|
||||||
|
return position + "th";
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ import java.util.List;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* RecyclerView adapter for displaying group match results in Main Menu.
|
* RecyclerView adapter for displaying group match results in Main Menu.
|
||||||
* Displays participants sorted by match score (descending) with their names, scores, and profile pictures.
|
* Displays participants sorted by match score (descending) with their names,
|
||||||
|
* scores, and profile pictures.
|
||||||
*/
|
*/
|
||||||
public class MainMenuGroupMatchAdapter extends RecyclerView.Adapter<MainMenuGroupMatchAdapter.GroupMatchHolder> {
|
public class MainMenuGroupMatchAdapter extends RecyclerView.Adapter<MainMenuGroupMatchAdapter.GroupMatchHolder> {
|
||||||
|
|
||||||
@@ -34,7 +35,7 @@ public class MainMenuGroupMatchAdapter extends RecyclerView.Adapter<MainMenuGrou
|
|||||||
/**
|
/**
|
||||||
* Creates a new ViewHolder with a PlayerItemView.
|
* Creates a new ViewHolder with a PlayerItemView.
|
||||||
*
|
*
|
||||||
* @param parent The parent ViewGroup.
|
* @param parent The parent ViewGroup.
|
||||||
* @param viewType The view type (unused).
|
* @param viewType The view type (unused).
|
||||||
* @return A new GroupMatchHolder.
|
* @return A new GroupMatchHolder.
|
||||||
*/
|
*/
|
||||||
@@ -42,27 +43,28 @@ public class MainMenuGroupMatchAdapter extends RecyclerView.Adapter<MainMenuGrou
|
|||||||
@Override
|
@Override
|
||||||
public GroupMatchHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
|
public GroupMatchHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
|
||||||
// Create a new PlayerItemView for displaying player information
|
// Create a new PlayerItemView for displaying player information
|
||||||
final PlayerItemView itemView = new PlayerItemView(parent.getContext());
|
final PlayerItemView itemView = new PlayerItemView(parent.getContext());
|
||||||
|
|
||||||
// Configure layout parameters for the item view
|
// Configure layout parameters for the item view
|
||||||
itemView.setLayoutParams(new RecyclerView.LayoutParams(
|
itemView.setLayoutParams(new RecyclerView.LayoutParams(
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||||
));
|
|
||||||
|
|
||||||
return new GroupMatchHolder(itemView);
|
return new GroupMatchHolder(itemView);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Binds player data to the ViewHolder at the specified position.
|
* Binds player data to the ViewHolder at the specified position.
|
||||||
*
|
*
|
||||||
* @param holder The ViewHolder to update.
|
* @param holder The ViewHolder to update.
|
||||||
* @param position The position in the data set.
|
* @param position The position in the data set.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull final GroupMatchHolder holder, final int position) {
|
public void onBindViewHolder(@NonNull final GroupMatchHolder holder, final int position) {
|
||||||
// Retrieve the participant at this position and bind it to the holder
|
// Retrieve the participant at this position and bind it to the holder
|
||||||
holder.setParticipant(mParticipantsList.get(position));
|
// Position is 0-indexed, but we want 1-indexed for display (1st, 2nd, 3rd,
|
||||||
|
// etc.)
|
||||||
|
holder.setParticipant(mParticipantsList.get(position), position + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -76,7 +78,8 @@ public class MainMenuGroupMatchAdapter extends RecyclerView.Adapter<MainMenuGrou
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the adapter with match data, sorting participants by match score (descending).
|
* Updates the adapter with match data, sorting participants by match score
|
||||||
|
* (descending).
|
||||||
*
|
*
|
||||||
* @param match The match containing participants to display.
|
* @param match The match containing participants to display.
|
||||||
*/
|
*/
|
||||||
@@ -95,17 +98,46 @@ public class MainMenuGroupMatchAdapter extends RecyclerView.Adapter<MainMenuGrou
|
|||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort participants by match score (highest to lowest)
|
// Sort participants by remaining score (lowest to highest = best to worst)
|
||||||
participants.sort((p1, p2) -> Integer.compare(p2.score, p1.score));
|
// In darts, 0 remaining = winner, so lowest score = best placement
|
||||||
|
participants.sort((p1, p2) -> Integer.compare(p1.score, p2.score));
|
||||||
|
|
||||||
// Add sorted participants to the display list
|
// Add sorted participants to the display list
|
||||||
mParticipantsList.addAll(participants);
|
mParticipantsList.addAll(participants);
|
||||||
|
|
||||||
// Notify RecyclerView to refresh the display
|
// Notify RecyclerView to refresh the display
|
||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the adapter with pre-enriched participant data (e.g., with profile
|
||||||
|
* pictures).
|
||||||
|
* Use this when participants have already been enriched with database data.
|
||||||
|
*
|
||||||
|
* @param participants Pre-enriched participant list to display.
|
||||||
|
*/
|
||||||
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
|
public void updateParticipants(final List<Match.ParticipantData> participants) {
|
||||||
|
// Clear any existing participant data
|
||||||
|
mParticipantsList.clear();
|
||||||
|
|
||||||
|
if (participants == null || participants.isEmpty()) {
|
||||||
|
Log.d(TAG, "updateParticipants: No participants provided, just clearing.");
|
||||||
|
notifyDataSetChanged();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort participants by remaining score (lowest to highest = best to worst)
|
||||||
|
// In darts, 0 remaining = winner, so lowest score = best placement
|
||||||
|
participants.sort((p1, p2) -> Integer.compare(p1.score, p2.score));
|
||||||
|
|
||||||
|
// Add sorted participants to the display list
|
||||||
|
mParticipantsList.addAll(participants);
|
||||||
|
|
||||||
|
// Notify RecyclerView to refresh the display
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ViewHolder for displaying player items in group match view.
|
* ViewHolder for displaying player items in group match view.
|
||||||
@@ -126,20 +158,25 @@ public class MainMenuGroupMatchAdapter extends RecyclerView.Adapter<MainMenuGrou
|
|||||||
public GroupMatchHolder(@NonNull final PlayerItemView itemView) {
|
public GroupMatchHolder(@NonNull final PlayerItemView itemView) {
|
||||||
super(itemView);
|
super(itemView);
|
||||||
mItemView = itemView;
|
mItemView = itemView;
|
||||||
|
|
||||||
// Hide the chevron icon as group match items are not interactive
|
// Hide the chevron icon as group match items are not interactive
|
||||||
itemView.findViewById(R.id.ivChevron).setVisibility(View.GONE);
|
itemView.findViewById(R.id.ivChevron).setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Binds participant data (player + match score) to this ViewHolder.
|
* Binds participant data (player + match score + game average + position) to
|
||||||
|
* this
|
||||||
|
* ViewHolder.
|
||||||
*
|
*
|
||||||
* @param participantData The participant data to display.
|
* @param participantData The participant data to display.
|
||||||
|
* @param position The player's position (1 = 1st place, 2 = 2nd place,
|
||||||
|
* etc.)
|
||||||
*/
|
*/
|
||||||
public void setParticipant(final Match.ParticipantData participantData) {
|
public void setParticipant(final Match.ParticipantData participantData, final int position) {
|
||||||
final Player player = participantData.player;
|
final Player player = participantData.player;
|
||||||
final int score = participantData.score;
|
final double gameAverage = participantData.gameAverage;
|
||||||
mItemView.bindWithScore(player, score);
|
final boolean isWinner = participantData.score == 0; // Winner has 0 remaining score
|
||||||
|
mItemView.bindWithGameAverageAndPosition(player, gameAverage, position, isWinner);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user