feat: Implement comprehensive player statistics with hit distribution heatmap
This commit introduces a complete statistics tracking and visualization system for the Oche Companion darts app, addressing multiple race condition issues and enhancing user experience with audio/vibration feedback controls. MAJOR FEATURES: Statistics & Database (Breaking Changes) - Upgraded Room database from v10 to v12 with destructive migration - Added hit distribution map (Map<String, Integer>) to Statistics entity - Implemented HitDistributionConverter with Gson for Map<->JSON persistence - Registered TypeConverter at database level for automatic conversion - Expanded Statistics entity to track: * Scoring milestones (60+, 100+, 140+, 180) * First 9 darts average (starting consistency metric) * Checkout statistics (successful finishes, highest checkout) * Double-out attempt tracking (success/miss rates) * Hit distribution for heat map visualization * Matches played counter GameActivity Enhancements - Added DartHit inner class to track baseValue and multiplier per dart - Implemented parallel tracking with mCurrentTurnDartHits list - Created recordTurnHitsToStatistics() for hit distribution updates - Added trackDoubleAttempt() for double-out success/failure recording - Added incrementMatchesPlayed() called on match completion - Fixed checkout value calculation (final dart, not full turn score) - Fixed bust tracking (addMissedDarts instead of saveDartsThrown) - Renamed Statistics getters/setters for consistency: * dartsThrown -> totalDartsThrown * overallPointsMade -> totalPoints * doubleOutsTargeted -> totalDartsAtDouble CRITICAL BUG FIXES: Race Condition Resolution - Fixed list clearing race: Pass copies (new ArrayList<>) to background threads - Fixed database race: Added per-player synchronization locks (mPlayerStatsLocks HashMap) - Implemented double synchronized block pattern: 1. synchronized(mPlayerStatsLocks) to get/create player lock 2. synchronized(lock) to protect entire READ-MODIFY-WRITE operation - Allows concurrent updates for different players while preventing data corruption User Feedback System - Added audio/vibration feedback toggle preferences - Implemented SharedPreferences reading in onResume() - Added conditional checks (mIsAudioEnabled, mIsVibrationEnabled) throughout - Created preference UI with toggle buttons and dynamic icons - Added Day/Night auto mode with mutual exclusion logic Visualization Components - Created HeatmapView custom view extending View - Implements dartboard rendering with Canvas path calculations - Color interpolation from cold (faded) to hot (volt green) - Splits board into concentric rings: doubles, outer singles, triples, inner singles - Added TestActivity for heatmap development/debugging - Added navigation from MainMenuActivity title click to TestActivity UI/UX Improvements - Added vector drawables for audio/vibration states (on/off) - Enhanced preferences screen with categorized sections - Improved settings fragment with preference interaction logic - Added Gson dependency (v2.13.2) for JSON serialization Code Quality - Added comprehensive JavaDoc to all new Statistics methods - Made all method parameters final for immutability - Added detailed logging for statistics operations - Improved error handling with try-catch blocks in background threads TECHNICAL NOTES: - Database migration is DESTRUCTIVE (all data lost on schema change) - Per-player locks enable parallel statistics updates across players - Hit distribution keys use format: "t20", "d16", "s5", "sb", "db" - Heatmap normalizes weights against max hits for consistent coloring - Statistics now tracks 15+ distinct performance metrics TESTING RECOMMENDATIONS: - Verify hit distribution persists correctly across matches - Test concurrent multi-player statistics updates - Confirm checkout values reflect final dart, not turn total - Validate milestone counters increment accurately - Test heatmap visualization with varied hit patterns
This commit is contained in:
11
app/src/main/res/drawable/ic_audio_off.xml
Normal file
11
app/src/main/res/drawable/ic_audio_off.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M16.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v2.21l2.45,2.45c0.03,-0.2 0.05,-0.41 0.05,-0.63zM19,12c0,0.94 -0.2,1.82 -0.54,2.64l1.51,1.51C20.63,14.91 21,13.5 21,12c0,-4.28 -2.99,-7.86 -7,-8.77v2.06c2.89,0.86 5,3.54 5,6.71zM4.27,3L3,4.27 7.73,9H3v6h4l5,5v-6.73l4.25,4.25c-0.67,0.52 -1.42,0.93 -2.25,1.18v2.06c1.38,-0.31 2.63,-0.95 3.69,-1.81L19.73,21 21,19.73l-9,-9L4.27,3zM12,4L9.91,6.09 12,8.18V4z"/>
|
||||
</vector>
|
||||
11
app/src/main/res/drawable/ic_audio_on.xml
Normal file
11
app/src/main/res/drawable/ic_audio_on.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M3,9v6h4l5,5V4L7,9H3zM16.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v8.05c1.48,-0.73 2.5,-2.25 2.5,-4.02zM14,3.23v2.06c2.89,0.86 5,3.54 5,6.71s-2.11,5.85 -5,6.71v2.06c4.01,-0.91 7,-4.49 7,-8.77s-2.99,-7.86 -7,-8.77z"/>
|
||||
</vector>
|
||||
11
app/src/main/res/drawable/ic_vibration_off.xml
Normal file
11
app/src/main/res/drawable/ic_vibration_off.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M16.5,3h-9C6.67,3 6,3.67 6,4.5v15c0,0.83 0.67,1.5 1.5,1.5h9c0.83,0 1.5,-0.67 1.5,-1.5v-15c0,-0.83 -0.67,-1.5 -1.5,-1.5zM16,19H8V5h8v14z"/>
|
||||
</vector>
|
||||
11
app/src/main/res/drawable/ic_vibration_on.xml
Normal file
11
app/src/main/res/drawable/ic_vibration_on.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M0,15h2V9H0v6zm3,2h2V7H3v10zm19,-8v6h2V9h-2zm-3,8h2V7h-2v10zM16.5,3h-9C6.67,3 6,3.67 6,4.5v15c0,0.83 0.67,1.5 1.5,1.5h9c0.83,0,1.5,-0.67 1.5,-1.5v-15c0,-0.83 -0.67,-1.5 -1.5,-1.5zM16,19H8V5h8v14z"/>
|
||||
</vector>
|
||||
@@ -26,6 +26,7 @@
|
||||
android:contentDescription="@string/cd_txt_oche_logo"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title_view"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/spacing_s"
|
||||
|
||||
16
app/src/main/res/layout/activity_test.xml
Normal file
16
app/src/main/res/layout/activity_test.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/main"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".TestActivity">
|
||||
|
||||
<com.aldo.apps.ochecompanion.ui.HeatmapView
|
||||
android:id="@+id/heatmap"
|
||||
android:layout_width="300dp"
|
||||
android:layout_height="300dp"
|
||||
android:layout_gravity="center" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -34,13 +34,21 @@
|
||||
<string name="txt_game_btn_submit">Submit Turn</string>
|
||||
|
||||
<!-- Preference Strings -->
|
||||
<string name="pref_key_day_night_mode_auto">day_night_mode_auto</string>
|
||||
<string name="pref_key_day_night_mode">day_night_mode</string>
|
||||
<string name="pref_key_standard_game_mode">standard_game_mode</string>
|
||||
<string name="pref_key_audio_feedback">audio_feedback</string>
|
||||
<string name="pref_key_vibration_feedback">vibration_feedback</string>
|
||||
<string name="pref_game_mode_701_value">Standard 701 - Double Out</string>
|
||||
<string name="pref_game_mode_501_value">Standard 501 - Double Out</string>
|
||||
<string name="pref_game_mode_301_value">Standard 301 - Double Out</string>
|
||||
<string name="pref_game_mode_cricket_value">Cricket</string>
|
||||
<string name="pref_desc_day_night_mode">Day/Night Mode</string>
|
||||
<string name="pref_desc_day_night_mode_auto">Day/Night Mode (Automatic)</string>
|
||||
<string name="pref_desc_day_night_mode">Manual Day/Night Mode</string>
|
||||
<string name="pref_title_audio_feedback">Audio Feedback</string>
|
||||
<string name="pref_desc_audio_feedback">Toggle announcer sounds</string>
|
||||
<string name="pref_title_vibration_feedback">Vibration Feedback</string>
|
||||
<string name="pref_desc_vibration_feedback">Toggle haptic effects</string>
|
||||
<string name="pref_title_standard_game_mode">Standard Game Mode</string>
|
||||
<string name="pref_desc_standard_game_mode">The Standard Game Mode to be selected for the Quick Start\nCurrently selected: %s</string>
|
||||
|
||||
|
||||
@@ -2,11 +2,6 @@
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<SwitchPreference
|
||||
app:key="@string/pref_key_day_night_mode"
|
||||
app:title="@string/pref_desc_day_night_mode"
|
||||
android:icon="@drawable/ic_day_night_mode"/>
|
||||
|
||||
<ListPreference
|
||||
app:key="@string/pref_key_standard_game_mode"
|
||||
app:title="@string/pref_title_standard_game_mode"
|
||||
@@ -16,4 +11,31 @@
|
||||
android:entries="@array/pref_standard_game_mode_labels"
|
||||
android:entryValues="@array/pref_standard_game_mode_values" />
|
||||
|
||||
<PreferenceCategory app:title="Game Feedback">
|
||||
<Preference
|
||||
app:key="@string/pref_key_audio_feedback"
|
||||
app:title="@string/pref_title_audio_feedback"
|
||||
app:summary="@string/pref_desc_audio_feedback"
|
||||
android:icon="@drawable/ic_audio_on" />
|
||||
|
||||
<Preference
|
||||
app:key="@string/pref_key_vibration_feedback"
|
||||
app:title="@string/pref_title_vibration_feedback"
|
||||
app:summary="@string/pref_desc_vibration_feedback"
|
||||
android:icon="@drawable/ic_vibration_on" />
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory app:title="Appearance">
|
||||
<SwitchPreference
|
||||
app:key="@string/pref_key_day_night_mode_auto"
|
||||
app:title="@string/pref_desc_day_night_mode_auto"
|
||||
android:defaultValue="true"
|
||||
android:icon="@drawable/ic_day_night_mode" />
|
||||
|
||||
<SwitchPreference
|
||||
app:key="@string/pref_key_day_night_mode"
|
||||
app:title="@string/pref_desc_day_night_mode"
|
||||
android:icon="@drawable/ic_day_night_mode" />
|
||||
</PreferenceCategory>
|
||||
|
||||
</PreferenceScreen>
|
||||
Reference in New Issue
Block a user