From 54bf6cd5d9fc38b1cc3be7191e8ccf49b1bfc7e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20L=C3=B6ffler?= <1911374@stud.hs-mannheim.de> Date: Wed, 10 Jan 2024 14:49:33 +0100 Subject: [PATCH] Update Pong/lib/main.dart --- Pong/lib/main.dart | 254 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 227 insertions(+), 27 deletions(-) diff --git a/Pong/lib/main.dart b/Pong/lib/main.dart index e8bdb92..80a7b30 100644 --- a/Pong/lib/main.dart +++ b/Pong/lib/main.dart @@ -1,6 +1,14 @@ +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 app runApp(const PongGame()); } @@ -10,9 +18,11 @@ class PongGame extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( + // Set the app title and theme title: 'Pong Game', - theme: - ThemeData(scaffoldBackgroundColor: Color.fromARGB(255, 41, 38, 38)), + theme: ThemeData( + scaffoldBackgroundColor: const Color.fromARGB(255, 41, 38, 38)), + // Set the initial screen to the StartScreen home: const StartScreen(), ); } @@ -26,6 +36,7 @@ class StartScreen extends StatelessWidget { return Scaffold( body: GestureDetector( onTap: () { + // Navigate to the GameScreen when tapped Navigator.of(context).push(MaterialPageRoute( builder: (context) { return const GameScreen(); @@ -33,9 +44,9 @@ class StartScreen extends StatelessWidget { )); }, child: const Center( + // Display a message prompting the user to touch the screen to begin child: Text( 'Berühren um zu beginnen!', - key: Key("tapToStart"), style: TextStyle( fontSize: 30, fontWeight: FontWeight.bold, @@ -52,20 +63,155 @@ class GameScreen extends StatefulWidget { const GameScreen({super.key}); @override - State createState() => _GameScreenState(); + State createState() => GameScreenState(); } -class _GameScreenState extends State { - final racketWidth = 120.0; - final racketHeight = 25.0; +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; - double ballX = 0; - double ballY = 0; + // 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; - double racketY = 0; + int score = 0; - moveRacket(double x) { + // 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; }); @@ -74,22 +220,70 @@ class _GameScreenState extends State { @override Widget build(BuildContext context) { return Scaffold( - body: GestureDetector( - onHorizontalDragUpdate: (details) { - moveRacket(details.globalPosition.dx); - }, - child: CustomPaint( - key: const Key("customPaint"), - painter: PongGamePainter( - racketHeight: 30, - racketWidth: 100, - racketX: racketX, - racketBottomOffset: racketBottomOffset, - ballSize: 20, - ballX: 10, - ballY: 10), - size: Size.infinite, - ), + 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'), + ), + ); + }); + }, + ), + ) + ], ), ); } @@ -105,6 +299,7 @@ class PongGamePainter extends CustomPainter { final double racketHeight; final double racketBottomOffset; + // Constructor to initialize the painter with required properties PongGamePainter({ required this.ballSize, required this.ballX, @@ -117,14 +312,18 @@ class PongGamePainter extends CustomPainter { @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, @@ -138,6 +337,7 @@ class PongGamePainter extends CustomPainter { @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;