Set up the Main Function and Snake Game Widget
First, create a simple app entry point with the main() function and define the SnakeGame widget, which will hold the entire game.
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(home: SnakeGame()));
}
class SnakeGame extends StatefulWidget {
@override
_SnakeGameState createState() => _SnakeGameState();
}
Initialize Game Variables and State
Inside _SnakeGameState, define essential game variables such as snake, food, direction, score, and the grid size. Also, initialize resetGame() to set up the initial state of the game.
class _SnakeGameState extends State<SnakeGame> {
final int gridSize = 20; // Define grid size
List<Point> snake = []; // Holds snake segments
Point food = Point(0, 0); // Position of food
Direction direction = Direction.right; // Initial snake direction
int score = 0; // Player's score
bool isGameRunning = false; // Game state flag
@override
void initState() {
super.initState();
resetGame(); // Reset game on start
}
void resetGame() {
snake = [
Point(gridSize ~/ 2, gridSize ~/ 2),
Point(gridSize ~/ 2 - 1, gridSize ~/ 2),
Point(gridSize ~/ 2 - 2, gridSize ~/ 2),
];
generateFood();
direction = Direction.right;
score = 0;
}
}
Generate Food
Define generateFood() to place food randomly on the grid where there are no snake segments.
void generateFood() {
final random = Random();
do {
food = Point(random.nextInt(gridSize), random.nextInt(gridSize));
} while (snake.contains(food)); // Ensure food is not on the snake
}
Start and End Game Logic
Add startGame() to initiate the game and endGame() to end it, including a dialog box to show the final score.
void startGame() {
if (!isGameRunning) {
setState(() {
isGameRunning = true;
resetGame();
});
Timer.periodic(Duration(milliseconds: 200), (timer) {
if (isGameRunning) updateGame();
});
}
}
void endGame() {
setState(() {
isGameRunning = false;
});
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => AlertDialog(
title: Text('Game Over'),
content: Text('Final Score: $score'),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
resetGame();
},
child: Text('Play Again'),
),
],
),
);
}
Update Game State and Handle Snake Growth
Implement updateGame() to manage the snakeβs movement, growth, and collisions with walls, itself, and food.
void updateGame() {
final head = snake.first;
Point newHead;
switch (direction) {
case Direction.right:
newHead = Point(head.x + 1, head.y);
break;
case Direction.left:
newHead = Point(head.x - 1, head.y);
break;
case Direction.up:
newHead = Point(head.x, head.y - 1);
break;
case Direction.down:
newHead = Point(head.x, head.y + 1);
break;
}
// Check for collisions
if (newHead.x < 0 || newHead.x >= gridSize || newHead.y < 0 || newHead.y >= gridSize || snake.contains(newHead)) {
endGame();
return;
}
setState(() {
snake.insert(0, newHead);
if (newHead == food) {
score += 10;
generateFood(); // New food
} else {
snake.removeLast(); // Remove tail if no food eaten
}
});
}
Build Game Interface (UI)
Design the game board UI with controls for starting and ending the game.
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Snake Game - Score: $score'),
backgroundColor: Colors.green,
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
buildGameBoard(),
buildControlButtons(),
],
),
),
);
}
Widget buildGameBoard() {
return Container(
width: MediaQuery.of(context).size.width * 0.8,
height: MediaQuery.of(context).size.width * 0.8,
decoration: BoxDecoration(
border: Border.all(color: Colors.green, width: 2),
color: Colors.white,
),
child: CustomPaint(
painter: SnakePainter(snake, food, gridSize),
),
);
}
Create Control Buttons for the Game
Add directional controls and buttons to start or end the game.
Widget buildControlButtons() {
return Column(
children: [
ElevatedButton(
onPressed: isGameRunning ? null : startGame,
child: Text('Start Game'),
),
SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
controlButton(Icons.arrow_upward, Direction.up),
Row(
children: [
controlButton(Icons.arrow_back, Direction.left),
SizedBox(width: 50),
controlButton(Icons.arrow_forward, Direction.right),
],
),
controlButton(Icons.arrow_downward, Direction.down),
],
),
],
);
}
Widget controlButton(IconData icon, Direction dir) {
return ElevatedButton(
onPressed: () {
if (isGameRunning && direction != dir.opposite) {
setState(() => direction = dir);
}
},
child: Icon(icon),
);
}
Draw the Snake and Food on the Canvas
Create SnakePainter, a CustomPainter, to visually draw the snake and food on the grid.
class SnakePainter extends CustomPainter {
final List<Point> snake;
final Point food;
final int gridSize;
SnakePainter(this.snake, this.food, this.gridSize);
@override
void paint(Canvas canvas, Size size) {
final cellSize = size.width / gridSize;
final paint = Paint();
// Draw food
paint.color = Colors.red;
canvas.drawRect(
Rect.fromLTWH(food.x * cellSize, food.y * cellSize, cellSize, cellSize),
paint,
);
// Draw snake
paint.color = Colors.green;
for (var point in snake) {
canvas.drawRect(
Rect.fromLTWH(point.x * cellSize, point.y * cellSize, cellSize, cellSize),
paint,
);
}
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => true;
}
enum Direction { up, down, left, right }
extension on Direction {
Direction get opposite {
switch (this) {
case Direction.up:
return Direction.down;
case Direction.down:
return Direction.up;
case Direction.left:
return Direction.right;
case Direction.right:
return Direction.left;
}
}
}