In diesem Blog-Beitrag geht es darum, wie man ein einfaches Pong-Browserspiel mit ChatGPT implementieren kann. Zunächst werden wir ganz kurz die Vor- und Nachteile der Nutzung von ChatGPT besprechen und dann praktisch den Code, welcher von ChatGPT generiert wurde, vorstellen.

Vorteile:

  1. Zeitersparnis: ChatGPT kann eine Menge Zeit sparen, da es eine schnelle und einfache Möglichkeit bietet, Texte oder Code-Blöcke zu generieren, ohne dass man selbst alles von Hand schreiben muss. Dies ist insbesondere für einfache Routineaufgaben sehr sinnvoll.
  2. Vielseitigkeit: ChatGPT kann in vielen verschiedenen Sprachen und Kontexten verwendet werden, was es zu einem vielseitigen Werkzeug macht. Es kann beispielsweise der Implementierung von Algorithmen und der Code-Dokumentation helfen, sowie bestehenden Code erklären.
  3. Anpassungsfähigkeit: ChatGPT kann an spezifische Anforderungen und Bedürfnisse angepasst werden und kontext-spezifischen Code erzeugen.

Nachteile

  1. Eingeschränkte Kontrolle: Da ChatGPT auf maschinellem Lernen basiert, hat man als Nutzer nicht die volle Kontrolle darüber, was generiert wird. Das Ergebnis kann manchmal unvorhersehbar sein und erfordert daher zusätzliche Überprüfungen.
  2. Begrenzte Antwortlänge: ChatGPT hat eine maximale Antwortlänge, was bei der Generierung von längeren Code-Abschnitten oder komplexen Texten ein Problem darstellen kann. In diesem Fall muss man ChatGPT bitten fortzufahren, was aber dazu führen kann, dass ChatGPT den Kontext „vergisst“ – was uns zum nächsten Nachteil führt:
  3. Eingeschränkte Fähigkeit zum Verständnis von Kontext: ChatGPT hat Schwierigkeiten beim Verständnis von Kontext und kann daher manchmal ungenaue oder unpassende Ergebnisse liefern. Es ist wichtig, dass man als Nutzer versteht, wie man ChatGPT am besten nutzt, um bestmögliche Ergebnisse zu erzielen. Idealerweise arbeitet man auch hier nach dem „Divide-and-Conquer“-Prinzip: Der Kontext sollte möglichst klein gehalten werden.

Praxisbeispiel

Kommen wir nun zum Praxis-Beispiel: Implementierung eines Pong-Browserspiels mit ChatGPT.

Ich habe ChatGPT gefragt, ob er mir ein Pong-Spiel für den Browser implementieren kann. Da der Code leider länger als die maximale Antwortlänge war, musste ich ChatGPT öfter bitten, fortzufahren. Außerdem habe ich ihm die Anweisung gegeben, wirklich nur den Code ohne Beschreibung auszugeben.

So sah die erste Antwort aus:

<!DOCTYPE html>
<html>
  <head>
    <title>Pong Game</title>
    <style>
      canvas {
        border: 1px solid black;
      }
    </style>
  </head>
  <body>
    <canvas id="canvas" width="600" height="400"></canvas>
    <script src="pong.js"></script>
  </body>
</html>
const canvas = document.getElementById("canvas");
const context = canvas.getContext("2d");

canvas.width = 800;
canvas.height = 600;

let player1 = {
  x: 20,
  y: canvas.height / 2 - 50,
  width: 10,
  height: 100,
  score: 0,
};

let player2 = {
  x: canvas.width - 30,
  y: canvas.height / 2 - 50,
  width: 10,
  height: 100,
  score: 0,
};

let ball = {
  x: canvas.width / 2,
  y: canvas.height / 2,
  radius: 10,
  speed: 7,
  dx: 7,
  dy: -7,
};

function drawNet() {
  context.fillStyle = "#FFF";
  for (let i = 0; i <= canvas.height; i += 15) {
    context.fillRect(canvas.width / 2 - 1, i, 2, 10);
  }
}

function drawRect(x, y, w, h, color) {
  context.fillStyle = color;
  context.fillRect(x, y, w, h);
}

function drawCircle(x, y, r, color) {
  context.fillStyle = color;
  context.beginPath();
  context.arc(x, y, r, 0, Math.PI * 2, false);
  context.closePath();
  context.fill();
}

function drawText(text, x, y, color) {
  context.fillStyle = color;
  context.font = "45px fantasy";
  context.fillText(text, x, y);
}

