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. 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 first answer keeps the moving parts visible. The snake, food, direction, and game-over flag are all state values. React rerenders the board when one of them changes.

The boardRef is attached to the board later, but the file doesn't use it for any behavior. You catch this kind of thing when reviewing generated code: the app can work while still holding unused pieces.

Keyboard handling

The keyboard handler listens for arrow keys and blocks direct reversal. The guard prevents the snake from colliding with its own body 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's easy to follow 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 depends on snake, so React recreates the interval after each movement. That holds up in this demo, and 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, then 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.

Normal movement drops the last segment:

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

This code reads directly and stays easy to follow. All behavior sits 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 isn't 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);
}

This version has no score, which gives you a natural next prompt for a coding assistant. Ask it to add score, show it above the board, and reset it when the game restarts.

Board rendering

The board renders as 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. 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 doesn't use those classes.

Notice this, because chat apps often leave project cleanup to you. A coding assistant can help, since it can look through which files and classes are still referenced.

Questions & Answers

Sign up to ask questions, track your progress, and get access to other workshops · Already have an account? Sign in