Rendition object (271)

AI Meets Programming Prowess: Building a Pong Game in Flutter

This article explores the synergy between GPT-4 and human expertise by taking on the challenge of creating a PONG game with Flutter in under 30 minutes.

In recent years, GPT models have garnered attention for their capabilities as powerful language models. This AI-driven technology has demonstrated remarkable capabilities in creating human-like text and even supporting programming tasks. However, despite GPT-4's impressive features, human programming skills remain an essential component for success in software development. To illustrate this synergy between GPT-4 and human expertise, we will take on the challenge of programming a PONG game using Flutter in less than 30 minutes.

GPT-4 as a Programming Assistant

GPT-4 has proven its worth in various aspects of programming, including code generation, debugging suggestions, and providing research and documentation assistance. These capabilities allow developers to save time and effort, enabling them to focus on more critical aspects of their projects.

However, GPT-4 has limitations that must be acknowledged. For instance, it may lack context in certain situations, fail to plan and strategize effectively, and often requires human input for quality control. These drawbacks highlight the importance of human programming skills, even when using advanced AI tools like GPT-4.

The Value of Human Programming Skills

A proficient programmer possesses a deep understanding of core programming concepts such as data structures, algorithms, and control structures. This foundational knowledge allows them to think critically and solve problems, ensuring that their code is efficient and reliable.

Additionally, a skilled programmer can adapt to new technologies and programming languages, which is crucial in the ever-evolving tech industry. As impressive as GPT-4 may be, it cannot replace the ingenuity, creativity, and adaptability that human programmers bring to the table.

Programming a PONG Game with Flutter in Under 30 Minutes

To demonstrate the harmonious collaboration between GPT-4 and a human programmer, let's consider the challenge of creating a PONG game with Flutter in under 30 minutes. GPT-4 can expedite the process by providing sample code snippets, offering documentation assistance, and suggesting optimizations.

My prompt:
I would like to program a simple Pong game. The game should have two players, one of which is the computer. There should also be a scoreboard and the possibility to start and stop the game. The game should be realized with Flutter for all platforms.

GPT-4 answer:

Step 1: Create a new Flutter project using the command:

flutter create pong_game
cd pong_game

Step 2: Add the 'provider' package as a dependency in pubspec.yaml:

dependencies:
  flutter:
    sdk: flutter
  provider: ^6.0.1

Step 3: Run flutter pub get to install the package.

Step 4: Create a new file game_model.dart to define the game state:

import 'dart:math';

import 'package:flutter/foundation.dart';

class GameModel with ChangeNotifier {
  static const double paddleWidth = 20;
  static const double paddleHeight = 100;
  static const double ballSize = 20;

  Rect leftPaddle;
  Rect rightPaddle;
  Rect ball;
  int leftScore = 0;
  int rightScore = 0;

  bool isPlaying = false;
  Offset ballVelocity;

  GameModel() {
    initGame();
  }

  void initGame() {
    leftPaddle = Rect.fromLTWH(20, 200, paddleWidth, paddleHeight);
    rightPaddle = Rect.fromLTWH(760, 200, paddleWidth, paddleHeight);
    ball = Rect.fromLTWH(400, 200, ballSize, ballSize);
    ballVelocity = Offset(4, 4);
    isPlaying = false;
  }

  void update(double height) {
    if (!isPlaying) return;

    // Update ball position
    ball = ball.shift(ballVelocity);

    // Update computer paddle position
    rightPaddle = Rect.fromLTWH(
        rightPaddle.left,
        (ball.top - paddleHeight / 2).clamp(0.0, height - paddleHeight),
        paddleWidth,
        paddleHeight,
    );

    // Check for collisions
    if (ball.top <= 0 || ball.bottom >= height) {
      ballVelocity = ballVelocity.scale(1, -1);
    }

    if (ball.overlaps(leftPaddle) || ball.overlaps(rightPaddle)) {
      ballVelocity = ballVelocity.scale(-1, 1);
    }

    // Update scores
    if (ball.left <= 0) {
      rightScore++;
      initGame();
    } else if (ball.right >= 800) {
      leftScore++;
      initGame();
    }

    notifyListeners();
  }

  void updateLeftPaddle(double dy, double height) {
    leftPaddle = Rect.fromLTWH(
        leftPaddle.left,
        (leftPaddle.top + dy).clamp(0.0, height - paddleHeight),
        paddleWidth,
        paddleHeight,
    );
    notifyListeners();
  }

  void startGame() {
    isPlaying = true;
    notifyListeners();
  }

  void stopGame() {
    isPlaying = false;
    notifyListeners();
  }
}

Step 5: Replace the contents of main.dart with the following code:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import 'game_model.dart';

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => GameModel(),
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Pong Game',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: PongGame(),
    );
  }
}

class PongGame extends StatefulWidget {
  @override
  _PongGameState createState() => _PongGameState();
}

