import 'dart:async'; import 'dart:math'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:pong/pong_menu.dart'; void main() { // Run the PongGame runApp(const PongGame()); } class PongGame extends StatelessWidget { const PongGame({super.key}); @override Widget build(BuildContext context) { return MaterialApp( // Set the app title and theme title: 'Pong Game', theme: ThemeData( scaffoldBackgroundColor: const Color.fromARGB(255, 41, 38, 38)), // Set the initial screen to the StartScreen home: const StartScreen(), ); } } class StartScreen extends StatelessWidget { const StartScreen({super.key}); @override Widget build(BuildContext context) { return Scaffold( body: GestureDetector( onTap: () { // Navigate to the GameScreen when tapped Navigator.of(context).push(MaterialPageRoute( builder: (context) { return const GameScreen(); }, )); }, child: const Center( // Display a message prompting the user to touch the screen to begin child: Text( 'Berühren um zu beginnen!', style: TextStyle( fontSize: 30, fontWeight: FontWeight.bold, color: Colors.white, ), ), ), ), ); } } class GameScreen extends StatefulWidget { const GameScreen({super.key}); @override State createState() => GameScreenState(); } class GameScreenState extends State { // Constants defining properties of the game final ballSize = 20.0; final racketWidth = 100.0; final racketHeight = 10.0; final racketBottomOffset = 100.0; bool isPaused = false; // Initial speed of the ball final initialBallSpeed = 3.0; // Variables to track ball and racket positions, speed, score, and game state double ballPositionX = 0; double ballPositionY = 0; double ballSpeedX = 0; double ballSpeedY = 0; double racketX = 20; int score = 0; // Ticker for updating the game state late Ticker ticker; double ballSpeedMultiplier = 1.1; @override void initState() { super.initState(); // Initialize the game state when the widget is created startGame(); } // Initialize the game state void startGame() { final random = Random(); ballPositionX = 0; ballPositionY = 0; ballSpeedX = initialBallSpeed; ballSpeedY = initialBallSpeed; ballSpeedMultiplier = 1.1; racketX = 200; score = 0; // Randomize initial ball direction if (random.nextBool()) ballSpeedX = -ballSpeedX; if (random.nextBool()) ballSpeedY = -ballSpeedY; // Start the game loop after a delay continueGame(); } // Stop the game loop void stopGame() { ticker.dispose(); } // Continue the game loop with a delay void continueGame() { if (!isPaused) { Future.delayed(const Duration(seconds: 1), () { ticker = Ticker((elapsed) { setState(() { moveBall(ballSpeedMultiplier); }); }); ticker.start(); }); } } void moveBall(double ballSpeedMultiplier) { // Update ball position based on speed and direction ballPositionX += ballSpeedX * ballSpeedMultiplier; ballPositionY += ballSpeedY * ballSpeedMultiplier; // Get the screen size final Size screenSize = MediaQuery.of(context).size; // Check for collisions with horizontal walls and bounce the ball if (ballPositionX < 0 || ballPositionX > screenSize.width - ballSize) { ballSpeedX = -ballSpeedX; } // Check for collision with the top wall and bounce the ball if (ballPositionY < 0) { ballSpeedY = -ballSpeedY; } // Check if the ball has touched the floor var ballTouchesFloor = ballPositionY > screenSize.height - ballSize; // Check if the ball collides with the racket var ballCollidesWithRacket = ballPositionY + ballSize >= screenSize.height - racketBottomOffset && ballPositionY <= screenSize.height - racketBottomOffset && ballPositionX + ballSize >= racketX && ballPositionX <= racketX + racketWidth; // Handle collision with the racket if (ballCollidesWithRacket) { // Check if the ball hits the top of the racket if (ballPositionY <= screenSize.height - racketBottomOffset) { ballSpeedY = -ballSpeedY * ballSpeedMultiplier; // Bounce the ball upward setState(() { score += 1; // Increase the score debugPrint('ballSpeedMultiplier: $ballSpeedMultiplier'); }); ballPositionY = screenSize.height - racketBottomOffset - ballSize; // Adjust the Y position to stay above the racket } else { ballSpeedX = -ballSpeedX * ballSpeedMultiplier; // Bounce the ball backward // Adjust the Y position to prevent the ball from going through the racket double adjustedY = screenSize.height - racketBottomOffset - ballSize; if (ballPositionY < adjustedY) { ballPositionY = adjustedY; } } // Handle game over condition if the ball touches the floor } else if (ballTouchesFloor) { debugPrint('Game over'); stopGame(); showDialog( context: context, barrierDismissible: false, builder: (BuildContext context) { return PongMenu( title: 'Game over!', subTitle: 'Deine Punkte: $score', child: CupertinoButton( child: const Text('Nochmal spielen'), onPressed: () { Navigator.of(context).pop(); startGame(); }, ), ); }, ); } } void moveRacket(double x) { // Move the racket horizontally based on the user's touch position setState(() { racketX = x - racketWidth / 2; }); } @override Widget build(BuildContext context) { return Scaffold( body: Stack( children: [ GestureDetector( // Listen for horizontal drag updates to move the racket onHorizontalDragUpdate: (details) { moveRacket(details.globalPosition.dx); }, child: CustomPaint( painter: PongGamePainter( racketHeight: racketHeight, racketWidth: racketWidth, racketX: racketX, racketBottomOffset: racketBottomOffset, ballSize: ballSize, ballX: ballPositionX, ballY: ballPositionY, ), size: Size.infinite, ), ), Center( // Display the player's score in the center of the screen child: Text( 'Punkte: $score', style: const TextStyle( fontSize: 30, fontWeight: FontWeight.bold, ), ), ), Positioned( top: 40, right: 20, child: IconButton( icon: const Icon( CupertinoIcons.pause, size: 30, color: Colors.white, ), onPressed: () { // Pause the game and show dialog stopGame(); showDialog( context: context, barrierDismissible: false, builder: (BuildContext context) { return PongMenu( title: 'Pause', subTitle: 'Deine Punkte: $score', child: CupertinoButton( onPressed: () { // Continues the game Navigator.of(context).pop(); isPaused = false; continueGame(); }, child: const Text('Fortfahren'), ), ); }); }, ), ) ], ), ); } } class PongGamePainter extends CustomPainter { final double ballSize; final double ballX; final double ballY; final double racketX; final double racketWidth; final double racketHeight; final double racketBottomOffset; // Constructor to initialize the painter with required properties PongGamePainter({ required this.ballSize, required this.ballX, required this.ballY, required this.racketX, required this.racketWidth, required this.racketHeight, required this.racketBottomOffset, }); @override void paint(Canvas canvas, Size size) { // Paint object for drawing the racket final racketPaint = Paint()..color = Colors.white; // Paint object for drawing the ball final ballPaint = Paint()..color = Colors.white; // Draw the ball as an oval on the canvas canvas.drawOval( Rect.fromLTWH(ballX, ballY, ballSize, ballSize), ballPaint, ); // Draw the racket as a rectangle on the canvas canvas.drawRect( Rect.fromLTWH( racketX, size.height - racketHeight - racketBottomOffset, racketWidth, racketHeight, ), racketPaint, ); } @override bool shouldRepaint(covariant PongGamePainter oldDelegate) { // Repaint only if the ball or racket position changes return ballX != oldDelegate.ballX || ballY != oldDelegate.ballY || racketX != oldDelegate.racketX; } }