function draw() {
  drawRect(0, 0, canvas.width, canvas.height, "#000");

  drawRect(player1.x, player1.y, player1.width, player1.height, "#FFF");

  drawRect(
    player2.x,
    player2.y,
    player2.width,
    player2.height,
    "#FFF"
  );

  drawCircle(ball.x, ball.y, ball.radius, "#FFF");

  drawNet();
}

function ballWallCollision() {
  if (ball.y + ball.radius > canvas.height || ball.y - ball.radius < 0) {
    ball.dy = -ball.dy;
  }
}

function ballPaddleCollision(player) {
  if (
    ball.x + ball.radius > player.x &&
    ball.x - ball.radius < player.x + player.width &&
    ball.y + ball.radius > player.y &&
    ball.y - ball.radius < player.y + player.height
  ) {
    let collidePoint = ball.y - (player.y + player.height / 2);
    collidePoint = collidePoint / (player.height / 2);
    let angleRad = (Math.PI / 4) * collidePoint;
    let direction = ball.x + ball.radius < canvas.width / 2 ? 1 : -1;
    ball.dx = direction * ball.speed * Math.cos(angleRad);
    ball.dy = ball.speed * Math.sin(angleRad);
  }
}

function resetBall() {
  ball.x = canvas.width / 2;
  ball.y = canvas.height / 2;
  ball.speed = 7;
  ball.dx = ball.speed;
  ball.dy = -ball.speed;
}

function drawScore() {
  context.font = "32px Arial";
  context.fillText(player1.score, 100, 50);
  context.fillText(player2.score, canvas.width - 100, 50);
}

function checkGameOver() {
  if (player1.score === maxScore || player2.score === maxScore) {
    let winner = player1.score === maxScore ? "Player 1" : "Player 2";
    alert(`${winner} wins!`);
    player1.score = 0;
    player2.score = 0;
  }
}

function update() {
  ball.x += ball.dx;
  ball.y += ball.dy;

  ballWallCollision();

  ballPaddleCollision(player1);
  ballPaddleCollision(player2);

  if (ball.x - ball.radius < 0) {
    player2.score++;
    resetBall();
    } else if (ball.x + ball.radius > canvas.width) {
    player1.score++;
    resetBall();
  }

  checkGameOver();
}

function game() {
  update();
  draw();
}

let maxScore = 3;

canvas.addEventListener("mousemove", (event) => {
  let rect = canvas.getBoundingClientRect();
  player1.y = event.clientY - rect.top - player1.height / 2;
});

canvas.addEventListener("mousedown", () => {
  if (player1.score === maxScore || player2.score === maxScore) {
  player1.score = 0;
  player2.score = 0;
  }
});

setInterval(game, 1000 / 60);

Der Code ist prinzipiell lauffähig: Eine Game-Loop wurde implementiert, die Objekte werden gezeichnet, der Ball wird bewegt und es existiert eine Kollisionserkennung. Allerdings hat ChatGPT vergessen, die Bewegung der Paddle zu implementieren. Zusätzlich wurde zwar eine Funktion drawScore() implementiert, welche aber nirgends aufgerufen wird.

Hier ist es also notwendig, nachzubessern:

Beim Code fehlt noch das Bewegen der Paddle. Die Paddle sollen sich per Tastatur bewegen lassen. Bitte zeige nur die Stellen, wo Code geändert werden muss.
function movePaddle(evt) {
  let rect = canvas.getBoundingClientRect();
  if (evt.clientY - rect.top - player1.height / 2 > 0 && evt.clientY - rect.top + player1.height / 2 < canvas.height) {
    player1.y = evt.clientY - rect.top - player1.height / 2;
  }
}

function movePaddleUp() {
  player1.y -= 10;
}

function movePaddleDown() {
  player1.y += 10;
}

document.addEventListener("keydown", function (evt) {
  switch (evt.key) {
    case "w":
      movePaddleUp();
      break;
    case "s":
      movePaddleDown();
      break;
  }
});

Hier war ich anscheinend etwas zu ungenau – die Paddle-Bewegung für Spieler 1 wurde zwar implementiert, für Spieler 2 fehlt sie aber. Also weiter nachbessern:

Das gleiche nun bitte auch noch für Spieler 2. Dort sollen die Paddle mit "Pfeil oben" und "Pfeil unten bewegt" werden
function movePaddleUp(player) {
  player.y -= 10;
}

function movePaddleDown(player) {
  player.y += 10;
}

document.addEventListener("keydown", function (evt) {
  switch (evt.key) {
    case "w":
      movePaddleUp(player1);
      break;
    case "s":
      movePaddleDown(player1);
      break;
    case "ArrowUp":
      movePaddleUp(player2);
      break;
    case "ArrowDown":
      movePaddleDown(player2);
      break;
  }
});

Das sieht schon besser aus. Nun habe ich ChatGPT gebeten, noch einen Start-Button zu implementieren, mit welchem sich das Spiel zu Beginn und nach Spielende per Knopfdruck starten lässt. Auch hier sollte ChatGPT nur die relevanten Änderungen ausgeben:

<!DOCTYPE html>
<html>
  <head>
    <title>Pong Game</title>
    <style>
      canvas {
        border: 1px solid black;
      }
    </style>
  </head>
  <body>
    <canvas id="canvas" width="600" height="400"></canvas>
    <button id="startButton">Start</button>
    <script src="pong.js"></script>
  </body>
</html>
let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");

let startButton = document.getElementById("startButton");
startButton.addEventListener("click", startGame);

let gameStarted = false;

// Restlicher Code hier

function checkGameOver() {
  if (player1.score === maxScore || player2.score === maxScore) {
    let winner = player1.score === maxScore ? "Player 1" : "Player 2";
    alert(`${winner} wins!`);
    player1.score = 0;
    player2.score = 0;
    startButton.disabled = false;
    gameStarted = false;
    }
}

function startGame() {
  gameStarted = true;
  startButton.disabled = true;
}

function game() {
  if (!gameStarted) {
    return;
  }
  update();
  draw();
}

Nun ist das Spiel bereits benutzbar. Was die Usability und den Code selbst betrifft, sind wir allerdings noch etwas vom Ziel entfernt. Aber fragen wir doch ChatGPT selbst, was er am Code optimieren würde:

Die Bewegung der Schläger ist auf das Verschieben um 10 Pixel nach oben oder unten beschränkt. Das kann zu einer relativ langsamen Bewegung führen. Um die Reaktionsfähigkeit des Spiels zu verbessern, könnte man die Schläger schneller bewegen lassen, indem man den Schritt, um den sie sich bewegen, erhöht.

Das Spiel endet abrupt, sobald ein Spieler die maximale Punktzahl erreicht hat. Es könnte jedoch sinnvoller sein, den Spielern eine Chance zu geben, zu wissen, wer das letzte Spiel gewonnen hat, bevor das Spiel vollständig beendet wird. Eine Möglichkeit, dies zu tun, wäre, das Spiel für eine bestimmte Anzahl von Sekunden anzuhalten und den Namen des Gewinners auf dem Bildschirm anzuzeigen.

Der Code könnte besser organisiert werden, indem man einige der Funktionen in separate Dateien aufteilt. Zum Beispiel könnte man alle Funktionen, die das Zeichnen von Objekten auf dem Canvas betreffen, in eine Datei legen und sie von dort importieren. Das würde den Hauptcode übersichtlicher machen und die Wartbarkeit erhöhen.

Es sollte requestAnimationFrame() zur Aktualisierung der Game-Loop anstelle von setInterval oder setTimeout verwendet werden. requestAnimationFrame() ist effizienter und bietet eine bessere Synchronisation mit der Rendering-Pipeline des Browsers.

Das hört sich erstmal sinnvoll an. Der zweite Punkt ist allerdings nicht ganz korrket – es wird aktuell ein alert() nach Spielende aufgerufen. Das kann man aber definitiv benutzerfreundlicher implementieren.

Natürlich könnte das Design auch responsive sein, um auf unterschiedlichen Endgeräten sinnvoll spielbar zu sein, eine einheitliche Verwendung von „let“ bzw. „const“ wäre wünschenswert. Außerdem fehlen jegliche Kommentare.

Fazit

Zusammenfassend bietet ChatGPT sowohl Vor- als auch Nachteile bei der Generierung von Programmcode. Auf der positiven Seite kann es Zeit sparen, vielseitig eingesetzt werden und automatisierte Prozesse ermöglichen. Andererseits kann es Schwierigkeiten beim Verständnis von Kontext haben, begrenzte Antwortlängen aufweisen und eine eingeschränkte Kontrolle über das generierte Ergebnis bieten.

Um bestmögliche Ergebnisse zu erzielen, ist es daher wichtig, als Nutzer den Kontext klar zu definieren und ChatGPT durch gezielte Anfragen zu führen. Es ist auch hilfreich, das Ergebnis auf bestimmte Anforderungen und Bedürfnisse feinzutunen und das generierte Ergebnis sorgfältig zu überprüfen. Mit diesen Tipps kann ChatGPT ein wertvolles Werkzeug sein, um Texte und Code-Blöcke zu generieren und komplexe Aufgaben zu automatisieren.

Außerdem kann ChatGPT, wie man oben sieht, bestehenden Code analysieren und Optimierungen vorschlagen.

ChatGPT und ich werden also noch weiter am Code arbeiten und Euch auf dem Laufenden halten!