class _PongGameState extends State<PongGame> {
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_) {
      final model = context.read<GameModel>();
      model.initGame();

      Future.doWhile(() async {
        await Future.delayed(Duration(milliseconds: 16));
        final height = context.size.height;
        model.update(height);
        return true;
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    final model = context.watch<GameModel>();

    return Scaffold(
      appBar: AppBar(
        title: Text('Pong Game'),
        actions: [
          IconButton(
            icon: Icon(model.isPlaying ? Icons.pause : Icons.play_arrow),
            onPressed: model.isPlaying ? model.stopGame : model.startGame,
          ),
        ],
      ),
      body: GestureDetector(
        onVerticalDragUpdate: (details) {
          model.updateLeftPaddle(details.delta.dy, context.size.height);
        },
        child: CustomPaint(
          painter: PongPainter(model),
          child: Container(),
        ),
      ),
    );
  }
}

class PongPainter extends CustomPainter {
  final GameModel model;

  PongPainter(this.model);

  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()..color = Colors.blue;

    // Draw paddles
    canvas.drawRect(model.leftPaddle, paint);
    canvas.drawRect(model.rightPaddle, paint);

    // Draw ball
    canvas.drawRect(model.ball, paint);

    // Draw score
    final textPainter = TextPainter(
      text: TextSpan(
        text: '${model.leftScore} - ${model.rightScore}',
        style: TextStyle(color: Colors.blue, fontSize: 24),
      ),
      textAlign: TextAlign.center,
      textDirection: TextDirection.ltr,
    );
    textPainter.layout();
    textPainter.paint(
      canvas,
      Offset((size.width - textPainter.width) / 2, 20),
    );
  }

  @override
  bool shouldRepaint(PongPainter old) => true;
}

OK, so far so good. But of course the delivered code was not yet usable.

The first mistake the AI made, which it could not solve even when asked, was the wrong import in the file. The following import alone would solve the problem:

//game_model.dart
import 'dart:ui';
import 'package:flutter/foundation.dart';

The next problem was the GameModel() initialization. The required constructors were missing and again the AI could not provide a solution on its own even with several correction loops.

//game_model.dart
...
 GameModel(
    this.leftPaddle,
    this.rightPaddle,
    this.ball,
    this.leftScore,
    this.rightScore,
    this.isPlaying,
    this.ballVelocity,
  ) {
    initGame();
  }
...

Due to the changes in the game_model.dart, the corresponding changes in the GameModel() had to be made in the main.dart file as well.

//main.dart
void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => GameModel(
        const Rect.fromLTWH(20, 200, GameModel.paddleWidth, GameModel.paddleHeight),
        const Rect.fromLTWH(760, 200, GameModel.paddleWidth, GameModel.paddleHeight),
        const Rect.fromLTWH(400, 200, GameModel.ballSize, GameModel.ballSize),
        0,
        0,
        false,
        const Offset(4, 4),
      ),
      child: const MyApp(),
    ),
  );
}

With these few changes we can compile the app and get a simple first version of a PONG game.

ipad_PONG_1200

But the game still doesn't look like a typical PONG game and the layout of the playing area is not optimal either. I'll try another prompt:

The game should be changed as follows: The game area should cover the entire surface of the screen. The paddles and the ball as well as the scoreboard should be white. The background of the playing area should be black.

GPT-4 answer:

Step 1: Change the color of the paddles, ball, and scoreboard to white by updating the paint object in the PongPainter class:

class PongPainter extends CustomPainter {
  final GameModel model;

  PongPainter(this.model);

  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()..color = Colors.white;

    // ... rest of the class remains unchanged
  }

  // ... rest of the class remains unchanged
}

Step 2: Change the background color of the playing area to black by setting the backgroundColor property of the Scaffold widget in the _PongGameState class:

class _PongGameState extends State<PongGame> {
  // ... rest of the class remains unchanged

  @override
  Widget build(BuildContext context) {
    final model = context.watch<GameModel>();

    return Scaffold(
      backgroundColor: Colors.black,
      appBar: AppBar(
        // ... rest of the AppBar remains unchanged
      ),
      body: GestureDetector(
        // ... rest of the body remains unchanged
      ),
    );
  }
}

Unfortunately, these changes did not fix the play area layout problems. I'll try another prompt:

Ok, but the game area still does not fill the entire screen.

GPT-4 answer:

Step 1: First, update the initGame() method in game_model.dart to accept Size screenSize as an argument:

void initGame(Size screenSize) {
  double screenWidth = screenSize.width;
  double screenHeight = screenSize.height;

  leftPaddle = Rect.fromLTWH(20, screenHeight / 2 - paddleHeight / 2, paddleWidth, paddleHeight);
  rightPaddle = Rect.fromLTWH(screenWidth - 40, screenHeight / 2 - paddleHeight / 2, paddleWidth, paddleHeight);
  ball = Rect.fromLTWH(screenWidth / 2 - ballSize / 2, screenHeight / 2 - ballSize / 2, ballSize, ballSize);
  ballVelocity = Offset(4, 4);
  isPlaying = false;
}

Step 1: Replace the body property of the Scaffold widget in the _PongGameState class with the following code:

body: LayoutBuilder(
  builder: (BuildContext context, BoxConstraints constraints) {
    final screenSize = Size(constraints.maxWidth, constraints.maxHeight);
    context.read<GameModel>().initGame(screenSize);

    return GestureDetector(
      onVerticalDragUpdate: (details) {
        context.read<GameModel>().updateLeftPaddle(details.delta.dy, screenSize.height);
      },
      child: CustomPaint(
        painter: PongPainter(context.watch<GameModel>()),
        child: Container(),
      ),
    );
  },
),

However, GPT proposals led to multiple problems in the existing code. With some further prompts and hints from my side, my wishes could finally be implemented.

ipad_PONG_v2_1200

Conclusion

GPT-4 and human programming skills complement each other, allowing for increased efficiency and innovation in software development. While GPT-4 can assist with many tasks, it is ultimately the human programmer who ensures the success of a project. Embracing the power of GPT-4 while continuing to hone one's own programming skills is the key to thriving in today's tech landscape.