Created
December 7, 2021 18:57
-
-
Save loloof64/f623b88218c030b9b74dc35c33becb97 to your computer and use it in GitHub Desktop.
TicTacToe in Jetpack Compose Desktop (No winner check)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import androidx.compose.desktop.DesktopMaterialTheme | |
import androidx.compose.desktop.ui.tooling.preview.Preview | |
import androidx.compose.foundation.Canvas | |
import androidx.compose.foundation.background | |
import androidx.compose.foundation.gestures.detectTapGestures | |
import androidx.compose.foundation.layout.* | |
import androidx.compose.material.Button | |
import androidx.compose.material.Text | |
import androidx.compose.runtime.Composable | |
import androidx.compose.runtime.mutableStateOf | |
import androidx.compose.runtime.remember | |
import androidx.compose.ui.Alignment | |
import androidx.compose.ui.Modifier | |
import androidx.compose.ui.geometry.Offset | |
import androidx.compose.ui.geometry.Size | |
import androidx.compose.ui.graphics.Color | |
import androidx.compose.ui.graphics.SolidColor | |
import androidx.compose.ui.graphics.drawscope.DrawScope | |
import androidx.compose.ui.graphics.drawscope.Stroke | |
import androidx.compose.ui.platform.LocalDensity | |
import androidx.compose.ui.unit.Dp | |
import androidx.compose.ui.unit.dp | |
import androidx.compose.ui.unit.sp | |
import androidx.compose.ui.window.Window | |
import androidx.compose.ui.window.application | |
import androidx.compose.runtime.getValue | |
import androidx.compose.runtime.setValue | |
import androidx.compose.ui.input.pointer.pointerInput | |
private val gridBackgroundColor = Color(0xffE7F0E7) | |
private val gridBorderColor = Color.Black | |
private val crossColor = Color.Red | |
private val circleColor = Color.Blue | |
enum class CellValue { | |
None, Cross, Circle | |
} | |
private fun DrawScope.drawGridCell(sizePx: Float, offset: Offset, value: CellValue) { | |
val innerSizePx = sizePx * 0.8f | |
val innerShiftPx = sizePx * 0.1f | |
val innerOffset = offset + Offset(x = innerShiftPx, y = innerShiftPx) | |
drawRect( | |
brush = SolidColor(gridBorderColor), | |
topLeft = offset, | |
size = Size(width = sizePx, height = sizePx), | |
) | |
drawRect( | |
brush = SolidColor(gridBackgroundColor), | |
topLeft = innerOffset, | |
size = Size(width = innerSizePx, height = innerSizePx), | |
) | |
when (value) { | |
CellValue.Cross -> drawCellCross(sizePx, offset) | |
CellValue.Circle -> drawCellCircle(sizePx, offset) | |
else -> {} | |
} | |
} | |
private fun DrawScope.drawCellCircle(sizePx: Float, offset: Offset) { | |
val innerSizePx = sizePx * 0.8f | |
val crossSizePx = innerSizePx * 0.75f | |
val innerShiftPx = sizePx * 0.2f | |
val innerOffset = offset + Offset(x = innerShiftPx, y = innerShiftPx) | |
val center = innerOffset + Offset(x = crossSizePx * 0.5f, y = crossSizePx * 0.5f) | |
val strokeWidth = innerSizePx * 0.15f | |
drawCircle( | |
brush = SolidColor(circleColor), | |
center = center, | |
radius = crossSizePx * 0.5f, | |
style = Stroke(width = strokeWidth) | |
) | |
} | |
private fun DrawScope.drawCellCross(sizePx: Float, offset: Offset) { | |
val innerSizePx = sizePx * 0.8f | |
val crossSizePx = innerSizePx * 0.75f | |
val innerShiftPx = sizePx * 0.2f | |
val start1 = offset + Offset(x = innerShiftPx, y = innerShiftPx) | |
val end1 = start1 + Offset(x = crossSizePx, y = crossSizePx) | |
val start2 = offset + Offset(x = innerShiftPx, y = innerShiftPx + crossSizePx) | |
val end2 = start2 + Offset(x = crossSizePx, y = -crossSizePx) | |
val strokeWidth = innerSizePx * 0.15f | |
drawLine( | |
brush = SolidColor(crossColor), | |
start = start1, | |
end = end1, | |
strokeWidth = strokeWidth, | |
) | |
drawLine( | |
brush = SolidColor(crossColor), | |
start = start2, | |
end = end2, | |
strokeWidth = strokeWidth, | |
) | |
} | |
fun getGridValue(values: List<CellValue>, col: Int, row: Int) = values[col + 3*row] | |
fun setGridValue(values: List<CellValue>, col: Int, row: Int, newValue: CellValue) : List<CellValue> { | |
val valuesCopy = values.toMutableList() | |
valuesCopy[col + 3*row] = newValue | |
return valuesCopy | |
} | |
@Composable | |
fun Grid(modifier: Modifier = Modifier, size: Dp, values: List<CellValue>, clickHandler: (Int, Int) -> Unit = {_,_ -> }) { | |
val cellsSize = size / 3 | |
val cellsSizePx = with(LocalDensity.current) { | |
cellsSize.toPx() | |
} | |
Canvas(modifier = modifier.background(gridBackgroundColor).width(size).height(size) | |
.pointerInput(Unit) { | |
detectTapGestures(onTap = { | |
val col = (it.x/cellsSizePx).toInt() | |
val row = (it.y/cellsSizePx).toInt() | |
clickHandler(col, row) | |
}) | |
}) { | |
(0 until 3).map { row -> | |
(0 until 3).map { col -> | |
val offset = Offset(x = col * cellsSizePx, y = row * cellsSizePx) | |
drawGridCell( | |
offset = offset, | |
sizePx = cellsSizePx, | |
value = getGridValue(values = values, col = col, row = row) | |
) | |
} | |
} | |
} | |
} | |
@Composable | |
@Preview | |
fun App() { | |
val turnSize = 30.dp | |
val turnSizePx = with(LocalDensity.current) { | |
turnSize.toPx() | |
} | |
var turn by remember { | |
mutableStateOf(CellValue.Cross) | |
} | |
var values by remember { | |
mutableStateOf(List(9) { CellValue.None }) | |
} | |
fun toggleTurn() { | |
turn = when(turn) { | |
CellValue.Circle -> CellValue.Cross | |
CellValue.Cross -> CellValue.Circle | |
else -> CellValue.None | |
} | |
} | |
fun play(col: Int, row: Int) { | |
val currentValue = getGridValue(values = values, col = col, row = row) | |
if (currentValue != CellValue.None) return | |
values = setGridValue(values = values, col = col, row = row, newValue = turn) | |
toggleTurn() | |
} | |
fun reset() { | |
values = MutableList(9) { CellValue.None } | |
turn = CellValue.Cross | |
} | |
DesktopMaterialTheme { | |
Column( | |
modifier = Modifier.background(Color(0xff486439)).fillMaxSize(), | |
horizontalAlignment = Alignment.CenterHorizontally, | |
verticalArrangement = Arrangement.SpaceEvenly, | |
) { | |
Button(onClick = {reset()}) { | |
Text("New game") | |
} | |
Row(verticalAlignment = Alignment.CenterVertically) { | |
Text("Turn: ", color = Color.Black, fontSize = 20.sp) | |
Canvas(modifier = Modifier.size(turnSize).background(Color.Transparent)) { | |
when (turn) { | |
CellValue.Cross -> drawCellCross(sizePx = turnSizePx, offset = Offset.Zero) | |
CellValue.Circle -> drawCellCircle(sizePx = turnSizePx, offset = Offset.Zero) | |
else -> {} | |
} | |
} | |
} | |
Grid(size = 300.dp, values = values, clickHandler = { col, row -> | |
play(col = col, row = row) | |
}) | |
} | |
} | |
} | |
fun main() = application { | |
Window(onCloseRequest = ::exitApplication, title = "Tic Tac Toe") { | |
App() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment