Close Menu
    DevStackTipsDevStackTips
    • Home
    • News & Updates
      1. Tech & Work
      2. View All

      Top 10 Use Cases of Vibe Coding in Large-Scale Node.js Applications

      September 3, 2025

      Cloudsmith launches ML Model Registry to provide a single source of truth for AI models and datasets

      September 3, 2025

      Kong Acquires OpenMeter to Unlock AI and API Monetization for the Agentic Era

      September 3, 2025

      Microsoft Graph CLI to be retired

      September 2, 2025

      ‘Cronos: The New Dawn’ was by far my favorite experience at Gamescom 2025 — Bloober might have cooked an Xbox / PC horror masterpiece

      September 4, 2025

      ASUS built a desktop gaming PC around a mobile CPU — it’s an interesting, if flawed, idea

      September 4, 2025

      Hollow Knight: Silksong arrives on Xbox Game Pass this week — and Xbox’s September 1–7 lineup also packs in the horror. Here’s every new game.

      September 4, 2025

      The Xbox remaster that brought Gears to PlayStation just passed a huge milestone — “ending the console war” and proving the series still has serious pulling power

      September 4, 2025
    • Development
      1. Algorithms & Data Structures
      2. Artificial Intelligence
      3. Back-End Development
      4. Databases
      5. Front-End Development
      6. Libraries & Frameworks
      7. Machine Learning
      8. Security
      9. Software Engineering
      10. Tools & IDEs
      11. Web Design
      12. Web Development
      13. Web Security
      14. Programming Languages
        • PHP
        • JavaScript
      Featured

      Magento (Adobe Commerce) or Optimizely Configured Commerce: Which One to Choose

      September 4, 2025
      Recent

      Magento (Adobe Commerce) or Optimizely Configured Commerce: Which One to Choose

      September 4, 2025

      Updates from N|Solid Runtime: The Best Open-Source Node.js RT Just Got Better

      September 3, 2025

      Scale Your Business with AI-Powered Solutions Built for Singapore’s Digital Economy

      September 3, 2025
    • Operating Systems
      1. Windows
      2. Linux
      3. macOS
      Featured

      ‘Cronos: The New Dawn’ was by far my favorite experience at Gamescom 2025 — Bloober might have cooked an Xbox / PC horror masterpiece

      September 4, 2025
      Recent

      ‘Cronos: The New Dawn’ was by far my favorite experience at Gamescom 2025 — Bloober might have cooked an Xbox / PC horror masterpiece

      September 4, 2025

      ASUS built a desktop gaming PC around a mobile CPU — it’s an interesting, if flawed, idea

      September 4, 2025

      Hollow Knight: Silksong arrives on Xbox Game Pass this week — and Xbox’s September 1–7 lineup also packs in the horror. Here’s every new game.

      September 4, 2025
    • Learning Resources
      • Books
      • Cheatsheets
      • Tutorials & Guides
    Home»Development»How to Build a Snake Game using Phaser.js

    How to Build a Snake Game using Phaser.js

    August 30, 2025

    If you’ve ever wanted to make a small game that runs in the browser, Phaser.js is a great place to start. It’s a simple JavaScript library that helps you build interactive 2-D games that you can play in the browser.

    In this guide, you’ll learn what Phaser is and then use it to build the popular Snake game. The snake moves on a grid. It eats food to grow. It dies if it hits a wall or itself. The full project fits in two files, and the code is split into small blocks so it’s easy to follow.

    By the end, you’ll be able to understand and copy the code, run it, and tweak it. You’ll also learn why each part exists and how it fits into the Phaser way of doing things.

    Play the game here to get a feel for what you’ll be building.

    Table of Contents

    • What is Phaser.js?

    • Project Setup

    • Grid, Colors, and Game Config

    • Scene State and Helper Functions

    • Preload and Create

    • Initialize the Game

    • Reading Input Each Frame

    • Stepping the Snake, Eating, and Drawing

    • Spawning Food and Increasing Speed

    • Game Over and Restart

    • How the Whole Thing Fits Together

    • Useful Next Steps

    • Final Thoughts

    What is Phaser.js?

    Phaser is a free JavaScript library for 2D games. You write plain JavaScript and let Phaser do the heavy lifting. You don’t need a build system or a game engine installer. You can start with a single HTML file and one JavaScript file.

    Phaser organizes code into scenes. A scene has three common steps. You load assets in preload, you set up images and variables in create, and you update your game each frame in update. That small loop is the core of most arcade games.

    Now let’s setup the project.

    Project Setup

    Create a folder with two files named index.html and main.js. The HTML page loads Phaser from a CDN and then loads your script.

    <span class="hljs-comment"><!-- index.html --></span>
    <span class="hljs-meta"><!DOCTYPE <span class="hljs-meta-keyword">html</span>></span>
    <span class="hljs-tag"><<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>></span>
      <span class="hljs-tag"><<span class="hljs-name">head</span>></span>
        <span class="hljs-tag"><<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span> /></span>
        <span class="hljs-tag"><<span class="hljs-name">title</span>></span>Phaser Snake<span class="hljs-tag"></<span class="hljs-name">title</span>></span>
        <span class="hljs-tag"><<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1.0"</span> /></span>
        <span class="hljs-tag"><<span class="hljs-name">style</span>></span><span class="css"><span class="hljs-selector-tag">body</span> { <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span>; <span class="hljs-attribute">background</span>: <span class="hljs-number">#111</span>; }</span><span class="hljs-tag"></<span class="hljs-name">style</span>></span>
      <span class="hljs-tag"></<span class="hljs-name">head</span>></span>
      <span class="hljs-tag"><<span class="hljs-name">body</span>></span>
        <span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"game"</span>></span><span class="hljs-tag"></<span class="hljs-name">div</span>></span>
        <span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://cdn.jsdelivr.net/npm/phaser@3/dist/phaser.min.js"</span>></span><span class="hljs-tag"></<span class="hljs-name">script</span>></span>
        <span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"main.js"</span>></span><span class="hljs-tag"></<span class="hljs-name">script</span>></span>
      <span class="hljs-tag"></<span class="hljs-name">body</span>></span>
    <span class="hljs-tag"></<span class="hljs-name">html</span>></span>
    

    This page adds a container div and includes Phaser 3 from jsDelivr. Your main.js file will create the game and attach it to that div.

    Grid, Colors, and Game Config

    Snake is easiest on a grid. You choose a tile size and a number of tiles wide and high. You also define colors for the background, the snake, and the food. Then you create a Phaser game config that points to your scene functions.

    <span class="hljs-comment">// main.js (part 1)</span>
    
    <span class="hljs-comment">// Size of one grid tile in pixels</span>
    <span class="hljs-keyword">const</span> TILE = <span class="hljs-number">16</span>;
    
    <span class="hljs-comment">// Number of tiles across (columns) and down (rows)</span>
    <span class="hljs-comment">// Game area = 40 * 16px wide (640px) and 30 * 16px tall (480px)</span>
    <span class="hljs-keyword">const</span> COLS = <span class="hljs-number">40</span>;                 
    <span class="hljs-keyword">const</span> ROWS = <span class="hljs-number">30</span>;                 
    
    <span class="hljs-comment">// Total pixel width and height of the game canvas</span>
    <span class="hljs-keyword">const</span> WIDTH = COLS * TILE;
    <span class="hljs-keyword">const</span> HEIGHT = ROWS * TILE;
    
    <span class="hljs-comment">// Colors for background, snake head, snake body, and food</span>
    <span class="hljs-keyword">const</span> COLORS = {
      <span class="hljs-attr">bg</span>: <span class="hljs-number">0x1d1d1d</span>,   <span class="hljs-comment">// dark gray background</span>
      <span class="hljs-attr">head</span>: <span class="hljs-number">0x30c452</span>, <span class="hljs-comment">// bright green head</span>
      <span class="hljs-attr">body</span>: <span class="hljs-number">0x2aa04a</span>, <span class="hljs-comment">// darker green body</span>
      <span class="hljs-attr">food</span>: <span class="hljs-number">0xe94f37</span>, <span class="hljs-comment">// red food</span>
    };
    
    <span class="hljs-comment">// Directions represented as x and y offsets on the grid</span>
    <span class="hljs-comment">// For example, moving left means x decreases by 1, y stays the same</span>
    <span class="hljs-keyword">const</span> DIR = {
      <span class="hljs-attr">left</span>:  { <span class="hljs-attr">x</span>: <span class="hljs-number">-1</span>, <span class="hljs-attr">y</span>:  <span class="hljs-number">0</span>, <span class="hljs-attr">name</span>: <span class="hljs-string">'left'</span>  },
      <span class="hljs-attr">right</span>: { <span class="hljs-attr">x</span>:  <span class="hljs-number">1</span>, <span class="hljs-attr">y</span>:  <span class="hljs-number">0</span>, <span class="hljs-attr">name</span>: <span class="hljs-string">'right'</span> },
      <span class="hljs-attr">up</span>:    { <span class="hljs-attr">x</span>:  <span class="hljs-number">0</span>, <span class="hljs-attr">y</span>: <span class="hljs-number">-1</span>, <span class="hljs-attr">name</span>: <span class="hljs-string">'up'</span>    },
      <span class="hljs-attr">down</span>:  { <span class="hljs-attr">x</span>:  <span class="hljs-number">0</span>, <span class="hljs-attr">y</span>:  <span class="hljs-number">1</span>, <span class="hljs-attr">name</span>: <span class="hljs-string">'down'</span>  },
    };
    
    <span class="hljs-comment">// Phaser game configuration</span>
    <span class="hljs-comment">// - type: Phaser will use WebGL if possible, otherwise Canvas</span>
    <span class="hljs-comment">// - parent: attach game canvas to <div id="game"></span>
    <span class="hljs-comment">// - width/height: set canvas size</span>
    <span class="hljs-comment">// - backgroundColor: dark background from COLORS</span>
    <span class="hljs-comment">// - scene: defines which functions run during preload, create, and update</span>
    <span class="hljs-keyword">const</span> config = {
      <span class="hljs-attr">type</span>: Phaser.AUTO,
      <span class="hljs-attr">parent</span>: <span class="hljs-string">'game'</span>,
      <span class="hljs-attr">width</span>: WIDTH,
      <span class="hljs-attr">height</span>: HEIGHT,
      <span class="hljs-attr">backgroundColor</span>: COLORS.bg,
      <span class="hljs-attr">scene</span>: { preload, create, update }
    };
    
    <span class="hljs-comment">// Create a new Phaser game with the config</span>
    <span class="hljs-keyword">new</span> Phaser.Game(config);
    

    The config tells Phaser to create a canvas, set its size, and use your scene functions. Phaser.AUTO selects WebGL if possible and falls back to Canvas.

    Scene State and Helper Functions

    You need to store the snake’s body as grid cells, the rectangles that draw those cells, the direction of travel, the queued input, the food cell, the score, and the movement timer. A few helper functions keep the math clean.

    <span class="hljs-comment">// main.js (part 2)</span>
    
    <span class="hljs-comment">// Snake state</span>
    <span class="hljs-keyword">let</span> snake;           <span class="hljs-comment">// Array of grid cells [{x, y}, ...]; index 0 = head</span>
    <span class="hljs-keyword">let</span> snakeRects;      <span class="hljs-comment">// Array of Phaser rectangles drawn at snake cell positions</span>
    <span class="hljs-keyword">let</span> direction;       <span class="hljs-comment">// Current direction of snake movement (object from DIR)</span>
    <span class="hljs-keyword">let</span> nextDirection;   <span class="hljs-comment">// Next direction chosen by player input (applied on step)</span>
    <span class="hljs-keyword">let</span> food;            <span class="hljs-comment">// Current food cell {x, y}</span>
    <span class="hljs-keyword">let</span> score = <span class="hljs-number">0</span>;       <span class="hljs-comment">// Current score count</span>
    <span class="hljs-keyword">let</span> scoreText;       <span class="hljs-comment">// Phaser text object that displays the score</span>
    <span class="hljs-keyword">let</span> moveEvent;       <span class="hljs-comment">// Phaser timer event to move snake at fixed intervals</span>
    <span class="hljs-keyword">let</span> speedMs = <span class="hljs-number">130</span>;   <span class="hljs-comment">// Delay in milliseconds between moves (lower = faster)</span>
    
    <span class="hljs-comment">// Input state</span>
    <span class="hljs-keyword">let</span> cursors;         <span class="hljs-comment">// Phaser helper object for arrow keys</span>
    <span class="hljs-keyword">let</span> spaceKey;        <span class="hljs-comment">// Phaser Key object for Space bar (restart the game)</span>
    
    <span class="hljs-comment">/**
     * Convert a grid cell (x,y) to its pixel center (px,py) on the canvas.
     * Example: (0,0) -> (8,8) if TILE=16. Ensures rectangles are centered.
     */</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">gridToPixelCenter</span>(<span class="hljs-params">x, y</span>) </span>{
      <span class="hljs-keyword">return</span> { <span class="hljs-attr">px</span>: x * TILE + TILE / <span class="hljs-number">2</span>, <span class="hljs-attr">py</span>: y * TILE + TILE / <span class="hljs-number">2</span> };
    }
    
    <span class="hljs-comment">/**
     * Pick a random grid cell that is not occupied by any cell in excludeCells.
     * - Creates a Set of occupied cells as "x,y" strings for fast lookup.
     * - Keeps generating random cells until it finds a free one.
     * Used to place food so it never spawns on the snake.
     */</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">randomFreeCell</span>(<span class="hljs-params">excludeCells</span>) </span>{
      <span class="hljs-keyword">const</span> occupied = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Set</span>(excludeCells.map(<span class="hljs-function"><span class="hljs-params">c</span> =></span> <span class="hljs-string">`<span class="hljs-subst">${c.x}</span>,<span class="hljs-subst">${c.y}</span>`</span>));
      <span class="hljs-keyword">while</span> (<span class="hljs-literal">true</span>) {
        <span class="hljs-keyword">const</span> x = <span class="hljs-built_in">Math</span>.floor(<span class="hljs-built_in">Math</span>.random() * COLS);
        <span class="hljs-keyword">const</span> y = <span class="hljs-built_in">Math</span>.floor(<span class="hljs-built_in">Math</span>.random() * ROWS);
        <span class="hljs-keyword">if</span> (!occupied.has(<span class="hljs-string">`<span class="hljs-subst">${x}</span>,<span class="hljs-subst">${y}</span>`</span>)) <span class="hljs-keyword">return</span> { x, y };
      }
    }
    
    <span class="hljs-comment">/**
     * Check if direction 'a' is exactly the opposite of direction 'b'.
     * Example: left vs right, or up vs down.
     * This prevents the snake from instantly turning 180° into itself.
     */</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">isOpposite</span>(<span class="hljs-params">a, b</span>) </span>{
      <span class="hljs-keyword">return</span> a.x === -b.x && a.y === -b.y;
    }
    

    gridToPixelCenter converts a grid cell to the center point in pixels so rectangles line up. randomFreeCell finds a cell not used by the snake. isOpposite helps block instant reversals that would cause a crash.

    Preload and Create

    This game uses simple vector rectangles, so there are no images to load. You still define the scene functions since Phaser calls them by name from your config.

    <span class="hljs-comment">// main.js (part 3)</span>
    
    <span class="hljs-comment">/**
     * preload()
     * Runs once before the game starts.
     * Used for loading images, sounds, and other assets.
     * In this version, we use simple colored rectangles (no assets needed).
     */</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">preload</span>(<span class="hljs-params"></span>) </span>{
      <span class="hljs-comment">// No assets to load in this version.</span>
    }
    
    <span class="hljs-comment">/**
     * create()
     * Runs once after preload. Sets up the game scene.
     * - Prepares keyboard input
     * - Calls initGame() to build the snake, food, score UI, and start movement
     */</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">create</span>(<span class="hljs-params"></span>) </span>{
      <span class="hljs-comment">// Phaser helper that gives us arrow key input (up, down, left, right)</span>
      cursors = <span class="hljs-built_in">this</span>.input.keyboard.createCursorKeys();
    
      <span class="hljs-comment">// Register the Space bar key to restart the game later</span>
      spaceKey = <span class="hljs-built_in">this</span>.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE);
    
      <span class="hljs-comment">// Initialize the game state (snake, food, score, timer)</span>
      <span class="hljs-comment">// Using call(this) so that initGame runs in the context of the scene</span>
      initGame.call(<span class="hljs-built_in">this</span>);
    }
    

    The create step sets up keyboard input. It then calls initGame to build the first snake, place food, and start the timer. The call(this) ensures the helper function can use the scene’s this.

    Initialize the Game

    On a fresh start or a restart, you need to clear old timers and shapes, reset the score, build a short snake in the middle, draw it, place food, and start the movement loop.

    <span class="hljs-comment">// main.js (part 4)</span>
    
    <span class="hljs-comment">/**
     * initGame()
     * Called when the game first starts or after pressing Space to restart.
     * - Clears old state (snake, food, timers)
     * - Resets score
     * - Creates a new snake in the center of the grid
     * - Spawns the first food
     * - Sets up score text
     * - Starts the timed loop that moves the snake
     */</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">initGame</span>(<span class="hljs-params"></span>) </span>{
      <span class="hljs-comment">// If an old movement timer exists, stop it (avoid multiple timers running)</span>
      <span class="hljs-keyword">if</span> (moveEvent) moveEvent.remove(<span class="hljs-literal">false</span>);
    
      <span class="hljs-comment">// If old snake rectangles exist, destroy them to clean the screen</span>
      <span class="hljs-keyword">if</span> (snakeRects) snakeRects.forEach(<span class="hljs-function"><span class="hljs-params">r</span> =></span> r.destroy());
    
      <span class="hljs-comment">// Reset the score and snake direction</span>
      score = <span class="hljs-number">0</span>;
      direction = DIR.right;     <span class="hljs-comment">// Snake starts moving to the right</span>
      nextDirection = DIR.right; <span class="hljs-comment">// Player input queue also points right</span>
    
      <span class="hljs-comment">// Find the starting position near the center of the grid</span>
      <span class="hljs-keyword">const</span> startX = <span class="hljs-built_in">Math</span>.floor(COLS / <span class="hljs-number">2</span>);
      <span class="hljs-keyword">const</span> startY = <span class="hljs-built_in">Math</span>.floor(ROWS / <span class="hljs-number">2</span>);
    
      <span class="hljs-comment">// Snake starts with 3 segments, head + 2 body pieces</span>
      snake = [
        { <span class="hljs-attr">x</span>: startX,     <span class="hljs-attr">y</span>: startY },     <span class="hljs-comment">// head (middle)</span>
        { <span class="hljs-attr">x</span>: startX - <span class="hljs-number">1</span>, <span class="hljs-attr">y</span>: startY },     <span class="hljs-comment">// body segment left of head</span>
        { <span class="hljs-attr">x</span>: startX - <span class="hljs-number">2</span>, <span class="hljs-attr">y</span>: startY },     <span class="hljs-comment">// tail further left</span>
      ];
    
      <span class="hljs-comment">// Create rectangle objects in Phaser to visually draw the snake</span>
      snakeRects = snake.map(<span class="hljs-function">(<span class="hljs-params">cell, i</span>) =></span> {
        <span class="hljs-keyword">const</span> { px, py } = gridToPixelCenter(cell.x, cell.y); <span class="hljs-comment">// convert grid to pixel center</span>
        <span class="hljs-keyword">const</span> color = i === <span class="hljs-number">0</span> ? COLORS.head : COLORS.body;    <span class="hljs-comment">// head is a brighter green</span>
        <span class="hljs-keyword">const</span> rect = <span class="hljs-built_in">this</span>.add.rectangle(px, py, TILE - <span class="hljs-number">2</span>, TILE - <span class="hljs-number">2</span>, color); <span class="hljs-comment">// slightly smaller for spacing</span>
        rect.setOrigin(<span class="hljs-number">0.5</span>, <span class="hljs-number">0.5</span>);                             <span class="hljs-comment">// center anchor point</span>
        <span class="hljs-keyword">return</span> rect;
      });
    
      <span class="hljs-comment">// Spawn food at a random free cell (not overlapping snake)</span>
      food = randomFreeCell(snake);
      <span class="hljs-keyword">const</span> { px, py } = gridToPixelCenter(food.x, food.y);
    
      <span class="hljs-comment">// If food already exists from a previous run, remove it first</span>
      <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.foodRect) <span class="hljs-built_in">this</span>.foodRect.destroy();
    
      <span class="hljs-comment">// Draw the new food as a red rectangle</span>
      <span class="hljs-built_in">this</span>.foodRect = <span class="hljs-built_in">this</span>.add.rectangle(px, py, TILE - <span class="hljs-number">2</span>, TILE - <span class="hljs-number">2</span>, COLORS.food);
    
      <span class="hljs-comment">// If score text does not exist yet, create it</span>
      <span class="hljs-comment">// Otherwise (on restart), just reset its value</span>
      <span class="hljs-keyword">if</span> (!scoreText) {
        scoreText = <span class="hljs-built_in">this</span>.add.text(<span class="hljs-number">8</span>, <span class="hljs-number">6</span>, <span class="hljs-string">'Score: 0'</span>, { <span class="hljs-attr">fontFamily</span>: <span class="hljs-string">'monospace'</span>, <span class="hljs-attr">fontSize</span>: <span class="hljs-number">18</span>, <span class="hljs-attr">color</span>: <span class="hljs-string">'#fff'</span> });
        <span class="hljs-built_in">this</span>.add.text(<span class="hljs-number">8</span>, <span class="hljs-number">28</span>, <span class="hljs-string">'Arrows to move. Space to restart.'</span>, { <span class="hljs-attr">fontFamily</span>: <span class="hljs-string">'monospace'</span>, <span class="hljs-attr">fontSize</span>: <span class="hljs-number">14</span>, <span class="hljs-attr">color</span>: <span class="hljs-string">'#aaa'</span> });
      } <span class="hljs-keyword">else</span> {
        scoreText.setText(<span class="hljs-string">'Score: 0'</span>);
      }
    
      <span class="hljs-comment">// Reset speed and create a repeating timer</span>
      <span class="hljs-comment">// Every "speedMs" milliseconds, stepSnake() will run to move the snake</span>
      speedMs = <span class="hljs-number">130</span>;
      moveEvent = <span class="hljs-built_in">this</span>.time.addEvent({
        <span class="hljs-attr">delay</span>: speedMs,
        <span class="hljs-attr">loop</span>: <span class="hljs-literal">true</span>,
        <span class="hljs-attr">callback</span>: <span class="hljs-function">() =></span> stepSnake.call(<span class="hljs-built_in">this</span>) <span class="hljs-comment">// .call(this) keeps Phaser scene context</span>
      });
    
      <span class="hljs-comment">// If a "Game Over" message exists from the last run, remove it</span>
      <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.gameOverText) {
        <span class="hljs-built_in">this</span>.gameOverText.destroy();
        <span class="hljs-built_in">this</span>.gameOverText = <span class="hljs-literal">null</span>;
      }
    }
    

    The rectangles are one or two pixels smaller than the tile so you get a neat gap between cells. The time event calls stepSnake on a fixed rhythm. This rhythm is separate from the frame rate, which keeps movement stable across machines.

    Reading Input Each Frame

    You will read the arrow keys in update. You don’t move the snake here. You only set the next direction. Movement happens on the timer so the game has a steady pace.

    <span class="hljs-comment">// main.js (part 5)</span>
    
    <span class="hljs-comment">/**
     * update()
     * This runs every frame (Phaser’s game loop).
     * - Reads player input from arrow keys
     * - Updates "nextDirection" so the snake will turn on the next step
     * - Listens for Space bar press to restart the game if it’s over
     */</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">update</span>(<span class="hljs-params"></span>) </span>{
      <span class="hljs-comment">// Check if LEFT arrow is pressed AND it’s not the opposite of current direction</span>
      <span class="hljs-keyword">if</span> (cursors.left.isDown && !isOpposite(DIR.left, direction)) {
        nextDirection = DIR.left;
    
      <span class="hljs-comment">// Check if RIGHT arrow is pressed</span>
      } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (cursors.right.isDown && !isOpposite(DIR.right, direction)) {
        nextDirection = DIR.right;
    
      <span class="hljs-comment">// Check if UP arrow is pressed</span>
      } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (cursors.up.isDown && !isOpposite(DIR.up, direction)) {
        nextDirection = DIR.up;
    
      <span class="hljs-comment">// Check if DOWN arrow is pressed</span>
      } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (cursors.down.isDown && !isOpposite(DIR.down, direction)) {
        nextDirection = DIR.down;
      }
    
      <span class="hljs-comment">// If the game is over (a "Game Over" text exists)</span>
      <span class="hljs-comment">// AND the Space bar was just pressed → restart the game</span>
      <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.gameOverText && Phaser.Input.Keyboard.JustDown(spaceKey)) {
        initGame.call(<span class="hljs-built_in">this</span>); <span class="hljs-comment">// Reset everything (snake, food, score, timer)</span>
      }
    }
    

    This pattern prevents the snake from skipping cells if the frame rate spikes. It also makes the game feel fair. Your key presses get picked up, but the body moves on the beat set by the timer.

    Stepping the Snake, Eating, and Drawing

    This function is the heart of the game. It picks up the queued input, computes the next head cell, checks collisions, moves or grows the snake, updates the score, and refreshes colors.

    <span class="hljs-comment">// main.js (part 6)</span>
    
    <span class="hljs-comment">/**
     * stepSnake()
     * This function runs every "tick" (based on the timer).
     * - Moves the snake forward by one cell
     * - Checks for collisions (wall or self)
     * - Handles eating food (grow + score)
     * - Updates the snake's rectangles on the screen
     */</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">stepSnake</span>(<span class="hljs-params"></span>) </span>{
      <span class="hljs-comment">// Apply the direction chosen in update() (queued by player input)</span>
      direction = nextDirection;
    
      <span class="hljs-comment">// Get the current head of the snake</span>
      <span class="hljs-keyword">const</span> head = snake[<span class="hljs-number">0</span>];
    
      <span class="hljs-comment">// Create a new head position by moving one cell in the current direction</span>
      <span class="hljs-keyword">const</span> newHead = { <span class="hljs-attr">x</span>: head.x + direction.x, <span class="hljs-attr">y</span>: head.y + direction.y };
    
      <span class="hljs-comment">// === Collision Check #1: Wall ===</span>
      <span class="hljs-comment">// If the new head is outside the grid, the game ends</span>
      <span class="hljs-keyword">if</span> (newHead.x < <span class="hljs-number">0</span> || newHead.x >= COLS || newHead.y < <span class="hljs-number">0</span> || newHead.y >= ROWS) {
        <span class="hljs-keyword">return</span> endGame.call(<span class="hljs-built_in">this</span>);
      }
    
      <span class="hljs-comment">// === Collision Check #2: Self ===</span>
      <span class="hljs-comment">// If the new head overlaps any snake cell, the game ends</span>
      <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i < snake.length; i++) {
        <span class="hljs-keyword">if</span> (snake[i].x === newHead.x && snake[i].y === newHead.y) {
          <span class="hljs-keyword">return</span> endGame.call(<span class="hljs-built_in">this</span>);
        }
      }
    
      <span class="hljs-comment">// === Check if food was eaten ===</span>
      <span class="hljs-keyword">const</span> ate = newHead.x === food.x && newHead.y === food.y;
    
      <span class="hljs-comment">// Add the new head cell to the front of the snake array</span>
      snake.unshift(newHead);
    
      <span class="hljs-keyword">if</span> (!ate) {
        <span class="hljs-comment">// Case: Snake did NOT eat food → keep length the same</span>
        <span class="hljs-comment">// Remove last cell from snake array (tail)</span>
        snake.pop();
    
        <span class="hljs-comment">// Reuse the last rectangle object for performance</span>
        <span class="hljs-keyword">const</span> tailRect = snakeRects.pop();
        <span class="hljs-keyword">const</span> { px, py } = gridToPixelCenter(newHead.x, newHead.y);
        tailRect.setPosition(px, py);       <span class="hljs-comment">// Move it to the new head position</span>
        snakeRects.unshift(tailRect);       <span class="hljs-comment">// Put it at the front of the rectangle list</span>
      } <span class="hljs-keyword">else</span> {
        <span class="hljs-comment">// Case: Snake DID eat food → grow longer</span>
        <span class="hljs-comment">// Create a new rectangle for the new head (since length increases)</span>
        <span class="hljs-keyword">const</span> { px, py } = gridToPixelCenter(newHead.x, newHead.y);
        <span class="hljs-keyword">const</span> headRect = <span class="hljs-built_in">this</span>.add.rectangle(px, py, TILE - <span class="hljs-number">2</span>, TILE - <span class="hljs-number">2</span>, COLORS.head);
        snakeRects.unshift(headRect);
    
        <span class="hljs-comment">// Increase score and update text</span>
        score += <span class="hljs-number">10</span>;
        scoreText.setText(<span class="hljs-string">`Score: <span class="hljs-subst">${score}</span>`</span>);
    
        <span class="hljs-comment">// Place new food somewhere else</span>
        placeFood.call(<span class="hljs-built_in">this</span>);
    
        <span class="hljs-comment">// Speed up slightly as difficulty curve</span>
        maybeSpeedUp.call(<span class="hljs-built_in">this</span>);
      }
    
      <span class="hljs-comment">// === Update Colors ===</span>
      <span class="hljs-comment">// Ensure only index 0 is drawn as the "head" (bright green),</span>
      <span class="hljs-comment">// and the next segment becomes part of the "body"</span>
      <span class="hljs-keyword">if</span> (snakeRects[<span class="hljs-number">1</span>]) snakeRects[<span class="hljs-number">1</span>].setFillStyle(COLORS.body);
      snakeRects[<span class="hljs-number">0</span>].setFillStyle(COLORS.head);
    }
    

    The movement rule is simple. Add a head in the current direction. If you did not eat, remove the tail. If you ate, keep the tail to grow by one. To draw this, you reuse the last rectangle when you only move. You move it to the head’s new pixel location and put it at the front of the list. When you grow, you create a new rectangle for the new head.

    Spawning Food and Increasing Speed

    After eating, you need to place food in a new free cell. You can also nudge the speed so the game gets harder over time.

    <span class="hljs-comment">// main.js (part 7)</span>
    
    <span class="hljs-comment">/**
     * placeFood()
     * Spawns food at a new random cell that is NOT part of the snake.
     * - Uses randomFreeCell() to avoid collisions with the snake
     * - Moves the existing red rectangle (foodRect) to the new spot
     */</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">placeFood</span>(<span class="hljs-params"></span>) </span>{
      <span class="hljs-comment">// Pick a random free cell on the grid (not occupied by the snake)</span>
      food = randomFreeCell(snake);
    
      <span class="hljs-comment">// Convert that cell into pixel coordinates</span>
      <span class="hljs-keyword">const</span> { px, py } = gridToPixelCenter(food.x, food.y);
    
      <span class="hljs-comment">// Move the existing food rectangle to the new position</span>
      <span class="hljs-built_in">this</span>.foodRect.setPosition(px, py);
    }
    
    <span class="hljs-comment">/**
     * maybeSpeedUp()
     * Makes the game a little harder each time food is eaten.
     * - Decreases the move delay (snake moves faster)
     * - Restarts the timer with the new speed
     * - Stops speeding up once a lower bound (70ms) is reached
     */</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">maybeSpeedUp</span>(<span class="hljs-params"></span>) </span>{
      <span class="hljs-comment">// Only speed up if current speed is above the minimum threshold</span>
      <span class="hljs-keyword">if</span> (speedMs > <span class="hljs-number">70</span>) {
        <span class="hljs-comment">// Make the snake faster by reducing the delay</span>
        speedMs -= <span class="hljs-number">3</span>;
    
        <span class="hljs-comment">// Remove the old movement timer</span>
        moveEvent.remove(<span class="hljs-literal">false</span>);
    
        <span class="hljs-comment">// Create a new timer with the updated speed</span>
        moveEvent = <span class="hljs-built_in">this</span>.time.addEvent({
          <span class="hljs-attr">delay</span>: speedMs,            <span class="hljs-comment">// shorter delay = faster movement</span>
          <span class="hljs-attr">loop</span>: <span class="hljs-literal">true</span>,                <span class="hljs-comment">// repeat forever until game over</span>
          <span class="hljs-attr">callback</span>: <span class="hljs-function">() =></span> stepSnake.call(<span class="hljs-built_in">this</span>) <span class="hljs-comment">// keep "this" as the scene</span>
        });
      }
    }
    

    There is a lower bound for speed so the game does not become unreadable. You can tune these numbers to match the feel you want.

    Game Over and Restart

    When the snake hits a wall or itself, the run ends. You stop the timer and show a message. Pressing space restarts the game.

    <span class="hljs-comment">// main.js (part 8)</span>
    
    <span class="hljs-comment">/**
     * endGame()
     * Called when the snake hits a wall or itself.
     * - Stops the movement timer (snake no longer moves)
     * - Displays a "Game Over" message with the final score
     * - Waits for the player to press Space (handled in update()) to restart
     */</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">endGame</span>(<span class="hljs-params"></span>) </span>{
      <span class="hljs-comment">// Stop the movement timer so the snake no longer steps forward</span>
      moveEvent.remove(<span class="hljs-literal">false</span>);
    
      <span class="hljs-comment">// Define text style for the "Game Over" message</span>
      <span class="hljs-keyword">const</span> style = {
        <span class="hljs-attr">fontFamily</span>: <span class="hljs-string">'monospace'</span>,
        <span class="hljs-attr">fontSize</span>: <span class="hljs-number">28</span>,
        <span class="hljs-attr">color</span>: <span class="hljs-string">'#fff'</span>,
        <span class="hljs-attr">align</span>: <span class="hljs-string">'center'</span>
      };
    
      <span class="hljs-comment">// Message shows "Game Over", final score, and restart instructions</span>
      <span class="hljs-keyword">const</span> msg = <span class="hljs-string">`Game OvernScore: <span class="hljs-subst">${score}</span>nPress Space to Restart`</span>;
    
      <span class="hljs-comment">// Add text to the center of the screen</span>
      <span class="hljs-comment">// .setOrigin(0.5, 0.5) makes the text anchor at its center</span>
      <span class="hljs-built_in">this</span>.gameOverText = <span class="hljs-built_in">this</span>.add.text(WIDTH / <span class="hljs-number">2</span>, HEIGHT / <span class="hljs-number">2</span>, msg, style).setOrigin(<span class="hljs-number">0.5</span>, <span class="hljs-number">0.5</span>);
    }
    

    The update function you wrote earlier listens for space using JustDown, so the restart happens only once per key press.

    How the Whole Thing Fits Together

    You now have the full loop of a Phaser scene. The preload is empty in this version because rectangles do not need assets. The create step connects keyboard input and calls initGame. The timer created in initGame keeps time for movement.

    The update step reads keys every frame and sets a future direction. The stepSnake function runs on the timer. It moves the head, checks for a crash, handles growth, reuses shapes for performance, and updates the score. When the run ends, endGame stops the timer and shows a clear message. A single key press calls initGame to start fresh.

    This style maps well to many other small games. If you can express your game state as data and move it on a schedule, you can draw it with simple shapes or sprites. Phaser’s time events give you a clean heartbeat. Its input system gives you easy key handling. Its drawing API makes it quick to show rectangles, text, or images.

    Useful Next Steps

    There are many small features you can add without changing the core. You can store a high score in localStorage. You can add simple sounds when you eat or when the game ends by loading audio in preload and calling this.sound.play in the right places.

    You can make the world wrap at the edges by replacing the wall check with modulo math so the snake appears on the opposite side. You can theme the game by swapping rectangle colors or replacing rectangles with images.

    Each of these additions builds on the same base. Keep the grid logic simple. Keep the state in clear arrays and objects. Move on a fixed timer. Draw based on the state. That’s the pattern.

    Final Thoughts

    You started with a blank page and ended with a working browser game. You set up Phaser, built a scene, and used a timer to drive movement. You learned how to handle input in a safe way, how to grow the snake, how to detect collisions, and how to draw with rectangles. You kept the code tidy with small helper functions and clear names.

    From here, you can branch out to other grid games like Tetris or Minesweeper, or you can try a different style like Pong or Breakout. The structure will be similar. A scene to set things up, a timer or physics step to move things along, and a few rules that define the fun. That’s the beauty of Phaser for beginners.

    If you’re into online gaming, GameBoost is the place to be. Discover GTA Modded Accounts packed with exclusive upgrades. You’ll also find accounts for other fan-favorite titles like Fortnite, Grow a Garden, Clash of Clans, and more – all in one trusted marketplace.

    Source: freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More 

    Facebook Twitter Reddit Email Copy Link
    Previous ArticleHow to Debug Kubernetes Pods with Traceloop: A Complete Beginner’s Guide
    Next Article How to use AI as an accelerator, not a crutch, with freelance engineer Ankur Tyagi [Podcast #186]

    Related Posts

    Development

    How to Make Bluetooth on Android More Reliable

    September 4, 2025
    Development

    Learn Mandarin Chinese for Beginners – Full HSK 1 Level

    September 4, 2025
    Leave A Reply Cancel Reply

    For security, use of Google's reCAPTCHA service is required which is subject to the Google Privacy Policy and Terms of Use.

    Continue Reading

    CVE-2025-4023 – iSourcecode Placement Management System SQL Injection

    Common Vulnerabilities and Exposures (CVEs)
    FOSS Weekly #25.15: Clapgrep, APT 3.0, Vibe Coding, AI in Firefox and More

    FOSS Weekly #25.15: Clapgrep, APT 3.0, Vibe Coding, AI in Firefox and More

    Linux

    Improve Vision Language Model Chain-of-thought Reasoning

    Machine Learning

    Vxceed secures transport operations with Amazon Bedrock

    Machine Learning

    Highlights

    AI agent deployments will grow 327% during the next two years. Here’s what to do now

    May 5, 2025

    HR chiefs recognize the transformative power of agents. Organizations must focus on strategy, skills, and…

    CVE-2025-40596 – SMA100 Series Web Interface Stack-based Buffer Overflow Vulnerability

    July 23, 2025

    Hibernate vs Sleep in Windows 11: What’s the Difference?

    August 7, 2025

    Raspberry Pi 5 Desktop Mini PC

    June 14, 2025
    © DevStackTips 2025. All rights reserved.
    • Contact
    • Privacy Policy

    Type above and press Enter to search. Press Esc to cancel.