Skip to content

Commit dc10d3f

Browse files
[Release:Part I] New gamemode - Retina
Implemented a new game which is a two-player reflex test.
1 parent 73a6f6b commit dc10d3f

24 files changed

+681
-57
lines changed

README.md

Lines changed: 1 addition & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,43 +4,7 @@
44

55
# fOX - TicTacToe
66

7-
A minimally functioning TicTacToe Android application!
8-
9-
This app is made for learning purposes only and shouldn't be taken as ground truth for best development practices.
10-
11-
## fOX v1 - core application
12-
13-
[Commit 96690](https://github.com/simplicity-load/fOX/tree/9669077e547074db5b34dfe1ea493071a7eb3fcc)
14-
15-
The TicTacToe logic and the UI that supports the full extent of the game.
16-
17-
This includes the TicTacToe class, the MainActivity class and the main layout file.
18-
19-
## fOX v1.1 - fragments and navigation
20-
21-
[Commit c2d8b](https://github.com/simplicity-load/fOX/tree/c2d8b4a4370c5e7563c8a40fda5df6840872459c)
22-
23-
This update splits the main activity into fragments and enables navigation between two fragments, the Start screen and the Game screen.
24-
25-
### Screenshots
26-
27-
<img src="./imgs/start_screen.png" alt="Start Screen" width="30%"/><img src="./imgs/game_screen.png" alt="Game Screen" width="30%"/><img src="./imgs/gamewon_screen.png" alt="Game Won Screen" width="30%"/>
28-
29-
## fOX v1.2 - safeArgs
30-
31-
[Commit 1986c](https://github.com/simplicity-load/fOX/tree/1986c5b20bad9701c39bcb5bbda3d2c5dfc46066)
32-
33-
Added two fragments (GameDrawFragments and GameWonFragment) and sent safe arguments to one of them.
34-
35-
### Screenshots
36-
37-
<img src="./imgs/gamewon_v1.2_screen.png" alt="Game Won Screen" width="30%"/><img src="./imgs/gamedraw_v1.2_screen.png" alt="Game Draw Screen" width="30%"/>
38-
39-
## fOX v1.2.1 - Data binding improvement
40-
41-
Moved the data binding onClick assignments to the layout file.
42-
43-
[Commit latest](https://github.com/simplicity-load/fOX)
7+
My collection of games in one program. Continue this on next commit.
448

459
# Licensing
4610

app/build.gradle

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
plugins {
22
id 'com.android.application'
33
id 'org.jetbrains.kotlin.android'
4+
id 'androidx.navigation.safeargs.kotlin'
45
}
56

6-
apply plugin: 'androidx.navigation.safeargs'
7-
87
android {
98
namespace 'com.fovsol.tictactoe'
109
compileSdk 33
@@ -43,9 +42,17 @@ dependencies {
4342
implementation 'androidx.appcompat:appcompat:1.5.1'
4443
implementation 'com.google.android.material:material:1.7.0'
4544
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
45+
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
46+
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.0'
47+
implementation 'androidx.core:core-ktx:1.9.0'
48+
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'
4649
testImplementation 'junit:junit:4.13.2'
4750
androidTestImplementation 'androidx.test.ext:junit:1.1.4'
4851
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
4952
implementation "androidx.navigation:navigation-fragment-ktx:$navigationVersion"
5053
implementation "androidx.navigation:navigation-ui-ktx:$navigationVersion"
54+
55+
// ViewModel
56+
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'
57+
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
5158
}

app/src/main/java/com/fovsol/tictactoe/StartFragment.kt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,13 @@ class StartFragment : Fragment() {
2222
R.layout.fragment_start, container, false
2323
)
2424

25-
binding.playButton.setOnClickListener {
26-
it.findNavController().navigate(R.id.action_startFragment_to_gameFragment)
25+
binding.apply {
26+
playFovis.setOnClickListener {
27+
it.findNavController().navigate(R.id.action_startFragment_to_gameFragment)
28+
}
29+
playRetina.setOnClickListener {
30+
it.findNavController().navigate(R.id.action_startFragment_to_reflexFragment)
31+
}
2732
}
2833

2934
return binding.root
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package com.fovsol.tictactoe.flexmp
2+
3+
import android.os.Bundle
4+
import android.view.LayoutInflater
5+
import android.view.View
6+
import android.view.ViewGroup
7+
import androidx.databinding.DataBindingUtil
8+
import androidx.fragment.app.Fragment
9+
import androidx.lifecycle.ViewModelProvider
10+
import com.fovsol.tictactoe.R
11+
import com.fovsol.tictactoe.databinding.FragmentReflexBinding
12+
13+
class ReflexFragment : Fragment() {
14+
15+
private lateinit var binding: FragmentReflexBinding
16+
private lateinit var viewModel: ReflexViewModel
17+
18+
override fun onCreateView(
19+
inflater: LayoutInflater, container: ViewGroup?,
20+
savedInstanceState: Bundle?
21+
): View? {
22+
23+
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_reflex, container, false)
24+
viewModel = ViewModelProvider(this).get(ReflexViewModel::class.java)
25+
binding.lifecycleOwner = viewLifecycleOwner
26+
binding.viewModel = viewModel
27+
28+
viewModel.topPlayerTurn.observe(viewLifecycleOwner) {
29+
binding.apply {
30+
viewModel?.let { vm ->
31+
if (!vm.bottomPlayerPenalty.value!! && !vm.topPlayerPenalty.value!!) {
32+
if (it and vm.gameStarted) {
33+
topField.setImageResource(R.drawable.field_green)
34+
bottomField.setImageResource(R.drawable.field_gray)
35+
} else if (vm.gameStarted) {
36+
topField.setImageResource(R.drawable.field_gray)
37+
bottomField.setImageResource(R.drawable.field_green)
38+
} else {
39+
topField.setImageResource(R.drawable.field_gray)
40+
bottomField.setImageResource(R.drawable.field_gray)
41+
}
42+
}
43+
}
44+
}
45+
}
46+
47+
viewModel.topPlayerPenalty.observe(viewLifecycleOwner) {
48+
binding.apply {
49+
viewModel?.let { vm ->
50+
if (vm.gameStarted)
51+
if (it) {
52+
topField.setImageResource(R.drawable.field_red)
53+
bottomField.setImageResource(R.drawable.field_gray)
54+
} else {
55+
topField.setImageResource(R.drawable.field_green)
56+
bottomField.setImageResource(R.drawable.field_gray)
57+
}
58+
}
59+
}
60+
}
61+
62+
viewModel.bottomPlayerPenalty.observe(viewLifecycleOwner) {
63+
binding.apply {
64+
viewModel?.let { vm ->
65+
if (vm.gameStarted)
66+
if (it) {
67+
topField.setImageResource(R.drawable.field_gray)
68+
bottomField.setImageResource(R.drawable.field_red)
69+
} else {
70+
topField.setImageResource(R.drawable.field_gray)
71+
bottomField.setImageResource(R.drawable.field_green)
72+
}
73+
}
74+
}
75+
}
76+
77+
return binding.root
78+
}
79+
80+
}
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
package com.fovsol.tictactoe.flexmp
2+
3+
import android.os.Handler
4+
import android.os.Looper
5+
import androidx.lifecycle.MutableLiveData
6+
import androidx.lifecycle.ViewModel
7+
import androidx.lifecycle.map
8+
9+
class ReflexViewModel : ViewModel() {
10+
11+
val topTimer = MutableLiveData<Long>()
12+
val topCumTimer = MutableLiveData<Long>()
13+
14+
val bottomTimer = MutableLiveData<Long>()
15+
val bottomCumTimer = MutableLiveData<Long>()
16+
17+
18+
private val endGameScore = MutableLiveData<Int>()
19+
val endGameScoreDisplay = endGameScore.map {
20+
it.div(1000)
21+
}
22+
23+
val penaltyScore = MutableLiveData<Long>()
24+
val penaltyScoreDisplay = penaltyScore.map {
25+
it.toFloat().div(1000)
26+
}
27+
28+
var topPlayerTurn = MutableLiveData<Boolean>()
29+
30+
var topPlayerPenalty = MutableLiveData<Boolean>()
31+
var bottomPlayerPenalty = MutableLiveData<Boolean>()
32+
33+
var gameStarted: Boolean = false
34+
var gameEnded: Boolean = false
35+
36+
private val mInterval = 10L
37+
private var mHandler: Handler? = null
38+
39+
private var mStatusChecker: Runnable = object : Runnable {
40+
override fun run() {
41+
if (topPlayerTurn.value!!) {
42+
topTimer.value = (topTimer.value)?.plus(mInterval)
43+
if (topTimer.value!! > penaltyScore.value!! && topPlayerPenalty.value!!)
44+
topPlayerPenalty.value = false
45+
} else {
46+
bottomTimer.value = (bottomTimer.value)?.plus(mInterval)
47+
if (bottomTimer.value!! > penaltyScore.value!! && bottomPlayerPenalty.value!!)
48+
bottomPlayerPenalty.value = false
49+
}
50+
if (topCumTimer.value!! + topTimer.value!! > endGameScore.value!! || bottomCumTimer.value!! + bottomTimer.value!! > endGameScore.value!!) {
51+
gameStarted = false
52+
gameEnded = true
53+
stopTimer()
54+
}
55+
if (!gameEnded)
56+
mHandler!!.postDelayed(this, mInterval)
57+
}
58+
}
59+
60+
init {
61+
endGameScore.value = 5_000
62+
penaltyScore.value = 500L
63+
restartGame()
64+
}
65+
66+
fun restartGame() {
67+
topTimer.value = 0L
68+
topCumTimer.value = 0L
69+
70+
bottomTimer.value = 0L
71+
bottomCumTimer.value = 0L
72+
73+
gameEnded = false
74+
gameStarted = false
75+
76+
topPlayerTurn.value = false
77+
topPlayerPenalty.value = false
78+
bottomPlayerPenalty.value = false
79+
80+
stopTimer()
81+
}
82+
83+
private fun startTimer() {
84+
mHandler = Handler(Looper.getMainLooper())
85+
mStatusChecker.run()
86+
}
87+
88+
private fun stopTimer() {
89+
// Update the observer on ReflexFragment class
90+
topPlayerTurn.value = !topPlayerTurn.value!!
91+
topPlayerTurn.value = !topPlayerTurn.value!!
92+
mHandler?.removeCallbacks(mStatusChecker)
93+
}
94+
95+
override fun onCleared() {
96+
super.onCleared()
97+
stopTimer()
98+
}
99+
100+
private fun resetTurn(timer: MutableLiveData<Long>, cumTimer: MutableLiveData<Long>) {
101+
cumTimer.value = (cumTimer.value)?.plus(timer.value ?: 0)
102+
timer.value = 0L
103+
}
104+
105+
fun topPlayerButton() {
106+
if (!gameEnded)
107+
if (gameStarted && !bottomPlayerPenalty.value!! && !topPlayerPenalty.value!!) {
108+
stopTimer()
109+
if (!topPlayerTurn.value!!) {
110+
resetTurn(bottomTimer, bottomCumTimer)
111+
topPlayerPenalty.value = true
112+
topPlayerTurn.value = true
113+
} else {
114+
resetTurn(topTimer, topCumTimer)
115+
topPlayerTurn.value = false
116+
}
117+
startTimer()
118+
} else {
119+
gameStarted = true
120+
if (!bottomPlayerPenalty.value!! && !topPlayerPenalty.value!!) {
121+
stopTimer()
122+
topPlayerTurn.value = false
123+
startTimer()
124+
}
125+
}
126+
}
127+
128+
fun bottomPlayerButton() {
129+
if (!gameEnded)
130+
if (gameStarted && !bottomPlayerPenalty.value!! && !topPlayerPenalty.value!!) {
131+
stopTimer()
132+
if (topPlayerTurn.value!!) {
133+
resetTurn(topTimer, topCumTimer)
134+
bottomPlayerPenalty.value = true
135+
topPlayerTurn.value = false
136+
} else {
137+
resetTurn(bottomTimer, bottomCumTimer)
138+
topPlayerTurn.value = true
139+
}
140+
startTimer()
141+
} else {
142+
gameStarted = true
143+
if (!bottomPlayerPenalty.value!! && !topPlayerPenalty.value!!) {
144+
stopTimer()
145+
topPlayerTurn.value = true
146+
startTimer()
147+
}
148+
}
149+
}
150+
151+
// Game Adjusting
152+
fun penaltyIncrease() {
153+
if (!gameStarted)
154+
penaltyScore.value = penaltyScore.value?.plus(100)
155+
}
156+
157+
fun penaltyDecrease() = penaltyScore.value?.let {
158+
if (!gameStarted && it > 100)
159+
penaltyScore.value = it.minus(100)
160+
}
161+
162+
fun endGameIncrease() {
163+
if (!gameStarted)
164+
endGameScore.value = endGameScore.value?.plus(1000)
165+
}
166+
167+
fun endGameDecrease() = endGameScore.value?.let {
168+
if (!gameStarted && it > 1000)
169+
endGameScore.value = it.minus(1000)
170+
}
171+
}

app/src/main/java/com/fovsol/tictactoe/GameDrawFragment.kt renamed to app/src/main/java/com/fovsol/tictactoe/tictactoe/GameDrawFragment.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.fovsol.tictactoe
1+
package com.fovsol.tictactoe.tictactoe
22

33
import android.os.Bundle
44
import android.view.LayoutInflater
@@ -7,6 +7,7 @@ import android.view.ViewGroup
77
import androidx.databinding.DataBindingUtil
88
import androidx.fragment.app.Fragment
99
import androidx.navigation.findNavController
10+
import com.fovsol.tictactoe.R
1011
import com.fovsol.tictactoe.databinding.FragmentGameDrawBinding
1112

1213
class GameDrawFragment : Fragment() {

app/src/main/java/com/fovsol/tictactoe/GameFragment.kt renamed to app/src/main/java/com/fovsol/tictactoe/tictactoe/GameFragment.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.fovsol.tictactoe
1+
package com.fovsol.tictactoe.tictactoe
22

33
import android.annotation.SuppressLint
44
import android.os.Bundle
@@ -9,6 +9,8 @@ import android.widget.ImageView
99
import androidx.databinding.DataBindingUtil
1010
import androidx.fragment.app.Fragment
1111
import androidx.navigation.fragment.findNavController
12+
import com.fovsol.tictactoe.R
13+
import com.fovsol.tictactoe.TicTacToe
1214
import com.fovsol.tictactoe.databinding.FragmentGameBinding
1315

1416
class GameFragment : Fragment() {

app/src/main/java/com/fovsol/tictactoe/GameWonFragment.kt renamed to app/src/main/java/com/fovsol/tictactoe/tictactoe/GameWonFragment.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.fovsol.tictactoe
1+
package com.fovsol.tictactoe.tictactoe
22

33
import android.os.Bundle
44
import android.view.LayoutInflater
@@ -7,6 +7,7 @@ import android.view.ViewGroup
77
import androidx.databinding.DataBindingUtil
88
import androidx.fragment.app.Fragment
99
import androidx.navigation.findNavController
10+
import com.fovsol.tictactoe.R
1011
import com.fovsol.tictactoe.databinding.FragmentGameWonBinding
1112

1213
class GameWonFragment : Fragment() {

0 commit comments

Comments
 (0)