commit 6efa7f9f697090ca47805cfbbf8de5916e703a1a
Author: ocean <503259349@qq.com>
Date: Fri Dec 5 17:45:05 2025 +0800
first commit
diff --git a/.claude/settings.local.json b/.claude/settings.local.json
new file mode 100644
index 0000000..60ad445
--- /dev/null
+++ b/.claude/settings.local.json
@@ -0,0 +1,10 @@
+{
+ "permissions": {
+ "allow": [
+ "Bash(mkdir:*)",
+ "Bash(cp:*)",
+ "Bash(rm:*)"
+ ],
+ "deny": []
+ }
+}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..aa724b7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..26d3352
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/.idea/.name b/.idea/.name
new file mode 100644
index 0000000..0922920
--- /dev/null
+++ b/.idea/.name
@@ -0,0 +1 @@
+Minesweeper Go
\ No newline at end of file
diff --git a/.idea/AndroidProjectSystem.xml b/.idea/AndroidProjectSystem.xml
new file mode 100644
index 0000000..4a53bee
--- /dev/null
+++ b/.idea/AndroidProjectSystem.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..b86273d
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
new file mode 100644
index 0000000..b268ef3
--- /dev/null
+++ b/.idea/deploymentTargetSelector.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
new file mode 100644
index 0000000..639c779
--- /dev/null
+++ b/.idea/gradle.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..7061a0d
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
new file mode 100644
index 0000000..fdf8d99
--- /dev/null
+++ b/.idea/kotlinc.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/migrations.xml b/.idea/migrations.xml
new file mode 100644
index 0000000..f8051a6
--- /dev/null
+++ b/.idea/migrations.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..14be325
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
new file mode 100644
index 0000000..16660f1
--- /dev/null
+++ b/.idea/runConfigurations.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
new file mode 100644
index 0000000..a3a5650
--- /dev/null
+++ b/app/build.gradle.kts
@@ -0,0 +1,124 @@
+import groovy.xml.XmlParser
+import java.text.SimpleDateFormat
+import java.util.Date
+
+
+plugins {
+ alias(libs.plugins.android.application)
+ alias(libs.plugins.jetbrains.kotlin.android)
+ id("com.google.gms.google-services")
+ id("com.google.firebase.crashlytics")
+}
+// 生成时间戳(格式 MMddHHmm)
+val timestamp: String = SimpleDateFormat("MMddHHmm").format(Date())
+
+fun getAppNameFromStringsFile(): String {
+ val stringsFile = file("src/main/res/values/strings.xml")
+ if (!stringsFile.exists()) {
+ return "UnknownApp"
+ }
+
+ val xmlParser = XmlParser()
+ val rootNode = xmlParser.parse(stringsFile)
+
+ return rootNode.children()
+ .filterIsInstance()
+ .find { it.attribute("name") == "app_name" }
+ ?.text() ?: "UnknownApp"
+}
+android {
+ namespace = "com.gogame.minesweeper"
+ compileSdk = 36
+
+ defaultConfig {
+ applicationId = "com.gogame.minesweeper"
+ minSdk = 24
+ targetSdk = 36
+ versionCode = 3
+ versionName = "1.0.3"
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+
+ setProperty("archivesBaseName", "${getAppNameFromStringsFile()}-$versionName($versionCode)-${timestamp}")
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = true
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+ }
+ kotlinOptions {
+ jvmTarget = "11"
+ }
+ buildFeatures {
+ viewBinding = true
+ }
+}
+
+dependencies {
+ implementation(files("libs/UpLoadLibrary_12_03_15_13-release.aar"))
+ implementation(libs.androidx.core.ktx)
+ implementation(libs.androidx.appcompat)
+ implementation(libs.material)
+ implementation(libs.androidx.constraintlayout)
+ implementation(libs.androidx.navigation.fragment.ktx)
+ implementation(libs.androidx.navigation.ui.ktx)
+ testImplementation(libs.junit)
+ androidTestImplementation(libs.androidx.junit)
+ androidTestImplementation(libs.androidx.espresso.core)
+ implementation(libs.gson)
+ implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
+ // Import the Firebase BoM
+ implementation(platform("com.google.firebase:firebase-bom:34.6.0"))
+ implementation("com.google.firebase:firebase-crashlytics-ndk")
+ implementation("com.google.firebase:firebase-analytics")
+ // google ads
+ implementation ("com.google.android.gms:play-services-ads-identifier:18.0.1")
+ // okhttp
+ implementation ("com.squareup.okhttp3:okhttp:4.12.0")
+ implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")
+
+ //TU (Necessary)
+ api("com.thinkup.sdk:core-tpn:6.5.16")
+ api("com.thinkup.sdk:nativead-tpn:6.5.16")
+ api("com.thinkup.sdk:banner-tpn:6.5.16")
+ api("com.thinkup.sdk:interstitial-tpn:6.5.16")
+ api("com.thinkup.sdk:rewardedvideo-tpn:6.5.16")
+ api("com.thinkup.sdk:splash-tpn:6.5.16")
+//Androidx (Necessary)
+ api("androidx.appcompat:appcompat:1.6.1")
+ api("androidx.browser:browser:1.4.0")
+//Vungle
+ api("com.thinkup.sdk:adapter-tpn-vungle:6.5.16")
+ api("com.vungle:vungle-ads:7.5.0")
+ api("com.google.android.gms:play-services-basement:18.1.0")
+ api("com.google.android.gms:play-services-ads-identifier:18.0.1")
+//UnityAds
+ api("com.thinkup.sdk:adapter-tpn-unityads:6.5.16")
+ api("com.unity3d.ads:unity-ads:4.14.0")
+//Ironsource
+ api("com.thinkup.sdk:adapter-tpn-ironsource:6.5.16")
+ api("com.ironsource.sdk:mediationsdk:8.7.0")
+ api("com.google.android.gms:play-services-appset:16.0.2")
+ api("com.google.android.gms:play-services-ads-identifier:18.0.1")
+ api("com.google.android.gms:play-services-basement:18.1.0")
+//Bigo
+ api("com.thinkup.sdk:adapter-tpn-bigo:6.5.16.1")
+ api("com.bigossp:bigo-ads:5.5.1")
+//Mintegral
+ api("com.thinkup.sdk:adapter-tpn-mintegral:6.5.16.1")
+ api("com.mbridge.msdk.oversea:mbridge_android_sdk:16.9.91")
+ api("androidx.recyclerview:recyclerview:1.1.0")
+//Pangle
+ api("com.thinkup.sdk:adapter-tpn-pangle:6.5.16.2")
+ api("com.pangle.global:pag-sdk:7.6.0.2")
+ api("com.google.android.gms:play-services-ads-identifier:18.0.1")
+}
\ No newline at end of file
diff --git a/app/google-services.json b/app/google-services.json
new file mode 100644
index 0000000..8baf96e
--- /dev/null
+++ b/app/google-services.json
@@ -0,0 +1,29 @@
+{
+ "project_info": {
+ "project_number": "500252155158",
+ "project_id": "minesweeper-go-1e611",
+ "storage_bucket": "minesweeper-go-1e611.firebasestorage.app"
+ },
+ "client": [
+ {
+ "client_info": {
+ "mobilesdk_app_id": "1:500252155158:android:9296fdaa061e12de05c437",
+ "android_client_info": {
+ "package_name": "com.gogame.minesweeper"
+ }
+ },
+ "oauth_client": [],
+ "api_key": [
+ {
+ "current_key": "AIzaSyDdKXNF1RXKY_9WbrQYKZbWFs4tMr8ajKI"
+ }
+ ],
+ "services": {
+ "appinvite_service": {
+ "other_platform_oauth_client": []
+ }
+ }
+ }
+ ],
+ "configuration_version": "1"
+}
\ No newline at end of file
diff --git a/app/libs/UpLoadLibrary_12_03_15_13-release.aar b/app/libs/UpLoadLibrary_12_03_15_13-release.aar
new file mode 100644
index 0000000..a3e30aa
Binary files /dev/null and b/app/libs/UpLoadLibrary_12_03_15_13-release.aar differ
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..9711844
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,93 @@
+#start topon 聚合混淆
+# Vungle
+-dontwarn com.vungle.ads.**
+-keepclassmembers class com.vungle.ads.** {
+ *;
+}
+-keep class com.vungle.ads.**
+
+
+
+# Google
+-keep class com.google.android.gms.** { *; }
+-dontwarn com.google.android.gms.**
+
+
+
+
+# START OkHttp + Okio
+# JSR 305 annotations are for embedding nullability information.
+-dontwarn javax.annotation.**
+
+
+# A resource is loaded with a relative path so the package of this class must be preserved.
+-adaptresourcefilenames okhttp3/internal/publicsuffix/PublicSuffixDatabase.gz
+
+
+# Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java.
+-dontwarn org.codehaus.mojo.animal_sniffer.*
+
+
+# OkHttp platform used only on JVM and when Conscrypt and other security providers are available.
+-dontwarn okhttp3.internal.platform.**
+-dontwarn org.conscrypt.**
+-dontwarn org.bouncycastle.**
+-dontwarn org.openjsse.**
+
+
+# Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java.
+-dontwarn org.codehaus.mojo.animal_sniffer.*
+
+
+# END OkHttp + Okio
+
+
+# START Protobuf
+-dontwarn com.google.protobuf.**
+-keepclassmembers class com.google.protobuf.** {
+ *;
+}
+-keep class * extends com.google.protobuf.GeneratedMessageLite { *; }
+
+
+# END Protobuf
+-keepclassmembers class com.ironsource.sdk.controller.IronSourceWebView$JSInterface {
+ public *;
+}
+-keepclassmembers class * implements android.os.Parcelable {
+ public static final android.os.Parcelable$Creator *;
+}
+-keep public class com.google.android.gms.ads.** {
+ public *;
+}
+-keep class com.ironsource.adapters.** { *;
+}
+-dontwarn com.ironsource.mediationsdk.**
+-dontwarn com.ironsource.adapters.**
+-keepattributes JavascriptInterface
+-keepclassmembers class * {
+ @android.webkit.JavascriptInterface ;
+}
+-keepattributes Signature
+-keepattributes *Annotation*
+-keep class com.mbridge.** {*; }
+-keep interface com.mbridge.** {*; }
+-dontwarn com.mbridge.**
+-keepclassmembers class **.R$* { public static final int mbridge*; }
+
+-keep public class com.mbridge.* extends androidx.** { *; }
+-keep public class androidx.viewpager.widget.PagerAdapter{*;}
+-keep public class androidx.viewpager.widget.ViewPager.OnPageChangeListener{*;}
+-keep interface androidx.annotation.IntDef{*;}
+-keep interface androidx.annotation.Nullable{*;}
+-keep interface androidx.annotation.CheckResult{*;}
+-keep interface androidx.annotation.NonNull{*;}
+-keep public class androidx.fragment.app.Fragment{*;}
+-keep public class androidx.core.content.FileProvider{*;}
+-keep public class androidx.core.app.NotificationCompat{*;}
+-keep public class androidx.appcompat.widget.AppCompatImageView {*;}
+-keep public class androidx.recyclerview.*{*;}
+-keep class com.mbridge.msdk.foundation.tools.FastKV{*;}
+-keep class com.mbridge.msdk.foundation.tools.FastKV$Builder{*;}
+-keep class com.bytedance.sdk.** { *; }
+#end topon 聚合混淆
diff --git a/app/release/Minesweeper Go-1.0.3(3)-12041449-release.apk b/app/release/Minesweeper Go-1.0.3(3)-12041449-release.apk
new file mode 100644
index 0000000..2a4bc99
Binary files /dev/null and b/app/release/Minesweeper Go-1.0.3(3)-12041449-release.apk differ
diff --git a/app/release/Minesweeper Go-1.0.3(3)-12041501-release.aab b/app/release/Minesweeper Go-1.0.3(3)-12041501-release.aab
new file mode 100644
index 0000000..a1cc058
Binary files /dev/null and b/app/release/Minesweeper Go-1.0.3(3)-12041501-release.aab differ
diff --git a/app/release/baselineProfiles/0/Minesweeper Go-1.0.3(3)-12041449-release.dm b/app/release/baselineProfiles/0/Minesweeper Go-1.0.3(3)-12041449-release.dm
new file mode 100644
index 0000000..70e29ee
Binary files /dev/null and b/app/release/baselineProfiles/0/Minesweeper Go-1.0.3(3)-12041449-release.dm differ
diff --git a/app/release/baselineProfiles/1/Minesweeper Go-1.0.3(3)-12041449-release.dm b/app/release/baselineProfiles/1/Minesweeper Go-1.0.3(3)-12041449-release.dm
new file mode 100644
index 0000000..8787bff
Binary files /dev/null and b/app/release/baselineProfiles/1/Minesweeper Go-1.0.3(3)-12041449-release.dm differ
diff --git a/app/release/output-metadata.json b/app/release/output-metadata.json
new file mode 100644
index 0000000..91e0f50
--- /dev/null
+++ b/app/release/output-metadata.json
@@ -0,0 +1,37 @@
+{
+ "version": 3,
+ "artifactType": {
+ "type": "APK",
+ "kind": "Directory"
+ },
+ "applicationId": "com.gogame.minesweeper",
+ "variantName": "release",
+ "elements": [
+ {
+ "type": "SINGLE",
+ "filters": [],
+ "attributes": [],
+ "versionCode": 3,
+ "versionName": "1.0.3",
+ "outputFile": "Minesweeper Go-1.0.3(3)-12041449-release.apk"
+ }
+ ],
+ "elementType": "File",
+ "baselineProfiles": [
+ {
+ "minApi": 28,
+ "maxApi": 30,
+ "baselineProfiles": [
+ "baselineProfiles/1/Minesweeper Go-1.0.3(3)-12041449-release.dm"
+ ]
+ },
+ {
+ "minApi": 31,
+ "maxApi": 2147483647,
+ "baselineProfiles": [
+ "baselineProfiles/0/Minesweeper Go-1.0.3(3)-12041449-release.dm"
+ ]
+ }
+ ],
+ "minSdkVersionForDexing": 24
+}
\ No newline at end of file
diff --git a/app/src/androidTest/java/com/gogame/minesweeper/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/gogame/minesweeper/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..de99528
--- /dev/null
+++ b/app/src/androidTest/java/com/gogame/minesweeper/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.gogame.minesweeper
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.gogame.minesweeper", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..47de22e
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/app_logo-playstore.png b/app/src/main/app_logo-playstore.png
new file mode 100644
index 0000000..60ce9e7
Binary files /dev/null and b/app/src/main/app_logo-playstore.png differ
diff --git a/app/src/main/java/com/gogame/minesweeper/App.kt b/app/src/main/java/com/gogame/minesweeper/App.kt
new file mode 100644
index 0000000..5adee3c
--- /dev/null
+++ b/app/src/main/java/com/gogame/minesweeper/App.kt
@@ -0,0 +1,28 @@
+package com.gogame.minesweeper
+
+import android.app.Application
+import android.content.Context
+import com.thinkup.core.api.TUSDK
+import com.up.uploadlibrary.UpLoadManager
+
+class App : Application() {
+
+ companion object {
+ private lateinit var instance: App
+
+ fun getInstance(): App = instance
+
+ fun getContext(): Context = instance.applicationContext
+ }
+
+ override fun onCreate() {
+ super.onCreate()
+ // 上传
+ UpLoadManager.init(context = this, tag = "PRApp_minesweeper_task") { _, _ -> }
+ initAd()
+ }
+
+ private fun initAd() {
+ TUSDK.init(this,"h692ffae5586bf","a764e3497a06ccd54a03aef53f022ef74")
+ }
+}
diff --git a/app/src/main/java/com/gogame/minesweeper/BaseActivity.kt b/app/src/main/java/com/gogame/minesweeper/BaseActivity.kt
new file mode 100644
index 0000000..6a025d1
--- /dev/null
+++ b/app/src/main/java/com/gogame/minesweeper/BaseActivity.kt
@@ -0,0 +1,36 @@
+package com.gogame.minesweeper
+
+import android.os.Bundle
+import androidx.activity.OnBackPressedCallback
+import androidx.appcompat.app.AppCompatActivity
+
+open class BaseActivity: AppCompatActivity() {
+ protected var backPressedCallback: OnBackPressedCallback? = null
+ /** 子类是否需要拦截返回 */
+ protected open fun shouldInterceptBackPress(): Boolean = false
+
+ /** 子类定义拦截后的操作(例如弹窗) */
+ protected open fun onInterceptBackPressed() {}
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setupBackPressedCallback()//初始化back事件
+ }
+ private fun setupBackPressedCallback() {
+ backPressedCallback = object : OnBackPressedCallback(true) {
+ override fun handleOnBackPressed() {
+ if (shouldInterceptBackPress()) {
+ // 由子类处理拦截动作
+ onInterceptBackPressed()
+ } else {
+ // 不拦截:关闭自己
+ isEnabled = false
+ onBackPressedDispatcher.onBackPressed()
+ }
+ }
+ }
+
+ onBackPressedDispatcher.addCallback(this, backPressedCallback!!)
+
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/gogame/minesweeper/DifficultyLevel.kt b/app/src/main/java/com/gogame/minesweeper/DifficultyLevel.kt
new file mode 100644
index 0000000..e0ee87d
--- /dev/null
+++ b/app/src/main/java/com/gogame/minesweeper/DifficultyLevel.kt
@@ -0,0 +1,8 @@
+package com.gogame.minesweeper
+
+enum class DifficultyLevel(val xLength: Int, val yLength: Int, val mineNum: Int) {
+ BEGINNER(9, 9, 10), // 新手难度
+ INTERMEDIATE(16, 16, 40), // 中等难度
+ EXPERT(24, 24, 99), // 专家难度
+ MASTER(50, 50, 450) // 大师难度
+}
diff --git a/app/src/main/java/com/gogame/minesweeper/MainActivity.kt b/app/src/main/java/com/gogame/minesweeper/MainActivity.kt
new file mode 100644
index 0000000..c0eb1ff
--- /dev/null
+++ b/app/src/main/java/com/gogame/minesweeper/MainActivity.kt
@@ -0,0 +1,114 @@
+package com.gogame.minesweeper
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.view.View
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowCompat
+import androidx.core.view.WindowInsetsCompat
+import com.gogame.minesweeper.bean.GameState
+import com.gogame.minesweeper.databinding.ActivityMainBinding
+import com.gogame.minesweeper.swipe.SwipeActivity
+import com.google.gson.Gson
+
+class MainActivity : BaseActivity() {
+
+ private lateinit var binding: ActivityMainBinding
+
+ private lateinit var difficultyLevels: Array
+ private var currentDifficultyIndex = 0
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivityMainBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+ immersionBar()
+ initView()
+ }
+
+ private fun immersionBar() {
+ //自适应沉浸式
+ ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
+ val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
+ v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
+ insets
+ }
+
+ val windowInsetsController = WindowCompat.getInsetsController(window, window.decorView)
+ windowInsetsController.isAppearanceLightStatusBars = true//状态栏文字颜色
+ }
+
+ private fun initView() {
+ // 设置难度级别
+ difficultyLevels = DifficultyLevel.entries.toTypedArray() // 获取所有的难度级别
+
+ // 显示初始难度
+ binding.tvDifficulty.text = difficultyLevels[currentDifficultyIndex].name
+
+ binding.btnPrevious.setOnClickListener {
+ if (currentDifficultyIndex > 0) {
+ currentDifficultyIndex--
+ binding.tvDifficulty.text = difficultyLevels[currentDifficultyIndex].name
+ }
+ updateContinueState()
+ }
+
+ binding.btnNext.setOnClickListener {
+ if (currentDifficultyIndex < difficultyLevels.size - 1) {
+ currentDifficultyIndex++
+ binding.tvDifficulty.text = difficultyLevels[currentDifficultyIndex].name
+ }
+ updateContinueState()
+ }
+
+ binding.newGameBtn.setOnClickListener {
+ startGameWithDifficulty()
+ }
+ binding.continueBtn.setOnClickListener {
+ continuePreviousGame()
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+ updateContinueState()
+ }
+
+ private fun startGameWithDifficulty() {
+ val intent = Intent(this, SwipeActivity::class.java)
+ // 传递当前难度给 SwipeActivity
+ intent.putExtra(
+ "difficulty",
+ difficultyLevels[currentDifficultyIndex]
+ ) // 直接传递 DifficultyLevel
+ intent.putExtra("isNewGame", true)
+ startActivity(intent)
+ }
+
+ private fun continuePreviousGame() {
+ val intent = Intent(this, SwipeActivity::class.java)
+ // 传递当前难度给 SwipeActivity
+ intent.putExtra(
+ "difficulty",
+ difficultyLevels[currentDifficultyIndex]
+ ) // 直接传递 DifficultyLevel
+ intent.putExtra("isNewGame", false)
+ startActivity(intent)
+ }
+
+ private fun updateContinueState() {
+ val sharedPreferences = getSharedPreferences("minesweeper_prefs", Context.MODE_PRIVATE)
+ val gameStateJson = sharedPreferences.getString("game_state", null)
+ if (gameStateJson.isNullOrEmpty()) {
+ binding.continueBtn.visibility = View.GONE
+ return
+ }
+ val gameState: GameState = Gson().fromJson(gameStateJson, GameState::class.java)
+ if (gameState.difficulty == difficultyLevels[currentDifficultyIndex]) {
+ binding.continueBtn.visibility = View.VISIBLE
+ } else {
+ binding.continueBtn.visibility = View.GONE
+ }
+ }
+}
diff --git a/app/src/main/java/com/gogame/minesweeper/SplashActivity.kt b/app/src/main/java/com/gogame/minesweeper/SplashActivity.kt
new file mode 100644
index 0000000..96873eb
--- /dev/null
+++ b/app/src/main/java/com/gogame/minesweeper/SplashActivity.kt
@@ -0,0 +1,107 @@
+package com.gogame.minesweeper
+
+import android.content.Intent
+import android.os.Bundle
+import android.os.Handler
+import android.os.Looper
+import androidx.activity.enableEdgeToEdge
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowCompat
+import androidx.core.view.WindowInsetsCompat
+import com.gogame.minesweeper.ad.AdShowFailed
+import com.gogame.minesweeper.ad.AdsInsUtil
+import com.gogame.minesweeper.ad.LoadListener
+import com.gogame.minesweeper.ad.ShowListener
+import com.gogame.minesweeper.databinding.ActivitySplashBinding
+import com.thinkup.core.api.TUAdInfo
+import kotlin.random.Random
+
+class SplashActivity : BaseActivity() {
+
+ companion object {
+ private const val AD_TIMEOUT = 15000L // 广告加载超时时间 15 秒
+ }
+
+ private lateinit var binding: ActivitySplashBinding
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivitySplashBinding.inflate(layoutInflater)
+ enableEdgeToEdge()
+ setContentView(binding.root)
+ ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
+ val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
+ v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
+ insets
+ }
+ val windowInsetsController = WindowCompat.getInsetsController(window, window.decorView)
+ windowInsetsController.isAppearanceLightStatusBars = true//状态栏文字颜色
+ loadSplashAd()
+ }
+
+
+ private val adHandler = Handler(Looper.getMainLooper())
+ private val adTimeoutRunnable = Runnable {
+ navigateToNext() // 超时直接跳转
+ }
+
+ private fun loadSplashAd() {
+ adHandler.postDelayed(adTimeoutRunnable, AD_TIMEOUT)
+
+ // 生成一个随机索引
+ val randomIndex = Random.nextInt(AdsInsUtil.Placement.adPlaceAllList.size)
+ // 获取随机的广告位
+ val randomAdPlace = AdsInsUtil.Placement.adPlaceAllList[randomIndex]
+ AdsInsUtil.loadAd(
+ act = this,
+ adID =randomAdPlace,
+ loadListener = object : LoadListener {
+ override fun loaded(ad: TUAdInfo) {
+ adHandler.removeCallbacks(adTimeoutRunnable)
+ showSplashAd(randomAdPlace)
+ }
+
+ override fun loadFailed(error: String) {
+ adHandler.removeCallbacks(adTimeoutRunnable)
+ navigateToNext() // 加载失败直接跳转
+ }
+ })
+ }
+
+ private fun showSplashAd(randomAdPlace: String) {
+ AdsInsUtil.showAd(
+ act = this,
+ adID = randomAdPlace,
+ listener = object : ShowListener {
+ override fun onAdShown(ad: TUAdInfo?) {
+ }
+
+ override fun onAdShowFailed(error: AdShowFailed?) {
+ navigateToNext() // 展示失败也直接跳转
+ }
+
+ override fun onAdClosed() {
+ navigateToNext() // 广告关闭后跳转
+ }
+ })
+ }
+
+ private var hasNavigated = false
+
+ //保证只跳转一次
+ private fun navigateToNext() {
+ if (hasNavigated) return
+ hasNavigated = true
+ startActivity(Intent(this, MainActivity::class.java))
+ finish()
+ }
+
+ override fun shouldInterceptBackPress(): Boolean = true
+ override fun onInterceptBackPressed() {
+
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ adHandler.removeCallbacks(adTimeoutRunnable)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/gogame/minesweeper/ad/AdInstLoad.kt b/app/src/main/java/com/gogame/minesweeper/ad/AdInstLoad.kt
new file mode 100644
index 0000000..5d15209
--- /dev/null
+++ b/app/src/main/java/com/gogame/minesweeper/ad/AdInstLoad.kt
@@ -0,0 +1,68 @@
+package com.gogame.minesweeper.ad
+
+import android.app.Activity
+import android.util.Log
+import com.thinkup.core.api.AdError
+import com.thinkup.core.api.TUAdInfo
+import com.thinkup.interstitial.api.TUInterstitial
+import com.thinkup.interstitial.api.TUInterstitialListener
+
+class AdInstLoad {
+ private var mPlace: String
+ private var adLoadListener: LoadListener? = null
+ private var activity: Activity? = null
+
+ constructor(activity: Activity, place: String, listener: LoadListener?) {
+ this.mPlace = place
+ this.adLoadListener = listener
+ this.activity = activity
+ init()
+ }
+
+ constructor(place: String, listener: LoadListener?) {
+ this.mPlace = place
+ this.adLoadListener = listener
+ init()
+ }
+
+ private fun init() {
+ val mInterstitialAd = TUInterstitial(activity,mPlace)
+ mInterstitialAd.setAdListener(object : TUInterstitialListener{
+ override fun onInterstitialAdLoaded() {
+ Log.d("ocean", "load ad loaded")
+ val info = mInterstitialAd.checkAdStatus().tuTopAdInfo
+ if (info != null) {
+ InstAdCacheManager.instance.setAdCache(mPlace, mInterstitialAd)
+ adLoadListener?.loaded(info)
+ } else {
+ adLoadListener?.loadFailed("AdInfo null")
+ }
+ }
+
+ override fun onInterstitialAdLoadFail(adError: AdError?) {
+ adLoadListener?.loadFailed("code:${adError?.code}")
+ Log.d("ocean", "load ad onError->${adError.toString()}")
+ }
+
+ override fun onInterstitialAdClicked(p0: TUAdInfo?) {
+ }
+
+ override fun onInterstitialAdShow(p0: TUAdInfo?) {
+ }
+
+ override fun onInterstitialAdClose(p0: TUAdInfo?) {
+ }
+
+ override fun onInterstitialAdVideoStart(p0: TUAdInfo?) {
+ }
+
+ override fun onInterstitialAdVideoEnd(p0: TUAdInfo?) {
+ }
+
+ override fun onInterstitialAdVideoError(p0: AdError?) {
+ }
+
+ })
+ mInterstitialAd.load()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/gogame/minesweeper/ad/AdInstShower.kt b/app/src/main/java/com/gogame/minesweeper/ad/AdInstShower.kt
new file mode 100644
index 0000000..27e3b3e
--- /dev/null
+++ b/app/src/main/java/com/gogame/minesweeper/ad/AdInstShower.kt
@@ -0,0 +1,71 @@
+package com.gogame.minesweeper.ad
+
+import android.app.Activity
+import android.util.Log
+import com.thinkup.core.api.AdError
+import com.thinkup.core.api.TUAdInfo
+import com.thinkup.interstitial.api.TUInterstitialListener
+
+class AdInstShower {
+ private var mPlace: String
+ private var showListener: ShowListener? = null
+ private var activity: Activity? = null
+
+ constructor(activity: Activity, place: String, showListener: ShowListener?) {
+ this.mPlace = place
+ this.showListener = showListener
+ this.activity = activity
+ init()
+ }
+
+ constructor(place: String, showListener: ShowListener?) {
+ this.mPlace = place
+ this.showListener = showListener
+ init()
+ }
+
+ private fun init() {
+ val atInterstitial = InstAdCacheManager.instance.getAdCache(mPlace)
+ atInterstitial?.setAdListener(object : TUInterstitialListener {
+ override fun onInterstitialAdLoaded() {
+
+ }
+
+ override fun onInterstitialAdLoadFail(p0: AdError?) {
+
+ }
+
+ override fun onInterstitialAdClicked(p0: TUAdInfo?) {
+ showListener?.onAdClicked()
+ Log.d("ocean", "广告点击回调")
+ }
+
+ override fun onInterstitialAdShow(p0: TUAdInfo?) {
+ showListener?.onAdShown(p0)
+ Log.d("ocean", "广告展示回调")
+ }
+
+ override fun onInterstitialAdClose(p0: TUAdInfo?) {
+ showListener?.onAdClosed()
+ Log.d("ocean", "广告关闭回调")
+ }
+
+ override fun onInterstitialAdVideoStart(p0: TUAdInfo?) {
+ Log.d("ocean", "视频广告开始播放回调")
+ }
+
+ override fun onInterstitialAdVideoEnd(p0: TUAdInfo?) {
+ Log.d("ocean", "视频广告播放结束回调")
+ }
+
+ override fun onInterstitialAdVideoError(adError: AdError?) {
+ Log.d("ocean", "视频广告播放失败回调->${adError.toString()}")
+ showListener?.onAdShowFailed(AdShowFailed(adError.toString()))
+ }
+
+ })
+ if (atInterstitial?.isAdReady == true) {
+ atInterstitial.show(activity)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/gogame/minesweeper/ad/AdShowFailed.kt b/app/src/main/java/com/gogame/minesweeper/ad/AdShowFailed.kt
new file mode 100644
index 0000000..3060321
--- /dev/null
+++ b/app/src/main/java/com/gogame/minesweeper/ad/AdShowFailed.kt
@@ -0,0 +1,5 @@
+package com.gogame.minesweeper.ad
+
+data class AdShowFailed(
+ val msg: String = "",
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/gogame/minesweeper/ad/AdsInsUtil.kt b/app/src/main/java/com/gogame/minesweeper/ad/AdsInsUtil.kt
new file mode 100644
index 0000000..8ef7996
--- /dev/null
+++ b/app/src/main/java/com/gogame/minesweeper/ad/AdsInsUtil.kt
@@ -0,0 +1,34 @@
+package com.gogame.minesweeper.ad
+
+import android.app.Activity
+
+object AdsInsUtil {
+
+ object Placement {
+ const val TOP_ON_AD_ONE = "n692ffb09edbb6"
+ const val TOP_ON_AD_TOW = "n692ffb09bc89d"
+ const val TOP_ON_AD_THREE = "n692ffb098d0d5"
+
+ val adPlaceAllList = listOf(
+ TOP_ON_AD_ONE,
+ TOP_ON_AD_TOW,
+ TOP_ON_AD_THREE
+ )
+ }
+
+ fun loadAd(
+ act: Activity,
+ adID: String,
+ loadListener: LoadListener?
+ ): AdInstLoad {
+ return AdInstLoad(act, adID, loadListener)
+ }
+
+ fun showAd(
+ act: Activity,
+ adID: String,
+ listener: ShowListener?
+ ): AdInstShower {
+ return AdInstShower(act, adID, listener)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/gogame/minesweeper/ad/InstAdCacheManager.kt b/app/src/main/java/com/gogame/minesweeper/ad/InstAdCacheManager.kt
new file mode 100644
index 0000000..144a4e6
--- /dev/null
+++ b/app/src/main/java/com/gogame/minesweeper/ad/InstAdCacheManager.kt
@@ -0,0 +1,36 @@
+package com.gogame.minesweeper.ad
+
+import com.thinkup.interstitial.api.TUInterstitial
+
+
+class InstAdCacheManager {
+ private val mAdCacheDict: MutableMap = mutableMapOf()
+
+ companion object {
+ val instance: InstAdCacheManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
+ InstAdCacheManager()
+ }
+ }
+
+ fun setAdCache(place: String, adCache: TUInterstitial) {
+ mAdCacheDict[place] = adCache
+ }
+
+ fun getAdCache(place: String): TUInterstitial? {
+ return mAdCacheDict[place]
+ }
+
+ fun getLoadedInstCount(): Int {
+ var count = 0
+ try {
+ mAdCacheDict.forEach { (key, value) ->
+ if (value.isAdReady) {
+ count += 1
+ }
+ }
+ } catch (_: Exception) {
+
+ }
+ return count
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/gogame/minesweeper/ad/LoadListener.kt b/app/src/main/java/com/gogame/minesweeper/ad/LoadListener.kt
new file mode 100644
index 0000000..2c69cf1
--- /dev/null
+++ b/app/src/main/java/com/gogame/minesweeper/ad/LoadListener.kt
@@ -0,0 +1,8 @@
+package com.gogame.minesweeper.ad
+
+import com.thinkup.core.api.TUAdInfo
+
+interface LoadListener {
+ fun loadFailed(error: String) {}
+ fun loaded(ad: TUAdInfo) {}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/gogame/minesweeper/ad/ShowListener.kt b/app/src/main/java/com/gogame/minesweeper/ad/ShowListener.kt
new file mode 100644
index 0000000..d8171c6
--- /dev/null
+++ b/app/src/main/java/com/gogame/minesweeper/ad/ShowListener.kt
@@ -0,0 +1,10 @@
+package com.gogame.minesweeper.ad
+
+import com.thinkup.core.api.TUAdInfo
+
+interface ShowListener {
+ fun onAdShown(ad: TUAdInfo?) {}
+ fun onAdShowFailed(error: AdShowFailed?) {}
+ fun onAdClosed() {}
+ fun onAdClicked() {}
+}
diff --git a/app/src/main/java/com/gogame/minesweeper/bean/CellState.kt b/app/src/main/java/com/gogame/minesweeper/bean/CellState.kt
new file mode 100644
index 0000000..0d36061
--- /dev/null
+++ b/app/src/main/java/com/gogame/minesweeper/bean/CellState.kt
@@ -0,0 +1,7 @@
+package com.gogame.minesweeper.bean
+
+data class CellState(
+ var logicState: Int, // 逻辑状态
+ var viewState: Int, // 视图状态
+ var around: Int // 周围雷数
+)
diff --git a/app/src/main/java/com/gogame/minesweeper/bean/GameState.kt b/app/src/main/java/com/gogame/minesweeper/bean/GameState.kt
new file mode 100644
index 0000000..9a444b0
--- /dev/null
+++ b/app/src/main/java/com/gogame/minesweeper/bean/GameState.kt
@@ -0,0 +1,32 @@
+package com.gogame.minesweeper.bean
+
+import com.gogame.minesweeper.DifficultyLevel
+
+data class GameState(
+ val grid: Array>, // 游戏网格
+ val remainingMines: Int, // 剩余雷数
+ val difficulty: DifficultyLevel, // 难度
+ val minutes : Int,
+ val seconds : Int,
+) {
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+
+ other as GameState
+
+ if (!grid.contentDeepEquals(other.grid)) return false
+ if (remainingMines != other.remainingMines) return false
+ if (difficulty != other.difficulty) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = grid.contentDeepHashCode()
+ result = 31 * result + remainingMines
+ result = 31 * result + difficulty.hashCode()
+ return result
+ }
+
+}
diff --git a/app/src/main/java/com/gogame/minesweeper/swipe/LogicHelper.kt b/app/src/main/java/com/gogame/minesweeper/swipe/LogicHelper.kt
new file mode 100644
index 0000000..a077fe5
--- /dev/null
+++ b/app/src/main/java/com/gogame/minesweeper/swipe/LogicHelper.kt
@@ -0,0 +1,66 @@
+package com.gogame.minesweeper.swipe
+
+import android.annotation.SuppressLint
+import java.util.*
+
+object LogicHelper {
+ fun randomMines(xlength: Int, ylength: Int, x: Int, y: Int, count: Int): ArrayList {
+ val total = xlength * ylength - 1
+ if (count >= total || count <= 0) {
+ throw IllegalArgumentException("Invalid mine count: $count")
+ }
+
+ val random = Random()
+ val randoms = hashSetOf(getIndexByXY(xlength, ylength, x, y)) // 用HashSet防止重复
+
+ while (randoms.size < count + 1) {
+ val next = random.nextInt(xlength * ylength)
+ randoms.add(next) // HashSet 自动忽略重复的数字
+ }
+
+ randoms.remove(getIndexByXY(xlength, ylength, x, y)) // 移除点击位置
+ return ArrayList(randoms)
+ }
+
+
+ fun getIndexByXY(xlength: Int, ylength: Int, x: Int, y: Int): Int {
+ return x + y * xlength
+ }
+
+ fun getXYbyIndex(xlength:Int,ylength:Int,index:Int):IntArray{
+ val x = index%ylength
+ val y = index/ylength
+ return intArrayOf(x,y)
+ }
+
+ fun getAroundItems(xLength: Int, yLength: Int, index: Int): List {
+ val result = mutableListOf()
+ val x = index % xLength // 根据列数获取x坐标
+ val y = index / xLength // 根据行数获取y坐标
+
+ // 检查周围的8个格子
+ for (i in -1..1) {
+ for (j in -1..1) {
+ if (i == 0 && j == 0) continue // 跳过当前格子
+ val newX = x + i
+ val newY = y + j
+ // 检查边界
+ if (newX in 0 until xLength && newY in 0 until yLength) {
+ result.add(newY * xLength + newX)
+ }
+ }
+ }
+ return result
+ }
+
+
+
+ /**
+ * 转换成三位数文字
+ */
+ @SuppressLint("DefaultLocale")
+ fun int2String(int: Int): String {
+ return String.format("%03d", int)
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/gogame/minesweeper/swipe/SingleView.kt b/app/src/main/java/com/gogame/minesweeper/swipe/SingleView.kt
new file mode 100644
index 0000000..bd4652d
--- /dev/null
+++ b/app/src/main/java/com/gogame/minesweeper/swipe/SingleView.kt
@@ -0,0 +1,134 @@
+package com.gogame.minesweeper.swipe
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.media.MediaPlayer
+import android.util.AttributeSet
+import android.view.MotionEvent
+import android.view.View
+import android.view.View.OnTouchListener
+import android.widget.ImageView
+import com.gogame.minesweeper.R
+
+@SuppressLint("AppCompatCustomView")
+class SingleView(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) :
+ ImageView(context, attrs, defStyleAttr), View.OnClickListener, View.OnLongClickListener{
+
+
+ companion object {
+ //常量定义
+ const val VIEWSTATE_DEFAULT = 0
+ const val VIEWSTATE_FLAG = 1//标记这里是雷
+ const val VIEWSTATE_UNSURE = 2//? 不确定
+ const val VIEWSTATE_TIP = 3//显示周围雷数 需要配合around参数
+ const val VIEWSTATE_THUNDER = 4//游戏结束的时候显示这里的确是雷
+ const val VIEWSTATE_BOMB = 5//游戏失败时最后点到的雷
+
+ const val LOGICSTATE_NOT_INITED = -1
+ const val LOGICSTATE_BOMB = -2
+ }
+
+ // val mPaint = Paint()
+ var around = 0
+ //周围有几颗雷
+ set(value) {
+ if (value > 8)
+ throw IllegalArgumentException("around should not be this value:$value")
+ else
+ field = value
+ }
+ var viewState = 0
+ var logicState = LOGICSTATE_NOT_INITED//-1 未初始化 0~8周围地雷数 -2雷 只有这几种可能
+ var enable = true//点击过以后不能再次点击 或者也有可能设置不可点击就行了?
+ var gridX = -1
+ var gridY = -1
+ lateinit var mGrid: SwipeGridLayout
+
+
+ init {
+ isClickable = true
+ setOnClickListener(this)
+ setOnLongClickListener(this)
+ setBackgroundResource(R.drawable.sel_bg_single)
+ scaleType = ScaleType.CENTER_CROP
+
+ updateInsideImg()
+ if (attrs != null) {
+ //nothing to do
+ }
+ }
+
+ constructor(context: Context?, x: Int, y: Int, mGridLayout: SwipeGridLayout) : this(context) {
+ this.gridX = x
+ this.gridY = y
+ this.mGrid = mGridLayout
+ }
+
+ constructor(context: Context?, attrs: AttributeSet?) : this(context, attrs, 0)
+ constructor(context: Context?) : this(context, null)
+
+ fun updateInsideImg(): Unit {
+ setImageResource(
+ when (viewState) {
+ VIEWSTATE_DEFAULT -> R.mipmap.tile
+ VIEWSTATE_BOMB -> R.mipmap.mine_exploded
+ VIEWSTATE_THUNDER -> R.mipmap.mine
+ VIEWSTATE_FLAG -> R.mipmap.flag
+ VIEWSTATE_UNSURE -> R.mipmap.unsuer
+ VIEWSTATE_TIP -> when (around) {
+ 0 -> R.mipmap.empty
+ 1 -> R.mipmap.n1
+ 2 -> R.mipmap.n2
+ 3 -> R.mipmap.n3
+ 4 -> R.mipmap.n4
+ 5 -> R.mipmap.n5
+ 6 -> R.mipmap.n6
+ 7 -> R.mipmap.n7
+ 8 -> R.mipmap.n8
+ else -> throw IllegalArgumentException("illegal around")
+ }
+
+ else -> throw IllegalArgumentException("illegal viewState")
+ }
+ )
+ invalidate()//理论上应该都是在主线程调用的该方法
+ }
+
+ override fun onClick(v: View?) {
+ if (!enable)
+ return
+ if (viewState == VIEWSTATE_FLAG)//fix 插旗以后应该不再能点开了,防误触
+ return
+
+ mGrid.onChildClicked(this, gridX, gridY)
+ }
+
+ override fun onLongClick(v: View?): Boolean {
+ if (!enable)
+ return true
+ if (!mGrid.started)
+ return true
+
+ viewState = when (viewState) {
+ VIEWSTATE_DEFAULT -> VIEWSTATE_FLAG
+ VIEWSTATE_FLAG -> VIEWSTATE_UNSURE
+ VIEWSTATE_UNSURE -> VIEWSTATE_DEFAULT
+ else -> viewState
+ }
+ updateInsideImg()
+
+ mGrid.onChildLongClicked(this, gridX, gridY, viewState)
+ return true
+ }
+
+ fun reset() {
+ viewState = VIEWSTATE_DEFAULT
+ logicState = LOGICSTATE_NOT_INITED
+ around = 0
+ enable = true
+ }
+
+
+}
+
+
diff --git a/app/src/main/java/com/gogame/minesweeper/swipe/SwipeActivity.kt b/app/src/main/java/com/gogame/minesweeper/swipe/SwipeActivity.kt
new file mode 100644
index 0000000..2d9353f
--- /dev/null
+++ b/app/src/main/java/com/gogame/minesweeper/swipe/SwipeActivity.kt
@@ -0,0 +1,368 @@
+package com.gogame.minesweeper.swipe
+
+import android.app.Dialog
+import android.content.Context
+import android.media.MediaPlayer
+import android.os.Bundle
+import android.os.Handler
+import android.os.Looper
+import android.view.View
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowCompat
+import androidx.core.view.WindowInsetsCompat
+import com.gogame.minesweeper.DifficultyLevel
+import com.gogame.minesweeper.R
+import com.gogame.minesweeper.databinding.ActivitySwipeBinding
+import com.gogame.minesweeper.BaseActivity
+import com.gogame.minesweeper.ad.AdShowFailed
+import com.gogame.minesweeper.ad.AdsInsUtil
+import com.gogame.minesweeper.ad.LoadListener
+import com.gogame.minesweeper.ad.ShowListener
+import com.gogame.minesweeper.bean.GameState
+import com.google.gson.Gson
+import com.ironsource.fa
+import com.thinkup.core.api.TUAdInfo
+import kotlin.random.Random
+
+class SwipeActivity : BaseActivity() {
+
+ private lateinit var binding: ActivitySwipeBinding
+
+ private var minutes = 0
+ private var seconds = 0
+ private var isRunning = false
+ private var pausedTime = 0L // 暂停时保存的时间戳
+ private var isGameStarted = false //游戏是否开始了
+ private var isGameOver = false //游戏是否结束了(不管成功还是失败)
+ private val handler = Handler(Looper.getMainLooper())
+ private lateinit var runnable: Runnable
+ private lateinit var winSoundPlayer: MediaPlayer
+ private lateinit var loseSoundPlayer: MediaPlayer
+ private var isNewGame = true
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivitySwipeBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+ loadAd()
+ immersionBar()
+ winSoundPlayer = MediaPlayer.create(this, R.raw.win)
+ loseSoundPlayer = MediaPlayer.create(this, R.raw.explosion)
+
+ var difficulty = intent.getSerializableExtra("difficulty") as? DifficultyLevel
+ if (difficulty == null) {
+ difficulty = DifficultyLevel.BEGINNER
+ }
+ isNewGame = intent.getBooleanExtra("isNewGame", true)
+ initTimeRun()
+
+ resetTimer()
+
+ val sharedPreferences = getSharedPreferences("minesweeper_prefs", Context.MODE_PRIVATE)
+ val gameStateJson = sharedPreferences.getString("game_state", null)
+
+ binding.gridSwipe.postDelayed({
+ binding.gridSwipe.setDifficulty(difficulty)
+ setNumberImage(binding.gridSwipe.mineNum, binding.hundreds, binding.tens, binding.ones)
+ binding.gridSwipe.fillAll()
+
+ if (!isNewGame) {
+ if (!gameStateJson.isNullOrEmpty()) {
+ val gameState: GameState = Gson().fromJson(gameStateJson, GameState::class.java)
+ minutes = gameState.minutes
+ seconds = gameState.seconds
+
+ startTimeRun()
+ }
+ binding.gridSwipe.loadGameState()
+ }
+ }, 120)
+
+ binding.btnStart.setOnClickListener {
+ handleButtonClick()
+ }
+ binding.gridSwipe.listener = object : SwipeGridLayout.GameStateChange {
+ override fun onStart(rest: Int) {
+ isGameStarted = true
+ binding.stateImg.setImageResource(R.mipmap.smile)
+ startTimeRun()
+ }
+
+ override fun onFlagsChange(flags: Int, rest: Int) {
+ setNumberImage(rest, binding.hundreds, binding.tens, binding.ones)
+ }
+
+ override fun victory() {
+ isGameOver = true
+ playWinSound()
+ showResultDialog(true)
+ stopTimer()
+
+ binding.gridSwipe.clearGameStateSP()
+ }
+
+ override fun defeat() {
+ isGameOver = true
+ playLoseSound()
+ binding.stateImg.setImageResource(R.mipmap.sorrow)
+ showResultDialog(false)
+ stopTimer()
+
+ binding.gridSwipe.clearGameStateSP()
+ }
+
+ }
+ }
+
+ private fun immersionBar() {
+ //自适应沉浸式
+ ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
+ val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
+ v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
+ insets
+ }
+
+ val windowInsetsController = WindowCompat.getInsetsController(window, window.decorView)
+ windowInsetsController.isAppearanceLightStatusBars = true//状态栏文字颜色
+ }
+
+ private val successImages = arrayOf(
+ R.drawable.win_emoji_partying_face, // 替换为你的成功图片资源
+ R.drawable.win_emoji_cowboy_hat_face,
+ R.drawable.win_emoji_smiling_face_with_sunglasses,
+ R.drawable.win_emoji_beaming_face_with_smiling_eyes
+ )
+
+ private val failureImages = arrayOf(
+ R.drawable.lose_emoji_bomb, // 替换为你的失败图片资源
+ R.drawable.lose_emoji_confounded_face,
+ R.drawable.lose_emoji_anguished_face,
+ R.drawable.lose_emoji_exploding_head, // 替换为你的失败图片资源
+ R.drawable.lose_emoji_grimacing_face,
+ R.drawable.lose_emoji_downcast_face_with_sweat,
+ R.drawable.lose_emoji_face_with_head_bandage,
+ R.drawable.lose_emoji_sad_but_relieved_face
+ )
+
+ private fun showResultDialog(isVictory: Boolean) {
+ val dialog = Dialog(this)
+ dialog.setContentView(R.layout.dialog_result)
+
+ val imageView = dialog.findViewById(R.id.imageView)
+ val titleTextView = dialog.findViewById(R.id.textViewTitle)
+ val messageTextView = dialog.findViewById(R.id.textViewMessage)
+
+ if (isVictory) {
+ val randomSuccessImage = successImages[Random.nextInt(successImages.size)]
+ imageView.setImageResource(randomSuccessImage)
+ titleTextView.text = getString(R.string.you_win)
+ messageTextView.text = getString(R.string.win_desc)
+ } else {
+ val randomFailureImage = failureImages[Random.nextInt(failureImages.size)]
+ imageView.setImageResource(randomFailureImage)
+ titleTextView.text = getString(R.string.you_lose)
+ messageTextView.text = getString(R.string.lose_desc)
+ }
+
+ val btn = dialog.findViewById(R.id.newGameBtn)
+ btn.setOnClickListener {
+ handleButtonClick()
+ dialog.dismiss()
+
+ showAd(randomAdPlace)
+ }
+ dialog.setCancelable(false)
+ dialog.show()
+ }
+
+
+ private fun playWinSound() {
+ if (!winSoundPlayer.isPlaying) {
+ winSoundPlayer.start()
+ }
+ }
+
+ private fun playLoseSound() {
+ if (!loseSoundPlayer.isPlaying) {
+ loseSoundPlayer.start()
+ }
+ }
+
+ private fun handleButtonClick() {
+ binding.stateImg.setImageResource(R.mipmap.smile)
+ binding.gridSwipe.reset() // 复位逻辑
+ setNumberImage(
+ binding.gridSwipe.mineNum, binding.hundreds, binding.tens, binding.ones
+ ) // 设置数字图片
+ stopTimer() // 停止计时器
+ resetTimer() // 重置计时器
+ }
+
+ private fun startTimeRun() {
+ if (!isRunning) {
+ isRunning = true
+ handler.post(runnable)
+ }
+ }
+
+ // 停止计时并移除 runnable
+ private fun stopTimer() {
+ isRunning = false
+ handler.removeCallbacks(runnable) // 移除 runnable,防止继续执行
+ }
+
+ private fun initTimeRun() {
+ // 初始化计时器
+ runnable = object : Runnable {
+ override fun run() {
+ if (isRunning) {
+ incrementTime()
+ updateTimeImage()
+ handler.postDelayed(this, 1000) // 每秒更新一次
+ }
+ }
+ }
+
+ }
+
+ private fun setNumberImage(
+ number: Int,
+ hundredsImageView: ImageView,
+ tensImageView: ImageView,
+ onesImageView: ImageView
+ ) {
+ // 数字限制在1-999
+ if (number < 1 || number > 999) return
+
+ // 获取百位、十位、个位
+ val hundreds = number / 100
+ val tens = (number % 100) / 10
+ val ones = number % 10
+
+ // 根据数字设置图片资源ID,假设图片资源名为c0到c9
+ val resIdHundreds = getResourceIdForNumber(hundreds)
+ val resIdTens = getResourceIdForNumber(tens)
+ val resIdOnes = getResourceIdForNumber(ones)
+
+ // 设置ImageView的图片资源
+ hundredsImageView.setImageResource(resIdHundreds)
+ tensImageView.setImageResource(resIdTens)
+ onesImageView.setImageResource(resIdOnes)
+ }
+
+ // 获取数字对应的资源ID
+ private fun getResourceIdForNumber(num: Int): Int {
+ return when (num) {
+ 0 -> R.mipmap.c0
+ 1 -> R.mipmap.c1
+ 2 -> R.mipmap.c2
+ 3 -> R.mipmap.c3
+ 4 -> R.mipmap.c4
+ 5 -> R.mipmap.c5
+ 6 -> R.mipmap.c6
+ 7 -> R.mipmap.c7
+ 8 -> R.mipmap.c8
+ 9 -> R.mipmap.c9
+ else -> R.mipmap.c0 // 默认值,数字应为0-9
+ }
+ }
+
+ // 增加时间
+ private fun incrementTime() {
+ seconds++
+ if (seconds == 60) {
+ seconds = 0
+ minutes++
+ }
+
+ binding.gridSwipe.setTime(minutes, seconds)
+ }
+
+ // 更新时间的图片显示
+ private fun updateTimeImage() {
+ val minuteTens = minutes / 10
+ val minuteOnes = minutes % 10
+ val secondTens = seconds / 10
+ val secondOnes = seconds % 10
+
+ binding.minuteTens.setImageResource(getResourceIdForNumber(minuteTens))
+ binding.minuteOnes.setImageResource(getResourceIdForNumber(minuteOnes))
+ binding.secondTens.setImageResource(getResourceIdForNumber(secondTens))
+ binding.secondOnes.setImageResource(getResourceIdForNumber(secondOnes))
+ }
+
+ override fun onResume() {
+ super.onResume()
+ //如果游戏开始了并且游戏没有结束
+ if (isGameStarted && !isGameOver) {
+ if (pausedTime > 0L) {
+ // 继续计时
+ handler.post(runnable)
+ isRunning = true
+ }
+ }
+ }
+
+ override fun onPause() {
+ super.onPause()
+
+ // 保存当前的计时状态
+ pausedTime = System.currentTimeMillis()
+ isRunning = false
+ handler.removeCallbacks(runnable) // 停止计时
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ // 重置计时器
+ resetTimer()
+ //释放资源
+ binding.gridSwipe.onPlayerRelease()
+ winSoundPlayer.release()
+ loseSoundPlayer.release()
+ }
+
+ // 重置计时器为00:00
+ private fun resetTimer() {
+ minutes = 0
+ seconds = 0
+ updateTimeImage()
+ }
+
+ private var randomAdPlace = ""
+ private fun loadAd() {
+ // 生成一个随机索引
+ val randomIndex = Random.nextInt(AdsInsUtil.Placement.adPlaceAllList.size)
+ // 获取随机的广告位
+ randomAdPlace = AdsInsUtil.Placement.adPlaceAllList[randomIndex]
+ AdsInsUtil.loadAd(
+ act = this,
+ adID = randomAdPlace,
+ loadListener = object : LoadListener {
+ override fun loaded(ad: TUAdInfo) {
+ }
+
+ override fun loadFailed(error: String) {
+ }
+ })
+ }
+ private fun showAd(randomAdPlace: String) {
+ AdsInsUtil.showAd(
+ act = this,
+ adID = randomAdPlace,
+ listener = object : ShowListener {
+ override fun onAdShown(ad: TUAdInfo?) {
+ }
+
+ override fun onAdShowFailed(error: AdShowFailed?) {
+ loadAd()
+ }
+
+ override fun onAdClosed() {
+ loadAd()
+ }
+ })
+ }
+}
diff --git a/app/src/main/java/com/gogame/minesweeper/swipe/SwipeGridLayout.kt b/app/src/main/java/com/gogame/minesweeper/swipe/SwipeGridLayout.kt
new file mode 100644
index 0000000..fb73868
--- /dev/null
+++ b/app/src/main/java/com/gogame/minesweeper/swipe/SwipeGridLayout.kt
@@ -0,0 +1,518 @@
+package com.gogame.minesweeper.swipe
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Paint
+import android.media.MediaPlayer
+import android.util.AttributeSet
+import android.util.Log
+import android.view.MotionEvent
+import android.view.ScaleGestureDetector
+import android.widget.GridLayout
+import com.gogame.minesweeper.bean.CellState
+import com.gogame.minesweeper.DifficultyLevel
+import com.gogame.minesweeper.bean.GameState
+import com.gogame.minesweeper.R
+import com.google.gson.Gson
+import kotlin.math.abs
+
+
+class SwipeGridLayout @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0,
+ var xLength: Int = 9, // 网格的行数
+ var yLength: Int = 9, // 网格的列数
+ var mineNum: Int = 10 // 地雷的数量
+) : GridLayout(context, attrs, defStyleAttr) {
+ private val sharedPreferences =
+ context.getSharedPreferences("minesweeper_prefs", Context.MODE_PRIVATE)
+ private var myLevel = DifficultyLevel.BEGINNER
+ private var minutes = 0
+ private var seconds = 0
+
+ // 固定格子初始大小
+ private var fixedSize = 98
+
+ // 点击音效的MediaPlayer
+ private var clickSoundPlayer: MediaPlayer = MediaPlayer.create(context, R.raw.tile)
+
+ // 长按音效的MediaPlayer
+ private var clickLongSoundPlayer: MediaPlayer = MediaPlayer.create(context, R.raw.flag)
+
+ private fun playClickSound() {
+ if (!clickSoundPlayer.isPlaying) {
+ clickSoundPlayer.start()
+ }
+ }
+
+ private fun playLongClickSound() {
+ if (!clickLongSoundPlayer.isPlaying) {
+ clickLongSoundPlayer.start()
+ }
+ }
+
+ var goods: Int//标记剩余未点开的非雷格子,用于判断游戏胜利
+ var started = false//是否已经点击过第一个格子
+ private var scaleFactor = 1.0f // 初始缩放比例
+ private var scaleGestureDetector: ScaleGestureDetector
+ private var isScaling = false // 是否正在缩放
+
+ init {
+ goods = xLength * yLength - mineNum// 初始化非雷格子数
+
+ // 初始化缩放手势检测器
+ scaleGestureDetector = ScaleGestureDetector(context, ScaleListener())
+ }
+
+
+ private inner class ScaleListener : ScaleGestureDetector.SimpleOnScaleGestureListener() {
+ override fun onScaleBegin(detector: ScaleGestureDetector): Boolean {
+ // 缩放开始时,设置标志位为 true
+ isScaling = true
+ return super.onScaleBegin(detector)
+ }
+
+ override fun onScale(detector: ScaleGestureDetector): Boolean {
+ // 更新缩放比例
+ scaleFactor *= detector.scaleFactor
+
+ // 限制缩放比例在 0.5x 到 3x 之间
+ scaleFactor = scaleFactor.coerceIn(0.5f, 1.5f)
+
+ // 请求重新布局
+ requestLayout()
+ return true
+ }
+
+ override fun onScaleEnd(detector: ScaleGestureDetector) {
+ // 缩放结束时,重置标志位为 false
+ isScaling = false
+ super.onScaleEnd(detector)
+ }
+ }
+
+
+ // 在填充格子时,初始化为固定大小
+ fun fillAll() {
+ removeAllViews()
+ rowCount = xLength
+ columnCount = yLength
+ for (j in 0 until xLength) {
+ for (i in 0 until yLength) {
+ val one = SingleView(context, i, j, this)
+ addView(one, fixedSize, fixedSize) // 使用固定大小
+ }
+ }
+ }
+
+ private var lastX = 0f
+ private var lastY = 0f
+ override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
+ val x = event.x
+ val y = event.y
+
+
+ when (event.action) {
+ MotionEvent.ACTION_DOWN -> {
+ lastX = x
+ lastY = y
+
+ Log.d("ocean", "onInterceptTouchEvent ACTION_DOWN")
+ Log.d(
+ "ocean",
+ "onInterceptTouchEvent ACTION_DOWN width->${width} height->${height}"
+ )
+ }
+
+ MotionEvent.ACTION_MOVE -> {
+ val deltaX = x - lastX
+ val deltaY = y - lastY
+
+ // 判断是否已经拖动,并且是否超过最小拖动距离
+ if (abs(deltaX) > 10 || abs(deltaY) > 10) {
+ Log.d("ocean", "onInterceptTouchEvent ACTION_MOVE")
+ // 开始处理拖动时,拦截事件
+ return true
+ }
+ }
+ }
+ return super.onInterceptTouchEvent(event)
+ }
+
+ override fun onLayout(
+ changed: Boolean,
+ left: Int,
+ top: Int,
+ right: Int,
+ bottom: Int
+ ) {
+ super.onLayout(changed, left, top, right, bottom)
+
+ // 遍历所有的子视图,调整每个格子的大小
+ val childSize = (fixedSize * scaleFactor).toInt() // 缩放后的格子大小
+ for (i in 0 until childCount) {
+ val child = getChildAt(i)
+ val row = i / columnCount
+ val col = i % columnCount
+
+ // 根据缩放后的大小设置每个子视图的位置和大小
+ val childLeft = col * childSize
+ val childTop = row * childSize
+ child.layout(
+ childLeft,
+ childTop,
+ childLeft + childSize,
+ childTop + childSize
+ )
+ }
+ }
+
+ override fun requestLayout() {
+ super.requestLayout()
+ invalidate()
+ }
+
+ override fun onTouchEvent(event: MotionEvent): Boolean {
+
+ // 让 scaleGestureDetector 处理缩放事件
+ scaleGestureDetector.onTouchEvent(event)
+ // 如果正在缩放,阻止其他事件处理
+ if (isScaling) {
+ return true
+ }
+
+ val x = event.x
+ val y = event.y
+
+ when (event.action) {
+ MotionEvent.ACTION_DOWN -> {
+ Log.d("ocean", "onTouchEvent ACTION_DOWN")
+ lastX = x
+ lastY = y
+ }
+
+ MotionEvent.ACTION_MOVE -> {
+ Log.d("ocean", "onTouchEvent ACTION_MOVE x->${event.x} y->${event.y}")
+ Log.d("ocean", "onTouchEvent ACTION_MOVE lastX->${lastX} lastY->${lastY}")
+ val dx = event.x - lastX
+ val dy = event.y - lastY
+ // 移动整个布局
+ scrollBy(-dx.toInt(), -dy.toInt())
+ lastX = event.x
+ lastY = event.y
+
+ Log.d("ocean", "onTouchEvent ACTION_MOVE dx->$dx dy->$dy")
+ return true
+ }
+
+ MotionEvent.ACTION_UP -> {
+ Log.d("ocean", "onTouchEvent ACTION_UP")
+ }
+
+ MotionEvent.ACTION_CANCEL -> {
+ Log.d("ocean", "onTouchEvent ACTION_CANCEL")
+ }
+ }
+ return super.onTouchEvent(event)
+ }
+
+ // 处理网格点击事件
+ fun onChildClicked(v: SingleView, x: Int, y: Int) {
+
+ playClickSound()
+ // 如果这是第一个点击,初始化游戏
+ if (!started) {
+ initCurGame(x, y)
+ started = true
+ listener?.onStart(mineNum)
+ }
+ // 初始化完游戏之后 每一次(包括第一次)按下之后的逻辑
+ when (v.logicState) {
+ SingleView.LOGICSTATE_BOMB -> {
+ doEachSingle { singleView ->
+ if (singleView.logicState == SingleView.LOGICSTATE_BOMB) {
+ singleView.viewState = SingleView.VIEWSTATE_THUNDER
+ }
+ singleView.enable = false
+ }
+ v.viewState = SingleView.VIEWSTATE_BOMB
+ listener?.defeat()
+
+ clearGameStateSP()
+ }
+
+ in 1..8 -> { // 显示周围的雷数
+ v.viewState = SingleView.VIEWSTATE_TIP
+ goods--
+ }
+
+ 0 -> {// 如果点击的格子周围没有雷,展开周围的格子
+ openAround(v, LogicHelper.getIndexByXY(xLength, yLength, x, y))
+ }
+ }
+
+ v.enable = false // 禁止再次点击已经翻开的格子
+ doEachSingle { singleView -> singleView.updateInsideImg() }
+ // 判断是否胜利
+ if (goods <= 0) {
+ listener?.victory()
+ doEachSingle { singleView -> singleView.enable = false }
+
+ clearGameStateSP()
+ }
+ }
+
+ // 打开周围的非雷格子
+ private fun openAround(v: SingleView, index: Int) {
+ v.enable = false
+ v.viewState = SingleView.VIEWSTATE_TIP
+ goods--
+ for (around in LogicHelper.getAroundItems(xLength, yLength, index)) {
+ val item = getChildByIndex(around)
+ if (item.viewState == SingleView.VIEWSTATE_DEFAULT && item.logicState >= 0) {
+ if (item.around == 0) {
+ openAround(getChildByIndex(around), around)
+ } else {
+ item.enable = false
+ item.viewState = SingleView.VIEWSTATE_TIP
+ goods--
+ }
+ }
+ }
+ }
+
+ // 初始化当前游戏,设置地雷
+ private fun initCurGame(firstX: Int, firstY: Int) {
+ goods = xLength * yLength - mineNum
+ //初始化所有格子
+ initAllChildBefore()
+ // 随机布雷,确保第一个点击的不是雷
+ val randomMines = LogicHelper.randomMines(xLength, yLength, firstX, firstY, mineNum)
+ for (i: Int in randomMines) {
+ val singleView = getChildByIndex(i)
+ singleView.logicState = SingleView.LOGICSTATE_BOMB
+ singleView.viewState = SingleView.VIEWSTATE_DEFAULT//test 显示雷
+ singleView.updateInsideImg()
+ }
+ calMinesAround(randomMines)
+ }
+
+ // 计算每个格子周围的雷数
+ private fun calMinesAround(randomMines: ArrayList) {
+ for (i: Int in randomMines) {
+ for (j in LogicHelper.getAroundItems(xLength, yLength, i)) {
+ val view = getChildByIndex(j)
+ view.around++
+ if (view.logicState >= 0)//如果不是雷的话
+ view.logicState = view.around
+ view.updateInsideImg()
+ }
+ }
+ }
+
+ private fun getChildByIndex(index: Int): SingleView {
+ return getChildAt(index) as SingleView
+ }
+
+ private fun initAllChildBefore() {
+ doEachSingle { singleView: SingleView ->
+ singleView.logicState = 0//初始化逻辑状态 Default
+ singleView.viewState = SingleView.VIEWSTATE_DEFAULT//
+ singleView.around = 0
+ singleView.updateInsideImg()
+ }
+ }
+
+ var flags = 0
+ fun onChildLongClicked(v: SingleView, x: Int, y: Int, viewState: Int) {
+ playLongClickSound()
+ when (viewState) {
+ SingleView.VIEWSTATE_FLAG -> flags++
+ SingleView.VIEWSTATE_UNSURE -> flags--
+ }
+ listener?.onFlagsChange(flags, mineNum - flags)
+ }
+
+ fun getChildByXY(x: Int, y: Int): SingleView {
+ return getChildAt(y * columnCount + x) as SingleView
+ }
+
+ private fun doEachSingle(m: (singleView: SingleView) -> Unit) {
+ for (i in 0..childCount - 1) {
+ m(getChildByIndex(i))
+ }
+ }
+
+ //传送难度数据
+ fun setDifficulty(level: DifficultyLevel) {
+ this.xLength = level.xLength
+ this.yLength = level.yLength
+ this.mineNum = level.mineNum
+ this.myLevel = level
+ }
+
+ //传送时间
+ fun setTime(min: Int, sec: Int) {
+ this.minutes = min
+ this.seconds = sec
+
+ //每一秒都进行存储
+ saveGameState()
+ }
+
+ /**
+ * 开始
+ */
+ fun reset() {
+ doEachSingle { singleView ->
+ flags = 0
+ goods = xLength * yLength - mineNum
+ started = false//要点击第一个之后才会变成true
+ singleView.reset()
+ singleView.updateInsideImg()
+ }
+ }
+
+
+ var listener: GameStateChange? = null
+
+ interface GameStateChange {
+ fun onStart(rest: Int)
+ fun onFlagsChange(flags: Int, rest: Int)
+ fun victory()
+ fun defeat()
+ }
+
+ /**
+ * 画分割线
+ */
+ override fun dispatchDraw(canvas: Canvas) {
+ super.dispatchDraw(canvas)
+ if (childCount <= 0) return
+ val column = columnCount
+ val childCount = childCount
+ val localPaint = Paint()
+ localPaint.style = Paint.Style.STROKE
+ localPaint.setColor(Color.parseColor("#303030"))
+ localPaint.isAntiAlias = true
+ localPaint.strokeWidth = 1f
+ for (i in 0 until childCount) {
+ val cellView = getChildAt(i)
+ if (i < columnCount) {
+ canvas.drawLine(
+ cellView.left.toFloat(),
+ cellView.top.toFloat(),
+ cellView.right.toFloat(),
+ cellView.top.toFloat(),
+ localPaint
+ )
+ }
+ if (i % columnCount == 0) {
+ canvas.drawLine(
+ cellView.left.toFloat(),
+ cellView.top.toFloat(),
+ cellView.left.toFloat(),
+ cellView.bottom.toFloat(),
+ localPaint
+ )
+ }
+ canvas.drawLine(
+ cellView.right.toFloat(),
+ cellView.top.toFloat(),
+ cellView.right.toFloat(),
+ cellView.bottom.toFloat(),
+ localPaint
+ )
+ canvas.drawLine(
+ cellView.left.toFloat(),
+ cellView.bottom.toFloat(),
+ cellView.right.toFloat(),
+ cellView.bottom.toFloat(),
+ localPaint
+ )
+ }
+ if (childCount % column != 0) {
+ for (j in 0 until column - childCount % column) {
+ val lastView = getChildAt(childCount - 1)
+ canvas.drawLine(
+ (lastView.right + lastView.width * j).toFloat(),
+ lastView.top.toFloat(),
+ (lastView.right + lastView.width * j).toFloat(),
+ lastView.bottom.toFloat(),
+ localPaint
+ )
+ }
+ }
+ }
+
+ fun onPlayerRelease() {
+ clickSoundPlayer.release() // 释放资源
+ clickLongSoundPlayer.release()
+ }
+
+ fun clearGameStateSP() {
+ val editor = sharedPreferences.edit()
+ editor.remove("game_state")
+ editor.apply()
+ }
+
+ fun saveGameState() {
+ val gridState = Array(xLength) {
+ Array(yLength) {
+ CellState(
+ 0,
+ SingleView.VIEWSTATE_DEFAULT, 0
+ )
+ }
+ }
+
+ // 填充格子的状态
+ for (y in 0 until xLength) {
+ for (x in 0 until yLength) {
+ val singleView = getChildByXY(x, y)
+ gridState[y][x] =
+ CellState(singleView.logicState, singleView.viewState, singleView.around)
+ }
+ }
+
+ val gameState = GameState(gridState, goods, myLevel, minutes, seconds)
+ val json = Gson().toJson(gameState)
+
+
+ val editor = sharedPreferences.edit()
+ // 保存的游戏状态json
+ editor.putString("game_state", json)
+ // 提交保存
+ editor.apply()
+ }
+
+ fun loadGameState() {
+ // 获取保存的游戏状态 JSON 字符串
+ val gameStateJson = sharedPreferences.getString("game_state", null)
+ if (gameStateJson.isNullOrEmpty()) {
+ return
+ }
+ val gameState: GameState = Gson().fromJson(gameStateJson, GameState::class.java)
+ //如果保存的难度和进来的难度不一致则不往下执行。
+ if (gameState.difficulty != myLevel) {
+ return
+ }
+ // 恢复格子的状态
+ for (y in 0 until xLength) {
+ for (x in 0 until yLength) {
+ val singleView = getChildByXY(x, y)
+ val cellState = gameState.grid[y][x]
+ singleView.logicState = cellState.logicState
+ singleView.viewState = cellState.viewState
+ singleView.around = cellState.around
+ singleView.updateInsideImg() // 更新格子显示
+ }
+ }
+ goods = gameState.remainingMines
+ mineNum = gameState.difficulty.mineNum
+ started = true
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/res/color/text_color_selector.xml b/app/src/main/res/color/text_color_selector.xml
new file mode 100644
index 0000000..aa5b9e9
--- /dev/null
+++ b/app/src/main/res/color/text_color_selector.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2b068d1
--- /dev/null
+++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/btn_bg_selector.xml b/app/src/main/res/drawable/btn_bg_selector.xml
new file mode 100644
index 0000000..bff1a7a
--- /dev/null
+++ b/app/src/main/res/drawable/btn_bg_selector.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/drw_black_btn_bg.xml b/app/src/main/res/drawable/drw_black_btn_bg.xml
new file mode 100644
index 0000000..b847226
--- /dev/null
+++ b/app/src/main/res/drawable/drw_black_btn_bg.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/drw_dialog_bg.xml b/app/src/main/res/drawable/drw_dialog_bg.xml
new file mode 100644
index 0000000..992ad75
--- /dev/null
+++ b/app/src/main/res/drawable/drw_dialog_bg.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/drw_frame_grey_btn_bg.xml b/app/src/main/res/drawable/drw_frame_grey_btn_bg.xml
new file mode 100644
index 0000000..62cd44b
--- /dev/null
+++ b/app/src/main/res/drawable/drw_frame_grey_btn_bg.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/drw_grey_btn_bg.xml b/app/src/main/res/drawable/drw_grey_btn_bg.xml
new file mode 100644
index 0000000..634db10
--- /dev/null
+++ b/app/src/main/res/drawable/drw_grey_btn_bg.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/drw_red_btn_bg.xml b/app/src/main/res/drawable/drw_red_btn_bg.xml
new file mode 100644
index 0000000..8c7e62d
--- /dev/null
+++ b/app/src/main/res/drawable/drw_red_btn_bg.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..07d5da9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/lose_emoji_anguished_face.xml b/app/src/main/res/drawable/lose_emoji_anguished_face.xml
new file mode 100644
index 0000000..03949ac
--- /dev/null
+++ b/app/src/main/res/drawable/lose_emoji_anguished_face.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/lose_emoji_bomb.xml b/app/src/main/res/drawable/lose_emoji_bomb.xml
new file mode 100644
index 0000000..ec8ed91
--- /dev/null
+++ b/app/src/main/res/drawable/lose_emoji_bomb.xml
@@ -0,0 +1,260 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/lose_emoji_confounded_face.xml b/app/src/main/res/drawable/lose_emoji_confounded_face.xml
new file mode 100644
index 0000000..f0c2ce4
--- /dev/null
+++ b/app/src/main/res/drawable/lose_emoji_confounded_face.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/lose_emoji_downcast_face_with_sweat.xml b/app/src/main/res/drawable/lose_emoji_downcast_face_with_sweat.xml
new file mode 100644
index 0000000..46f063d
--- /dev/null
+++ b/app/src/main/res/drawable/lose_emoji_downcast_face_with_sweat.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/lose_emoji_exploding_head.xml b/app/src/main/res/drawable/lose_emoji_exploding_head.xml
new file mode 100644
index 0000000..0508b66
--- /dev/null
+++ b/app/src/main/res/drawable/lose_emoji_exploding_head.xml
@@ -0,0 +1,131 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/lose_emoji_face_with_head_bandage.xml b/app/src/main/res/drawable/lose_emoji_face_with_head_bandage.xml
new file mode 100644
index 0000000..b6e4ddf
--- /dev/null
+++ b/app/src/main/res/drawable/lose_emoji_face_with_head_bandage.xml
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/lose_emoji_grimacing_face.xml b/app/src/main/res/drawable/lose_emoji_grimacing_face.xml
new file mode 100644
index 0000000..ca67564
--- /dev/null
+++ b/app/src/main/res/drawable/lose_emoji_grimacing_face.xml
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/lose_emoji_sad_but_relieved_face.xml b/app/src/main/res/drawable/lose_emoji_sad_but_relieved_face.xml
new file mode 100644
index 0000000..5ce13e0
--- /dev/null
+++ b/app/src/main/res/drawable/lose_emoji_sad_but_relieved_face.xml
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/sel_bg_single.xml b/app/src/main/res/drawable/sel_bg_single.xml
new file mode 100644
index 0000000..359edc0
--- /dev/null
+++ b/app/src/main/res/drawable/sel_bg_single.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/win_emoji_beaming_face_with_smiling_eyes.xml b/app/src/main/res/drawable/win_emoji_beaming_face_with_smiling_eyes.xml
new file mode 100644
index 0000000..33b902d
--- /dev/null
+++ b/app/src/main/res/drawable/win_emoji_beaming_face_with_smiling_eyes.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/win_emoji_cowboy_hat_face.xml b/app/src/main/res/drawable/win_emoji_cowboy_hat_face.xml
new file mode 100644
index 0000000..62bed1f
--- /dev/null
+++ b/app/src/main/res/drawable/win_emoji_cowboy_hat_face.xml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/win_emoji_partying_face.xml b/app/src/main/res/drawable/win_emoji_partying_face.xml
new file mode 100644
index 0000000..1ef150f
--- /dev/null
+++ b/app/src/main/res/drawable/win_emoji_partying_face.xml
@@ -0,0 +1,156 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/win_emoji_smiling_face_with_sunglasses.xml b/app/src/main/res/drawable/win_emoji_smiling_face_with_sunglasses.xml
new file mode 100644
index 0000000..b331e0a
--- /dev/null
+++ b/app/src/main/res/drawable/win_emoji_smiling_face_with_sunglasses.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..7ce2288
--- /dev/null
+++ b/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,113 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_splash.xml b/app/src/main/res/layout/activity_splash.xml
new file mode 100644
index 0000000..d73f52f
--- /dev/null
+++ b/app/src/main/res/layout/activity_splash.xml
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_splash2.xml b/app/src/main/res/layout/activity_splash2.xml
new file mode 100644
index 0000000..f1a67a8
--- /dev/null
+++ b/app/src/main/res/layout/activity_splash2.xml
@@ -0,0 +1,10 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_swipe.xml b/app/src/main/res/layout/activity_swipe.xml
new file mode 100644
index 0000000..64bd571
--- /dev/null
+++ b/app/src/main/res/layout/activity_swipe.xml
@@ -0,0 +1,131 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/dialog_result.xml b/app/src/main/res/layout/dialog_result.xml
new file mode 100644
index 0000000..4f52970
--- /dev/null
+++ b/app/src/main/res/layout/dialog_result.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/app_logo.xml b/app/src/main/res/mipmap-anydpi-v26/app_logo.xml
new file mode 100644
index 0000000..87a192a
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/app_logo.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/app_logo_round.xml b/app/src/main/res/mipmap-anydpi-v26/app_logo_round.xml
new file mode 100644
index 0000000..87a192a
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/app_logo_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..6f3b755
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..6f3b755
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/app_logo.webp b/app/src/main/res/mipmap-hdpi/app_logo.webp
new file mode 100644
index 0000000..f41aa96
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/app_logo.webp differ
diff --git a/app/src/main/res/mipmap-hdpi/app_logo_foreground.webp b/app/src/main/res/mipmap-hdpi/app_logo_foreground.webp
new file mode 100644
index 0000000..7b5653d
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/app_logo_foreground.webp differ
diff --git a/app/src/main/res/mipmap-hdpi/app_logo_round.webp b/app/src/main/res/mipmap-hdpi/app_logo_round.webp
new file mode 100644
index 0000000..6ec9f23
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/app_logo_round.webp differ
diff --git a/app/src/main/res/mipmap-hdpi/c0.png b/app/src/main/res/mipmap-hdpi/c0.png
new file mode 100644
index 0000000..c9b9c45
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/c0.png differ
diff --git a/app/src/main/res/mipmap-hdpi/c1.png b/app/src/main/res/mipmap-hdpi/c1.png
new file mode 100644
index 0000000..6b0e091
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/c1.png differ
diff --git a/app/src/main/res/mipmap-hdpi/c2.png b/app/src/main/res/mipmap-hdpi/c2.png
new file mode 100644
index 0000000..18d72fd
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/c2.png differ
diff --git a/app/src/main/res/mipmap-hdpi/c3.png b/app/src/main/res/mipmap-hdpi/c3.png
new file mode 100644
index 0000000..ddb8369
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/c3.png differ
diff --git a/app/src/main/res/mipmap-hdpi/c4.png b/app/src/main/res/mipmap-hdpi/c4.png
new file mode 100644
index 0000000..ce9106a
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/c4.png differ
diff --git a/app/src/main/res/mipmap-hdpi/c5.png b/app/src/main/res/mipmap-hdpi/c5.png
new file mode 100644
index 0000000..b654ff9
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/c5.png differ
diff --git a/app/src/main/res/mipmap-hdpi/c6.png b/app/src/main/res/mipmap-hdpi/c6.png
new file mode 100644
index 0000000..9a1e95a
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/c6.png differ
diff --git a/app/src/main/res/mipmap-hdpi/c7.png b/app/src/main/res/mipmap-hdpi/c7.png
new file mode 100644
index 0000000..75b4f8f
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/c7.png differ
diff --git a/app/src/main/res/mipmap-hdpi/c8.png b/app/src/main/res/mipmap-hdpi/c8.png
new file mode 100644
index 0000000..87114f3
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/c8.png differ
diff --git a/app/src/main/res/mipmap-hdpi/c9.png b/app/src/main/res/mipmap-hdpi/c9.png
new file mode 100644
index 0000000..fc08b61
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/c9.png differ
diff --git a/app/src/main/res/mipmap-hdpi/czz.png b/app/src/main/res/mipmap-hdpi/czz.png
new file mode 100644
index 0000000..5ddd824
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/czz.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 0000000..c209e78
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..b2dfe3d
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-hdpi/smile.png b/app/src/main/res/mipmap-hdpi/smile.png
new file mode 100644
index 0000000..6215148
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/smile.png differ
diff --git a/app/src/main/res/mipmap-hdpi/sorrow.png b/app/src/main/res/mipmap-hdpi/sorrow.png
new file mode 100644
index 0000000..eaaa6d7
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/sorrow.png differ
diff --git a/app/src/main/res/mipmap-mdpi/app_logo.webp b/app/src/main/res/mipmap-mdpi/app_logo.webp
new file mode 100644
index 0000000..6a5f1bd
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/app_logo.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/app_logo_foreground.webp b/app/src/main/res/mipmap-mdpi/app_logo_foreground.webp
new file mode 100644
index 0000000..3f035ff
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/app_logo_foreground.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/app_logo_round.webp b/app/src/main/res/mipmap-mdpi/app_logo_round.webp
new file mode 100644
index 0000000..5c3e734
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/app_logo_round.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 0000000..4f0f1d6
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..62b611d
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/app_logo.webp b/app/src/main/res/mipmap-xhdpi/app_logo.webp
new file mode 100644
index 0000000..f439e1a
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/app_logo.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/app_logo_foreground.webp b/app/src/main/res/mipmap-xhdpi/app_logo_foreground.webp
new file mode 100644
index 0000000..40350cf
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/app_logo_foreground.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/app_logo_no_bg.png b/app/src/main/res/mipmap-xhdpi/app_logo_no_bg.png
new file mode 100644
index 0000000..c44eccc
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/app_logo_no_bg.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/app_logo_round.webp b/app/src/main/res/mipmap-xhdpi/app_logo_round.webp
new file mode 100644
index 0000000..a5c2dee
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/app_logo_round.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/empty.png b/app/src/main/res/mipmap-xhdpi/empty.png
new file mode 100644
index 0000000..c64755a
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/empty.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/flag.png b/app/src/main/res/mipmap-xhdpi/flag.png
new file mode 100644
index 0000000..18628ad
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/flag.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/flag2.png b/app/src/main/res/mipmap-xhdpi/flag2.png
new file mode 100644
index 0000000..f3dd5ae
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/flag2.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 0000000..948a307
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..1b9a695
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/mine.png b/app/src/main/res/mipmap-xhdpi/mine.png
new file mode 100644
index 0000000..e79e728
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/mine.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/mine_exploded.png b/app/src/main/res/mipmap-xhdpi/mine_exploded.png
new file mode 100644
index 0000000..6bb7249
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/mine_exploded.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/mine_transparent.png b/app/src/main/res/mipmap-xhdpi/mine_transparent.png
new file mode 100644
index 0000000..45afec0
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/mine_transparent.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/mine_wrong.png b/app/src/main/res/mipmap-xhdpi/mine_wrong.png
new file mode 100644
index 0000000..66cde2f
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/mine_wrong.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/n1.png b/app/src/main/res/mipmap-xhdpi/n1.png
new file mode 100644
index 0000000..e5ab556
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/n1.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/n2.png b/app/src/main/res/mipmap-xhdpi/n2.png
new file mode 100644
index 0000000..05117bf
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/n2.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/n3.png b/app/src/main/res/mipmap-xhdpi/n3.png
new file mode 100644
index 0000000..f9cc4d5
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/n3.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/n4.png b/app/src/main/res/mipmap-xhdpi/n4.png
new file mode 100644
index 0000000..66c796f
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/n4.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/n5.png b/app/src/main/res/mipmap-xhdpi/n5.png
new file mode 100644
index 0000000..5b90cd4
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/n5.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/n6.png b/app/src/main/res/mipmap-xhdpi/n6.png
new file mode 100644
index 0000000..05d9c0c
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/n6.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/n7.png b/app/src/main/res/mipmap-xhdpi/n7.png
new file mode 100644
index 0000000..d549aec
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/n7.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/n8.png b/app/src/main/res/mipmap-xhdpi/n8.png
new file mode 100644
index 0000000..477e584
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/n8.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/n9.png b/app/src/main/res/mipmap-xhdpi/n9.png
new file mode 100644
index 0000000..289f070
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/n9.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/tile.png b/app/src/main/res/mipmap-xhdpi/tile.png
new file mode 100644
index 0000000..6b8530e
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/tile.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/unsuer.png b/app/src/main/res/mipmap-xhdpi/unsuer.png
new file mode 100644
index 0000000..2da1a57
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/unsuer.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/app_logo.webp b/app/src/main/res/mipmap-xxhdpi/app_logo.webp
new file mode 100644
index 0000000..5dcabb7
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/app_logo.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/app_logo_foreground.webp b/app/src/main/res/mipmap-xxhdpi/app_logo_foreground.webp
new file mode 100644
index 0000000..c76ed79
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/app_logo_foreground.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/app_logo_round.webp b/app/src/main/res/mipmap-xxhdpi/app_logo_round.webp
new file mode 100644
index 0000000..04e20f0
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/app_logo_round.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..28d4b77
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9287f50
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/icon.jpg b/app/src/main/res/mipmap-xxhdpi/icon.jpg
new file mode 100644
index 0000000..fe376ac
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/icon.jpg differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/app_logo.webp b/app/src/main/res/mipmap-xxxhdpi/app_logo.webp
new file mode 100644
index 0000000..0371273
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/app_logo.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/app_logo_foreground.webp b/app/src/main/res/mipmap-xxxhdpi/app_logo_foreground.webp
new file mode 100644
index 0000000..b38ed38
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/app_logo_foreground.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/app_logo_round.webp b/app/src/main/res/mipmap-xxxhdpi/app_logo_round.webp
new file mode 100644
index 0000000..1f21d70
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/app_logo_round.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..aa7d642
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9126ae3
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/raw/explosion.mp3 b/app/src/main/res/raw/explosion.mp3
new file mode 100644
index 0000000..5b74206
Binary files /dev/null and b/app/src/main/res/raw/explosion.mp3 differ
diff --git a/app/src/main/res/raw/flag.mp3 b/app/src/main/res/raw/flag.mp3
new file mode 100644
index 0000000..fffede4
Binary files /dev/null and b/app/src/main/res/raw/flag.mp3 differ
diff --git a/app/src/main/res/raw/tile.mp3 b/app/src/main/res/raw/tile.mp3
new file mode 100644
index 0000000..27d85c7
Binary files /dev/null and b/app/src/main/res/raw/tile.mp3 differ
diff --git a/app/src/main/res/raw/win.mp3 b/app/src/main/res/raw/win.mp3
new file mode 100644
index 0000000..8b5a34b
Binary files /dev/null and b/app/src/main/res/raw/win.mp3 differ
diff --git a/app/src/main/res/values-land/dimens.xml b/app/src/main/res/values-land/dimens.xml
new file mode 100644
index 0000000..22d7f00
--- /dev/null
+++ b/app/src/main/res/values-land/dimens.xml
@@ -0,0 +1,3 @@
+
+ 48dp
+
\ No newline at end of file
diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml
new file mode 100644
index 0000000..c7d0ac7
--- /dev/null
+++ b/app/src/main/res/values-night/themes.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-v23/themes.xml b/app/src/main/res/values-v23/themes.xml
new file mode 100644
index 0000000..62b11cb
--- /dev/null
+++ b/app/src/main/res/values-v23/themes.xml
@@ -0,0 +1,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-w1240dp/dimens.xml b/app/src/main/res/values-w1240dp/dimens.xml
new file mode 100644
index 0000000..d73f4a3
--- /dev/null
+++ b/app/src/main/res/values-w1240dp/dimens.xml
@@ -0,0 +1,3 @@
+
+ 200dp
+
\ No newline at end of file
diff --git a/app/src/main/res/values-w600dp/dimens.xml b/app/src/main/res/values-w600dp/dimens.xml
new file mode 100644
index 0000000..22d7f00
--- /dev/null
+++ b/app/src/main/res/values-w600dp/dimens.xml
@@ -0,0 +1,3 @@
+
+ 48dp
+
\ No newline at end of file
diff --git a/app/src/main/res/values/app_logo_background.xml b/app/src/main/res/values/app_logo_background.xml
new file mode 100644
index 0000000..1694378
--- /dev/null
+++ b/app/src/main/res/values/app_logo_background.xml
@@ -0,0 +1,4 @@
+
+
+ #FFFFFF
+
\ No newline at end of file
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
new file mode 100644
index 0000000..e52391d
--- /dev/null
+++ b/app/src/main/res/values/attrs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..692e27d
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,15 @@
+
+
+ #FF000000
+ #FFFFFFFF
+ #FF0000
+ #C62828
+ #b3b3b3
+ #222222
+ #222222
+ #FF039BE5
+ #FF01579B
+ #FF40C4FF
+ #FF00B0FF
+ #66000000
+
\ No newline at end of file
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..125df87
--- /dev/null
+++ b/app/src/main/res/values/dimens.xml
@@ -0,0 +1,3 @@
+
+ 16dp
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..ed95b54
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,13 @@
+
+ Minesweeper Go
+ You lose
+ You win
+ Wish you good luck next time
+ Go challenge a new difficulty
+ New Game
+ Continue
+ FullscreenActivity
+ Dummy Button
+ DUMMY\nCONTENT
+ SplashActivity
+
\ No newline at end of file
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..087cc24
--- /dev/null
+++ b/app/src/main/res/values/styles.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
new file mode 100644
index 0000000..17601c0
--- /dev/null
+++ b/app/src/main/res/values/themes.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/backup_rules.xml b/app/src/main/res/xml/backup_rules.xml
new file mode 100644
index 0000000..fa0f996
--- /dev/null
+++ b/app/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,13 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/data_extraction_rules.xml b/app/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 0000000..9ee9997
--- /dev/null
+++ b/app/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/test/java/com/gogame/minesweeper/ExampleUnitTest.kt b/app/src/test/java/com/gogame/minesweeper/ExampleUnitTest.kt
new file mode 100644
index 0000000..dec297c
--- /dev/null
+++ b/app/src/test/java/com/gogame/minesweeper/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.gogame.minesweeper
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
new file mode 100644
index 0000000..918d5f6
--- /dev/null
+++ b/build.gradle.kts
@@ -0,0 +1,7 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+plugins {
+ alias(libs.plugins.android.application) apply false
+ alias(libs.plugins.jetbrains.kotlin.android) apply false
+ id("com.google.gms.google-services") version "4.4.4" apply false
+ id("com.google.firebase.crashlytics") version "3.0.6" apply false
+}
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..20e2a01
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,23 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. For more details, visit
+# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Kotlin code style for this project: "official" or "obsolete":
+kotlin.code.style=official
+# Enables namespacing of each library's R class so that its R class includes only the
+# resources declared in the library itself and none from the library's dependencies,
+# thereby reducing the size of the R class for that library
+android.nonTransitiveRClass=true
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
new file mode 100644
index 0000000..c516b2c
--- /dev/null
+++ b/gradle/libs.versions.toml
@@ -0,0 +1,30 @@
+[versions]
+agp = "8.4.0"
+gson = "2.10.1"
+kotlin = "2.0.21"
+coreKtx = "1.13.1"
+junit = "4.13.2"
+junitVersion = "1.2.1"
+espressoCore = "3.6.1"
+appcompat = "1.7.0"
+material = "1.12.0"
+constraintlayout = "2.1.4"
+navigationFragmentKtx = "2.7.7"
+navigationUiKtx = "2.7.7"
+
+[libraries]
+androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
+gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
+junit = { group = "junit", name = "junit", version.ref = "junit" }
+androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
+androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
+androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
+material = { group = "com.google.android.material", name = "material", version.ref = "material" }
+androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
+androidx-navigation-fragment-ktx = { group = "androidx.navigation", name = "navigation-fragment-ktx", version.ref = "navigationFragmentKtx" }
+androidx-navigation-ui-ktx = { group = "androidx.navigation", name = "navigation-ui-ktx", version.ref = "navigationUiKtx" }
+
+[plugins]
+android-application = { id = "com.android.application", version.ref = "agp" }
+jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
+
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..e708b1c
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..8ad049f
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Thu Sep 26 15:16:24 CST 2024
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..4f906e0
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=`expr $i + 1`
+ done
+ case $i in
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..ac1b06f
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/info/info.txt b/info/info.txt
new file mode 100644
index 0000000..1ec045f
--- /dev/null
+++ b/info/info.txt
@@ -0,0 +1,2 @@
+saolei_key0
+123456
\ No newline at end of file
diff --git a/info/saolei.jks b/info/saolei.jks
new file mode 100644
index 0000000..40ccb3e
Binary files /dev/null and b/info/saolei.jks differ
diff --git a/settings.gradle.kts b/settings.gradle.kts
new file mode 100644
index 0000000..10650df
--- /dev/null
+++ b/settings.gradle.kts
@@ -0,0 +1,42 @@
+pluginManagement {
+ repositories {
+ google {
+ content {
+ includeGroupByRegex("com\\.android.*")
+ includeGroupByRegex("com\\.google.*")
+ includeGroupByRegex("androidx.*")
+ }
+ }
+ mavenCentral()
+ gradlePluginPortal()
+ maven(url = "https://jitpack.io")
+ }
+}
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ google()
+ mavenCentral()
+ gradlePluginPortal()
+ maven("https://jitpack.io")
+ flatDir {
+ dirs("libs")
+ }
+
+ // TU(Core)
+ maven(url = "https://jfrog.anythinktech.com/artifactory/overseas_sdk")
+
+ // Ironsource
+ maven(url = "https://android-sdk.is.com/")
+
+ // Mintegral
+ maven(url = "https://dl-maven-android.mintegral.com/repository/mbridge_android_sdk_oversea")
+
+ // Pangle
+ maven(url = "https://artifact.bytedance.com/repository/pangle")
+ }
+}
+
+rootProject.name = "Minesweeper Go"
+include(":app")
+
\ No newline at end of file