diff --git a/analysis_options.yaml b/analysis_options.yaml index 0d29021..0439848 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -23,6 +23,5 @@ linter: rules: # avoid_print: false # Uncomment to disable the `avoid_print` rule # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule - # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options diff --git a/lib/main.dart b/lib/main.dart index 35d9332..80a7b30 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -8,6 +8,7 @@ import 'package:flutter/scheduler.dart'; import 'package:pong/pong_menu.dart'; void main() { + // Run the PongGame app runApp(const PongGame()); } @@ -17,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(), ); } @@ -33,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(); @@ -40,6 +44,7 @@ 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!', style: TextStyle( @@ -58,85 +63,131 @@ class GameScreen extends StatefulWidget { const GameScreen({super.key}); @override - State createState() => _GameScreenState(); + State createState() => GameScreenState(); } -@visibleForTesting -class _GameScreenState extends State { +class GameScreenState extends State { + // Constants defining properties of the game final ballSize = 20.0; - final racketWidth = 240.0; - final racketHeight = 25.0; + final racketWidth = 100.0; + final racketHeight = 10.0; final racketBottomOffset = 100.0; + bool isPaused = false; - final initialBallSpeed = 2.0; + // Initial speed of the ball + final initialBallSpeed = 3.0; - double ballX = 0; - double ballY = 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; - final double ballSpeedMultiplier = 2; + double ballSpeedMultiplier = 1.1; @override void initState() { super.initState(); + // Initialize the game state when the widget is created startGame(); } - @override - void dispose() { - super.dispose(); - stopGame(); - } - + // Initialize the game state void startGame() { final random = Random(); - ballX = 0; - ballY = 0; + 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; - Future.delayed(const Duration(seconds: 1), () { - ticker = Ticker((elapsed) { - setState(() { - moveBall(ballSpeedMultiplier); - }); - }); - ticker.start(); - }); + // Start the game loop after a delay + continueGame(); } + // Stop the game loop void stopGame() { ticker.dispose(); } - void moveBall(double ballSPeedMultiplier) { - ballX += ballSpeedX * ballSpeedMultiplier; - ballY += ballSpeedY * ballSpeedMultiplier; - final Size size = MediaQuery.of(context).size; + // 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(); + }); + } + } - if (ballX < 0 || ballX > size.width - ballSize) { + 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; } - if (ballY < 0) { + // Check for collision with the top wall and bounce the ball + if (ballPositionY < 0) { ballSpeedY = -ballSpeedY; } - // Was prüft diese if-Anweisung? - if (ballY > size.height - ballSize - racketHeight - racketBottomOffset && - ballX >= racketX && - ballX <= racketX + racketWidth) { - ballSpeedY = -ballSpeedY; - } else if (ballY > size.height - ballSize) { + // 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( @@ -147,7 +198,7 @@ class _GameScreenState extends State { title: 'Game over!', subTitle: 'Deine Punkte: $score', child: CupertinoButton( - child: const Text('Nochmal Spielen'), + child: const Text('Nochmal spielen'), onPressed: () { Navigator.of(context).pop(); startGame(); @@ -159,7 +210,8 @@ class _GameScreenState extends State { } } - moveRacket(double x) { + void moveRacket(double x) { + // Move the racket horizontally based on the user's touch position setState(() { racketX = x - racketWidth / 2; }); @@ -171,6 +223,7 @@ class _GameScreenState extends State { body: Stack( children: [ GestureDetector( + // Listen for horizontal drag updates to move the racket onHorizontalDragUpdate: (details) { moveRacket(details.globalPosition.dx); }, @@ -181,13 +234,14 @@ class _GameScreenState extends State { racketX: racketX, racketBottomOffset: racketBottomOffset, ballSize: ballSize, - ballX: ballX, - ballY: ballY, + 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( @@ -197,24 +251,32 @@ class _GameScreenState extends State { ), ), Positioned( - top: 20, + 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, - builder: (context) { + barrierDismissible: false, + builder: (BuildContext context) { return PongMenu( title: 'Pause', subTitle: 'Deine Punkte: $score', child: CupertinoButton( - onPressed: () {}, - child: const Text('Weiter'), + onPressed: () { + // Continues the game + Navigator.of(context).pop(); + isPaused = false; + continueGame(); + }, + child: const Text('Fortfahren'), ), ); }); @@ -237,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, @@ -249,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, @@ -270,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; diff --git a/lib/pong_menu.dart b/lib/pong_menu.dart index f938c03..725e696 100644 --- a/lib/pong_menu.dart +++ b/lib/pong_menu.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; class PongMenu extends StatelessWidget { + // Constructor to initialize the PongMenu with required properties const PongMenu({ super.key, required this.title, @@ -14,9 +15,13 @@ class PongMenu extends StatelessWidget { @override Widget build(BuildContext context) { + // Get the size of the screen final mq = MediaQuery.of(context).size; + // Calculate the height and width of the dialog final dialogHeight = mq.height * 0.4; final dialogWidth = mq.width * 0.8; + + // Return a custom-styled Dialog with specified properties return Dialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), elevation: 0, @@ -25,27 +30,32 @@ class PongMenu extends StatelessWidget { width: dialogWidth, height: dialogHeight, decoration: BoxDecoration( - color: Colors.white, + color: const Color.fromARGB(255, 33, 33, 33), borderRadius: BorderRadius.circular(25), ), child: Column( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ + // Display the title with custom styling Text( title, style: const TextStyle( fontSize: 30, fontWeight: FontWeight.bold, + color: Colors.white, ), ), + // Display the subTitle with custom styling Text( subTitle, style: const TextStyle( fontSize: 30, fontWeight: FontWeight.bold, + color: Colors.white, ), ), + // Display the child widget child, ], ), diff --git a/pubspec.lock b/pubspec.lock index 32958ff..019d111 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -66,10 +66,10 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 + sha256: e2a421b7e59244faef694ba7b30562e489c2b489866e505074eb005cd7060db7 url: "https://pub.dev" source: hosted - version: "2.0.3" + version: "3.0.1" flutter_test: dependency: "direct dev" description: flutter @@ -79,10 +79,10 @@ packages: dependency: transitive description: name: lints - sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" + sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "3.0.0" matcher: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index a058bd5..113e09a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -44,7 +44,7 @@ dev_dependencies: # activated in the `analysis_options.yaml` file located at the root of your # package. See that file for information about deactivating specific lint # rules and activating additional ones. - flutter_lints: ^2.0.0 + flutter_lints: ^3.0.1 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/test/unit_test.dart b/test/unit_test.dart index c464b01..8c740f0 100644 --- a/test/unit_test.dart +++ b/test/unit_test.dart @@ -1,9 +1,19 @@ import 'package:flutter_test/flutter_test.dart'; - import 'package:pong/main.dart'; void main() { - test('moveRacket', () { - ; + test('moveRacket adjusts racketX correctly', () { + // Arrange + final gameScreenState = GameScreenState(); + const initialRacketX = 20.0; + const racketWidth = 100.0; + + // Act + gameScreenState.moveRacket(initialRacketX + + 50.0); // Simuliere einen horizontalen Drag an der Position initialRacketX + 50 + + // Assert + expect(gameScreenState.racketX, + equals(initialRacketX + 50.0 - racketWidth / 2)); }); } diff --git a/test/widget_test.dart b/test/widget_test.dart index 0322b2e..113e32c 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:pong/main.dart'; @@ -12,11 +13,11 @@ void main() { expect(tapToStart, findsOneWidget); }); - testWidgets('Tests ', (WidgetTester tester) async { - final tapToStart = find.text("Berühren um zu beginnen!"); + testWidgets('Tests "Punkte:"', (WidgetTester tester) async { + final points = find.text("Punkte: 0"); - await tester.pumpWidget(const MaterialApp(home: StartScreen())); - await tester.tap(tapToStart); - await tester.pump(); + await tester.pumpWidget(const MaterialApp(home: GameScreen())); + + expect(points, findsOneWidget); }); }