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

      This week in AI updates: Mistral’s new Le Chat features, ChatGPT updates, and more (September 5, 2025)

      September 6, 2025

      Designing For TV: Principles, Patterns And Practical Guidance (Part 2)

      September 5, 2025

      Neo4j introduces new graph architecture that allows operational and analytics workloads to be run together

      September 5, 2025

      Beyond the benchmarks: Understanding the coding personalities of different LLMs

      September 5, 2025

      Development Release: KDE Linux 20250906

      September 6, 2025

      Hitachi Energy Pledges $1B to Strengthen US Grid, Build Largest Transformer Plant in Virginia

      September 5, 2025

      How to debug a web app with Playwright MCP and GitHub Copilot

      September 5, 2025

      Between Strategy and Story: Thierry Chopain’s Creative Path

      September 5, 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

      Health Monitoring Android App using SQLite

      September 7, 2025
      Recent

      Health Monitoring Android App using SQLite

      September 7, 2025

      Convertedbook – Live LaTeX Preview in the Browser

      September 7, 2025

      Why browsers throttle JavaScript timers (and what to do about it)

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

      Development Release: KDE Linux 20250906

      September 6, 2025
      Recent

      Development Release: KDE Linux 20250906

      September 6, 2025

      Harnessing GitOps on Linux for Seamless, Git-First Infrastructure Management

      September 6, 2025

      How DevOps Teams Are Redefining Reliability with NixOS and OSTree-Powered Linux

      September 5, 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

    What is New in Go 1.25? Explained with Examples

    September 7, 2025
    Artificial Intelligence

    Scaling Up Reinforcement Learning for Traffic Smoothing: A 100-AV Highway Deployment

    September 7, 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-48883 – Chrome CSS Selector XSS

    Common Vulnerabilities and Exposures (CVEs)

    CVE-2025-6807 – Marvell QConvergeConsole Directory Traversal Information Disclosure

    Common Vulnerabilities and Exposures (CVEs)

    Public exploits released for CitrixBleed 2 NetScaler flaw, patch now

    Security

    CVE-2025-45491 – Linksys E5600 Command Injection Vulnerability

    Common Vulnerabilities and Exposures (CVEs)

    Highlights

    This Amazon Wi-Fi 7 router solved my biggest smart home internet issue – and it’s on sale

    May 23, 2025

    The Eero Outdoor 7 managed to deliver a strong signal in outdoor areas that previously…

    CVE-2023-47294 – NCR Terminal Handler Session Cookie Manipulation Vulnerability

    June 23, 2025

    CVE-2025-2938 – GitLab Elevation of Privilege Vulnerability

    June 26, 2025

    CVE-2025-45779 – Tenda AC10 Unauthenticated Buffer Overflow

    May 12, 2025
    © DevStackTips 2025. All rights reserved.
    • Contact
    • Privacy Policy

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