Part 1 continued: ChatGPT Snake code

The chat app gives you a component that you still need to place inside a project. The snake-chatgpt App.jsx file shows what the generated code looks like after you move it into Vite.

The whole game sits in src/App.jsx. That makes the first version easy to paste and run, and it also gives you one file to review before asking for the next change.

State and constants

The component starts with a few constants and four pieces of React state:

import React, { useEffect, useState, useRef } from 'react';

const BOARD_SIZE = 20;
const INITIAL_SNAKE = [{ x: 8, y: 8 }];
const INITIAL_DIRECTION = { x: 1, y: 0 };
const SPEED = 200;

export default function SnakeGame() {
  const [snake, setSnake] = useState(INITIAL_SNAKE);
  const [food, setFood] = useState(generateFood);
  const [direction, setDirection] = useState(INITIAL_DIRECTION);
  const [gameOver, setGameOver] = useState(false);
  const boardRef = useRef(null);

This is a good first answer because the moving parts are visible. The snake, food, direction, and game-over flag are all state values, so React rerenders the board when one of them changes.

The boardRef is attached to the board later, but the file does not use it for any behavior. That is a normal thing to catch when reviewing generated code: the app can work while still carrying unused pieces.

Keyboard handling

The keyboard handler listens for arrow keys and blocks direct reversal. The guard prevents the snake from colliding with itself immediately after it grows.

useEffect(() => {
  const handleKeyDown = (e) => {
    switch (e.key) {
      case 'ArrowUp': if (direction.y === 0) setDirection({ x: 0, y: -1 }); break;
      case 'ArrowDown': if (direction.y === 0) setDirection({ x: 0, y: 1 }); break;
      case 'ArrowLeft': if (direction.x === 0) setDirection({ x: -1, y: 0 }); break;
      case 'ArrowRight': if (direction.x === 0) setDirection({ x: 1, y: 0 }); break;
      default: break;
    }
  };

The effect registers the listener and removes it when React reruns the effect or unmounts the component:

  window.addEventListener('keydown', handleKeyDown);
  return () => window.removeEventListener('keydown', handleKeyDown);
}, [direction]);

The dependency on direction means the handler always sees the latest direction. For this small game, that is easy to understand and good enough for the comparison.

Movement and collisions

The game loop runs every SPEED milliseconds while the game is active:

useEffect(() => {
  if (gameOver) return;
  const interval = setInterval(moveSnake, SPEED);
  return () => clearInterval(interval);
}, [snake, direction, gameOver]);

This effect is worth checking. It depends on snake, so React recreates the interval after each movement. That is acceptable in this demo, but an IDE assistant could later move the timing logic into a hook or use a functional state update.

The movement function calculates a new head and ends the game if the head hits a wall or the existing snake:

function moveSnake() {
  const newHead = { x: snake[0].x + direction.x, y: snake[0].y + direction.y };
  if (
    newHead.x < 0 || newHead.x >= BOARD_SIZE ||
    newHead.y < 0 || newHead.y >= BOARD_SIZE ||
    snake.some(segment => segment.x === newHead.x && segment.y === newHead.y)
  ) {
    setGameOver(true);
    return;
  }

After that, the function either grows the snake by keeping the new tail or moves normally by dropping the last segment:

  const newSnake = [newHead, ...snake];
  if (newHead.x === food.x && newHead.y === food.y) {
    setFood(generateFood);
  } else {
    newSnake.pop();
  }
  setSnake(newSnake);
}

The design is direct and readable. The limitation is that all behavior stays inside the component, which makes future tests harder unless you extract the game rules.

Food and restart

Food generation keeps picking random coordinates until it finds a cell that is not occupied by the snake:

function generateFood() {
  let newFood;
  do {
    newFood = {
      x: Math.floor(Math.random() * BOARD_SIZE),
      y: Math.floor(Math.random() * BOARD_SIZE),
    };
  } while (snake.some(segment => segment.x === newFood.x && segment.y === newFood.y));
  return newFood;
}

The restart function resets the original state:

function restartGame() {
  setSnake(INITIAL_SNAKE);
  setDirection(INITIAL_DIRECTION);
  setFood(generateFood);
  setGameOver(false);
}

There is no score in this version. That gives you a natural next prompt for a coding assistant: add score, show it above the board, and reset it when the game restarts.

Board rendering

The board is a CSS grid with 20 rows and 20 columns. Each cell decides whether it should look like snake, food, or empty space:

<div
  ref={boardRef}
  className="grid"
  style={{
    gridTemplateColumns: `repeat(${BOARD_SIZE}, 20px)`,
    gridTemplateRows: `repeat(${BOARD_SIZE}, 20px)`,
  }}
>

The render loop maps over all 400 cells:

{[...Array(BOARD_SIZE * BOARD_SIZE)].map((_, index) => {
  const x = index % BOARD_SIZE;
  const y = Math.floor(index / BOARD_SIZE);
  const isSnake = snake.some(segment => segment.x === x && segment.y === y);
  const isFood = food.x === x && food.y === y;
  return (
    <div
      key={index}
      className={`w-5 h-5 border border-gray-800 ${
        isSnake ? 'bg-green-500' : isFood ? 'bg-red-500' : 'bg-gray-700'
      }`}
    ></div>
  );
})}

The game-over UI appears only after collision:

{gameOver && (
  <div className="mt-4">
    <p className="text-red-500">Game Over</p>
    <button
      onClick={restartGame}
      className="mt-2 px-4 py-2 bg-blue-600 rounded hover:bg-blue-700"
    >
      Restart
    </button>
  </div>
)}

Run the app and test the basics before comparing tools further:

npm run dev

Use the arrow keys, hit a wall, restart, and eat food. That manual check is small, but it tells you whether the generated component is a usable baseline or only a code-shaped answer.

Starter leftovers

The snake-chatgpt/src/App.css file still contains default Vite starter styles such as .logo, .card, and .read-the-docs. The generated game does not use those classes.

That is worth noticing because chat apps often leave project cleanup to you. A coding assistant can help with that cleanup because it can look through which files and classes are still referenced.

Questions & Answers (0)

Sign in to ask questions