Add roboletric and improve testing
This commit is contained in:
parent
eb92414df1
commit
0de579010c
23 changed files with 470 additions and 84 deletions
|
@ -30,9 +30,25 @@ android {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
targetCompatibility 1.8
|
||||||
|
sourceCompatibility 1.8
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||||
|
}
|
||||||
|
|
||||||
kapt {
|
kapt {
|
||||||
generateStubs true
|
generateStubs true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
testOptions {
|
||||||
|
unitTests {
|
||||||
|
includeAndroidResources true
|
||||||
|
animationsDisabled true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
@ -59,7 +75,20 @@ dependencies {
|
||||||
kapt "com.google.dagger:dagger-android-processor:$versions.dagger"
|
kapt "com.google.dagger:dagger-android-processor:$versions.dagger"
|
||||||
kapt "com.google.dagger:dagger-compiler:$versions.dagger"
|
kapt "com.google.dagger:dagger-compiler:$versions.dagger"
|
||||||
|
|
||||||
|
testImplementation "com.google.dagger:dagger-android:$versions.dagger"
|
||||||
|
testImplementation "com.google.dagger:dagger-android-support:$versions.dagger"
|
||||||
|
|
||||||
// Kotlin
|
// Kotlin
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$versions.coroutines"
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$versions.coroutines"
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$versions.kotlin"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$versions.kotlin"
|
||||||
|
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$versions.coroutines"
|
||||||
|
|
||||||
|
// Tests
|
||||||
|
testImplementation "junit:junit:$versions.junit"
|
||||||
|
testImplementation "androidx.test:core:$versions.testCore"
|
||||||
|
testImplementation "androidx.test:core-ktx:$versions.testCore"
|
||||||
|
testImplementation "androidx.test.espresso:espresso-core:$versions.espresso"
|
||||||
|
testImplementation "androidx.test.espresso:espresso-contrib:$versions.espresso"
|
||||||
|
testImplementation "androidx.fragment:fragment-testing:$versions.fragmentTest"
|
||||||
|
testImplementation "org.robolectric:robolectric:$versions.robolectric"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package dev.lucasnlm.antimine
|
package dev.lucasnlm.antimine
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.multidex.MultiDex
|
import androidx.multidex.MultiDex
|
||||||
import dagger.android.AndroidInjector
|
import dagger.android.AndroidInjector
|
||||||
|
@ -11,16 +12,20 @@ import dev.lucasnlm.antimine.di.AppModule
|
||||||
import dev.lucasnlm.antimine.di.DaggerAppComponent
|
import dev.lucasnlm.antimine.di.DaggerAppComponent
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class MainApplication : DaggerApplication() {
|
open class MainApplication : DaggerApplication() {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var analyticsManager: AnalyticsManager
|
lateinit var analyticsManager: AnalyticsManager
|
||||||
|
|
||||||
|
protected open fun appModule(application: Application) = AppModule(application)
|
||||||
|
|
||||||
|
protected open fun levelModule(application: Application) = LevelModule(application)
|
||||||
|
|
||||||
override fun applicationInjector(): AndroidInjector<out DaggerApplication> =
|
override fun applicationInjector(): AndroidInjector<out DaggerApplication> =
|
||||||
DaggerAppComponent.builder()
|
DaggerAppComponent.builder()
|
||||||
.application(this)
|
.application(this)
|
||||||
.appModule(AppModule(this))
|
.appModule(appModule(this))
|
||||||
.levelModule(LevelModule(this))
|
.levelModule(levelModule(this))
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
override fun attachBaseContext(base: Context?) {
|
override fun attachBaseContext(base: Context?) {
|
||||||
|
|
|
@ -47,7 +47,7 @@ class EndGameDialogFragment : DaggerAppCompatDialogFragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
arguments?.run {
|
arguments?.run {
|
||||||
isVictory = getBoolean(DIALOG_STATE) == true
|
isVictory = getBoolean(DIALOG_IS_VICTORY) == true
|
||||||
time = getLong(DIALOG_TIME)
|
time = getLong(DIALOG_TIME)
|
||||||
rightMines = getInt(DIALOG_RIGHT_MINES)
|
rightMines = getInt(DIALOG_RIGHT_MINES)
|
||||||
totalMines = getInt(DIALOG_TOTAL_MINES)
|
totalMines = getInt(DIALOG_TOTAL_MINES)
|
||||||
|
@ -134,14 +134,14 @@ class EndGameDialogFragment : DaggerAppCompatDialogFragment() {
|
||||||
fun newInstance(victory: Boolean, rightMines: Int, totalMines: Int, time: Long): EndGameDialogFragment =
|
fun newInstance(victory: Boolean, rightMines: Int, totalMines: Int, time: Long): EndGameDialogFragment =
|
||||||
EndGameDialogFragment().apply {
|
EndGameDialogFragment().apply {
|
||||||
arguments = Bundle().apply {
|
arguments = Bundle().apply {
|
||||||
putBoolean(DIALOG_STATE, victory)
|
putBoolean(DIALOG_IS_VICTORY, victory)
|
||||||
putInt(DIALOG_RIGHT_MINES, rightMines)
|
putInt(DIALOG_RIGHT_MINES, rightMines)
|
||||||
putInt(DIALOG_TOTAL_MINES, totalMines)
|
putInt(DIALOG_TOTAL_MINES, totalMines)
|
||||||
putLong(DIALOG_TIME, time)
|
putLong(DIALOG_TIME, time)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private const val DIALOG_STATE = "dialog_state"
|
const val DIALOG_IS_VICTORY = "dialog_state"
|
||||||
private const val DIALOG_TIME = "dialog_time"
|
private const val DIALOG_TIME = "dialog_time"
|
||||||
private const val DIALOG_RIGHT_MINES = "dialog_right_mines"
|
private const val DIALOG_RIGHT_MINES = "dialog_right_mines"
|
||||||
private const val DIALOG_TOTAL_MINES = "dialog_total_mines"
|
private const val DIALOG_TOTAL_MINES = "dialog_total_mines"
|
||||||
|
|
|
@ -17,18 +17,23 @@ class EngGameDialogViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun List<String>.safeRandomEmoji(except: String? = null, fallback: String = "\uD83D\uDCA3") =
|
||||||
|
this.filter { it != except && checkGlyphAvailability(it) }
|
||||||
|
.ifEmpty { listOf(fallback) }
|
||||||
|
.random()
|
||||||
|
|
||||||
fun randomVictoryEmoji(except: String? = null) = listOf(
|
fun randomVictoryEmoji(except: String? = null) = listOf(
|
||||||
"\uD83D\uDE00", "\uD83D\uDE0E", "\uD83D\uDE1D", "\uD83E\uDD73", "\uD83D\uDE06"
|
"\uD83D\uDE00", "\uD83D\uDE0E", "\uD83D\uDE1D", "\uD83E\uDD73", "\uD83D\uDE06"
|
||||||
).filter { it != except && checkGlyphAvailability(it) }.random()
|
).safeRandomEmoji()
|
||||||
|
|
||||||
fun randomNeutralEmoji(except: String? = null) = listOf(
|
fun randomNeutralEmoji(except: String? = null) = listOf(
|
||||||
"\uD83D\uDE01", "\uD83E\uDD14", "\uD83D\uDE42", "\uD83D\uDE09"
|
"\uD83D\uDE01", "\uD83E\uDD14", "\uD83D\uDE42", "\uD83D\uDE09"
|
||||||
).filter { it != except && checkGlyphAvailability(it) }.random()
|
).safeRandomEmoji()
|
||||||
|
|
||||||
fun randomGameOverEmoji(except: String? = null) = listOf(
|
fun randomGameOverEmoji(except: String? = null) = listOf(
|
||||||
"\uD83D\uDE10", "\uD83D\uDE44", "\uD83D\uDE25", "\uD83D\uDE13", "\uD83D\uDE31",
|
"\uD83D\uDE10", "\uD83D\uDE44", "\uD83D\uDE25", "\uD83D\uDE13", "\uD83D\uDE31",
|
||||||
"\uD83E\uDD2C", "\uD83E\uDD15", "\uD83D\uDE16", "\uD83D\uDCA3", "\uD83D\uDE05"
|
"\uD83E\uDD2C", "\uD83E\uDD15", "\uD83D\uDE16", "\uD83D\uDCA3", "\uD83D\uDE05"
|
||||||
).filter { it != except && checkGlyphAvailability(it) }.random()
|
).safeRandomEmoji()
|
||||||
|
|
||||||
fun messageTo(context: Context, rightMines: Int, totalMines: Int, time: Long, isVictory: Boolean): String =
|
fun messageTo(context: Context, rightMines: Int, totalMines: Int, time: Long, isVictory: Boolean): String =
|
||||||
if (totalMines != 0 && time != 0L) {
|
if (totalMines != 0 && time != 0L) {
|
||||||
|
|
|
@ -31,6 +31,7 @@
|
||||||
android:textSize="18sp"
|
android:textSize="18sp"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
android:textAlignment="center"
|
android:textAlignment="center"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/title_emoji"
|
app:layout_constraintTop_toBottomOf="@+id/title_emoji"
|
||||||
|
@ -45,6 +46,7 @@
|
||||||
android:textColor="@color/text_color"
|
android:textColor="@color/text_color"
|
||||||
android:textSize="14sp"
|
android:textSize="14sp"
|
||||||
android:textAlignment="center"
|
android:textAlignment="center"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/title"
|
app:layout_constraintTop_toBottomOf="@+id/title"
|
||||||
|
|
12
app/src/test/java/dev/lucasnlm/antimine/TestApplication.kt
Normal file
12
app/src/test/java/dev/lucasnlm/antimine/TestApplication.kt
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
package dev.lucasnlm.antimine
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import dev.lucasnlm.antimine.di.AppModule
|
||||||
|
import dev.lucasnlm.antimine.di.TestLevelModule
|
||||||
|
|
||||||
|
class TestApplication : MainApplication() {
|
||||||
|
|
||||||
|
override fun appModule(application: Application) = AppModule(application)
|
||||||
|
|
||||||
|
override fun levelModule(application: Application) = TestLevelModule(application)
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
package dev.lucasnlm.antimine.di
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import dev.lucasnlm.antimine.common.level.di.LevelModule
|
||||||
|
import dev.lucasnlm.antimine.common.level.models.Event
|
||||||
|
import dev.lucasnlm.antimine.common.level.repository.IDimensionRepository
|
||||||
|
import dev.lucasnlm.antimine.common.level.repository.IMinefieldRepository
|
||||||
|
import dev.lucasnlm.antimine.common.level.repository.ISavesRepository
|
||||||
|
import dev.lucasnlm.antimine.common.level.utils.Clock
|
||||||
|
import dev.lucasnlm.antimine.common.level.utils.IHapticFeedbackInteractor
|
||||||
|
import dev.lucasnlm.antimine.common.level.viewmodel.GameViewModelFactory
|
||||||
|
import dev.lucasnlm.antimine.core.analytics.AnalyticsManager
|
||||||
|
import dev.lucasnlm.antimine.core.preferences.IPreferencesRepository
|
||||||
|
import dev.lucasnlm.antimine.mocks.MockDimensionRepository
|
||||||
|
import dev.lucasnlm.antimine.mocks.MockHapticFeedbackInteractor
|
||||||
|
import dev.lucasnlm.antimine.mocks.MockMinefieldRepository
|
||||||
|
import dev.lucasnlm.antimine.mocks.MockSavesRepository
|
||||||
|
|
||||||
|
@Module
|
||||||
|
class TestLevelModule(
|
||||||
|
application: Application
|
||||||
|
) : LevelModule(application) {
|
||||||
|
@Provides
|
||||||
|
override fun provideGameEventObserver(): MutableLiveData<Event> = MutableLiveData()
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
override fun provideClock(): Clock = Clock()
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
override fun provideGameViewModelFactory(
|
||||||
|
application: Application,
|
||||||
|
eventObserver: MutableLiveData<Event>,
|
||||||
|
savesRepository: ISavesRepository,
|
||||||
|
dimensionRepository: IDimensionRepository,
|
||||||
|
preferencesRepository: IPreferencesRepository,
|
||||||
|
hapticFeedbackInteractor: IHapticFeedbackInteractor,
|
||||||
|
minefieldRepository: IMinefieldRepository,
|
||||||
|
analyticsManager: AnalyticsManager,
|
||||||
|
clock: Clock
|
||||||
|
) = GameViewModelFactory(
|
||||||
|
application,
|
||||||
|
eventObserver,
|
||||||
|
savesRepository,
|
||||||
|
dimensionRepository,
|
||||||
|
preferencesRepository,
|
||||||
|
hapticFeedbackInteractor,
|
||||||
|
minefieldRepository,
|
||||||
|
analyticsManager,
|
||||||
|
clock
|
||||||
|
)
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
override fun provideDimensionRepository(
|
||||||
|
context: Context,
|
||||||
|
preferencesRepository: IPreferencesRepository
|
||||||
|
): IDimensionRepository = MockDimensionRepository()
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
override fun provideSavesRepository(): ISavesRepository = MockSavesRepository()
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
override fun provideMinefieldRepository(): IMinefieldRepository = MockMinefieldRepository()
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
override fun provideHapticFeedbackInteractor(
|
||||||
|
application: Application,
|
||||||
|
preferencesRepository: IPreferencesRepository
|
||||||
|
): IHapticFeedbackInteractor = MockHapticFeedbackInteractor()
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
package dev.lucasnlm.antimine.level.view
|
||||||
|
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import androidx.test.core.app.launchActivity
|
||||||
|
import dev.lucasnlm.antimine.GameActivity
|
||||||
|
import dev.lucasnlm.antimine.R
|
||||||
|
import dev.lucasnlm.antimine.TestApplication
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertNotNull
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.robolectric.RobolectricTestRunner
|
||||||
|
import org.robolectric.annotation.Config
|
||||||
|
import org.robolectric.annotation.LooperMode
|
||||||
|
import org.robolectric.shadows.ShadowLooper
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
@RunWith(RobolectricTestRunner::class)
|
||||||
|
@Config(minSdk = 16, maxSdk = 27, application = TestApplication::class)
|
||||||
|
@LooperMode(LooperMode.Mode.PAUSED)
|
||||||
|
class LevelFragmentTest {
|
||||||
|
@Test
|
||||||
|
fun testShowGameOverWhenTapAMine() {
|
||||||
|
launchActivity<GameActivity>().onActivity { activity ->
|
||||||
|
ShadowLooper.runUiThreadTasks()
|
||||||
|
|
||||||
|
// First tap
|
||||||
|
activity.findViewById<RecyclerView>(R.id.recyclerGrid)
|
||||||
|
.findViewHolderForItemId(40).itemView.performClick()
|
||||||
|
|
||||||
|
ShadowLooper.runUiThreadTasks()
|
||||||
|
|
||||||
|
// Tap on a mine
|
||||||
|
activity.findViewById<RecyclerView>(R.id.recyclerGrid)
|
||||||
|
.findViewHolderForItemId(26).itemView.performClick()
|
||||||
|
|
||||||
|
ShadowLooper.idleMainLooper(2, TimeUnit.SECONDS)
|
||||||
|
ShadowLooper.runUiThreadTasks()
|
||||||
|
|
||||||
|
val endGame = activity.supportFragmentManager.findFragmentByTag(EndGameDialogFragment.TAG)
|
||||||
|
assertNotNull(endGame)
|
||||||
|
assertEquals(endGame?.arguments?.get(EndGameDialogFragment.DIALOG_IS_VICTORY), false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testShowVictoryWhenTapAllSafeAreas() {
|
||||||
|
val mines = sequenceOf(4, 9, 15, 26, 47, 53, 68, 71, 75)
|
||||||
|
val safeAreas = (0 until 81).filterNot { mines.contains(it) }.map { it.toLong() }
|
||||||
|
|
||||||
|
launchActivity<GameActivity>().onActivity { activity ->
|
||||||
|
ShadowLooper.runUiThreadTasks()
|
||||||
|
|
||||||
|
// First tap
|
||||||
|
activity.findViewById<RecyclerView>(R.id.recyclerGrid)
|
||||||
|
.findViewHolderForItemId(40).itemView.performClick()
|
||||||
|
|
||||||
|
ShadowLooper.runUiThreadTasks()
|
||||||
|
|
||||||
|
// Tap on safe places
|
||||||
|
safeAreas.forEach { safeArea ->
|
||||||
|
activity.findViewById<RecyclerView>(R.id.recyclerGrid)
|
||||||
|
.findViewHolderForItemId(safeArea).itemView.performClick()
|
||||||
|
ShadowLooper.runUiThreadTasks()
|
||||||
|
}
|
||||||
|
|
||||||
|
ShadowLooper.idleMainLooper(2, TimeUnit.SECONDS)
|
||||||
|
ShadowLooper.runUiThreadTasks()
|
||||||
|
|
||||||
|
val endGame = activity.supportFragmentManager.findFragmentByTag(EndGameDialogFragment.TAG)
|
||||||
|
assertNotNull(endGame)
|
||||||
|
assertEquals(endGame?.arguments?.get(EndGameDialogFragment.DIALOG_IS_VICTORY), true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package dev.lucasnlm.antimine.mocks
|
||||||
|
|
||||||
|
import dev.lucasnlm.antimine.common.level.repository.IDimensionRepository
|
||||||
|
import dev.lucasnlm.antimine.common.level.repository.Size
|
||||||
|
|
||||||
|
class MockDimensionRepository : IDimensionRepository {
|
||||||
|
override fun areaSize(): Float = 50.0f
|
||||||
|
|
||||||
|
override fun displaySize(): Size = Size(50 * 20, 50 * 30)
|
||||||
|
|
||||||
|
override fun actionBarSize(): Int = 50
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package dev.lucasnlm.antimine.mocks
|
||||||
|
|
||||||
|
import dev.lucasnlm.antimine.common.level.utils.IHapticFeedbackInteractor
|
||||||
|
|
||||||
|
class MockHapticFeedbackInteractor : IHapticFeedbackInteractor {
|
||||||
|
override fun toggleFlagFeedback() {
|
||||||
|
// Empty
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun explosionFeedback() {
|
||||||
|
// Empty
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package dev.lucasnlm.antimine.mocks
|
||||||
|
|
||||||
|
import dev.lucasnlm.antimine.common.level.models.Difficulty
|
||||||
|
import dev.lucasnlm.antimine.common.level.models.Minefield
|
||||||
|
import dev.lucasnlm.antimine.common.level.repository.IDimensionRepository
|
||||||
|
import dev.lucasnlm.antimine.common.level.repository.IMinefieldRepository
|
||||||
|
import dev.lucasnlm.antimine.core.preferences.IPreferencesRepository
|
||||||
|
|
||||||
|
class MockMinefieldRepository : IMinefieldRepository {
|
||||||
|
override fun fromDifficulty(
|
||||||
|
difficulty: Difficulty,
|
||||||
|
dimensionRepository: IDimensionRepository,
|
||||||
|
preferencesRepository: IPreferencesRepository
|
||||||
|
): Minefield = Minefield(9, 9, 9)
|
||||||
|
|
||||||
|
override fun randomSeed(): Long = 0L
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package dev.lucasnlm.antimine.mocks
|
||||||
|
|
||||||
|
import dev.lucasnlm.antimine.common.level.models.Minefield
|
||||||
|
import dev.lucasnlm.antimine.core.preferences.IPreferencesRepository
|
||||||
|
|
||||||
|
class MockPreferencesRepository : IPreferencesRepository {
|
||||||
|
override fun customGameMode(): Minefield = Minefield(9, 9, 9)
|
||||||
|
|
||||||
|
override fun updateCustomGameMode(minefield: Minefield) { }
|
||||||
|
|
||||||
|
override fun getBoolean(key: String, defaultValue: Boolean): Boolean = false
|
||||||
|
|
||||||
|
override fun getInt(key: String, defaultValue: Int): Int = 0
|
||||||
|
|
||||||
|
override fun putBoolean(key: String, value: Boolean) { }
|
||||||
|
|
||||||
|
override fun putInt(key: String, value: Int) { }
|
||||||
|
|
||||||
|
override fun useFlagAssistant(): Boolean = true
|
||||||
|
|
||||||
|
override fun useHapticFeedback(): Boolean = false
|
||||||
|
|
||||||
|
override fun useLargeAreas(): Boolean = false
|
||||||
|
|
||||||
|
override fun useAnimations(): Boolean = false
|
||||||
|
|
||||||
|
override fun useDoubleClickToOpen(): Boolean = false
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package dev.lucasnlm.antimine.mocks
|
||||||
|
|
||||||
|
import dev.lucasnlm.antimine.common.level.database.models.Save
|
||||||
|
import dev.lucasnlm.antimine.common.level.repository.ISavesRepository
|
||||||
|
|
||||||
|
class MockSavesRepository : ISavesRepository {
|
||||||
|
override suspend fun fetchCurrentSave(): Save? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun saveGame(save: Save): Long? {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setLimit(maxSavesStorage: Int) {
|
||||||
|
// Empty
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,8 +7,8 @@ import dev.lucasnlm.antimine.common.level.models.Difficulty
|
||||||
import dev.lucasnlm.antimine.common.level.models.Mark
|
import dev.lucasnlm.antimine.common.level.models.Mark
|
||||||
import dev.lucasnlm.antimine.common.level.models.Minefield
|
import dev.lucasnlm.antimine.common.level.models.Minefield
|
||||||
import dev.lucasnlm.antimine.common.level.models.Score
|
import dev.lucasnlm.antimine.common.level.models.Score
|
||||||
import java.util.Random
|
|
||||||
import kotlin.math.floor
|
import kotlin.math.floor
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
class LevelFacade {
|
class LevelFacade {
|
||||||
private val minefield: Minefield
|
private val minefield: Minefield
|
||||||
|
@ -28,7 +28,7 @@ class LevelFacade {
|
||||||
var mines: Sequence<Area> = sequenceOf()
|
var mines: Sequence<Area> = sequenceOf()
|
||||||
private set
|
private set
|
||||||
|
|
||||||
constructor(minefield: Minefield, seed: Long = randomSeed()) {
|
constructor(minefield: Minefield, seed: Long) {
|
||||||
this.minefield = minefield
|
this.minefield = minefield
|
||||||
this.randomGenerator = Random(seed)
|
this.randomGenerator = Random(seed)
|
||||||
this.seed = seed
|
this.seed = seed
|
||||||
|
@ -195,6 +195,7 @@ class LevelFacade {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun doubleClick(index: Int): Int = getArea(index).run {
|
fun doubleClick(index: Int): Int = getArea(index).run {
|
||||||
|
@Suppress("unused")
|
||||||
return when {
|
return when {
|
||||||
isCovered -> {
|
isCovered -> {
|
||||||
openField(getArea(index))
|
openField(getArea(index))
|
||||||
|
@ -212,6 +213,7 @@ class LevelFacade {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
fun longPressOpenArea(index: Int): Sequence<Area> {
|
fun longPressOpenArea(index: Int): Sequence<Area> {
|
||||||
val neighbors = getArea(index).findNeighbors().filter {
|
val neighbors = getArea(index).findNeighbors().filter {
|
||||||
it.mark.isNone() && it.isCovered
|
it.mark.isNone() && it.isCovered
|
||||||
|
@ -356,8 +358,4 @@ class LevelFacade {
|
||||||
fun setCurrentSaveId(id: Int) {
|
fun setCurrentSaveId(id: Int) {
|
||||||
this.saveId = id.coerceAtLeast(0)
|
this.saveId = id.coerceAtLeast(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
|
||||||
private fun randomSeed(): Long = Random().nextLong()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,9 +8,9 @@ import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dev.lucasnlm.antimine.common.level.models.Event
|
import dev.lucasnlm.antimine.common.level.models.Event
|
||||||
import dev.lucasnlm.antimine.common.level.database.AppDataBase
|
import dev.lucasnlm.antimine.common.level.database.AppDataBase
|
||||||
import dev.lucasnlm.antimine.common.level.database.dao.SaveDao
|
|
||||||
import dev.lucasnlm.antimine.common.level.repository.DimensionRepository
|
import dev.lucasnlm.antimine.common.level.repository.DimensionRepository
|
||||||
import dev.lucasnlm.antimine.common.level.repository.IDimensionRepository
|
import dev.lucasnlm.antimine.common.level.repository.IDimensionRepository
|
||||||
|
import dev.lucasnlm.antimine.common.level.repository.IMinefieldRepository
|
||||||
import dev.lucasnlm.antimine.common.level.repository.ISavesRepository
|
import dev.lucasnlm.antimine.common.level.repository.ISavesRepository
|
||||||
import dev.lucasnlm.antimine.common.level.repository.MinefieldRepository
|
import dev.lucasnlm.antimine.common.level.repository.MinefieldRepository
|
||||||
import dev.lucasnlm.antimine.common.level.repository.SavesRepository
|
import dev.lucasnlm.antimine.common.level.repository.SavesRepository
|
||||||
|
@ -22,7 +22,7 @@ import dev.lucasnlm.antimine.core.analytics.AnalyticsManager
|
||||||
import dev.lucasnlm.antimine.core.preferences.IPreferencesRepository
|
import dev.lucasnlm.antimine.core.preferences.IPreferencesRepository
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
class LevelModule(
|
open class LevelModule(
|
||||||
private val application: Application
|
private val application: Application
|
||||||
) {
|
) {
|
||||||
private val appDataBase by lazy {
|
private val appDataBase by lazy {
|
||||||
|
@ -40,20 +40,20 @@ class LevelModule(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
fun provideGameEventObserver(): MutableLiveData<Event> = MutableLiveData()
|
open fun provideGameEventObserver(): MutableLiveData<Event> = MutableLiveData()
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
fun provideClock(): Clock = Clock()
|
open fun provideClock(): Clock = Clock()
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
fun provideGameViewModelFactory(
|
open fun provideGameViewModelFactory(
|
||||||
application: Application,
|
application: Application,
|
||||||
eventObserver: MutableLiveData<Event>,
|
eventObserver: MutableLiveData<Event>,
|
||||||
savesRepository: ISavesRepository,
|
savesRepository: ISavesRepository,
|
||||||
dimensionRepository: IDimensionRepository,
|
dimensionRepository: IDimensionRepository,
|
||||||
preferencesRepository: IPreferencesRepository,
|
preferencesRepository: IPreferencesRepository,
|
||||||
hapticFeedbackInteractor: IHapticFeedbackInteractor,
|
hapticFeedbackInteractor: IHapticFeedbackInteractor,
|
||||||
minefieldRepository: MinefieldRepository,
|
minefieldRepository: IMinefieldRepository,
|
||||||
analyticsManager: AnalyticsManager,
|
analyticsManager: AnalyticsManager,
|
||||||
clock: Clock
|
clock: Clock
|
||||||
) = GameViewModelFactory(
|
) = GameViewModelFactory(
|
||||||
|
@ -69,26 +69,20 @@ class LevelModule(
|
||||||
)
|
)
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
fun provideDimensionRepository(
|
open fun provideDimensionRepository(
|
||||||
context: Context,
|
context: Context,
|
||||||
preferencesRepository: IPreferencesRepository
|
preferencesRepository: IPreferencesRepository
|
||||||
): IDimensionRepository =
|
): IDimensionRepository =
|
||||||
DimensionRepository(context, preferencesRepository)
|
DimensionRepository(context, preferencesRepository)
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
fun provideDataBase(): AppDataBase = appDataBase
|
open fun provideSavesRepository(): ISavesRepository = savesRepository
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
fun provideSaveDao(): SaveDao = savesDao
|
open fun provideMinefieldRepository(): IMinefieldRepository = MinefieldRepository()
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
fun provideSavesRepository(): ISavesRepository = savesRepository
|
open fun provideHapticFeedbackInteractor(
|
||||||
|
|
||||||
@Provides
|
|
||||||
fun provideMinefieldRepository(): MinefieldRepository = MinefieldRepository()
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
fun provideHapticFeedbackInteractor(
|
|
||||||
application: Application,
|
application: Application,
|
||||||
preferencesRepository: IPreferencesRepository
|
preferencesRepository: IPreferencesRepository
|
||||||
): IHapticFeedbackInteractor =
|
): IHapticFeedbackInteractor =
|
||||||
|
|
|
@ -3,16 +3,20 @@ package dev.lucasnlm.antimine.common.level.repository
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.content.res.TypedArray
|
import android.content.res.TypedArray
|
||||||
import android.util.DisplayMetrics
|
|
||||||
import dev.lucasnlm.antimine.common.R
|
import dev.lucasnlm.antimine.common.R
|
||||||
import dev.lucasnlm.antimine.core.preferences.IPreferencesRepository
|
import dev.lucasnlm.antimine.core.preferences.IPreferencesRepository
|
||||||
|
|
||||||
interface IDimensionRepository {
|
interface IDimensionRepository {
|
||||||
fun areaSize(): Float
|
fun areaSize(): Float
|
||||||
fun displaySize(): DisplayMetrics
|
fun displaySize(): Size
|
||||||
fun actionBarSize(): Int
|
fun actionBarSize(): Int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class Size(
|
||||||
|
val width: Int,
|
||||||
|
val height: Int
|
||||||
|
)
|
||||||
|
|
||||||
class DimensionRepository(
|
class DimensionRepository(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val preferencesRepository: IPreferencesRepository
|
private val preferencesRepository: IPreferencesRepository
|
||||||
|
@ -24,7 +28,9 @@ class DimensionRepository(
|
||||||
context.resources.getDimension(R.dimen.field_size)
|
context.resources.getDimension(R.dimen.field_size)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun displaySize(): DisplayMetrics = Resources.getSystem().displayMetrics
|
override fun displaySize(): Size = with(Resources.getSystem().displayMetrics) {
|
||||||
|
return Size(this.widthPixels, this.heightPixels)
|
||||||
|
}
|
||||||
|
|
||||||
override fun actionBarSize(): Int {
|
override fun actionBarSize(): Int {
|
||||||
val styledAttributes: TypedArray =
|
val styledAttributes: TypedArray =
|
||||||
|
|
|
@ -3,6 +3,7 @@ package dev.lucasnlm.antimine.common.level.repository
|
||||||
import dev.lucasnlm.antimine.common.level.models.Difficulty
|
import dev.lucasnlm.antimine.common.level.models.Difficulty
|
||||||
import dev.lucasnlm.antimine.common.level.models.Minefield
|
import dev.lucasnlm.antimine.common.level.models.Minefield
|
||||||
import dev.lucasnlm.antimine.core.preferences.IPreferencesRepository
|
import dev.lucasnlm.antimine.core.preferences.IPreferencesRepository
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
interface IMinefieldRepository {
|
interface IMinefieldRepository {
|
||||||
fun fromDifficulty(
|
fun fromDifficulty(
|
||||||
|
@ -10,6 +11,8 @@ interface IMinefieldRepository {
|
||||||
dimensionRepository: IDimensionRepository,
|
dimensionRepository: IDimensionRepository,
|
||||||
preferencesRepository: IPreferencesRepository
|
preferencesRepository: IPreferencesRepository
|
||||||
): Minefield
|
): Minefield
|
||||||
|
|
||||||
|
fun randomSeed(): Long
|
||||||
}
|
}
|
||||||
|
|
||||||
class MinefieldRepository : IMinefieldRepository {
|
class MinefieldRepository : IMinefieldRepository {
|
||||||
|
@ -46,11 +49,9 @@ class MinefieldRepository : IMinefieldRepository {
|
||||||
val fieldSize = dimensionRepository.areaSize()
|
val fieldSize = dimensionRepository.areaSize()
|
||||||
|
|
||||||
val display = dimensionRepository.displaySize()
|
val display = dimensionRepository.displaySize()
|
||||||
val width = display.widthPixels
|
|
||||||
val height = display.heightPixels
|
|
||||||
|
|
||||||
val finalWidth = ((width / fieldSize).toInt() - 1).coerceAtLeast(6)
|
val finalWidth = ((display.width / fieldSize).toInt() - 1).coerceAtLeast(6)
|
||||||
val finalHeight = ((height / fieldSize).toInt() - 3).coerceAtLeast(9)
|
val finalHeight = ((display.height / fieldSize).toInt() - 3).coerceAtLeast(9)
|
||||||
|
|
||||||
return Minefield(
|
return Minefield(
|
||||||
finalWidth,
|
finalWidth,
|
||||||
|
@ -58,4 +59,6 @@ class MinefieldRepository : IMinefieldRepository {
|
||||||
(finalWidth * finalHeight * 0.2).toInt()
|
(finalWidth * finalHeight * 0.2).toInt()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun randomSeed(): Long = Random.nextLong()
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ import android.app.Application
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import dev.lucasnlm.antimine.common.level.repository.MinefieldRepository
|
|
||||||
import dev.lucasnlm.antimine.common.level.LevelFacade
|
import dev.lucasnlm.antimine.common.level.LevelFacade
|
||||||
import dev.lucasnlm.antimine.common.level.models.Area
|
import dev.lucasnlm.antimine.common.level.models.Area
|
||||||
import dev.lucasnlm.antimine.common.level.models.Difficulty
|
import dev.lucasnlm.antimine.common.level.models.Difficulty
|
||||||
|
@ -12,6 +11,7 @@ import dev.lucasnlm.antimine.common.level.models.Event
|
||||||
import dev.lucasnlm.antimine.common.level.models.Minefield
|
import dev.lucasnlm.antimine.common.level.models.Minefield
|
||||||
import dev.lucasnlm.antimine.common.level.database.models.Save
|
import dev.lucasnlm.antimine.common.level.database.models.Save
|
||||||
import dev.lucasnlm.antimine.common.level.repository.IDimensionRepository
|
import dev.lucasnlm.antimine.common.level.repository.IDimensionRepository
|
||||||
|
import dev.lucasnlm.antimine.common.level.repository.IMinefieldRepository
|
||||||
import dev.lucasnlm.antimine.common.level.repository.ISavesRepository
|
import dev.lucasnlm.antimine.common.level.repository.ISavesRepository
|
||||||
import dev.lucasnlm.antimine.common.level.utils.Clock
|
import dev.lucasnlm.antimine.common.level.utils.Clock
|
||||||
import dev.lucasnlm.antimine.common.level.utils.IHapticFeedbackInteractor
|
import dev.lucasnlm.antimine.common.level.utils.IHapticFeedbackInteractor
|
||||||
|
@ -31,7 +31,7 @@ class GameViewModel(
|
||||||
private val dimensionRepository: IDimensionRepository,
|
private val dimensionRepository: IDimensionRepository,
|
||||||
private val preferencesRepository: IPreferencesRepository,
|
private val preferencesRepository: IPreferencesRepository,
|
||||||
private val hapticFeedbackInteractor: IHapticFeedbackInteractor,
|
private val hapticFeedbackInteractor: IHapticFeedbackInteractor,
|
||||||
private val minefieldRepository: MinefieldRepository,
|
private val minefieldRepository: IMinefieldRepository,
|
||||||
private val analyticsManager: AnalyticsManager,
|
private val analyticsManager: AnalyticsManager,
|
||||||
private val clock: Clock
|
private val clock: Clock
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
@ -55,7 +55,7 @@ class GameViewModel(
|
||||||
newDifficulty, dimensionRepository, preferencesRepository
|
newDifficulty, dimensionRepository, preferencesRepository
|
||||||
)
|
)
|
||||||
|
|
||||||
levelFacade = LevelFacade(minefield)
|
levelFacade = LevelFacade(minefield, minefieldRepository.randomSeed())
|
||||||
|
|
||||||
mineCount.postValue(minefield.mines)
|
mineCount.postValue(minefield.mines)
|
||||||
difficulty.postValue(newDifficulty)
|
difficulty.postValue(newDifficulty)
|
||||||
|
|
|
@ -6,8 +6,8 @@ import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import dev.lucasnlm.antimine.common.level.models.Event
|
import dev.lucasnlm.antimine.common.level.models.Event
|
||||||
import dev.lucasnlm.antimine.common.level.repository.IDimensionRepository
|
import dev.lucasnlm.antimine.common.level.repository.IDimensionRepository
|
||||||
|
import dev.lucasnlm.antimine.common.level.repository.IMinefieldRepository
|
||||||
import dev.lucasnlm.antimine.common.level.repository.ISavesRepository
|
import dev.lucasnlm.antimine.common.level.repository.ISavesRepository
|
||||||
import dev.lucasnlm.antimine.common.level.repository.MinefieldRepository
|
|
||||||
import dev.lucasnlm.antimine.common.level.utils.Clock
|
import dev.lucasnlm.antimine.common.level.utils.Clock
|
||||||
import dev.lucasnlm.antimine.common.level.utils.IHapticFeedbackInteractor
|
import dev.lucasnlm.antimine.common.level.utils.IHapticFeedbackInteractor
|
||||||
import dev.lucasnlm.antimine.core.analytics.AnalyticsManager
|
import dev.lucasnlm.antimine.core.analytics.AnalyticsManager
|
||||||
|
@ -21,14 +21,14 @@ class GameViewModelFactory @Inject constructor(
|
||||||
private val dimensionRepository: IDimensionRepository,
|
private val dimensionRepository: IDimensionRepository,
|
||||||
private val preferencesRepository: IPreferencesRepository,
|
private val preferencesRepository: IPreferencesRepository,
|
||||||
private val hapticFeedbackInteractor: IHapticFeedbackInteractor,
|
private val hapticFeedbackInteractor: IHapticFeedbackInteractor,
|
||||||
private val minefieldRepository: MinefieldRepository,
|
private val minefieldRepository: IMinefieldRepository,
|
||||||
private val analyticsManager: AnalyticsManager,
|
private val analyticsManager: AnalyticsManager,
|
||||||
private val clock: Clock
|
private val clock: Clock
|
||||||
) : ViewModelProvider.Factory {
|
) : ViewModelProvider.Factory {
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
override fun <T : ViewModel?> create(modelClass: Class<T>): T =
|
override fun <T : ViewModel?> create(modelClass: Class<T>): T =
|
||||||
if (modelClass.isAssignableFrom(GameViewModel::class.java)) {
|
if (modelClass.isAssignableFrom(GameViewModel::class.java)) {
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
GameViewModel(
|
GameViewModel(
|
||||||
application,
|
application,
|
||||||
eventObserver,
|
eventObserver,
|
||||||
|
|
|
@ -58,7 +58,7 @@ class LevelFacadeTest {
|
||||||
plantMinesExcept(3)
|
plantMinesExcept(3)
|
||||||
assertNotEquals(field.filter { it.hasMine }.map { it.id }.first(), 3)
|
assertNotEquals(field.filter { it.hasMine }.map { it.id }.first(), 3)
|
||||||
field.forEach {
|
field.forEach {
|
||||||
if (it.id == 7) {
|
if (it.id == 6) {
|
||||||
assertTrue(it.hasMine)
|
assertTrue(it.hasMine)
|
||||||
} else {
|
} else {
|
||||||
assertFalse(it.hasMine)
|
assertFalse(it.hasMine)
|
||||||
|
@ -84,35 +84,34 @@ class LevelFacadeTest {
|
||||||
assertTrue(
|
assertTrue(
|
||||||
levelFacadeOf(3, 3, 1, 200L).apply {
|
levelFacadeOf(3, 3, 1, 200L).apply {
|
||||||
plantMinesExcept(3)
|
plantMinesExcept(3)
|
||||||
}.at(7).hasMine
|
}.at(6).hasMine
|
||||||
)
|
)
|
||||||
assertTrue(
|
assertTrue(
|
||||||
levelFacadeOf(3, 3, 1, 250L).apply {
|
levelFacadeOf(3, 3, 1, 250L).apply {
|
||||||
plantMinesExcept(3)
|
plantMinesExcept(3)
|
||||||
}.at(0).hasMine
|
}.at(1).hasMine
|
||||||
)
|
)
|
||||||
assertTrue(
|
assertTrue(
|
||||||
levelFacadeOf(3, 3, 1, 100L).apply {
|
levelFacadeOf(3, 3, 1, 100L).apply {
|
||||||
plantMinesExcept(3)
|
plantMinesExcept(3)
|
||||||
}.at(8).hasMine
|
}.at(4).hasMine
|
||||||
)
|
)
|
||||||
assertTrue(
|
assertTrue(
|
||||||
levelFacadeOf(3, 3, 1, 170L).apply {
|
levelFacadeOf(3, 3, 1, 170L).apply {
|
||||||
plantMinesExcept(3)
|
plantMinesExcept(3)
|
||||||
println(field)
|
}.at(6).hasMine
|
||||||
}.at(2).hasMine
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testMineTips() {
|
fun testMineTips() {
|
||||||
levelFacadeOf(3, 3, 1, 200L).run {
|
levelFacadeOf(3, 3, 1, 150L).run {
|
||||||
plantMinesExcept(3)
|
plantMinesExcept(3)
|
||||||
assertEquals(
|
assertEquals(
|
||||||
listOf(
|
listOf(
|
||||||
0, 0, 0,
|
1, 0, 1,
|
||||||
1, 1, 1,
|
1, 1, 1,
|
||||||
1, 0, 1
|
0, 0, 0
|
||||||
),
|
),
|
||||||
field.map { it.minesAround }.toList()
|
field.map { it.minesAround }.toList()
|
||||||
)
|
)
|
||||||
|
@ -122,9 +121,9 @@ class LevelFacadeTest {
|
||||||
plantMinesExcept(3)
|
plantMinesExcept(3)
|
||||||
assertEquals(
|
assertEquals(
|
||||||
listOf(
|
listOf(
|
||||||
1, 0, 1,
|
0, 1, 1,
|
||||||
2, 2, 2,
|
1, 2, 0,
|
||||||
1, 0, 1
|
0, 2, 1
|
||||||
),
|
),
|
||||||
field.map { it.minesAround }.toList()
|
field.map { it.minesAround }.toList()
|
||||||
)
|
)
|
||||||
|
@ -134,9 +133,9 @@ class LevelFacadeTest {
|
||||||
plantMinesExcept(3)
|
plantMinesExcept(3)
|
||||||
assertEquals(
|
assertEquals(
|
||||||
listOf(
|
listOf(
|
||||||
0, 0, 1,
|
0, 2, 0,
|
||||||
3, 3, 2,
|
1, 3, 0,
|
||||||
1, 0, 1
|
0, 2, 1
|
||||||
),
|
),
|
||||||
field.map { it.minesAround }.toList()
|
field.map { it.minesAround }.toList()
|
||||||
)
|
)
|
||||||
|
@ -146,10 +145,10 @@ class LevelFacadeTest {
|
||||||
plantMinesExcept(3)
|
plantMinesExcept(3)
|
||||||
assertEquals(
|
assertEquals(
|
||||||
listOf(
|
listOf(
|
||||||
0, 0, 0, 1,
|
0, 0, 2, 1,
|
||||||
3, 0, 3, 1,
|
0, 5, 0, 2,
|
||||||
2, 3, 2, 1,
|
2, 4, 0, 2,
|
||||||
0, 2, 0, 1
|
0, 2, 1, 1
|
||||||
),
|
),
|
||||||
field.map { it.minesAround }.toList()
|
field.map { it.minesAround }.toList()
|
||||||
)
|
)
|
||||||
|
@ -159,10 +158,10 @@ class LevelFacadeTest {
|
||||||
plantMinesExcept(3)
|
plantMinesExcept(3)
|
||||||
assertEquals(
|
assertEquals(
|
||||||
listOf(
|
listOf(
|
||||||
0, 0, 1, 0,
|
0, 1, 1, 1,
|
||||||
2, 2, 1, 0,
|
0, 2, 0, 2,
|
||||||
0, 0, 0, 0,
|
0, 2, 0, 2,
|
||||||
0, 0, 0, 0
|
0, 1, 1, 1
|
||||||
),
|
),
|
||||||
field.map { it.minesAround }.toList()
|
field.map { it.minesAround }.toList()
|
||||||
)
|
)
|
||||||
|
@ -172,10 +171,10 @@ class LevelFacadeTest {
|
||||||
plantMinesExcept(3)
|
plantMinesExcept(3)
|
||||||
assertEquals(
|
assertEquals(
|
||||||
listOf(
|
listOf(
|
||||||
1, 0, 1, 0,
|
|
||||||
1, 1, 1, 0,
|
|
||||||
0, 0, 0, 0,
|
0, 0, 0, 0,
|
||||||
0, 0, 0, 0
|
0, 1, 1, 1,
|
||||||
|
0, 1, 0, 1,
|
||||||
|
0, 1, 1, 1
|
||||||
),
|
),
|
||||||
field.map { it.minesAround }.toList()
|
field.map { it.minesAround }.toList()
|
||||||
)
|
)
|
||||||
|
@ -324,11 +323,11 @@ class LevelFacadeTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testOpenSafeZone() {
|
fun testOpenSafeZone() {
|
||||||
levelFacadeOf(3, 3, 1, 200L).run {
|
levelFacadeOf(3, 3, 1, 0).run {
|
||||||
plantMinesExcept(3)
|
plantMinesExcept(3)
|
||||||
assertEquals(field.filter { it.isCovered }.count(), field.count())
|
assertEquals(field.filterNot { it.isCovered }.count(), 0)
|
||||||
singleClick(1)
|
singleClick(1)
|
||||||
assertEquals(field.filter { it.isCovered }.count(), field.count() - 6)
|
assertEquals(field.filterNot { it.isCovered }.count(), 6)
|
||||||
assertEquals(
|
assertEquals(
|
||||||
field.filterNot { it.isCovered }.map { it.id }.toList(),
|
field.filterNot { it.isCovered }.map { it.id }.toList(),
|
||||||
listOf(0, 1, 2, 3, 4, 5)
|
listOf(0, 1, 2, 3, 4, 5)
|
||||||
|
@ -381,7 +380,7 @@ class LevelFacadeTest {
|
||||||
plantMinesExcept(3)
|
plantMinesExcept(3)
|
||||||
val mine = field.last { it.hasMine }
|
val mine = field.last { it.hasMine }
|
||||||
assertEquals(
|
assertEquals(
|
||||||
listOf(35, 33, 22, 27, 17, 32, 25, 8, 7, 2),
|
listOf(33, 26, 28, 31, 19, 14, 18, 7, 6, 4),
|
||||||
takeExplosionRadius(mine).map { it.id }.toList()
|
takeExplosionRadius(mine).map { it.id }.toList()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -390,7 +389,7 @@ class LevelFacadeTest {
|
||||||
plantMinesExcept(3)
|
plantMinesExcept(3)
|
||||||
val mine = field.first { it.hasMine }
|
val mine = field.first { it.hasMine }
|
||||||
assertEquals(
|
assertEquals(
|
||||||
listOf(2, 8, 7, 17, 22, 25, 27, 32, 33, 35),
|
listOf(4, 14, 7, 28, 6, 19, 26, 18, 33, 31),
|
||||||
takeExplosionRadius(mine).map { it.id }.toList()
|
takeExplosionRadius(mine).map { it.id }.toList()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -399,7 +398,7 @@ class LevelFacadeTest {
|
||||||
plantMinesExcept(3)
|
plantMinesExcept(3)
|
||||||
val mine = field.filter { it.hasMine }.elementAt(4)
|
val mine = field.filter { it.hasMine }.elementAt(4)
|
||||||
assertEquals(
|
assertEquals(
|
||||||
listOf(22, 17, 27, 33, 35, 8, 32, 25, 2, 7),
|
listOf(18, 19, 6, 7, 14, 26, 31, 33, 28, 4),
|
||||||
takeExplosionRadius(mine).map { it.id }.toList()
|
takeExplosionRadius(mine).map { it.id }.toList()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -485,7 +484,7 @@ class LevelFacadeTest {
|
||||||
val width = 12
|
val width = 12
|
||||||
val height = 12
|
val height = 12
|
||||||
val mines = 9
|
val mines = 9
|
||||||
levelFacadeOf(width, height, mines, 200L).run {
|
levelFacadeOf(width, height, mines, 150L).run {
|
||||||
plantMinesExcept(3)
|
plantMinesExcept(3)
|
||||||
field.filterNot { it.hasMine }.take(width * height - 1 - mines).forEach {
|
field.filterNot { it.hasMine }.take(width * height - 1 - mines).forEach {
|
||||||
singleClick(it.id)
|
singleClick(it.id)
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
package dev.lucasnlm.antimine.common.level
|
package dev.lucasnlm.antimine.common.level
|
||||||
|
|
||||||
import android.util.DisplayMetrics
|
|
||||||
import com.nhaarman.mockitokotlin2.doReturn
|
import com.nhaarman.mockitokotlin2.doReturn
|
||||||
import com.nhaarman.mockitokotlin2.mock
|
import com.nhaarman.mockitokotlin2.mock
|
||||||
import dev.lucasnlm.antimine.common.level.models.Difficulty
|
import dev.lucasnlm.antimine.common.level.models.Difficulty
|
||||||
import dev.lucasnlm.antimine.common.level.models.Minefield
|
import dev.lucasnlm.antimine.common.level.models.Minefield
|
||||||
import dev.lucasnlm.antimine.common.level.repository.MinefieldRepository
|
import dev.lucasnlm.antimine.common.level.repository.MinefieldRepository
|
||||||
import dev.lucasnlm.antimine.common.level.repository.IDimensionRepository
|
import dev.lucasnlm.antimine.common.level.repository.IDimensionRepository
|
||||||
|
import dev.lucasnlm.antimine.common.level.repository.Size
|
||||||
import dev.lucasnlm.antimine.core.preferences.IPreferencesRepository
|
import dev.lucasnlm.antimine.core.preferences.IPreferencesRepository
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
@ -74,10 +74,7 @@ class MinefieldFactoryTest {
|
||||||
val dimensionRepository: IDimensionRepository = mock {
|
val dimensionRepository: IDimensionRepository = mock {
|
||||||
on { areaSize() } doReturn 10.0f
|
on { areaSize() } doReturn 10.0f
|
||||||
on { actionBarSize() } doReturn 10
|
on { actionBarSize() } doReturn 10
|
||||||
on { displaySize() } doReturn DisplayMetrics().apply {
|
on { displaySize() } doReturn Size(500, 1000)
|
||||||
widthPixels = 500
|
|
||||||
heightPixels = 1000
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MinefieldRepository().fromDifficulty(
|
MinefieldRepository().fromDifficulty(
|
||||||
|
|
|
@ -0,0 +1,97 @@
|
||||||
|
package dev.lucasnlm.antimine.common.level.di
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import dev.lucasnlm.antimine.common.level.database.models.Save
|
||||||
|
import dev.lucasnlm.antimine.common.level.models.Difficulty
|
||||||
|
import dev.lucasnlm.antimine.common.level.models.Event
|
||||||
|
import dev.lucasnlm.antimine.common.level.models.Minefield
|
||||||
|
import dev.lucasnlm.antimine.common.level.repository.IDimensionRepository
|
||||||
|
import dev.lucasnlm.antimine.common.level.repository.IMinefieldRepository
|
||||||
|
import dev.lucasnlm.antimine.common.level.repository.ISavesRepository
|
||||||
|
import dev.lucasnlm.antimine.common.level.repository.MinefieldRepository
|
||||||
|
import dev.lucasnlm.antimine.common.level.repository.Size
|
||||||
|
import dev.lucasnlm.antimine.common.level.utils.Clock
|
||||||
|
import dev.lucasnlm.antimine.common.level.utils.IHapticFeedbackInteractor
|
||||||
|
import dev.lucasnlm.antimine.common.level.viewmodel.GameViewModelFactory
|
||||||
|
import dev.lucasnlm.antimine.core.analytics.AnalyticsManager
|
||||||
|
import dev.lucasnlm.antimine.core.preferences.IPreferencesRepository
|
||||||
|
|
||||||
|
@Module
|
||||||
|
class TestLevelModule(
|
||||||
|
private val application: Application
|
||||||
|
) {
|
||||||
|
@Provides
|
||||||
|
fun provideGameEventObserver(): MutableLiveData<Event> = MutableLiveData()
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
fun provideClock(): Clock = Clock()
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
fun provideGameViewModelFactory(
|
||||||
|
application: Application,
|
||||||
|
eventObserver: MutableLiveData<Event>,
|
||||||
|
savesRepository: ISavesRepository,
|
||||||
|
dimensionRepository: IDimensionRepository,
|
||||||
|
preferencesRepository: IPreferencesRepository,
|
||||||
|
hapticFeedbackInteractor: IHapticFeedbackInteractor,
|
||||||
|
minefieldRepository: MinefieldRepository,
|
||||||
|
analyticsManager: AnalyticsManager,
|
||||||
|
clock: Clock
|
||||||
|
) = GameViewModelFactory(
|
||||||
|
application,
|
||||||
|
eventObserver,
|
||||||
|
savesRepository,
|
||||||
|
dimensionRepository,
|
||||||
|
preferencesRepository,
|
||||||
|
hapticFeedbackInteractor,
|
||||||
|
minefieldRepository,
|
||||||
|
analyticsManager,
|
||||||
|
clock
|
||||||
|
)
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
fun provideDimensionRepository(
|
||||||
|
context: Context,
|
||||||
|
preferencesRepository: IPreferencesRepository
|
||||||
|
): IDimensionRepository = object : IDimensionRepository {
|
||||||
|
override fun areaSize(): Float = 50.0f
|
||||||
|
|
||||||
|
override fun displaySize(): Size = Size(50 * 15, 50 * 30)
|
||||||
|
|
||||||
|
override fun actionBarSize(): Int = 50
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
fun provideSavesRepository(): ISavesRepository = object : ISavesRepository {
|
||||||
|
override suspend fun fetchCurrentSave(): Save? = null
|
||||||
|
|
||||||
|
override suspend fun saveGame(save: Save): Long? = null
|
||||||
|
|
||||||
|
override fun setLimit(maxSavesStorage: Int) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
fun provideMinefieldRepository(): IMinefieldRepository = object : IMinefieldRepository {
|
||||||
|
override fun fromDifficulty(
|
||||||
|
difficulty: Difficulty,
|
||||||
|
dimensionRepository: IDimensionRepository,
|
||||||
|
preferencesRepository: IPreferencesRepository
|
||||||
|
) = Minefield(9, 9, 9)
|
||||||
|
|
||||||
|
override fun randomSeed(): Long = 200
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
fun provideHapticFeedbackInteractor(
|
||||||
|
application: Application,
|
||||||
|
preferencesRepository: IPreferencesRepository
|
||||||
|
): IHapticFeedbackInteractor = object : IHapticFeedbackInteractor {
|
||||||
|
override fun toggleFlagFeedback() { }
|
||||||
|
|
||||||
|
override fun explosionFeedback() { }
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,7 +32,7 @@ ext.versions = [
|
||||||
|
|
||||||
// Kotlin
|
// Kotlin
|
||||||
kotlin : '1.3.70',
|
kotlin : '1.3.70',
|
||||||
coroutines : '1.3.4',
|
coroutines : '1.3.5',
|
||||||
|
|
||||||
// Jetpack
|
// Jetpack
|
||||||
lifecycle : '1.1.1',
|
lifecycle : '1.1.1',
|
||||||
|
@ -47,5 +47,8 @@ ext.versions = [
|
||||||
junit : '4.12',
|
junit : '4.12',
|
||||||
mockito : '2.24.0',
|
mockito : '2.24.0',
|
||||||
mockitoKotlin : '2.1.0',
|
mockitoKotlin : '2.1.0',
|
||||||
testCore : '1.2.0'
|
testCore : '1.2.0',
|
||||||
|
espresso : '3.2.0',
|
||||||
|
fragmentTest : '1.1.0',
|
||||||
|
robolectric : '4.3'
|
||||||
]
|
]
|
||||||
|
|
Loading…
Reference in a new issue