diff --git a/app/src/main/java/es/genol/tictactoe/GameChecks.kt b/app/src/main/java/es/genol/tictactoe/GameChecks.kt new file mode 100644 index 0000000..e1e0c0f --- /dev/null +++ b/app/src/main/java/es/genol/tictactoe/GameChecks.kt @@ -0,0 +1,57 @@ +package es.genol.tictactoe + +import es.genol.tictactoe.data.model.Ficha + +class GameChecks(private val player: Boolean, private val gridData: List) { + fun playerWinnerCheck(): Boolean { + if (diagonalCheck() || verticalCheck() || horizontalCheck()) return true + return false + } + + private fun diagonalCheck(): Boolean { + var result: Int + for (start in 0..2 step 2) { + var stage = 0 + when (start){ + 0 -> stage = 4 + 2 -> stage = 2 + } + result = 0 + for (gridId in start..(stage * 2 + start) step stage) { + if (gridData[gridId].player == player) { + result++ + } + if (result == 3) return true + } + } + return false + } + + private fun verticalCheck(): Boolean { + var result: Int + for (start in 0..2) { + result = 0 + for (gridId in start..start + 6 step 3) { + if (gridData[gridId].player == player) { + result++ + } + if (result == 3) return true + } + } + return false + } + + private fun horizontalCheck(): Boolean { + var result: Int + for (start in 0..6 step 3) { + result = 0 + for (gridId in start..start + 2) { + if (gridData[gridId].player == player) { + result++ + } + if (result == 3) return true + } + } + return false + } +} \ No newline at end of file diff --git a/app/src/main/java/es/genol/tictactoe/data/model/Ficha.kt b/app/src/main/java/es/genol/tictactoe/data/model/Ficha.kt index 7ba8455..f69ddc7 100644 --- a/app/src/main/java/es/genol/tictactoe/data/model/Ficha.kt +++ b/app/src/main/java/es/genol/tictactoe/data/model/Ficha.kt @@ -1,3 +1,3 @@ package es.genol.tictactoe.data.model -data class Ficha(val row: Int, val col: Int, var player: Boolean?) \ No newline at end of file +data class Ficha(var player: Boolean? = null) \ No newline at end of file diff --git a/app/src/main/java/es/genol/tictactoe/ui/elements/AppContent.kt b/app/src/main/java/es/genol/tictactoe/ui/elements/AppContent.kt index e2d287d..5b26d64 100644 --- a/app/src/main/java/es/genol/tictactoe/ui/elements/AppContent.kt +++ b/app/src/main/java/es/genol/tictactoe/ui/elements/AppContent.kt @@ -1,57 +1,30 @@ package es.genol.tictactoe.ui.elements -import android.content.res.Configuration import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material3.Button import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold -import androidx.compose.material3.SnackbarDuration -import androidx.compose.material3.SnackbarHost -import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel -import es.genol.tictactoe.ui.state.TicTacToeViewModel +import es.genol.tictactoe.ui.state.GameState import es.genol.tictactoe.ui.theme.TicTacToeTheme @OptIn(ExperimentalMaterial3Api::class) @Composable fun AppContent() { - val viewModel: TicTacToeViewModel = viewModel() - val configuration = LocalConfiguration.current - val orientationHeight: Float - val orientationWidth: Float - val snackBarState = remember { SnackbarHostState() } - - when (configuration.orientation) { - Configuration.ORIENTATION_PORTRAIT -> { - orientationHeight = .42f - orientationWidth = .65f - } - - else -> { - orientationHeight = 1f - orientationWidth = .35f - } - } - + val viewModel: GameState = viewModel() if (viewModel.isWinner) { - LaunchedEffect(key1 = viewModel.isWinner) { - viewModel.resetFromSnackBar(snackBarState.showSnackbar( - message = "Ganador", - actionLabel = "Reiniciar", - duration = SnackbarDuration.Indefinite - )) - } + } TicTacToeTheme { @@ -59,26 +32,32 @@ fun AppContent() { modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { - Scaffold(topBar = { - TopAppBar( - title = { Text(text = "TicTacToe") }, - actions = { - Button(onClick = { viewModel.boardReboot() }) { - Text(text = "REINICIAR") - } - }) - }, snackbarHost = { SnackbarHost(hostState = snackBarState) }) { - Column(Modifier.padding(it)) { + Scaffold( + topBar = { + TopAppBar( + title = { Text(text = "TicTacToe") }, + actions = { + Button(onClick = { viewModel.gridClean() }) { + Text(text = "REINICIAR") + } + }) + }, + ) { + Column( + Modifier + .padding(it) + .padding(vertical = 15.dp) + .fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) {println("Antes de gameboard") GameBoard( boardSize = 3, - orientationHeight = orientationHeight, - orientationWidth = orientationWidth, - playerValue = { row, col -> - viewModel.getPlayer(row, col) + playerValue = { gridId -> + viewModel.gridState[gridId].player }, buttonEnabled = !viewModel.isWinner - ) { row, col -> - viewModel.printPosition(row, col) + ) { gridId -> + viewModel.gridMarkPlayer(gridId) } } } diff --git a/app/src/main/java/es/genol/tictactoe/ui/elements/GameBoard.kt b/app/src/main/java/es/genol/tictactoe/ui/elements/GameBoard.kt index 43bfe8c..cc7c48c 100644 --- a/app/src/main/java/es/genol/tictactoe/ui/elements/GameBoard.kt +++ b/app/src/main/java/es/genol/tictactoe/ui/elements/GameBoard.kt @@ -3,10 +3,10 @@ package es.genol.tictactoe.ui.elements import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.Button import androidx.compose.runtime.Composable @@ -17,38 +17,37 @@ import androidx.compose.ui.unit.dp @Composable fun GameBoard( boardSize: Int, - orientationHeight: Float, - orientationWidth: Float, - playerValue: (Int, Int) -> Boolean?, + playerValue: (Int) -> Boolean?, buttonEnabled: Boolean, - onButtonClick: (Int, Int) -> Unit + onButtonClick: (Int) -> Unit ) { + var gridId = 0 + val grid = Array(boardSize) { IntArray(boardSize) { gridId++ } } + Column( - modifier = Modifier - .fillMaxWidth() - .fillMaxHeight(orientationHeight), + Modifier + .width(240.dp) + .aspectRatio(1f), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.SpaceAround ) { - if (orientationHeight == .42f) { - Spacer(modifier = Modifier.fillMaxHeight(.05f)) - } repeat(boardSize) { row -> Row( - modifier = Modifier.fillMaxWidth(orientationWidth), + Modifier + .fillMaxWidth(), horizontalArrangement = Arrangement.SpaceAround ) { repeat(boardSize) { col -> Button( - onClick = { onButtonClick(row, col) }, + onClick = { onButtonClick(grid[row][col]) }, modifier = Modifier.size(75.dp), enabled = buttonEnabled, shape = CircleShape ) { - playerValue(row, col)?.let { player -> - if (player){ + playerValue(grid[row][col])?.let { player -> + if (player) { CircleIcon() - }else if(!player){ + } else { CrossIcon() } } diff --git a/app/src/main/java/es/genol/tictactoe/ui/state/GameState.kt b/app/src/main/java/es/genol/tictactoe/ui/state/GameState.kt new file mode 100644 index 0000000..a232202 --- /dev/null +++ b/app/src/main/java/es/genol/tictactoe/ui/state/GameState.kt @@ -0,0 +1,37 @@ +package es.genol.tictactoe.ui.state + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.runtime.toMutableStateList +import androidx.lifecycle.ViewModel +import es.genol.tictactoe.GameChecks +import es.genol.tictactoe.data.model.Ficha +import kotlin.random.Random + +class GameState : ViewModel() { + private var _gridState = MutableList(9) { Ficha() }.toMutableStateList() + val gridState get() = _gridState.toList() + private var currentPlayer = ramdomPlayer() + + var isWinner by mutableStateOf(false) + private set + + fun gridMarkPlayer(gridId: Int) { + if (_gridState[gridId].player == null) { + _gridState[gridId] = _gridState[gridId].copy(player = currentPlayer) + isWinner = GameChecks(currentPlayer, gridData = gridState).playerWinnerCheck() + currentPlayer = !currentPlayer + } + } + + fun gridClean() { + repeat(9) { _gridState[it] = Ficha() } + currentPlayer = ramdomPlayer() + isWinner = false + } + + private fun ramdomPlayer() = (Random.nextBits(bitCount = 1) > 0) + +} + diff --git a/app/src/main/java/es/genol/tictactoe/ui/state/TicTacToeViewModel.kt b/app/src/main/java/es/genol/tictactoe/ui/state/TicTacToeViewModel.kt deleted file mode 100644 index 73dfec3..0000000 --- a/app/src/main/java/es/genol/tictactoe/ui/state/TicTacToeViewModel.kt +++ /dev/null @@ -1,77 +0,0 @@ -package es.genol.tictactoe.ui.state - -import androidx.compose.material3.SnackbarResult -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateListOf -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.setValue -import androidx.lifecycle.ViewModel -import es.genol.tictactoe.data.model.Ficha -import kotlin.random.Random - -class TicTacToeViewModel : ViewModel() { - private var buttonStateList = mutableStateListOf() - private var playerChange = ramdomPlayer() - - var isWinner by mutableStateOf(false) - private set - - - init { - fillBoardGame() - } - - fun printPosition(row: Int, col: Int) { - val index = buttonStateList.indexOf(buttonStateList.find { (it.row == row && it.col == col) }) - if (buttonStateList[index].player == null) { - buttonStateList[index] = buttonStateList[index].copy(player = playerChange) - isWinner = horizontalCheck(player = playerChange) - playerChange = !playerChange - } - } - - fun getPlayer(row: Int, col: Int): Boolean? { - return buttonStateList.find { (it.row == row && it.col == col) }?.player - } - - fun boardReboot() { - buttonStateList.clear() - playerChange = ramdomPlayer() - isWinner = false - fillBoardGame() - } - - private fun fillBoardGame() { - for (row in 0..2) { - for (col in 0..2) { - buttonStateList.add( - Ficha(row, col, null) - ) - } - } - } - - private fun ramdomPlayer() = (Random.nextBits(bitCount = 1) == 1) - - private fun horizontalCheck(player: Boolean): Boolean { - var result: Int - for (i in 0..6 step 3) { - result = 0 - for (n in i..i + 2) { - if (buttonStateList[n].player == player){ - result++ - } - if (result == 3) return true - } - } - return false - } - - fun resetFromSnackBar(result: SnackbarResult){ - when (result) { - SnackbarResult.Dismissed -> {} - SnackbarResult.ActionPerformed -> { boardReboot() } - } - } -} -