@Stable data class Particle( val id: Int, // stable key val type: MutableState, val x: MutableState, val y: MutableState, val dx: MutableState, val dy: MutableState ) @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().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)) }