diff --git a/README.md b/README.md index eb4281f..3e4bad7 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,6 @@ A modern falling block puzzle game for Android, featuring smooth animations, res - Hard drop and soft drop controls - Advanced move detection (e.g., T-Spins) and scoring - Persistent high score system (Top 5) -- Global leaderboard via Google Play Games Services ### Modern Android Features - Optimized for Android 11+ (API 30+) @@ -21,7 +20,6 @@ A modern falling block puzzle game for Android, featuring smooth animations, res - Automatic Dark theme support - Intuitive and responsive touch controls - Full edge-to-edge display utilization -- Google Play Games Services integration for leaderboards ### Scoring System @@ -76,12 +74,6 @@ Pixelmint Drop features a comprehensive scoring system designed to reward skillf - Multiple theme options with ability to change manually or enable Random Mode (unlocked when 2+ themes are available). - In Random Mode, themes change automatically every 10 line clears (1 level). -### Online Features -- Global leaderboard support through Google Play Games Services -- Sign in with your Google account to compete with players worldwide -- Your highest scores are automatically submitted to the leaderboard -- View the global leaderboard directly from the game - ## Technical Details ### Requirements diff --git a/app/build.gradle b/app/build.gradle index 872d5a3..0466731 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -11,8 +11,8 @@ android { applicationId "com.pixelmintdrop" minSdk 30 targetSdk 35 - versionCode 5 - versionName "0.4" + versionCode 2 + versionName "0.1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } @@ -63,8 +63,6 @@ dependencies { implementation 'androidx.window:window:1.2.0' // For better display support implementation 'androidx.window:window-java:1.2.0' implementation 'com.google.code.gson:gson:2.10.1' - implementation 'com.google.android.gms:play-services-games-v2:19.0.0' - implementation 'com.google.android.gms:play-services-auth:20.7.0' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 911f971..f3d41de 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,8 +2,6 @@ - - - - Color.WHITE } - // Apply theme to buttons + // Apply theme to back button binding.backButton.setTextColor(textColor) // Update adapter theme @@ -132,35 +122,6 @@ class HighScoresActivity : AppCompatActivity() { Log.e("HighScoresActivity", "Error updating high scores", e) } } - - private fun showGlobalLeaderboard() { - try { - if (!highScoreManager.isGooglePlaySignedIn()) { - Toast.makeText(this, "Signing in to Google Play Games...", Toast.LENGTH_SHORT).show() - } - - // Call the updated showLeaderboard with a listener - highScoreManager.getGooglePlayGamesManager().showLeaderboard(this, - object : GooglePlayGamesManager.LeaderboardIntentListener { - override fun onLeaderboardIntentSuccess(intent: Intent) { - try { - startActivityForResult(intent, RC_LEADERBOARD_UI_HS) - } catch (e: Exception) { - Log.e("HighScoresActivity", "Failed to start leaderboard activity for result", e) - Toast.makeText(this@HighScoresActivity, "Could not display leaderboard.", Toast.LENGTH_SHORT).show() - } - } - - override fun onLeaderboardIntentFailure(exception: Exception) { - Log.e("HighScoresActivity", "Failed to get leaderboard intent.", exception) - Toast.makeText(this@HighScoresActivity, "Could not display leaderboard.", Toast.LENGTH_SHORT).show() - } - }) - } catch (e: Exception) { - Log.e("HighScoresActivity", "Error showing leaderboard", e) - Toast.makeText(this, "Could not open leaderboard", Toast.LENGTH_SHORT).show() - } - } override fun onResume() { super.onResume() @@ -182,11 +143,6 @@ class HighScoresActivity : AppCompatActivity() { finish() return true } - KeyEvent.KEYCODE_BUTTON_Y -> { - // Y button shows global leaderboard - showGlobalLeaderboard() - return true - } } } diff --git a/app/src/main/java/com/pixelmintdrop/MainActivity.kt b/app/src/main/java/com/pixelmintdrop/MainActivity.kt index 1bf5875..5701d19 100644 --- a/app/src/main/java/com/pixelmintdrop/MainActivity.kt +++ b/app/src/main/java/com/pixelmintdrop/MainActivity.kt @@ -55,14 +55,6 @@ import androidx.core.view.updatePadding import kotlin.math.max import kotlin.math.min import kotlin.random.Random -// Google Play Games Services imports -import com.google.android.gms.games.PlayGames -import com.google.android.gms.games.PlayGamesSdk -import com.google.android.gms.games.GamesSignInClient -import com.google.android.gms.auth.api.signin.GoogleSignIn // Added for silent sign-in -import com.google.android.gms.auth.api.signin.GoogleSignInClient // Added for silent sign-in -import com.google.android.gms.auth.api.signin.GoogleSignInOptions // Added for silent sign-in -import com.pixelmintdrop.model.GooglePlayGamesManager // Added import for listener class MainActivity : AppCompatActivity(), GamepadController.GamepadConnectionListener, @@ -71,16 +63,11 @@ class MainActivity : AppCompatActivity(), companion object { private const val TAG = "MainActivity" - private const val RC_LEADERBOARD_UI = 9004 // Request code for leaderboard UI } // ViewModel private val viewModel: MainActivityViewModel by viewModels() // Added ViewModel - // Google Play Games Services - private lateinit var gamesSignInClient: GamesSignInClient - private lateinit var googleSignInClient: GoogleSignInClient // Added for silent sign-in - // UI components private lateinit var binding: ActivityMainBinding private lateinit var gameView: GameView @@ -170,16 +157,6 @@ class MainActivity : AppCompatActivity(), binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) - // Initialize Google Play Games Services - PlayGamesSdk.initialize(this) - gamesSignInClient = PlayGames.getGamesSignInClient(this) - // Initialize GoogleSignInClient for silent sign-in - val signInOptions = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN).build() - googleSignInClient = GoogleSignIn.getClient(this, signInOptions) - - // Request sign-in silently - signInToPlayGames() - // Store initial padding values before applying insets val initialPausePadding = Rect(binding.pauseContainer.paddingLeft, binding.pauseContainer.paddingTop, binding.pauseContainer.paddingRight, binding.pauseContainer.paddingBottom) @@ -473,10 +450,6 @@ class MainActivity : AppCompatActivity(), Log.d(TAG, "[GameOverDebug] Triples: $lastSessionTriples (from manager: ${statsManager.getSessionTriples()})") Log.d(TAG, "[GameOverDebug] Quads: $lastSessionQuads (from manager: ${statsManager.getSessionQuads()})") - // *** Submit score to Google Play Games *** - Log.d(TAG, "Attempting to submit end-game score to Play Games: $lastSessionScore") - highScoreManager.getGooglePlayGamesManager().submitScore(lastSessionScore.toLong(), this@MainActivity) - // End the session (updates lifetime stats) statsManager.endSession() @@ -556,16 +529,9 @@ class MainActivity : AppCompatActivity(), startGame() } - // High Scores button binding.highScoresButton.setOnClickListener { - binding.pauseContainer.visibility = View.GONE - val intent = Intent(this, HighScoresActivity::class.java) - startActivity(intent) - } - - // Leaderboard button - binding.leaderboardButton?.setOnClickListener { - showLeaderboard() + gameHaptics.performHapticFeedback(it, HapticFeedbackConstants.VIRTUAL_KEY) + showHighScores() } binding.pauseLevelUpButton.setOnClickListener { @@ -849,7 +815,6 @@ class MainActivity : AppCompatActivity(), binding.pauseRestartButton.setTextColor(themeColor) binding.resumeButton.setTextColor(themeColor) binding.highScoresButton.setTextColor(themeColor) - binding.leaderboardButton?.setTextColor(themeColor) binding.statsButton.setTextColor(themeColor) binding.pauseLevelUpButton?.setTextColor(themeColor) // Safe call binding.pauseLevelDownButton?.setTextColor(themeColor) // Safe call @@ -1081,10 +1046,6 @@ class MainActivity : AppCompatActivity(), Log.d(TAG, "[GameOverDebug] Triples: $lastSessionTriples (from manager: ${statsManager.getSessionTriples()})") Log.d(TAG, "[GameOverDebug] Quads: $lastSessionQuads (from manager: ${statsManager.getSessionQuads()})") - // *** Submit score to Google Play Games *** - Log.d(TAG, "Attempting to submit end-game score to Play Games: $lastSessionScore") - highScoreManager.getGooglePlayGamesManager().submitScore(lastSessionScore.toLong(), this@MainActivity) - // End the session (updates lifetime stats) statsManager.endSession() @@ -1679,7 +1640,6 @@ class MainActivity : AppCompatActivity(), // Group 2: Stats and Scoring orderedViews.add(binding.highScoresButton) - orderedViews.add(binding.leaderboardButton) orderedViews.add(binding.statsButton) // Group 3: Level selection (use safe calls) @@ -2246,82 +2206,4 @@ class MainActivity : AppCompatActivity(), Log.d("RandomMode", "Cannot apply random theme/skin - no unlocked options available") } } - - /** - * Handles sign-in to Google Play Games Services - */ - private fun signInToPlayGames() { - Log.d(TAG, "Attempting to sign into Google Play Games") - gamesSignInClient.isAuthenticated.addOnCompleteListener { task -> - val isAuthenticated = task.isSuccessful && task.result.isAuthenticated - - if (!isAuthenticated) { - // Silent sign-in failed, but don't prompt right away - Log.d(TAG, "Not authenticated with Play Games, will prompt for sign-in when needed") - - // Optional: Add a sign-in button to your menu or UI for manual sign-in - // We'll attempt sign-in when accessing leaderboards or submitting scores - } else { - Log.d(TAG, "Already authenticated with Play Games") - - // If we were already authenticated, make sure scores are synced - val lastHighScore = highScoreManager.getHighScores().maxByOrNull { it.score } - lastHighScore?.let { - // Submit the high score if we're already authenticated - Log.d(TAG, "User is signed in, submitting last high score: ${it.score}") - highScoreManager.getGooglePlayGamesManager().submitScore(it.score.toLong(), this) - } - } - } - } - - // Method to show Google Play Games leaderboard - private fun showLeaderboard() { - // New logic: Try silent sign-in first, then interactive if needed. - Log.d(TAG, "Attempting silent sign-in before showing leaderboard...") - // Use googleSignInClient for silentSignIn - googleSignInClient.silentSignIn().addOnCompleteListener { silentSignInTask -> - if (silentSignInTask.isSuccessful) { - // Silent sign-in successful OR user already signed in. - Log.d(TAG, "Silent sign-in successful/already signed in. Getting leaderboard intent.") - getAndShowLeaderboardIntent() - } else { - // Silent sign-in failed. Need explicit sign-in. - Log.w(TAG, "Silent sign-in failed. Attempting interactive sign-in.", silentSignInTask.exception) - // Use gamesSignInClient for interactive signIn - gamesSignInClient.signIn().addOnCompleteListener { interactiveSignInTask -> - if (interactiveSignInTask.isSuccessful) { - // Interactive sign-in successful - Log.d(TAG, "Interactive sign-in successful. Getting leaderboard intent.") - getAndShowLeaderboardIntent() - } else { - // Interactive sign-in failed - Log.e(TAG, "Interactive sign-in failed.", interactiveSignInTask.exception) - Toast.makeText(this, "Sign-in failed. Please try again.", Toast.LENGTH_SHORT).show() - } - } - } - } - } - - // Helper function to get the intent from GooglePlayGamesManager and show it - private fun getAndShowLeaderboardIntent() { - highScoreManager.getGooglePlayGamesManager().showLeaderboard(this, - object : GooglePlayGamesManager.LeaderboardIntentListener { - override fun onLeaderboardIntentSuccess(intent: Intent) { - Log.d(TAG, "Received leaderboard intent, starting activity for result.") - try { - startActivityForResult(intent, RC_LEADERBOARD_UI) - } catch (e: Exception) { - Log.e(TAG, "Failed to start leaderboard activity for result", e) - Toast.makeText(this@MainActivity, "Could not display leaderboard.", Toast.LENGTH_SHORT).show() - } - } - - override fun onLeaderboardIntentFailure(exception: Exception) { - Log.e(TAG, "Failed to get leaderboard intent.", exception) - Toast.makeText(this@MainActivity, "Could not display leaderboard.", Toast.LENGTH_SHORT).show() - } - }) - } } \ No newline at end of file diff --git a/app/src/main/java/com/pixelmintdrop/model/GooglePlayGamesManager.kt b/app/src/main/java/com/pixelmintdrop/model/GooglePlayGamesManager.kt deleted file mode 100644 index d88229a..0000000 --- a/app/src/main/java/com/pixelmintdrop/model/GooglePlayGamesManager.kt +++ /dev/null @@ -1,169 +0,0 @@ -package com.pixelmintdrop.model - -import android.app.Activity -import android.content.Context -import android.content.Intent -import android.util.Log -import androidx.activity.result.ActivityResultLauncher -import com.google.android.gms.auth.api.signin.GoogleSignIn -import com.google.android.gms.auth.api.signin.GoogleSignInAccount -import com.google.android.gms.auth.api.signin.GoogleSignInClient -import com.google.android.gms.auth.api.signin.GoogleSignInOptions -import com.google.android.gms.games.PlayGames -import com.google.android.gms.games.PlayGamesSdk -import com.google.android.gms.tasks.Task - -class GooglePlayGamesManager(private val context: Context) { - private val TAG = "GooglePlayGamesManager" - - // Define a listener interface for the leaderboard intent - interface LeaderboardIntentListener { - fun onLeaderboardIntentSuccess(intent: Intent) - fun onLeaderboardIntentFailure(exception: Exception) - } - - // Leaderboard ID - companion object { - const val LEADERBOARD_ID = "CgkImJW2mKsSEAIQAQ" - } - - // Initialize the Play Games SDK - init { - try { - PlayGamesSdk.initialize(context) - Log.d(TAG, "PlayGamesSdk initialized") - } catch (e: Exception) { - Log.e(TAG, "Error initializing PlayGamesSdk", e) - } - } - - // Check if user is already signed in - fun isSignedIn(): Boolean { - val account = GoogleSignIn.getLastSignedInAccount(context) - return account != null && !account.isExpired - } - - // Get the sign-in client - fun getSignInClient(): GoogleSignInClient { - val signInOptions = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN).build() - return GoogleSignIn.getClient(context, signInOptions) - } - - // Handle sign-in result - fun handleSignInResult(task: Task): Boolean { - return if (task.isSuccessful) { - Log.d(TAG, "Sign-in successful") - true - } else { - Log.e(TAG, "Sign-in failed", task.exception) - false - } - } - - // Submit score to leaderboard (requires an activity for potential sign-in) - fun submitScore(score: Long, activity: Activity? = null) { - if (activity == null) { - Log.w(TAG, "Activity context is required for score submission.") - return - } - - Log.d(TAG, "Attempting to submit score: $score") - - // Always force a sign-in to ensure we have a valid account - val signInClient = PlayGames.getGamesSignInClient(activity) - signInClient.signIn().addOnCompleteListener { signInTask -> - if (signInTask.isSuccessful) { - Log.d(TAG, "Sign-in successful, retrieving account for score submission") - - // After sign-in, get the account and submit score - try { - // Use PlayGames API directly to get client and submit score - val leaderboardsClient = PlayGames.getLeaderboardsClient(activity) - leaderboardsClient.submitScore(LEADERBOARD_ID, score) - Log.d(TAG, "Successfully submitted score $score to leaderboard $LEADERBOARD_ID") - - // Show confirmation to user - android.widget.Toast.makeText( - activity, - "Score submitted to leaderboard", - android.widget.Toast.LENGTH_SHORT - ).show() - } catch (e: Exception) { - Log.e(TAG, "Error submitting score after sign-in", e) - android.widget.Toast.makeText( - activity, - "Failed to submit score to leaderboard", - android.widget.Toast.LENGTH_SHORT - ).show() - } - } else { - Log.e(TAG, "Sign-in failed, unable to submit score", signInTask.exception) - android.widget.Toast.makeText( - activity, - "Sign-in required to submit score to leaderboard", - android.widget.Toast.LENGTH_SHORT - ).show() - } - } - } - - // Show the leaderboard - Now uses a listener to return the Intent - fun showLeaderboard(activity: Activity, listener: LeaderboardIntentListener) { - Log.d(TAG, "showLeaderboard called in GooglePlayGamesManager") - - // First check if already authenticated - PlayGames.getGamesSignInClient(activity).isAuthenticated.addOnCompleteListener { authTask -> - val isAuthenticated = authTask.isSuccessful && authTask.result.isAuthenticated - - if (isAuthenticated) { - // Already authenticated, get leaderboard - getLeaderboardIntent(activity, listener) - } else { - // Need to authenticate first - Log.d(TAG, "User not authenticated for leaderboard, attempting sign-in") - PlayGames.getGamesSignInClient(activity).signIn().addOnCompleteListener { signInTask -> - if (signInTask.isSuccessful) { - // Successfully signed in, now get leaderboard - Log.d(TAG, "Sign-in successful, getting leaderboard") - getLeaderboardIntent(activity, listener) - } else { - // Failed to sign in - Log.e(TAG, "Failed to sign in for leaderboard access", signInTask.exception) - listener.onLeaderboardIntentFailure( - Exception("Sign-in required to view leaderboard", signInTask.exception) - ) - - // Inform user - android.widget.Toast.makeText( - activity, - "Sign-in required to view leaderboard", - android.widget.Toast.LENGTH_SHORT - ).show() - } - } - } - } - } - - // Private helper to get leaderboard intent - private fun getLeaderboardIntent(activity: Activity, listener: LeaderboardIntentListener) { - Log.d(TAG, "Attempting to get LeaderboardsClient") - try { - val leaderboardsClient = PlayGames.getLeaderboardsClient(activity) - Log.d(TAG, "LeaderboardsClient obtained, attempting to get leaderboard intent for ID: $LEADERBOARD_ID") - leaderboardsClient - .getLeaderboardIntent(LEADERBOARD_ID) - .addOnSuccessListener { intent -> - Log.d(TAG, "Successfully obtained leaderboard intent. Calling listener.") - listener.onLeaderboardIntentSuccess(intent) - } - .addOnFailureListener { e -> - Log.e(TAG, "Error getting leaderboard intent", e) - listener.onLeaderboardIntentFailure(e) - } - } catch (e: Exception) { - Log.e(TAG, "Error obtaining LeaderboardsClient or getting leaderboard intent", e) - listener.onLeaderboardIntentFailure(e) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/pixelmintdrop/model/HighScoreManager.kt b/app/src/main/java/com/pixelmintdrop/model/HighScoreManager.kt index de48594..bf35f52 100644 --- a/app/src/main/java/com/pixelmintdrop/model/HighScoreManager.kt +++ b/app/src/main/java/com/pixelmintdrop/model/HighScoreManager.kt @@ -10,7 +10,6 @@ class HighScoreManager(private val context: Context) { private val prefs: SharedPreferences = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) private val gson = Gson() private val type: Type = object : TypeToken>() {}.type - private val googlePlayGamesManager = GooglePlayGamesManager(context) companion object { private const val PREFS_NAME = "pixelmintdrop_highscores" @@ -28,7 +27,6 @@ class HighScoreManager(private val context: Context) { } fun addHighScore(highScore: HighScore) { - // Save to local high scores val currentScores = getHighScores().toMutableList() currentScores.add(highScore) @@ -39,9 +37,6 @@ class HighScoreManager(private val context: Context) { // Save to SharedPreferences val json = gson.toJson(topScores) prefs.edit().putString(KEY_HIGHSCORES, json).apply() - - // Submit to Google Play Games leaderboard if signed in - submitScoreToGooglePlay(highScore.score) } fun isHighScore(score: Int): Boolean { @@ -49,26 +44,4 @@ class HighScoreManager(private val context: Context) { return currentScores.size < MAX_HIGHSCORES || score > (currentScores.lastOrNull()?.score ?: 0) } - - // Submit score to Google Play Games leaderboard - private fun submitScoreToGooglePlay(score: Int) { - // We don't have an activity here, so we can't submit the score directly - // The score will be submitted later when an activity is available - try { - googlePlayGamesManager.submitScore(score.toLong(), null) - } catch (e: Exception) { - // Log error but don't crash - android.util.Log.e("HighScoreManager", "Error submitting score to leaderboard", e) - } - } - - // Method to check if user is signed in to Google Play Games - fun isGooglePlaySignedIn(): Boolean { - return googlePlayGamesManager.isSignedIn() - } - - // Get Google Play Games manager - fun getGooglePlayGamesManager(): GooglePlayGamesManager { - return googlePlayGamesManager - } } \ No newline at end of file diff --git a/app/src/main/res/layout-land/activity_main.xml b/app/src/main/res/layout-land/activity_main.xml index 13f79f2..715a14d 100644 --- a/app/src/main/res/layout-land/activity_main.xml +++ b/app/src/main/res/layout-land/activity_main.xml @@ -438,20 +438,6 @@ android:singleLine="true" android:focusable="true" android:focusableInTouchMode="true" /> - - -