commit
f6c30a29c3
35 changed files with 598 additions and 277 deletions
|
@ -83,6 +83,10 @@
|
|||
<data
|
||||
android:host="load-game"
|
||||
android:scheme="antimine" />
|
||||
|
||||
<data
|
||||
android:host="retry-game"
|
||||
android:scheme="antimine" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter
|
||||
|
@ -126,15 +130,6 @@
|
|||
<!-- </intent-filter>-->
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="dev.lucasnlm.antimine.about.views.thirds.ThirdPartiesFragment"
|
||||
android:label="@string/licenses"
|
||||
android:theme="@style/AppTheme" />
|
||||
|
||||
<activity
|
||||
android:name="dev.lucasnlm.antimine.about.views.translators.TranslatorsFragment"
|
||||
android:theme="@style/AppTheme" />
|
||||
|
||||
<activity
|
||||
android:name="dev.lucasnlm.antimine.about.TextActivity"
|
||||
android:theme="@style/AppTheme">
|
||||
|
|
14
app/src/main/java/dev/lucasnlm/antimine/DeepLink.kt
Normal file
14
app/src/main/java/dev/lucasnlm/antimine/DeepLink.kt
Normal file
|
@ -0,0 +1,14 @@
|
|||
package dev.lucasnlm.antimine
|
||||
|
||||
object DeepLink {
|
||||
const val SCHEME = "antimine"
|
||||
|
||||
const val NEW_GAME_AUTHORITY = "new-game"
|
||||
const val RETRY_HOST_AUTHORITY = "retry-game"
|
||||
const val LOAD_GAME_AUTHORITY = "load-game"
|
||||
|
||||
const val BEGINNER_PATH = "beginner"
|
||||
const val INTERMEDIATE_PATH = "intermediate"
|
||||
const val EXPERT_PATH = "expert"
|
||||
const val STANDARD_PATH = "standard"
|
||||
}
|
|
@ -71,6 +71,7 @@ class GameActivity : DaggerAppCompatActivity() {
|
|||
private var totalArea: Int = 0
|
||||
private var rightMines: Int = 0
|
||||
private var currentTime: Long = 0
|
||||
private var currentSaveId: Long = 0
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
@ -95,38 +96,60 @@ class GameActivity : DaggerAppCompatActivity() {
|
|||
private fun bindViewModel() = viewModel.apply {
|
||||
var lastEvent: Event? = null // TODO use distinctUntilChanged when available
|
||||
|
||||
eventObserver.observe(this@GameActivity, Observer {
|
||||
if (lastEvent != it) {
|
||||
onGameEvent(it)
|
||||
lastEvent = it
|
||||
eventObserver.observe(
|
||||
this@GameActivity,
|
||||
Observer {
|
||||
if (lastEvent != it) {
|
||||
onGameEvent(it)
|
||||
lastEvent = it
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
elapsedTimeSeconds.observe(this@GameActivity, Observer {
|
||||
timer.apply {
|
||||
visibility = if (it == 0L) View.GONE else View.VISIBLE
|
||||
text = DateUtils.formatElapsedTime(it)
|
||||
elapsedTimeSeconds.observe(
|
||||
this@GameActivity,
|
||||
Observer {
|
||||
timer.apply {
|
||||
visibility = if (it == 0L) View.GONE else View.VISIBLE
|
||||
text = DateUtils.formatElapsedTime(it)
|
||||
}
|
||||
currentTime = it
|
||||
}
|
||||
currentTime = it
|
||||
})
|
||||
)
|
||||
|
||||
mineCount.observe(this@GameActivity, Observer {
|
||||
minesCount.apply {
|
||||
visibility = View.VISIBLE
|
||||
text = it.toString()
|
||||
mineCount.observe(
|
||||
this@GameActivity,
|
||||
Observer {
|
||||
minesCount.apply {
|
||||
visibility = View.VISIBLE
|
||||
text = it.toString()
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
difficulty.observe(this@GameActivity, Observer {
|
||||
onChangeDifficulty(it)
|
||||
})
|
||||
difficulty.observe(
|
||||
this@GameActivity,
|
||||
Observer {
|
||||
onChangeDifficulty(it)
|
||||
}
|
||||
)
|
||||
|
||||
field.observe(this@GameActivity, Observer { area ->
|
||||
val mines = area.filter { it.hasMine }
|
||||
totalArea = area.count()
|
||||
totalMines = mines.count()
|
||||
rightMines = mines.map { if (it.mark.isFlag()) 1 else 0 }.sum()
|
||||
})
|
||||
field.observe(
|
||||
this@GameActivity,
|
||||
Observer { area ->
|
||||
val mines = area.filter { it.hasMine }
|
||||
totalArea = area.count()
|
||||
totalMines = mines.count()
|
||||
rightMines = mines.map { if (it.mark.isFlag()) 1 else 0 }.sum()
|
||||
}
|
||||
)
|
||||
|
||||
saveId.observe(
|
||||
this@GameActivity,
|
||||
Observer {
|
||||
currentSaveId = it
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
|
@ -399,7 +422,8 @@ class GameActivity : DaggerAppCompatActivity() {
|
|||
victory,
|
||||
score?.rightMines ?: 0,
|
||||
score?.totalMines ?: 0,
|
||||
currentGameStatus.time
|
||||
currentGameStatus.time,
|
||||
currentSaveId
|
||||
).apply {
|
||||
showAllowingStateLoss(supportFragmentManager, EndGameDialogFragment.TAG)
|
||||
}
|
||||
|
@ -409,9 +433,13 @@ class GameActivity : DaggerAppCompatActivity() {
|
|||
|
||||
private fun waitAndShowEndGameDialog(victory: Boolean, await: Boolean) {
|
||||
if (await && viewModel.explosionDelay() != 0L) {
|
||||
postDelayed(Handler(), {
|
||||
showEndGameDialog(victory)
|
||||
}, null, (viewModel.explosionDelay() * 0.3).toLong())
|
||||
postDelayed(
|
||||
Handler(),
|
||||
{
|
||||
showEndGameDialog(victory)
|
||||
},
|
||||
null, (viewModel.explosionDelay() * 0.3).toLong()
|
||||
)
|
||||
} else {
|
||||
showEndGameDialog(victory)
|
||||
}
|
||||
|
|
|
@ -61,35 +61,50 @@ class TvGameActivity : DaggerAppCompatActivity() {
|
|||
}
|
||||
|
||||
private fun bindViewModel() = viewModel.apply {
|
||||
eventObserver.observe(this@TvGameActivity, Observer {
|
||||
onGameEvent(it)
|
||||
})
|
||||
|
||||
elapsedTimeSeconds.observe(this@TvGameActivity, Observer {
|
||||
timer.apply {
|
||||
visibility = if (it == 0L) View.GONE else View.VISIBLE
|
||||
text = DateUtils.formatElapsedTime(it)
|
||||
eventObserver.observe(
|
||||
this@TvGameActivity,
|
||||
Observer {
|
||||
onGameEvent(it)
|
||||
}
|
||||
currentTime = it
|
||||
})
|
||||
)
|
||||
|
||||
mineCount.observe(this@TvGameActivity, Observer {
|
||||
minesCount.apply {
|
||||
visibility = View.VISIBLE
|
||||
text = it.toString()
|
||||
elapsedTimeSeconds.observe(
|
||||
this@TvGameActivity,
|
||||
Observer {
|
||||
timer.apply {
|
||||
visibility = if (it == 0L) View.GONE else View.VISIBLE
|
||||
text = DateUtils.formatElapsedTime(it)
|
||||
}
|
||||
currentTime = it
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
difficulty.observe(this@TvGameActivity, Observer {
|
||||
// onChangeDifficulty(it)
|
||||
})
|
||||
mineCount.observe(
|
||||
this@TvGameActivity,
|
||||
Observer {
|
||||
minesCount.apply {
|
||||
visibility = View.VISIBLE
|
||||
text = it.toString()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
field.observe(this@TvGameActivity, Observer { area ->
|
||||
val mines = area.filter { it.hasMine }
|
||||
totalArea = area.count()
|
||||
totalMines = mines.count()
|
||||
rightMines = mines.map { if (it.mark.isFlag()) 1 else 0 }.sum()
|
||||
})
|
||||
difficulty.observe(
|
||||
this@TvGameActivity,
|
||||
Observer {
|
||||
// onChangeDifficulty(it)
|
||||
}
|
||||
)
|
||||
|
||||
field.observe(
|
||||
this@TvGameActivity,
|
||||
Observer { area ->
|
||||
val mines = area.filter { it.hasMine }
|
||||
totalArea = area.count()
|
||||
totalMines = mines.count()
|
||||
rightMines = mines.map { if (it.mark.isFlag()) 1 else 0 }.sum()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
|
@ -208,10 +223,36 @@ class TvGameActivity : DaggerAppCompatActivity() {
|
|||
|
||||
private fun waitAndShowConfirmNewGame() {
|
||||
if (keepConfirmingNewGame) {
|
||||
HandlerCompat.postDelayed(Handler(), {
|
||||
HandlerCompat.postDelayed(
|
||||
Handler(),
|
||||
{
|
||||
if (status is Status.Over && !isFinishing) {
|
||||
AlertDialog.Builder(this, R.style.MyDialog).apply {
|
||||
setTitle(R.string.new_game)
|
||||
setMessage(R.string.new_game_request)
|
||||
setPositiveButton(R.string.yes) { _, _ ->
|
||||
GlobalScope.launch {
|
||||
viewModel.startNewGame()
|
||||
}
|
||||
}
|
||||
setNegativeButton(R.string.cancel, null)
|
||||
}.show()
|
||||
|
||||
keepConfirmingNewGame = false
|
||||
}
|
||||
},
|
||||
null, DateUtils.SECOND_IN_MILLIS
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun waitAndShowGameOverConfirmNewGame() {
|
||||
HandlerCompat.postDelayed(
|
||||
Handler(),
|
||||
{
|
||||
if (status is Status.Over && !isFinishing) {
|
||||
AlertDialog.Builder(this, R.style.MyDialog).apply {
|
||||
setTitle(R.string.new_game)
|
||||
setTitle(R.string.you_lost)
|
||||
setMessage(R.string.new_game_request)
|
||||
setPositiveButton(R.string.yes) { _, _ ->
|
||||
GlobalScope.launch {
|
||||
|
@ -220,28 +261,10 @@ class TvGameActivity : DaggerAppCompatActivity() {
|
|||
}
|
||||
setNegativeButton(R.string.cancel, null)
|
||||
}.show()
|
||||
|
||||
keepConfirmingNewGame = false
|
||||
}
|
||||
}, null, DateUtils.SECOND_IN_MILLIS)
|
||||
}
|
||||
}
|
||||
|
||||
private fun waitAndShowGameOverConfirmNewGame() {
|
||||
HandlerCompat.postDelayed(Handler(), {
|
||||
if (status is Status.Over && !isFinishing) {
|
||||
AlertDialog.Builder(this, R.style.MyDialog).apply {
|
||||
setTitle(R.string.you_lost)
|
||||
setMessage(R.string.new_game_request)
|
||||
setPositiveButton(R.string.yes) { _, _ ->
|
||||
GlobalScope.launch {
|
||||
viewModel.startNewGame()
|
||||
}
|
||||
}
|
||||
setNegativeButton(R.string.cancel, null)
|
||||
}.show()
|
||||
}
|
||||
}, null, DateUtils.SECOND_IN_MILLIS)
|
||||
},
|
||||
null, DateUtils.SECOND_IN_MILLIS
|
||||
)
|
||||
}
|
||||
|
||||
private fun changeDifficulty(newDifficulty: Difficulty) {
|
||||
|
|
|
@ -25,22 +25,25 @@ class AboutActivity : AppCompatActivity() {
|
|||
|
||||
aboutViewModel = ViewModelProviders.of(this).get(AboutViewModel::class.java)
|
||||
|
||||
aboutViewModel.eventObserver.observe(this, Observer { event ->
|
||||
when (event) {
|
||||
AboutEvent.ThirdPartyLicenses -> {
|
||||
replaceFragment(ThirdPartiesFragment(), ThirdPartiesFragment::class.simpleName)
|
||||
}
|
||||
AboutEvent.SourceCode -> {
|
||||
openSourceCode()
|
||||
}
|
||||
AboutEvent.Translators -> {
|
||||
replaceFragment(TranslatorsFragment(), TranslatorsFragment::class.simpleName)
|
||||
}
|
||||
else -> {
|
||||
replaceFragment(AboutInfoFragment(), null)
|
||||
aboutViewModel.eventObserver.observe(
|
||||
this,
|
||||
Observer { event ->
|
||||
when (event) {
|
||||
AboutEvent.ThirdPartyLicenses -> {
|
||||
replaceFragment(ThirdPartiesFragment(), ThirdPartiesFragment::class.simpleName)
|
||||
}
|
||||
AboutEvent.SourceCode -> {
|
||||
openSourceCode()
|
||||
}
|
||||
AboutEvent.Translators -> {
|
||||
replaceFragment(TranslatorsFragment(), TranslatorsFragment::class.simpleName)
|
||||
}
|
||||
else -> {
|
||||
replaceFragment(AboutInfoFragment(), null)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
replaceFragment(AboutInfoFragment(), null)
|
||||
}
|
||||
|
|
|
@ -1,14 +1,18 @@
|
|||
package dev.lucasnlm.antimine.history.views
|
||||
|
||||
import android.content.Intent
|
||||
import android.graphics.PorterDuff
|
||||
import android.net.Uri
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import dev.lucasnlm.antimine.DeepLink
|
||||
import dev.lucasnlm.antimine.R
|
||||
import dev.lucasnlm.antimine.common.level.database.models.Save
|
||||
import dev.lucasnlm.antimine.common.level.database.models.SaveStatus
|
||||
import dev.lucasnlm.antimine.common.level.models.Difficulty
|
||||
import java.text.DateFormat
|
||||
|
||||
class HistoryAdapter(
|
||||
private val saveHistory: List<Save>
|
||||
|
@ -23,24 +27,60 @@ class HistoryAdapter(
|
|||
override fun getItemCount(): Int = saveHistory.size
|
||||
|
||||
override fun onBindViewHolder(holder: HistoryViewHolder, position: Int) = with(saveHistory[position]) {
|
||||
holder.difficulty.text = holder.itemView.context.getString(when (difficulty) {
|
||||
Difficulty.Beginner -> R.string.beginner
|
||||
Difficulty.Intermediate -> R.string.intermediate
|
||||
Difficulty.Expert -> R.string.expert
|
||||
Difficulty.Standard -> R.string.standard
|
||||
Difficulty.Custom -> R.string.custom
|
||||
})
|
||||
holder.difficulty.text = holder.itemView.context.getString(
|
||||
when (difficulty) {
|
||||
Difficulty.Beginner -> R.string.beginner
|
||||
Difficulty.Intermediate -> R.string.intermediate
|
||||
Difficulty.Expert -> R.string.expert
|
||||
Difficulty.Standard -> R.string.standard
|
||||
Difficulty.Custom -> R.string.custom
|
||||
}
|
||||
)
|
||||
|
||||
val context = holder.itemView.context
|
||||
holder.flag.setColorFilter(
|
||||
when (status) {
|
||||
SaveStatus.VICTORY -> ContextCompat.getColor(context, R.color.victory)
|
||||
SaveStatus.ON_GOING -> ContextCompat.getColor(context, R.color.ongoing)
|
||||
SaveStatus.DEFEAT -> ContextCompat.getColor(context, R.color.lose)
|
||||
}, PorterDuff.Mode.SRC_IN
|
||||
)
|
||||
|
||||
holder.minefieldSize.text = String.format("%d x %d", minefield.width, minefield.height)
|
||||
holder.minesCount.text = holder.itemView.context.getString(R.string.mines_remaining, minefield.mines)
|
||||
holder.date.text = DateFormat.getDateInstance().format(startDate)
|
||||
holder.minesCount.text = context.getString(R.string.mines_remaining, minefield.mines)
|
||||
|
||||
holder.itemView.setOnClickListener {
|
||||
val intent = Intent(Intent.ACTION_VIEW).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
data = Uri.parse("antimine://load-game/$uid")
|
||||
}
|
||||
it.context.startActivity(intent)
|
||||
if (status != SaveStatus.VICTORY) {
|
||||
holder.replay.setImageResource(R.drawable.replay)
|
||||
holder.replay.setOnClickListener { replayGame(it, uid) }
|
||||
} else {
|
||||
holder.replay.setImageResource(R.drawable.play)
|
||||
holder.replay.setOnClickListener { loadGame(it, uid) }
|
||||
}
|
||||
|
||||
holder.itemView.setOnClickListener { loadGame(it, uid) }
|
||||
}
|
||||
|
||||
private fun replayGame(view: View, uid: Int) {
|
||||
val intent = Intent(Intent.ACTION_VIEW).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
data = Uri.Builder()
|
||||
.scheme(DeepLink.SCHEME)
|
||||
.authority(DeepLink.RETRY_HOST_AUTHORITY)
|
||||
.appendPath(uid.toString())
|
||||
.build()
|
||||
}
|
||||
view.context.startActivity(intent)
|
||||
}
|
||||
|
||||
private fun loadGame(view: View, uid: Int) {
|
||||
val intent = Intent(Intent.ACTION_VIEW).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
data = Uri.Builder()
|
||||
.scheme(DeepLink.SCHEME)
|
||||
.authority(DeepLink.LOAD_GAME_AUTHORITY)
|
||||
.appendPath(uid.toString())
|
||||
.build()
|
||||
}
|
||||
view.context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,9 +50,12 @@ class HistoryFragment : DaggerFragment() {
|
|||
)
|
||||
layoutManager = LinearLayoutManager(view.context)
|
||||
|
||||
historyViewModel?.saves?.observe(viewLifecycleOwner, Observer {
|
||||
adapter = HistoryAdapter(it)
|
||||
})
|
||||
historyViewModel?.saves?.observe(
|
||||
viewLifecycleOwner,
|
||||
Observer {
|
||||
adapter = HistoryAdapter(it)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,12 +2,14 @@ package dev.lucasnlm.antimine.history.views
|
|||
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.widget.AppCompatImageView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import dev.lucasnlm.antimine.R
|
||||
|
||||
class HistoryViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
||||
val flag: AppCompatImageView = view.findViewById(R.id.badge)
|
||||
val difficulty: TextView = view.findViewById(R.id.difficulty)
|
||||
val minefieldSize: TextView = view.findViewById(R.id.minefieldSize)
|
||||
val minesCount: TextView = view.findViewById(R.id.minesCount)
|
||||
val date: TextView = view.findViewById(R.id.date)
|
||||
val replay: AppCompatImageView = view.findViewById(R.id.replay)
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ class EndGameDialogFragment : DaggerAppCompatDialogFragment() {
|
|||
private var time: Long = 0L
|
||||
private var rightMines: Int = 0
|
||||
private var totalMines: Int = 0
|
||||
private var saveId: Long = 0
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
@ -48,10 +49,11 @@ class EndGameDialogFragment : DaggerAppCompatDialogFragment() {
|
|||
|
||||
arguments?.run {
|
||||
isVictory = getBoolean(DIALOG_IS_VICTORY) == true
|
||||
time = getLong(DIALOG_TIME)
|
||||
rightMines = getInt(DIALOG_RIGHT_MINES)
|
||||
totalMines = getInt(DIALOG_TOTAL_MINES)
|
||||
time = getLong(DIALOG_TIME, 0L)
|
||||
rightMines = getInt(DIALOG_RIGHT_MINES, 0)
|
||||
totalMines = getInt(DIALOG_TOTAL_MINES, 0)
|
||||
hasValidData = (totalMines > 0)
|
||||
saveId = getLong(DIALOG_SAVE_ID, 0L)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -118,7 +120,7 @@ class EndGameDialogFragment : DaggerAppCompatDialogFragment() {
|
|||
instantAppManager.showInstallPrompt(this, null, 0, null)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
} else if (isVictory) {
|
||||
setNeutralButton(R.string.share) { _, _ ->
|
||||
val setup = viewModel.levelSetup.value
|
||||
val field = viewModel.field.value
|
||||
|
@ -127,17 +129,24 @@ class EndGameDialogFragment : DaggerAppCompatDialogFragment() {
|
|||
shareViewModel.share(setup, field, time)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setNeutralButton(R.string.retry) { _, _ ->
|
||||
GlobalScope.launch {
|
||||
viewModel.retryGame(saveId.toInt())
|
||||
}
|
||||
}
|
||||
}
|
||||
}.create()
|
||||
|
||||
companion object {
|
||||
fun newInstance(victory: Boolean, rightMines: Int, totalMines: Int, time: Long): EndGameDialogFragment =
|
||||
fun newInstance(victory: Boolean, rightMines: Int, totalMines: Int, time: Long, saveId: Long) =
|
||||
EndGameDialogFragment().apply {
|
||||
arguments = Bundle().apply {
|
||||
putBoolean(DIALOG_IS_VICTORY, victory)
|
||||
putInt(DIALOG_RIGHT_MINES, rightMines)
|
||||
putInt(DIALOG_TOTAL_MINES, totalMines)
|
||||
putLong(DIALOG_TIME, time)
|
||||
putLong(DIALOG_SAVE_ID, saveId)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -145,6 +154,7 @@ class EndGameDialogFragment : DaggerAppCompatDialogFragment() {
|
|||
private const val DIALOG_TIME = "dialog_time"
|
||||
private const val DIALOG_RIGHT_MINES = "dialog_right_mines"
|
||||
private const val DIALOG_TOTAL_MINES = "dialog_total_mines"
|
||||
private const val DIALOG_SAVE_ID = "dialog_save_id"
|
||||
|
||||
val TAG = EndGameDialogFragment::class.simpleName
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import androidx.lifecycle.Observer
|
|||
import androidx.lifecycle.ViewModelProviders
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import dagger.android.support.DaggerFragment
|
||||
import dev.lucasnlm.antimine.DeepLink
|
||||
import dev.lucasnlm.antimine.common.R
|
||||
import dev.lucasnlm.antimine.common.level.models.Difficulty
|
||||
import dev.lucasnlm.antimine.common.level.models.Event
|
||||
|
@ -67,10 +68,12 @@ open class LevelFragment : DaggerFragment() {
|
|||
GlobalScope.launch {
|
||||
val loadGameUid = checkLoadGameDeepLink()
|
||||
val newGameDeepLink = checkNewGameDeepLink()
|
||||
val retryDeepLink = checkRetryGameDeepLink()
|
||||
|
||||
val levelSetup = when {
|
||||
loadGameUid != null -> viewModel.loadGame(loadGameUid)
|
||||
newGameDeepLink != null -> viewModel.startNewGame(newGameDeepLink)
|
||||
retryDeepLink != null -> viewModel.retryGame(retryDeepLink)
|
||||
else -> viewModel.loadLastGame()
|
||||
}
|
||||
|
||||
|
@ -92,41 +95,53 @@ open class LevelFragment : DaggerFragment() {
|
|||
}
|
||||
|
||||
viewModel.run {
|
||||
field.observe(viewLifecycleOwner, Observer {
|
||||
areaAdapter.bindField(it)
|
||||
})
|
||||
field.observe(
|
||||
viewLifecycleOwner,
|
||||
Observer {
|
||||
areaAdapter.bindField(it)
|
||||
}
|
||||
)
|
||||
|
||||
levelSetup.observe(viewLifecycleOwner, Observer {
|
||||
recyclerGrid.layoutManager = makeNewLayoutManager(it.width, it.height)
|
||||
})
|
||||
levelSetup.observe(
|
||||
viewLifecycleOwner,
|
||||
Observer {
|
||||
recyclerGrid.layoutManager = makeNewLayoutManager(it.width, it.height)
|
||||
}
|
||||
)
|
||||
|
||||
fieldRefresh.observe(viewLifecycleOwner, Observer {
|
||||
areaAdapter.notifyItemChanged(it)
|
||||
})
|
||||
fieldRefresh.observe(
|
||||
viewLifecycleOwner,
|
||||
Observer {
|
||||
areaAdapter.notifyItemChanged(it)
|
||||
}
|
||||
)
|
||||
|
||||
eventObserver.observe(viewLifecycleOwner, Observer {
|
||||
when (it) {
|
||||
Event.ResumeGameOver,
|
||||
Event.GameOver,
|
||||
Event.Victory,
|
||||
Event.ResumeVictory -> areaAdapter.setClickEnabled(false)
|
||||
Event.Running,
|
||||
Event.ResumeGame,
|
||||
Event.StartNewGame -> areaAdapter.setClickEnabled(true)
|
||||
else -> {
|
||||
eventObserver.observe(
|
||||
viewLifecycleOwner,
|
||||
Observer {
|
||||
when (it) {
|
||||
Event.ResumeGameOver,
|
||||
Event.GameOver,
|
||||
Event.Victory,
|
||||
Event.ResumeVictory -> areaAdapter.setClickEnabled(false)
|
||||
Event.Running,
|
||||
Event.ResumeGame,
|
||||
Event.StartNewGame -> areaAdapter.setClickEnabled(true)
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkNewGameDeepLink(): Difficulty? = activity?.intent?.data?.let { uri ->
|
||||
if (uri.scheme == DEFAULT_SCHEME) {
|
||||
when (uri.schemeSpecificPart.removePrefix(DEEP_LINK_NEW_GAME_HOST)) {
|
||||
DEEP_LINK_BEGINNER -> Difficulty.Beginner
|
||||
DEEP_LINK_INTERMEDIATE -> Difficulty.Intermediate
|
||||
DEEP_LINK_EXPERT -> Difficulty.Expert
|
||||
DEEP_LINK_STANDARD -> Difficulty.Standard
|
||||
if (uri.scheme == DeepLink.SCHEME && uri.authority == DeepLink.NEW_GAME_AUTHORITY) {
|
||||
when (uri.pathSegments.firstOrNull()) {
|
||||
DeepLink.BEGINNER_PATH -> Difficulty.Beginner
|
||||
DeepLink.INTERMEDIATE_PATH -> Difficulty.Intermediate
|
||||
DeepLink.EXPERT_PATH -> Difficulty.Expert
|
||||
DeepLink.STANDARD_PATH -> Difficulty.Standard
|
||||
else -> null
|
||||
}
|
||||
} else {
|
||||
|
@ -135,8 +150,16 @@ open class LevelFragment : DaggerFragment() {
|
|||
}
|
||||
|
||||
private fun checkLoadGameDeepLink(): Int? = activity?.intent?.data?.let { uri ->
|
||||
if (uri.scheme == DEFAULT_SCHEME) {
|
||||
uri.schemeSpecificPart.removePrefix(DEEP_LINK_LOAD_GAME_HOST).toIntOrNull()
|
||||
if (uri.scheme == DeepLink.SCHEME && uri.authority == DeepLink.LOAD_GAME_AUTHORITY) {
|
||||
uri.pathSegments.firstOrNull()?.toIntOrNull()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkRetryGameDeepLink(): Int? = activity?.intent?.data?.let { uri ->
|
||||
if (uri.scheme == DeepLink.SCHEME && uri.authority == DeepLink.RETRY_HOST_AUTHORITY) {
|
||||
uri.pathSegments.firstOrNull()?.toIntOrNull()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
@ -154,15 +177,4 @@ open class LevelFragment : DaggerFragment() {
|
|||
((recyclerGrid.measuredHeight - dimensionRepository.areaSizeWithPadding() * boardHeight) / 2)
|
||||
.coerceAtLeast(0.0f)
|
||||
.toInt()
|
||||
|
||||
companion object {
|
||||
const val DEFAULT_SCHEME = "antimine"
|
||||
|
||||
const val DEEP_LINK_NEW_GAME_HOST = "//new-game/"
|
||||
const val DEEP_LINK_LOAD_GAME_HOST = "//load-game/"
|
||||
const val DEEP_LINK_BEGINNER = "beginner"
|
||||
const val DEEP_LINK_INTERMEDIATE = "intermediate"
|
||||
const val DEEP_LINK_EXPERT = "expert"
|
||||
const val DEEP_LINK_STANDARD = "standard"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,10 +66,13 @@ class ShareBuilder(
|
|||
val bitmap = Bitmap.createBitmap(imageWidth, imageHeight, Bitmap.Config.ARGB_8888)
|
||||
val canvas = Canvas(bitmap)
|
||||
|
||||
canvas.drawRect(0.0f, 0.0f, imageWidth.toFloat(), imageHeight.toFloat(), Paint().apply {
|
||||
color = Color.WHITE
|
||||
style = Paint.Style.FILL
|
||||
})
|
||||
canvas.drawRect(
|
||||
0.0f, 0.0f, imageWidth.toFloat(), imageHeight.toFloat(),
|
||||
Paint().apply {
|
||||
color = Color.WHITE
|
||||
style = Paint.Style.FILL
|
||||
}
|
||||
)
|
||||
|
||||
for (x in 0 until minefield.width) {
|
||||
for (y in 0 until minefield.height) {
|
||||
|
|
|
@ -14,17 +14,17 @@ class StatsViewModel : ViewModel() {
|
|||
|
||||
return if (statsCount > 0) {
|
||||
val result = stats.fold(
|
||||
StatsModel(statsCount, 0L, 0L, 0, 0, 0)
|
||||
) { acc, value ->
|
||||
StatsModel(
|
||||
acc.totalGames,
|
||||
acc.duration + value.duration,
|
||||
0,
|
||||
acc.mines + value.mines,
|
||||
acc.victory + value.victory,
|
||||
acc.openArea + value.openArea
|
||||
)
|
||||
}
|
||||
StatsModel(statsCount, 0L, 0L, 0, 0, 0)
|
||||
) { acc, value ->
|
||||
StatsModel(
|
||||
acc.totalGames,
|
||||
acc.duration + value.duration,
|
||||
0,
|
||||
acc.mines + value.mines,
|
||||
acc.victory + value.victory,
|
||||
acc.openArea + value.openArea
|
||||
)
|
||||
}
|
||||
result.copy(averageDuration = result.duration / result.totalGames)
|
||||
} else {
|
||||
StatsModel(0, 0, 0, 0, 0, 0)
|
||||
|
|
9
app/src/main/res/drawable/play.xml
Normal file
9
app/src/main/res/drawable/play.xml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M8,6.82v10.36c0,0.79 0.87,1.27 1.54,0.84l8.14,-5.18c0.62,-0.39 0.62,-1.29 0,-1.69L9.54,5.98C8.87,5.55 8,6.03 8,6.82z"/>
|
||||
</vector>
|
9
app/src/main/res/drawable/retry.xml
Normal file
9
app/src/main/res/drawable/retry.xml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M12,5V2.21c0,-0.45 -0.54,-0.67 -0.85,-0.35l-3.8,3.79c-0.2,0.2 -0.2,0.51 0,0.71l3.79,3.79c0.32,0.31 0.86,0.09 0.86,-0.36V7c3.73,0 6.68,3.42 5.86,7.29 -0.47,2.27 -2.31,4.1 -4.57,4.57 -3.57,0.75 -6.75,-1.7 -7.23,-5.01 -0.07,-0.48 -0.49,-0.85 -0.98,-0.85 -0.6,0 -1.08,0.53 -1,1.13 0.62,4.39 4.8,7.64 9.53,6.72 3.12,-0.61 5.63,-3.12 6.24,-6.24C20.84,9.48 16.94,5 12,5z"/>
|
||||
</vector>
|
|
@ -66,13 +66,16 @@
|
|||
app:layout_constraintTop_toBottomOf="@id/difficulty"
|
||||
tools:text="9 mines" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/date"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/open"
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/replay"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:contentDescription="@string/retry"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:tint="@color/text_color"
|
||||
app:srcCompat="@drawable/retry" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -36,7 +36,8 @@ class AreaScreenshot {
|
|||
Paint().apply {
|
||||
color = if (ambientMode) Color.BLACK else Color.WHITE
|
||||
style = Paint.Style.FILL
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
canvas.save()
|
||||
canvas.translate(testPadding * 0.5f, testPadding.toFloat() * 0.5f)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package dev.lucasnlm.antimine.common.level
|
||||
|
||||
import dev.lucasnlm.antimine.common.level.database.models.FirstOpen
|
||||
import dev.lucasnlm.antimine.common.level.database.models.Save
|
||||
import dev.lucasnlm.antimine.common.level.database.models.SaveStatus
|
||||
import dev.lucasnlm.antimine.common.level.database.models.Stats
|
||||
|
@ -16,6 +17,7 @@ class LevelFacade {
|
|||
private val randomGenerator: Random
|
||||
private val startTime = System.currentTimeMillis()
|
||||
private var saveId = 0
|
||||
private var firstOpen: FirstOpen = FirstOpen.Unknown
|
||||
|
||||
var hasMines = false
|
||||
private set
|
||||
|
@ -29,21 +31,24 @@ class LevelFacade {
|
|||
var mines: Sequence<Area> = sequenceOf()
|
||||
private set
|
||||
|
||||
constructor(minefield: Minefield, seed: Long) {
|
||||
constructor(minefield: Minefield, seed: Long, saveId: Int? = null) {
|
||||
this.minefield = minefield
|
||||
this.randomGenerator = Random(seed)
|
||||
this.seed = seed
|
||||
this.saveId = 0
|
||||
this.saveId = saveId ?: 0
|
||||
createEmptyField()
|
||||
}
|
||||
|
||||
constructor(save: Save) {
|
||||
this.minefield = save.minefield
|
||||
this.randomGenerator = Random(save.seed)
|
||||
this.saveId = save.uid
|
||||
this.seed = save.seed
|
||||
this.firstOpen = save.firstOpen
|
||||
|
||||
this.field = save.field.asSequence()
|
||||
this.mines = this.field.filter { it.hasMine }.asSequence()
|
||||
this.hasMines = this.mines.count() != 0
|
||||
this.saveId = save.uid
|
||||
}
|
||||
|
||||
private fun createEmptyField() {
|
||||
|
@ -55,6 +60,8 @@ class LevelFacade {
|
|||
val xPosition = (index % width)
|
||||
Area(index, xPosition, yPosition)
|
||||
}.asSequence()
|
||||
this.hasMines = false
|
||||
this.mines = sequenceOf()
|
||||
}
|
||||
|
||||
fun getArea(id: Int) = field.first { it.id == id }
|
||||
|
@ -108,6 +115,7 @@ class LevelFacade {
|
|||
}
|
||||
}
|
||||
|
||||
firstOpen = FirstOpen.Position(safeIndex)
|
||||
field.filterNot { it.safeZone }
|
||||
.toSet()
|
||||
.shuffled(randomGenerator)
|
||||
|
@ -350,6 +358,7 @@ class LevelFacade {
|
|||
duration,
|
||||
minefield,
|
||||
difficulty,
|
||||
firstOpen,
|
||||
saveStatus,
|
||||
field.toList()
|
||||
)
|
||||
|
|
|
@ -5,6 +5,7 @@ import androidx.room.RoomDatabase
|
|||
import androidx.room.TypeConverters
|
||||
import dev.lucasnlm.antimine.common.level.database.converters.DifficultyConverter
|
||||
import dev.lucasnlm.antimine.common.level.database.converters.FieldConverter
|
||||
import dev.lucasnlm.antimine.common.level.database.converters.FirstOpenConverter
|
||||
import dev.lucasnlm.antimine.common.level.database.converters.MinefieldConverter
|
||||
import dev.lucasnlm.antimine.common.level.database.converters.SaveStatusConverter
|
||||
import dev.lucasnlm.antimine.common.level.database.dao.SaveDao
|
||||
|
@ -16,13 +17,15 @@ import dev.lucasnlm.antimine.common.level.database.models.Stats
|
|||
entities = [
|
||||
Save::class,
|
||||
Stats::class
|
||||
], version = 3, exportSchema = false
|
||||
],
|
||||
version = 4, exportSchema = false
|
||||
)
|
||||
@TypeConverters(
|
||||
FieldConverter::class,
|
||||
SaveStatusConverter::class,
|
||||
MinefieldConverter::class,
|
||||
DifficultyConverter::class
|
||||
DifficultyConverter::class,
|
||||
FirstOpenConverter::class
|
||||
)
|
||||
abstract class AppDataBase : RoomDatabase() {
|
||||
abstract fun saveDao(): SaveDao
|
||||
|
|
|
@ -4,7 +4,6 @@ import androidx.room.TypeConverter
|
|||
import dev.lucasnlm.antimine.common.level.models.Difficulty
|
||||
|
||||
class DifficultyConverter {
|
||||
|
||||
@TypeConverter
|
||||
fun toDifficulty(difficulty: Int): Difficulty =
|
||||
when (difficulty) {
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
package dev.lucasnlm.antimine.common.level.database.converters
|
||||
|
||||
import androidx.room.TypeConverter
|
||||
import dev.lucasnlm.antimine.common.level.database.models.FirstOpen
|
||||
|
||||
class FirstOpenConverter {
|
||||
@TypeConverter
|
||||
fun toFirstOpen(value: Int): FirstOpen = when {
|
||||
(value < 0) -> FirstOpen.Unknown
|
||||
else -> FirstOpen.Position(value)
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun toInteger(firstOpen: FirstOpen): Int = firstOpen.toInt()
|
||||
}
|
|
@ -9,7 +9,8 @@ import dev.lucasnlm.antimine.common.level.models.Minefield
|
|||
class MinefieldConverter {
|
||||
private val moshi: Moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
|
||||
private val jsonAdapter: JsonAdapter<Minefield> = moshi.adapter(
|
||||
Minefield::class.java)
|
||||
Minefield::class.java
|
||||
)
|
||||
|
||||
@TypeConverter
|
||||
fun toMinefield(jsonInput: String): Minefield =
|
||||
|
|
|
@ -30,6 +30,6 @@ interface SaveDao {
|
|||
@Delete
|
||||
suspend fun delete(save: Save)
|
||||
|
||||
@Query("DELETE FROM save WHERE uid NOT IN (SELECT uid FROM save LIMIT :maxStorage)")
|
||||
@Query("DELETE FROM save WHERE uid NOT IN (SELECT uid FROM save WHERE status != 1 LIMIT :maxStorage)")
|
||||
suspend fun deleteOldSaves(maxStorage: Int)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
package dev.lucasnlm.antimine.common.level.database.models
|
||||
|
||||
/**
|
||||
* Used to define the position of the first open square.
|
||||
*/
|
||||
sealed class FirstOpen {
|
||||
/**
|
||||
* Used before the first step or before this value be recorded.
|
||||
*/
|
||||
object Unknown : FirstOpen()
|
||||
|
||||
/**
|
||||
* Describes the [value] of the first step.
|
||||
*/
|
||||
class Position(
|
||||
val value: Int
|
||||
) : FirstOpen()
|
||||
|
||||
override fun toString(): String = when (this) {
|
||||
is Position -> value.toString()
|
||||
else -> "Unknown"
|
||||
}
|
||||
|
||||
fun toInt(): Int = when (this) {
|
||||
is Position -> value
|
||||
else -> -1
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ import dev.lucasnlm.antimine.common.level.models.Area
|
|||
import dev.lucasnlm.antimine.common.level.models.Difficulty
|
||||
import dev.lucasnlm.antimine.common.level.models.Minefield
|
||||
import dev.lucasnlm.antimine.common.level.database.converters.FieldConverter
|
||||
import dev.lucasnlm.antimine.common.level.database.converters.FirstOpenConverter
|
||||
import dev.lucasnlm.antimine.common.level.database.converters.SaveStatusConverter
|
||||
|
||||
@Entity
|
||||
|
@ -30,6 +31,10 @@ data class Save(
|
|||
@ColumnInfo(name = "difficulty")
|
||||
val difficulty: Difficulty,
|
||||
|
||||
@TypeConverters(FirstOpenConverter::class)
|
||||
@ColumnInfo(name = "firstOpen")
|
||||
val firstOpen: FirstOpen,
|
||||
|
||||
@TypeConverters(SaveStatusConverter::class)
|
||||
@ColumnInfo(name = "status")
|
||||
val status: SaveStatus,
|
||||
|
|
|
@ -30,12 +30,15 @@ open class Clock {
|
|||
fun start(onTick: (seconds: Long) -> Unit) {
|
||||
stop()
|
||||
timer = provideTimer().apply {
|
||||
scheduleAtFixedRate(object : TimerTask() {
|
||||
override fun run() {
|
||||
elapsedTimeSeconds++
|
||||
onTick(elapsedTimeSeconds)
|
||||
}
|
||||
}, 1000L, 1000L)
|
||||
scheduleAtFixedRate(
|
||||
object : TimerTask() {
|
||||
override fun run() {
|
||||
elapsedTimeSeconds++
|
||||
onTick(elapsedTimeSeconds)
|
||||
}
|
||||
},
|
||||
1000L, 1000L
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -103,11 +103,12 @@ class AreaView : View {
|
|||
area.hasMine -> IMPORTANT_FOR_ACCESSIBILITY_YES
|
||||
area.mistake -> IMPORTANT_FOR_ACCESSIBILITY_YES
|
||||
area.mark.isNotNone() -> IMPORTANT_FOR_ACCESSIBILITY_YES
|
||||
!area.isCovered -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
|
||||
} else {
|
||||
IMPORTANT_FOR_ACCESSIBILITY_NO
|
||||
}
|
||||
!area.isCovered ->
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
|
||||
} else {
|
||||
IMPORTANT_FOR_ACCESSIBILITY_NO
|
||||
}
|
||||
else -> IMPORTANT_FOR_ACCESSIBILITY_YES
|
||||
}
|
||||
)
|
||||
|
|
|
@ -5,6 +5,7 @@ import android.os.Handler
|
|||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import dev.lucasnlm.antimine.common.level.LevelFacade
|
||||
import dev.lucasnlm.antimine.common.level.database.models.FirstOpen
|
||||
import dev.lucasnlm.antimine.common.level.models.Area
|
||||
import dev.lucasnlm.antimine.common.level.models.Difficulty
|
||||
import dev.lucasnlm.antimine.common.level.models.Event
|
||||
|
@ -48,6 +49,7 @@ class GameViewModel(
|
|||
val mineCount = MutableLiveData<Int>()
|
||||
val difficulty = MutableLiveData<Difficulty>()
|
||||
val levelSetup = MutableLiveData<Minefield>()
|
||||
val saveId = MutableLiveData<Long>()
|
||||
|
||||
fun startNewGame(newDifficulty: Difficulty = currentDifficulty): Minefield {
|
||||
clock.reset()
|
||||
|
@ -96,14 +98,49 @@ class GameViewModel(
|
|||
else -> eventObserver.postValue(Event.ResumeGame)
|
||||
}
|
||||
|
||||
saveId.postValue(save.uid.toLong())
|
||||
analyticsManager.sentEvent(Analytics.ResumePreviousGame())
|
||||
return setup
|
||||
}
|
||||
|
||||
private fun retryGame(save: Save): Minefield {
|
||||
clock.reset()
|
||||
elapsedTimeSeconds.postValue(0L)
|
||||
currentDifficulty = save.difficulty
|
||||
|
||||
val setup = save.minefield
|
||||
levelFacade = LevelFacade(setup, save.seed, save.uid).apply {
|
||||
if (save.firstOpen is FirstOpen.Position) {
|
||||
plantMinesExcept(save.firstOpen.value, true)
|
||||
singleClick(save.firstOpen.value)
|
||||
}
|
||||
}
|
||||
|
||||
mineCount.postValue(setup.mines)
|
||||
difficulty.postValue(save.difficulty)
|
||||
levelSetup.postValue(setup)
|
||||
refreshAll()
|
||||
|
||||
eventObserver.postValue(Event.StartNewGame)
|
||||
|
||||
analyticsManager.sentEvent(
|
||||
Analytics.RetryGame(
|
||||
setup, currentDifficulty,
|
||||
levelFacade.seed,
|
||||
useAccessibilityMode(),
|
||||
save.firstOpen.toInt()
|
||||
)
|
||||
)
|
||||
|
||||
saveId.postValue(save.uid.toLong())
|
||||
return setup
|
||||
}
|
||||
|
||||
suspend fun loadGame(uid: Int): Minefield = withContext(Dispatchers.IO) {
|
||||
val lastGame = savesRepository.loadFromId(uid)
|
||||
|
||||
if (lastGame != null) {
|
||||
saveId.postValue(uid.toLong())
|
||||
currentDifficulty = lastGame.difficulty
|
||||
resumeGameFromSave(lastGame)
|
||||
} else {
|
||||
|
@ -115,10 +152,27 @@ class GameViewModel(
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun retryGame(uid: Int): Minefield = withContext(Dispatchers.IO) {
|
||||
val save = savesRepository.loadFromId(uid)
|
||||
|
||||
if (save != null) {
|
||||
saveId.postValue(uid.toLong())
|
||||
currentDifficulty = save.difficulty
|
||||
retryGame(save)
|
||||
} else {
|
||||
// Fail to load
|
||||
startNewGame()
|
||||
}.also {
|
||||
initialized = true
|
||||
oldGame = true
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun loadLastGame(): Minefield = withContext(Dispatchers.IO) {
|
||||
val lastGame = savesRepository.fetchCurrentSave()
|
||||
|
||||
if (lastGame != null) {
|
||||
saveId.postValue(lastGame.uid.toLong())
|
||||
currentDifficulty = lastGame.difficulty
|
||||
resumeGameFromSave(lastGame)
|
||||
} else {
|
||||
|
@ -144,6 +198,7 @@ class GameViewModel(
|
|||
val id = savesRepository.saveGame(
|
||||
levelFacade.getSaveState(elapsedTimeSeconds.value ?: 0L, currentDifficulty)
|
||||
)
|
||||
saveId.postValue(id)
|
||||
levelFacade.setCurrentSaveId(id?.toInt() ?: 0)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -253,7 +253,8 @@ class FixedGridLayoutManager(
|
|||
for (offset in 0 until it.size()) {
|
||||
// Look for off-screen removals that are less-than this
|
||||
if (removedPositions.valueAt(offset) ==
|
||||
REMOVE_INVISIBLE && removedPositions.keyAt(offset) < nextPosition) {
|
||||
REMOVE_INVISIBLE && removedPositions.keyAt(offset) < nextPosition
|
||||
) {
|
||||
// Offset position to match
|
||||
offsetPosition--
|
||||
}
|
||||
|
|
|
@ -10,38 +10,59 @@ sealed class Analytics(
|
|||
) {
|
||||
class Open : Analytics("Open game")
|
||||
|
||||
class NewGame(minefield: Minefield, difficulty: Difficulty, seed: Long, useAccessibilityMode: Boolean) :
|
||||
Analytics("New Game", mapOf(
|
||||
class NewGame(
|
||||
minefield: Minefield,
|
||||
difficulty: Difficulty,
|
||||
seed: Long,
|
||||
useAccessibilityMode: Boolean
|
||||
) : Analytics(
|
||||
"New Game",
|
||||
mapOf(
|
||||
"Seed" to seed.toString(),
|
||||
"Difficulty Preset" to difficulty.text,
|
||||
"Width" to minefield.width.toString(),
|
||||
"Height" to minefield.height.toString(),
|
||||
"Mines" to minefield.mines.toString(),
|
||||
"Accessibility" to useAccessibilityMode.toString()
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
class RetryGame(
|
||||
minefield: Minefield,
|
||||
difficulty: Difficulty,
|
||||
seed: Long,
|
||||
useAccessibilityMode: Boolean,
|
||||
firstOpen: Int
|
||||
) : Analytics(
|
||||
"Retry Game",
|
||||
mapOf(
|
||||
"Seed" to seed.toString(),
|
||||
"Difficulty Preset" to difficulty.text,
|
||||
"Width" to minefield.width.toString(),
|
||||
"Height" to minefield.height.toString(),
|
||||
"Mines" to minefield.mines.toString(),
|
||||
"Accessibility" to useAccessibilityMode.toString(),
|
||||
"First Open" to firstOpen.toString()
|
||||
)
|
||||
)
|
||||
|
||||
class ResumePreviousGame : Analytics("Resume previous game")
|
||||
|
||||
class LongPressArea(index: Int) : Analytics("Long press area",
|
||||
mapOf("Index" to index.toString())
|
||||
)
|
||||
class LongPressArea(index: Int) : Analytics("Long press area", mapOf("Index" to index.toString()))
|
||||
|
||||
class LongPressMultipleArea(index: Int) : Analytics("Long press to open multiple",
|
||||
mapOf("Index" to index.toString())
|
||||
)
|
||||
class LongPressMultipleArea(index: Int) :
|
||||
Analytics("Long press to open multiple", mapOf("Index" to index.toString()))
|
||||
|
||||
class PressArea(index: Int) : Analytics("Press area",
|
||||
mapOf("Index" to index.toString())
|
||||
)
|
||||
class PressArea(index: Int) : Analytics("Press area", mapOf("Index" to index.toString()))
|
||||
|
||||
class GameOver(time: Long, score: Score) : Analytics("Game Over",
|
||||
class GameOver(time: Long, score: Score) : Analytics(
|
||||
"Game Over",
|
||||
mapOf(
|
||||
"Time" to time.toString(),
|
||||
"Right Mines" to score.rightMines.toString(),
|
||||
"Total Mines" to score.totalMines.toString(),
|
||||
"Total Area" to score.totalArea.toString()
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
class Victory(time: Long, score: Score, difficulty: Difficulty) : Analytics(
|
||||
|
@ -71,17 +92,9 @@ sealed class Analytics(
|
|||
|
||||
class OpenSaveHistory : Analytics("Open Save History")
|
||||
|
||||
class ShowRatingRequest(usages: Int) : Analytics("Shown Rating Request",
|
||||
mapOf(
|
||||
"Usages" to usages.toString()
|
||||
))
|
||||
class ShowRatingRequest(usages: Int) : Analytics("Shown Rating Request", mapOf("Usages" to usages.toString()))
|
||||
|
||||
class TapRatingRequest(from: String) : Analytics("Rating Request",
|
||||
mapOf(
|
||||
"From" to from
|
||||
))
|
||||
class TapRatingRequest(from: String) : Analytics("Rating Request", mapOf("From" to from))
|
||||
|
||||
class TapGameReset(resign: Boolean) : Analytics("Game reset",
|
||||
mapOf("Resign" to resign.toString())
|
||||
)
|
||||
class TapGameReset(resign: Boolean) : Analytics("Game reset", mapOf("Resign" to resign.toString()))
|
||||
}
|
||||
|
|
|
@ -6,6 +6,10 @@
|
|||
<color name="primary">#212121</color>
|
||||
<color name="primary_dark">#212121</color>
|
||||
|
||||
<color name="victory">#FFFFFF</color>
|
||||
<color name="ongoing">#616161</color>
|
||||
<color name="lose">#616161</color>
|
||||
|
||||
<color name="mines_around_1">#d5d2cc</color>
|
||||
<color name="mines_around_2">#d5d2cc</color>
|
||||
<color name="mines_around_3">#d5d2cc</color>
|
||||
|
|
|
@ -6,6 +6,10 @@
|
|||
<color name="primary">#FFFFFF</color>
|
||||
<color name="primary_dark">#9E9E9E</color>
|
||||
|
||||
<color name="victory">#4CAF50</color>
|
||||
<color name="ongoing">#455a64</color>
|
||||
<color name="lose">#F44336</color>
|
||||
|
||||
<color name="mines_around_1">#527F8D</color>
|
||||
<color name="mines_around_2">#2B8D43</color>
|
||||
<color name="mines_around_3">#E65100</color>
|
||||
|
|
|
@ -305,7 +305,8 @@ class LevelFacadeTest {
|
|||
1, 1, 1, 1, 1,
|
||||
1, 1, 0, 1, 1,
|
||||
1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1),
|
||||
1, 1, 1, 1, 1
|
||||
),
|
||||
field.map { if (it.isCovered) 1 else 0 }.toList()
|
||||
)
|
||||
openNeighbors(12)
|
||||
|
@ -315,7 +316,8 @@ class LevelFacadeTest {
|
|||
1, 0, 0, 0, 1,
|
||||
1, 0, 0, 0, 1,
|
||||
1, 0, 0, 0, 1,
|
||||
1, 1, 1, 1, 1),
|
||||
1, 1, 1, 1, 1
|
||||
),
|
||||
field.map { if (it.isCovered) 1 else 0 }.toList()
|
||||
)
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
<uses-feature android:name="android.hardware.type.watch" />
|
||||
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
|
||||
<application
|
||||
android:name="dev.lucasnlm.antimine.wear.MainApplication"
|
||||
|
|
|
@ -12,7 +12,6 @@ import dev.lucasnlm.antimine.common.level.models.Status
|
|||
import dev.lucasnlm.antimine.common.level.utils.Clock
|
||||
import dev.lucasnlm.antimine.common.level.viewmodel.GameViewModel
|
||||
import dev.lucasnlm.antimine.common.level.viewmodel.GameViewModelFactory
|
||||
import dev.lucasnlm.antimine.core.preferences.IPreferencesRepository
|
||||
import kotlinx.android.synthetic.main.activity_level.*
|
||||
import javax.inject.Inject
|
||||
import android.text.format.DateFormat
|
||||
|
@ -33,9 +32,6 @@ class WatchGameActivity : DaggerAppCompatActivity(), AmbientModeSupport.AmbientC
|
|||
@Inject
|
||||
lateinit var viewModelFactory: GameViewModelFactory
|
||||
|
||||
@Inject
|
||||
lateinit var preferencesRepository: IPreferencesRepository
|
||||
|
||||
private lateinit var viewModel: GameViewModel
|
||||
private lateinit var ambientController: AmbientModeSupport.AmbientController
|
||||
|
||||
|
@ -138,20 +134,32 @@ class WatchGameActivity : DaggerAppCompatActivity(), AmbientModeSupport.AmbientC
|
|||
}
|
||||
|
||||
private fun bindViewModel() = viewModel.apply {
|
||||
eventObserver.observe(this@WatchGameActivity, Observer {
|
||||
onGameEvent(it)
|
||||
})
|
||||
elapsedTimeSeconds.observe(this@WatchGameActivity, Observer {
|
||||
// Nothing
|
||||
})
|
||||
mineCount.observe(this@WatchGameActivity, Observer {
|
||||
if (it > 0) {
|
||||
messageText.text = applicationContext.getString(R.string.mines_remaining, it)
|
||||
eventObserver.observe(
|
||||
this@WatchGameActivity,
|
||||
Observer {
|
||||
onGameEvent(it)
|
||||
}
|
||||
})
|
||||
difficulty.observe(this@WatchGameActivity, Observer {
|
||||
// Nothing
|
||||
})
|
||||
)
|
||||
elapsedTimeSeconds.observe(
|
||||
this@WatchGameActivity,
|
||||
Observer {
|
||||
// Nothing
|
||||
}
|
||||
)
|
||||
mineCount.observe(
|
||||
this@WatchGameActivity,
|
||||
Observer {
|
||||
if (it > 0) {
|
||||
messageText.text = applicationContext.getString(R.string.mines_remaining, it)
|
||||
}
|
||||
}
|
||||
)
|
||||
difficulty.observe(
|
||||
this@WatchGameActivity,
|
||||
Observer {
|
||||
// Nothing
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun onGameEvent(event: Event) {
|
||||
|
@ -194,17 +202,21 @@ class WatchGameActivity : DaggerAppCompatActivity(), AmbientModeSupport.AmbientC
|
|||
}
|
||||
|
||||
private fun waitAndShowNewGameButton(wait: Long = DateUtils.SECOND_IN_MILLIS) {
|
||||
HandlerCompat.postDelayed(Handler(), {
|
||||
if (this.status is Status.Over && !isFinishing) {
|
||||
newGame.visibility = View.VISIBLE
|
||||
newGame.setOnClickListener {
|
||||
it.visibility = View.GONE
|
||||
GlobalScope.launch {
|
||||
viewModel.startNewGame()
|
||||
HandlerCompat.postDelayed(
|
||||
Handler(),
|
||||
{
|
||||
if (this.status is Status.Over && !isFinishing) {
|
||||
newGame.visibility = View.VISIBLE
|
||||
newGame.setOnClickListener {
|
||||
it.visibility = View.GONE
|
||||
GlobalScope.launch {
|
||||
viewModel.startNewGame()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, null, wait)
|
||||
},
|
||||
null, wait
|
||||
)
|
||||
}
|
||||
|
||||
override fun getAmbientCallback(): AmbientCallback = ambientMode
|
||||
|
|
|
@ -78,27 +78,39 @@ class WatchLevelFragment : DaggerFragment() {
|
|||
}
|
||||
|
||||
viewModel.run {
|
||||
field.observe(viewLifecycleOwner, Observer {
|
||||
areaAdapter.bindField(it)
|
||||
})
|
||||
levelSetup.observe(viewLifecycleOwner, Observer {
|
||||
recyclerGrid.layoutManager =
|
||||
GridLayoutManager(activity, it.width, RecyclerView.VERTICAL, false)
|
||||
})
|
||||
fieldRefresh.observe(viewLifecycleOwner, Observer {
|
||||
areaAdapter.notifyItemChanged(it)
|
||||
})
|
||||
eventObserver.observe(viewLifecycleOwner, Observer {
|
||||
if (it == Event.StartNewGame) {
|
||||
recyclerGrid.scrollToPosition(areaAdapter.itemCount / 2)
|
||||
field.observe(
|
||||
viewLifecycleOwner,
|
||||
Observer {
|
||||
areaAdapter.bindField(it)
|
||||
}
|
||||
)
|
||||
levelSetup.observe(
|
||||
viewLifecycleOwner,
|
||||
Observer {
|
||||
recyclerGrid.layoutManager =
|
||||
GridLayoutManager(activity, it.width, RecyclerView.VERTICAL, false)
|
||||
}
|
||||
)
|
||||
fieldRefresh.observe(
|
||||
viewLifecycleOwner,
|
||||
Observer {
|
||||
areaAdapter.notifyItemChanged(it)
|
||||
}
|
||||
)
|
||||
eventObserver.observe(
|
||||
viewLifecycleOwner,
|
||||
Observer {
|
||||
if (it == Event.StartNewGame) {
|
||||
recyclerGrid.scrollToPosition(areaAdapter.itemCount / 2)
|
||||
}
|
||||
|
||||
when (it) {
|
||||
Event.ResumeGameOver, Event.GameOver,
|
||||
Event.Victory, Event.ResumeVictory -> areaAdapter.setClickEnabled(false)
|
||||
else -> areaAdapter.setClickEnabled(true)
|
||||
when (it) {
|
||||
Event.ResumeGameOver, Event.GameOver,
|
||||
Event.Victory, Event.ResumeVictory -> areaAdapter.setClickEnabled(false)
|
||||
else -> areaAdapter.setClickEnabled(true)
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue