Skip to content

Instantly share code, notes, and snippets.

@ardakazanci
Created July 7, 2025 16:31
Show Gist options
  • Save ardakazanci/425a364f26a72b4e087e1336bd537f11 to your computer and use it in GitHub Desktop.
Save ardakazanci/425a364f26a72b4e087e1336bd537f11 to your computer and use it in GitHub Desktop.
RockPaperScissors Jetpack Compose
@Stable
data class Particle(
val id: Int, // stable key
val type: MutableState<RPS>,
val x: MutableState<Float>,
val y: MutableState<Float>,
val dx: MutableState<Float>,
val dy: MutableState<Float>
)
@Composable
fun RockPaperScissorsGame(modifier: Modifier) {
val particleSizeDp = 48.dp
val particleSizePx = with(LocalDensity.current) { particleSizeDp.toPx() }
val rockPainter = painterResource(id = R.drawable.stone)
val paperPainter = painterResource(id = R.drawable.paper)
val scissorsPainter = painterResource(id = R.drawable.scissors)
val particles = remember {
mutableStateListOf<Particle>().apply {
repeat(15) {
add(randomParticle(RPS.ROCK, it))
}
repeat(15) {
add(randomParticle(RPS.PAPER, 100 + it))
}
repeat(15) {
add(randomParticle(RPS.SCISSORS, 200 + it))
}
}
}
var gameWidth by remember { mutableStateOf(0f) }
var gameHeight by remember { mutableStateOf(0f) }
LaunchedEffect(Unit) {
while (true) {
particles.forEach { p ->
p.x.value += p.dx.value
p.y.value += p.dy.value
// impact effect
if (p.x.value < 0) {
p.x.value = 0f
p.dx.value *= -1
}
if (p.x.value > gameWidth - particleSizePx) {
p.x.value = gameWidth - particleSizePx
p.dx.value *= -1
}
if (p.y.value < 0) {
p.y.value = 0f
p.dy.value *= -1
}
if (p.y.value > gameHeight - particleSizePx) {
p.y.value = gameHeight - particleSizePx
p.dy.value *= -1
}
}
// Collision
particles.forEach { p1 ->
particles.forEach { p2 ->
if (p1 != p2 &&
distance(p1.x.value, p1.y.value, p2.x.value, p2.y.value) < particleSizePx
) {
val winner = winner(p1.type.value, p2.type.value)
if (winner != null) {
if (winner == p1.type.value) p2.type.value = winner
else p1.type.value = winner
}
}
}
}
delay(16)
}
}
Column {
Row(
modifier
.fillMaxWidth()
.height(56.dp),
horizontalArrangement = Arrangement.SpaceEvenly
) {
Text(
"Rock: ${particles.count { it.type.value == RPS.ROCK }}",
fontSize = 24.sp
)
Text(
"Paper: ${particles.count { it.type.value == RPS.PAPER }}",
fontSize = 24.sp
)
Text(
"Scissors: ${particles.count { it.type.value == RPS.SCISSORS }}",
fontSize = 24.sp
)
}
Box(
Modifier
.fillMaxSize()
.onGloballyPositioned {
gameWidth = it.size.width.toFloat()
gameHeight = it.size.height.toFloat()
}
) {
particles.forEach { p ->
val painter = when (p.type.value) {
RPS.ROCK -> rockPainter
RPS.PAPER -> paperPainter
RPS.SCISSORS -> scissorsPainter
}
Image(
painter = painter,
contentDescription = null,
modifier = Modifier
.size(particleSizeDp)
.offset {
IntOffset(
p.x.value.toInt(),
p.y.value.toInt()
)
}
)
}
}
}
}
// The winning rate of scissors will be very high :)
fun randomParticle(type: RPS, id: Int): Particle {
val (xRange, yRange) = when (type) {
RPS.ROCK -> {
// Bottom Left random position
(50..300) to (800..1100)
}
RPS.PAPER -> {
// Top Right r-p
(800..1100) to (50..300)
}
RPS.SCISSORS -> {
// Bottom Right r-p
(800..1100) to (800..1100)
}
}
return Particle(
id = id,
type = mutableStateOf(type),
x = mutableFloatStateOf(xRange.random().toFloat()),
y = mutableFloatStateOf(yRange.random().toFloat()),
dx = mutableFloatStateOf(listOf(-4f, -3f, 3f, 4f).random()),
dy = mutableFloatStateOf(listOf(-4f, -3f, 3f, 4f).random())
)
}
fun winner(a: RPS, b: RPS): RPS? {
return when {
a == b -> null
a == RPS.ROCK && b == RPS.SCISSORS -> a
a == RPS.SCISSORS && b == RPS.PAPER -> a
a == RPS.PAPER && b == RPS.ROCK -> a
b == RPS.ROCK && a == RPS.SCISSORS -> b
b == RPS.SCISSORS && a == RPS.PAPER -> b
b == RPS.PAPER && a == RPS.ROCK -> b
else -> null
}
}
fun distance(x1: Float, y1: Float, x2: Float, y2: Float): Float {
return sqrt((x1 - x2).pow(2) + (y1 - y2).pow(2))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment