Why is it Called "Pong"?
Gameplay:
Key Components:
1. Game Setup (Paddle, Ball, and Screen Dimensions)
2. LaunchedEffect and Game Loop
LaunchedEffect(Unit) {
while (true) {
ballPosition.value = ballPosition.value.copy(
x = ballPosition.value.x + ballVelocity.value.x,
y = ballPosition.value.y + ballVelocity.value.y
)
// other game logic...
delay(16L) // Pause for 16 ms (simulate 60 FPS)
}
}3. Ball Movement and Collisions
if (ballPosition.value.x - ballRadius <= paddleWidth &&
ballPosition.value.y in leftPaddleY.floatValue..(leftPaddleY.floatValue + paddleHeight)
) {
ballVelocity.value = ballVelocity.value.copy(x = -ballVelocity.value.x)
}4. Score Keeping
if (ballPosition.value.x <= 0) { // Left player misses
rightPlayerScore.value += 1
resetBall(ballPosition, ballVelocity, screenWidth, screenHeight)
}
if (ballPosition.value.x >= screenWidth) { // Right player misses
leftPlayerScore.value += 1
resetBall(ballPosition, ballVelocity, screenWidth, screenHeight)
}5. Player Paddle Control
.pointerInput(Unit) {
detectVerticalDragGestures { _, dragAmount ->
leftPaddleY.floatValue =
(leftPaddleY.floatValue + dragAmount).coerceIn(0f, screenHeight - paddleHeight)
}
}6. AI Paddle Movement
rightPaddleY.floatValue = ballPosition.value.y.coerceIn(0f, screenHeight - paddleHeight)
7. Drawing the Game
Canvas(modifier = Modifier.fillMaxSize()) {
// Draw left paddle
drawRect(
color = Color.Blue,
topLeft = Offset(0f, leftPaddleY.floatValue),
size = Size(paddleWidth, paddleHeight)
)
// Draw right paddle
drawRect(
color = Color.Red,
topLeft = Offset(size.width - paddleWidth, rightPaddleY.floatValue),
size = Size(paddleWidth, paddleHeight)
)
// Draw the ball
drawCircle(
color = Color.Green,
center = ballPosition.value,
radius = ballRadius
)
}8. Resetting the Ball
fun resetBall(
ballPosition: MutableState,
ballVelocity: MutableState,
screenWidth: Float,
screenHeight: Float
) {
ballPosition.value = Offset(screenWidth / 2, screenHeight / 2)
ballVelocity.value = ballVelocity.value.copy(
x = if (ballVelocity.value.x > 0) -6f else 6f,
y = ballVelocity.value.y
)
} MainActivity
package com.codingbihar.composepractice
import ...
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
ComposePracticeTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
PongGame()
}
}
}
}
}PongGameApp
package com.codingbihar.composepractice
import ...
@Composable
fun PongGame() {
// Get screen dimensions based on the device
val configuration = LocalConfiguration.current
val screenWidthDp = configuration.screenWidthDp.dp
val screenHeightDp = configuration.screenHeightDp.dp
// Use LocalDensity to convert dp to pixels
val density = LocalDensity.current
val screenWidth = with(density) { screenWidthDp.toPx() }
val screenHeight = with(density) { screenHeightDp.toPx() }
// Game state variables
val ballPosition = remember { mutableStateOf(Offset(screenWidth / 2, screenHeight / 2)) }
val ballVelocity = remember { mutableStateOf(Offset(6f, 6f)) }
val leftPaddleY = remember { mutableFloatStateOf(screenHeight / 2 - 100f) }
val rightPaddleY = remember { mutableFloatStateOf(screenHeight / 2 - 100f) }
val leftPlayerScore = remember { mutableIntStateOf(0) }
val rightPlayerScore = remember { mutableIntStateOf(0) }
val gameOver = remember { mutableStateOf(false) }
val winner = remember { mutableStateOf("") }
val paddleHeight = 200f
val paddleWidth = 20f
val ballRadius = 45f
val winningScore = 5 // Set the winning score
// Game loop: update ball position
LaunchedEffect(Unit) {
while (!gameOver.value) {
ballPosition.value = ballPosition.value.copy(
x = ballPosition.value.x + ballVelocity.value.x,
y = ballPosition.value.y + ballVelocity.value.y
)
// Ball bounce off top and bottom walls
if (ballPosition.value.y <= 0 || ballPosition.value.y >= screenHeight - ballRadius) {
ballVelocity.value = ballVelocity.value.copy(y = -ballVelocity.value.y)
}
// Ball collision with left paddle
if (ballPosition.value.x - ballRadius <= paddleWidth &&
ballPosition.value.y in leftPaddleY.floatValue..(leftPaddleY.floatValue + paddleHeight)
) {
ballVelocity.value = ballVelocity.value.copy(x = -ballVelocity.value.x)
}
// Ball collision with right paddle
if (ballPosition.value.x + ballRadius >= screenWidth - paddleWidth &&
ballPosition.value.x <= screenWidth &&
ballPosition.value.y in rightPaddleY.floatValue..(rightPaddleY.floatValue + paddleHeight)
) {
ballVelocity.value = ballVelocity.value.copy(x = -ballVelocity.value.x)
}
// Ball passes left paddle (right player scores)
if (ballPosition.value.x <= 0) {
rightPlayerScore.value += 1
if (rightPlayerScore.intValue >= winningScore) {
gameOver.value = true
winner.value = "Player 2 Wins!"
} else {
resetBall(ballPosition, ballVelocity, screenWidth, screenHeight)
}
}
// Ball passes right paddle (left player scores)
if (ballPosition.value.x >= screenWidth) {
leftPlayerScore.value += 1
if (leftPlayerScore.intValue >= winningScore) {
gameOver.value = true
winner.value = "Player 1 Wins!"
} else {
resetBall(ballPosition, ballVelocity, screenWidth, screenHeight)
}
}
delay(16L) // ~60 FPS
}
}
// Drawing the game
Box(modifier = Modifier.fillMaxSize()) {
if (gameOver.value) {
// Display the winner message when game is over
Text(
text = winner.value,
fontSize = 32.sp,
fontWeight = FontWeight.Bold,
color = Color.Green,
modifier = Modifier.align(Alignment.Center)
)
} else {
// Display the score at the top of the screen
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = "Player 1: ${leftPlayerScore.intValue}",
fontSize = 24.sp,
fontWeight = FontWeight.Bold,
color = Color.Blue
)
Text(
text = "Player 2: ${rightPlayerScore.intValue}",
fontSize = 24.sp,
fontWeight = FontWeight.Bold,
color = Color.Red
)
}
// Game canvas
Canvas(modifier = Modifier
.fillMaxSize()
.pointerInput(Unit) {
detectVerticalDragGestures { _, dragAmount ->
// Move left paddle with drag
leftPaddleY.floatValue =
(leftPaddleY.floatValue + dragAmount).coerceIn(0f, screenHeight - paddleHeight)
}
}
) {
// Draw left paddle
drawRect(
color = Color.Blue,
topLeft = Offset(0f, leftPaddleY.floatValue),
size = Size(paddleWidth, paddleHeight)
)
// Draw right paddle (AI control)
drawRect(
color = Color.Red,
topLeft = Offset(size.width - paddleWidth, rightPaddleY.floatValue),
size = Size(paddleWidth, paddleHeight)
)
// Simple AI: Move the right paddle towards the ball
rightPaddleY.floatValue = ballPosition.value.y.coerceIn(0f, screenHeight - paddleHeight)
// Draw the ball
drawCircle(
color = Color.Green,
center = ballPosition.value,
radius = ballRadius
)
}
}
}
}
// Reset the ball to the center and invert direction
fun resetBall(
ballPosition: MutableState,
ballVelocity: MutableState,
screenWidth: Float,
screenHeight: Float
) {
ballPosition.value = Offset(screenWidth / 2, screenHeight / 2)
ballVelocity.value = ballVelocity.value.copy(
x = if (ballVelocity.value.x > 0) -6f else 6f,
y = ballVelocity.value.y
)
}


.webp)