Sets up the navigation for the app using NavHost.
What we need?
Screens:
1. Navigation Setup
2. Start Screen (StartScreenSnake)
3. Game Screen (SnakeGameScreen)
4. Input Handling (SnakeGameInput)
5. Drawing the Game (SnakeGameGrid)
6. Collision Detection and Game Logic
7. Result Screen (ResultScreen)
Dependency needed for navigation
implementation("androidx.navigation:navigation-compose:2.8.6")
MainActivity
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
JetpackSkillTheme {
SnakeGameApp()
}
}
}
}
SnakeGameApp
package com.example.jetpackskill
import android.media.MediaPlayer
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import kotlinx.coroutines.delay
import kotlin.random.Random
@Composable
fun SnakeGameApp() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "start") {
composable("start") {
StartScreen(navController)
}
composable("start_game") {
SnakeGameScreen(navController)
}
composable(
"result_screen/{score}",
arguments = listOf(navArgument("score") { type = NavType.IntType })
) { backStackEntry ->
val score = backStackEntry.arguments?.getInt("score") ?: 0
ResultScreen(navController, score)
}
}
}
@Composable
fun ResultScreen(navController: NavController, score: Int) {
Column(
modifier = Modifier
.fillMaxSize()
.background(color = Color.Black)
.padding(16.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "Game Over!",
fontSize = 32.sp,
color = Color.Red,
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = "Your Score: $score",
fontSize = 24.sp,
color = Color. Green,
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(32.dp))
Button(onClick = {
// Navigate back to the start screen to play again
navController.navigate("start_game") {
popUpTo("result_screen") { inclusive = true }
}
}) {
Text("Play Again")
}
}
}
@Composable
fun StartScreen(navController: NavController) {
Box(
Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Button(onClick = { navController.navigate("start_game") }
) {
Text("Start Game")
}
}
}
@Composable
fun SnakeGameScreen(navController: NavController) {
val context = LocalContext.current
val mediaPlayer = remember {
// Check if the sound resource is valid
MediaPlayer.create(context, R.raw.eat_sound)
//?: throw IllegalArgumentException("Sound resource not found")
}
var screenWidthPx by remember { mutableIntStateOf(0) }
var screenHeightPx by remember { mutableIntStateOf(0) }
var isPlaying by remember { mutableStateOf(true) } // Start as false to avoid immediate game start
val cellSize = with(LocalDensity.current) { (20.dp).toPx().toInt() }
var snake by remember { mutableStateOf(listOf(Position(10 * cellSize, 10 * cellSize))) }
var direction by remember { mutableStateOf(Direction.RIGHT) }
var score by remember { mutableIntStateOf(0) }
// Food with random position and color
var food by remember {
mutableStateOf(
Food(
position = Position(0, 0), // Temporary position until screen size is set
color = Color.Green
)
)
}
LaunchedEffect(isPlaying, screenWidthPx, screenHeightPx) {
if (screenWidthPx > 0 && screenHeightPx > 0 && isPlaying) { // Check valid dimensions and if playing
food = Food(
position = Position(
x = Random.nextInt(1, (screenWidthPx / cellSize) - 1) * cellSize,
y = Random.nextInt(1, ((screenHeightPx - 100) / cellSize) - 1) * cellSize
),
color = Color(
red = Random.nextInt(256) / 255f,
green = Random.nextInt(256) / 255f,
blue = Random.nextInt(256) / 255f
)
)
while (isPlaying) {
snake = moveSnake(
snake, direction, screenWidthPx, screenHeightPx - 100, cellSize, mediaPlayer, navController, score
)
// Check if snake's head collides with the food
if (snake.first().x == food.position.x && snake.first().y == food.position.y) {
mediaPlayer.start() // Play sound when food is eaten
score += 1
// Grow the snake and move food
snake = snake + snake.last()
food = Food(
position = Position(
Random.nextInt(screenWidthPx / cellSize) * cellSize,
Random.nextInt((screenHeightPx - 100) / cellSize) * cellSize
),
color = Color(
red = Random.nextInt(256) / 255f,
green = Random.nextInt(256) / 255f,
blue = Random.nextInt(256) / 255f
)
)
}
delay(150L)
}
}
}
Column(
modifier = Modifier
.fillMaxSize()
.onSizeChanged { size ->
screenWidthPx = size.width
screenHeightPx = size.height
},
verticalArrangement = Arrangement.SpaceBetween
) {
// Display Score at the top
Text(
text = "Score: $score",
color = Color.Black,
fontSize = 24.sp,
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
textAlign = TextAlign.End
)
// Game area
Box(
modifier = Modifier
.fillMaxWidth()
.weight(1f)
.background(Color.Black)
) {
Canvas(modifier = Modifier.fillMaxSize()) {
snake.forEach {
drawCircle(
color = Color.Green,
radius = cellSize / 2f,
center = Offset(it.x.toFloat(), it.y.toFloat())
)
}
drawCircle(
color = food.color,
radius = cellSize / 2f,
center = Offset(food.position.x.toFloat(), food.position.y.toFloat())
)
}
}
// Control Buttons
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalArrangement = Arrangement.SpaceBetween
) {
Button(onClick = { direction = Direction.LEFT }) { Text("Left") }
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Button(onClick = { direction = Direction.UP }) { Text("Up") }
Spacer(modifier = Modifier.height(8.dp))
Button(onClick = { direction = Direction.DOWN }) { Text("Down") }
}
Button(onClick = { direction = Direction.RIGHT }) { Text("Right") }
}
// Play/Pause Button and Score Display
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalArrangement = Arrangement.SpaceAround
) {
Button(onClick = { isPlaying = !isPlaying }) {
Text(if (isPlaying) "Pause" else "Play")
}
Text("Score: $score", color = Color.White)
}
}
}
fun moveSnake(
snake: List<Position>, // Specify List type
direction: Direction,
maxWidth: Int,
maxHeight: Int,
cellSize: Int,
mediaPlayer: MediaPlayer,
navController: NavController,
score: Int
): List<Position> { // Ensure return type matches
val head = snake.first()
val newHead = when (direction) {
Direction.UP -> head.copy(y = head.y - cellSize)
Direction.DOWN -> head.copy(y = head.y + cellSize)
Direction.LEFT -> head.copy(x = head.x - cellSize)
Direction.RIGHT -> head.copy(x = head.x + cellSize)
}
// Play sound if snake touches the edges and navigate to result screen
if (newHead.x < 0 || newHead.x >= maxWidth || newHead.y < 0 || newHead.y >= maxHeight) {
if (!mediaPlayer.isPlaying) {
mediaPlayer.start()
navController.navigate("result_screen/${score}") {
popUpTo("start_game") { inclusive = true }
}
}
}
// Ensure the snake stays within the screen bounds
val boundedHead = Position(
x = newHead.x.coerceIn(0, maxWidth - cellSize),
y = newHead.y.coerceIn(0, maxHeight - cellSize)
)
return listOf(boundedHead) + snake.dropLast(1)
}
GameState
package com.example.jetpackskill
import androidx.compose.ui.graphics.Color
data class Position(val x: Int, val y: Int)
data class Food(val position: Position, val color: Color)
// Data Classes
enum class Direction { UP, DOWN, LEFT, RIGHT }
OUTPUT:
Snake Game App
Full source code with app icon, atrractive UI. It handles game logic, rendering, and user input effectively within the Composable functions provided by Jetpack Compose.
₹199.00 Source Code






