Skip to content

Commit

Permalink
Add a card view displaying info if a game was won
Browse files Browse the repository at this point in the history
  • Loading branch information
meikpiep committed Jan 29, 2024
1 parent 69ad4cc commit f5ceee8
Show file tree
Hide file tree
Showing 25 changed files with 427 additions and 76 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- When solving a game, the keypad gets swapped by a card view which states the win of a game. It
also shows if it was the first game solved ever, or the first kind been solved, or a best time
which could be made.

### Changed

- Move the popup messages of the main screen to the absolute bottom of the screen, now covering the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package org.piepmeyer.gauguin.preferences

import android.content.SharedPreferences
import androidx.core.content.edit
import org.piepmeyer.gauguin.Utils
import org.piepmeyer.gauguin.grid.Grid
import kotlin.time.Duration
import kotlin.time.Duration.Companion.ZERO
import kotlin.time.Duration.Companion.milliseconds

Expand Down Expand Up @@ -34,27 +34,31 @@ class StatisticsManagerImpl(
}
}

override fun storeStatisticsAfterFinishedGame(grid: Grid): String? {
val gridsize = grid.gridSize

// assess hint penalty - gridsize^2/2 seconds for each cell
val penalty = grid.countCheated().toLong() * 500 * gridsize.surfaceArea
grid.playTime = grid.playTime + penalty.milliseconds
override fun storeStatisticsAfterFinishedGame(grid: Grid) {
val key = getBestTimeKey(grid)
val solvetime = grid.playTime

val timestat = stats.getLong("solvedtime$gridsize", 0).milliseconds
val editor = stats.edit()
val recordTime =
if (timestat == ZERO || timestat > solvetime) {
editor.putLong("solvedtime$gridsize", solvetime.inWholeMilliseconds)
Utils.displayableGameDuration(solvetime)
val bestTime = stats.getLong(key, 0).milliseconds

if (bestTime == ZERO || bestTime > solvetime) {
stats.edit { putLong(key, solvetime.inWholeMilliseconds) }

if (bestTime == ZERO) {
grid.solvedFirstTimeOfKind = true
} else {
null
grid.solvedBestTimeOfKind = true
}
editor.apply()
return recordTime
}
}

override fun getBestTime(grid: Grid): Duration {
val key = getBestTimeKey(grid)

return stats.getLong(key, 0).milliseconds
}

private fun getBestTimeKey(grid: Grid) = "solvedtime${grid.gridSize}"

override fun storeStreak(isSolved: Boolean) {
val solvedStreak = currentStreak()
val longestStreak = longestStreak()
Expand Down Expand Up @@ -84,4 +88,22 @@ class StatisticsManagerImpl(
override fun clearStatistics() {
stats.edit { clear() }
}

override fun typeOfSolution(grid: Grid): TypeOfSolution {
if (totalSolved() == 1) {
return TypeOfSolution.FirstGame
}

if (grid.solvedFirstTimeOfKind) {
return TypeOfSolution.FirstGameOfKind
}

if (grid.solvedBestTimeOfKind) {
println("-> " + getBestTime(grid))
println(grid.playTime)
return TypeOfSolution.BestTimeOfKind
}

return TypeOfSolution.Regular
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package org.piepmeyer.gauguin.ui.main

import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import org.piepmeyer.gauguin.R
import org.piepmeyer.gauguin.Utils
import org.piepmeyer.gauguin.databinding.FragmentMainGameSolvedBinding
import org.piepmeyer.gauguin.game.Game
import org.piepmeyer.gauguin.game.GameSolvedListener
import org.piepmeyer.gauguin.game.GridCreationListener
import org.piepmeyer.gauguin.preferences.StatisticsManager
import org.piepmeyer.gauguin.preferences.TypeOfSolution
import org.piepmeyer.gauguin.ui.MainDialogs
import org.piepmeyer.gauguin.ui.StatisticsActivity

class GameSolvedFragment :
Fragment(R.layout.fragment_main_game_solved),
KoinComponent,
GameSolvedListener,
GridCreationListener {
private val game: Game by inject()
private val statisticsManager: StatisticsManager by inject()

private lateinit var binding: FragmentMainGameSolvedBinding

override fun onCreateView(
inflater: LayoutInflater,
parent: ViewGroup?,
savedInstanceState: Bundle?,
): View {
binding = FragmentMainGameSolvedBinding.inflate(inflater, parent, false)
return binding.root
}

override fun onViewCreated(
view: View,
savedInstanceState: Bundle?,
) {
binding.showStatisticsButton.setOnClickListener {
requireActivity().startActivity(
Intent(
requireActivity(),
StatisticsActivity::class.java,
),
)
}

binding.playGameWithSameConfig.setOnClickListener {
(this.activity as MainActivity).postNewGame(startedFromMainActivityWithSameVariant = true)
}

binding.playGameWithOtherConfig.setOnClickListener {
MainDialogs(this.activity as MainActivity).newGameGridDialog()
}

game.addGameSolvedHandler(this)
game.addGridCreationListener(this)

freshGridWasCreated()
}

override fun puzzleSolved(troughReveal: Boolean) {
binding.detailsIcon

if (!troughReveal) {
val icon =
when (statisticsManager.typeOfSolution(game.grid)) {
TypeOfSolution.FirstGame -> R.drawable.trophy_variant_outline
TypeOfSolution.FirstGameOfKind -> R.drawable.trophy_variant_outline
TypeOfSolution.BestTimeOfKind -> R.drawable.podium_gold
TypeOfSolution.Regular -> null
}

val text =
when (statisticsManager.typeOfSolution(game.grid)) {
TypeOfSolution.FirstGame -> getString(R.string.puzzle_solved_type_of_solution_first_game_solved)
TypeOfSolution.FirstGameOfKind -> getString(R.string.puzzle_solved_type_of_solution_first_game_of_kind_solved)
TypeOfSolution.BestTimeOfKind -> getString(R.string.puzzle_solved_type_of_solution_best_time_of_kind_solved)
TypeOfSolution.Regular ->
getString(
R.string.puzzle_solved_type_of_solution_regular_display_best_time,
Utils.displayableGameDuration(statisticsManager.getBestTime(game.grid)),
)
}

if (icon != null) {
binding.detailsIcon.setImageResource(icon)
binding.detailsIcon.visibility = View.VISIBLE
} else {
binding.detailsIcon.visibility = View.INVISIBLE
}

binding.detailsText.text = text
binding.detailsText.visibility = View.VISIBLE
} else {
binding.detailsIcon.visibility = View.INVISIBLE
binding.detailsText.visibility = View.INVISIBLE
}

binding.gameSolvedCardView.visibility = View.VISIBLE
}

override fun freshGridWasCreated() {
if (game.grid.isSolved()) {
puzzleSolved(false)
} else {
binding.gameSolvedCardView.visibility = View.GONE
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ class GameTopFragment :

private lateinit var binding: FragmentMainGameTopBinding

private var timeDescription: String? = null
private var showtimer = false

override fun onCreateView(
Expand Down Expand Up @@ -134,7 +133,7 @@ class GameTopFragment :
binding.ratingStarThree.visibility = visibilityOfStars
binding.ratingStarFour.visibility = visibilityOfStars

timeDescription?.let { binding.playtime.text = it }
binding.playtime.text = Utils.displayableGameDuration(game.grid.playTime)
}
}

Expand Down Expand Up @@ -182,12 +181,8 @@ class GameTopFragment :
}

fun setGameTime(gameDuration: Duration) {
val durationString = Utils.displayableGameDuration(gameDuration)

if (this::binding.isInitialized) {
binding.playtime.text = durationString
} else {
this.timeDescription = durationString
binding.playtime.text = Utils.displayableGameDuration(gameDuration)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,6 @@ class MainActivity : AppCompatActivity(), GridCreationListener {
private lateinit var binding: ActivityMainBinding
private lateinit var bottomAppBarService: MainBottomAppBarService

private var gameEndedSnackbar: Snackbar? = null

private lateinit var specialListener: OnSharedPreferenceChangeListener

public override fun onCreate(savedInstanceState: Bundle?) {
Expand All @@ -70,6 +68,7 @@ class MainActivity : AppCompatActivity(), GridCreationListener {
topFragment = GameTopFragment()
ft.replace(R.id.keypadFrame, KeyPadFragment())
ft.replace(R.id.fastFinishingModeFrame, FastFinishingModeFragment())
ft.replace(R.id.gameSolvedFrame, GameSolvedFragment())
ft.replace(R.id.gameTopFrame, topFragment)
ft.commit()

Expand All @@ -79,7 +78,7 @@ class MainActivity : AppCompatActivity(), GridCreationListener {
binding.gridview.invalidate()
}

game.setSolvedHandler { reveal -> gameSolved(reveal) }
game.addGameSolvedHandler { reveal -> gameSolved(reveal) }

registerForContextMenu(binding.gridview)

Expand Down Expand Up @@ -146,20 +145,12 @@ class MainActivity : AppCompatActivity(), GridCreationListener {
private fun gameSolved(reveal: Boolean) {
gameLifecycle.gameSolved()

gameEndedSnackbar = showSnackbar(getString(R.string.puzzle_solved))

bottomAppBarService.updateAppBarState()

binding.hintOrNewGame.hide()
binding.hintOrNewGame.show()

statisticsManager.storeStreak(!reveal)
topFragment.setGameTime(game.grid.playTime)

val recordTime = statisticsManager.storeStatisticsAfterFinishedGame(game.grid)

if (!reveal) {
recordTime?.let { gameEndedSnackbar = showSnackbar("${getString(R.string.puzzle_record_time)} $it") }
val konfettiView = binding.konfettiView

val emitterConfig = Emitter(8L, TimeUnit.SECONDS).perSecond(150)
Expand Down Expand Up @@ -285,10 +276,7 @@ class MainActivity : AppCompatActivity(), GridCreationListener {
MainDialogs(this).newGameGridDialog()
}

private fun postNewGame(startedFromMainActivityWithSameVariant: Boolean = false) {
gameEndedSnackbar?.dismiss()
gameEndedSnackbar = null

fun postNewGame(startedFromMainActivityWithSameVariant: Boolean = false) {
if (game.grid.isActive && game.grid.startedToBePlayed) {
statisticsManager.storeStreak(false)
}
Expand Down Expand Up @@ -352,15 +340,11 @@ class MainActivity : AppCompatActivity(), GridCreationListener {
// calculationService.calculateNextGrid(lifecycleScope);
}

fun checkProgressOrStartNewGame() {
fun checkProgress() {
if (game.grid.isSolved()) {
postNewGame(startedFromMainActivityWithSameVariant = true)
} else {
checkProgress()
return
}
}

private fun checkProgress() {
val mistakes = game.grid.numberOfMistakes()
val filled = game.grid.numberOfFilledCells()
val text = (
Expand Down Expand Up @@ -402,22 +386,13 @@ class MainActivity : AppCompatActivity(), GridCreationListener {
if (mistakes > 0 && game.undoManager.isUndoPossible()) {
snackbar.setAction(resources.getText(R.string.hint_as_toast_undo_last_step)) {
game.undoOneStep()
checkProgressOrStartNewGame()
checkProgress()
}
}

snackbar.show()
}

private fun showSnackbar(string: String): Snackbar {
val snackbar =
Snackbar.make(binding.root, string, Snackbar.LENGTH_LONG)

snackbar.show()

return snackbar
}

fun gameSaved() {
Snackbar.make(binding.root, resources.getText(R.string.main_activity_current_game_saved), Snackbar.LENGTH_LONG)
.show()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class MainBottomAppBarService(

game.undoManager = undoList

binding.hintOrNewGame.setOnClickListener { mainActivity.checkProgressOrStartNewGame() }
binding.hint.setOnClickListener { mainActivity.checkProgress() }
undoButton.setOnClickListener { game.undoOneStep() }
eraserButton?.setOnClickListener { game.eraseSelectedCell() }

Expand All @@ -55,14 +55,13 @@ class MainBottomAppBarService(

fun updateAppBarState() {
if (game.grid.isSolved()) {
binding.hintOrNewGame.isEnabled = true
binding.hintOrNewGame.setImageResource(R.drawable.outline_add_24)
binding.hint.hide()

undoButton.visibility = View.GONE
eraserButton?.visibility = View.GONE
} else {
binding.hintOrNewGame.isEnabled = true
binding.hintOrNewGame.setImageResource(R.drawable.baseline_question_mark_24)
binding.hint.isEnabled = true
binding.hint.show()

undoButton.visibility = View.VISIBLE
undoButton.isEnabled = false
Expand Down
1 change: 1 addition & 0 deletions gauguin-app/src/main/res/drawable/cog.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<!-- drawable/cog.xml --><vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24"><path android:fillColor="#000000" android:pathData="M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z" /></vector>
1 change: 1 addition & 0 deletions gauguin-app/src/main/res/drawable/flag_checkered.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<!-- drawable/flag_checkered.xml --><vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24"><path android:fillColor="#000000" android:pathData="M14.4,6H20V16H13L12.6,14H7V21H5V4H14L14.4,6M14,14H16V12H18V10H16V8H14V10L13,8V6H11V8H9V6H7V8H9V10H7V12H9V10H11V12H13V10L14,12V14M11,10V8H13V10H11M14,10H16V12H14V10Z" /></vector>
1 change: 1 addition & 0 deletions gauguin-app/src/main/res/drawable/human_handsup.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<!-- drawable/human_handsup.xml --><vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24"><path android:fillColor="#000000" android:pathData="M5,1C5,3.7 6.56,6.16 9,7.32V22H11V15H13V22H15V7.31C17.44,6.16 19,3.7 19,1H17A5,5 0 0,1 12,6A5,5 0 0,1 7,1M12,1C10.89,1 10,1.89 10,3C10,4.11 10.89,5 12,5C13.11,5 14,4.11 14,3C14,1.89 13.11,1 12,1Z" /></vector>
1 change: 1 addition & 0 deletions gauguin-app/src/main/res/drawable/play.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<!-- drawable/play.xml --><vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24"><path android:fillColor="#000000" android:pathData="M8,5.14V19.14L19,12.14L8,5.14Z" /></vector>
1 change: 1 addition & 0 deletions gauguin-app/src/main/res/drawable/podium_gold.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<!-- drawable/podium_gold.xml --><vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24"><path android:fillColor="#000000" android:pathData="M12,7.09L14.45,8.58L13.8,5.77L16,3.89L13.11,3.64L12,1L10.87,3.64L8,3.89L10.18,5.77L9.5,8.58L12,7.09M15,23H9V10H15V23M1,17V23H7V17H1M5,21H3V19H5V21M17,13V23H23V13H17M21,21H19V15H21V21Z" /></vector>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<!-- drawable/trophy_variant_outline.xml --><vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24"><path android:fillColor="#000000" android:pathData="M17 4V2H7V4H2V11C2 12.1 2.9 13 4 13H7.1C7.5 14.96 9.04 16.5 11 16.9V19.08C8 19.54 8 22 8 22H16C16 22 16 19.54 13 19.08V16.9C14.96 16.5 16.5 14.96 16.9 13H20C21.1 13 22 12.1 22 11V4H17M4 11V6H7V11L4 11M15 12C15 13.65 13.65 15 12 15S9 13.65 9 12V4H15V12M20 11L17 11V6H20L20 11Z" /></vector>
Loading

0 comments on commit f5ceee8

Please sign in to comment.