Skip to content

Instantly share code, notes, and snippets.

@loloof64
Created December 7, 2021 18:57
Show Gist options
  • Save loloof64/f623b88218c030b9b74dc35c33becb97 to your computer and use it in GitHub Desktop.
Save loloof64/f623b88218c030b9b74dc35c33becb97 to your computer and use it in GitHub Desktop.
TicTacToe in Jetpack Compose Desktop (No winner check)
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