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

      The Double-Edged Sustainability Sword Of AI In Web Design

      August 20, 2025

      Top 12 Reasons Enterprises Choose Node.js Development Services for Scalable Growth

      August 20, 2025

      GitHub’s coding agent can now be launched from anywhere on platform using new Agents panel

      August 20, 2025

      Stop writing tests: Automate fully with Generative AI

      August 19, 2025

      I’m a diehard Pixel fan, but I’m not upgrading to the Pixel 10. Here’s why

      August 21, 2025

      Google Pixel Watch 4 vs. Samsung Galaxy Watch 8: I compared the two best Androids, and here’s the winner

      August 21, 2025

      Get a free Amazon gift card up to $300 when you preorder a new Google Pixel 10 phone – here’s how

      August 21, 2025

      Everything announced at Made by Google 2025: Pixel 10 Pro, Fold, Watch 4, and more

      August 21, 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

      Copy Errors as Markdown to Share With AI in Laravel 12.25

      August 21, 2025
      Recent

      Copy Errors as Markdown to Share With AI in Laravel 12.25

      August 21, 2025

      Deconstructing the Request Lifecycle in Sitecore Headless – Part 2: SSG and ISR Modes in Next.js

      August 20, 2025

      Susan Etlinger, AI Analyst and Industry Watcher on Building Trust

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

      TerraMaster D1 SSD Plus Review: Experience a Faster External SSD

      August 20, 2025
      Recent

      TerraMaster D1 SSD Plus Review: Experience a Faster External SSD

      August 20, 2025

      Microsoft is investigating Windows 11 KB5063878 SSD data corruption/failure issue

      August 20, 2025

      Microsoft Surface Won’t Turn On: 6 Tested Solutions to Fix

      August 20, 2025
    • Learning Resources
      • Books
      • Cheatsheets
      • Tutorials & Guides
    Home»Development»How to Build a Tic Tac Toe Game with Phaser.js

    How to Build a Tic Tac Toe Game with Phaser.js

    August 20, 2025

    Tic-Tac-Toe is a great project for beginners who want to learn how to build games. It’s simple to understand but gives you the chance to learn about game state, player turns, winning logic, and user input.

    In this tutorial, you’ll learn how to build tic-tac-toe using Phaser.js, a fast, fun, and open source framework for making 2D games in the browser.

    If you’re new to Phaser.js, don’t worry. We’ll walk through everything step-by-step. By the end, you’ll have a working game that you can play, share, or build upon.

    You can play the game here to get a feel of what you are going to build.

    Table of Contents

    • What is Phaser.js?

    • Project Setup

    • How to Set Up the Game Configuration

    • How to Preload Assets

    • How to Create the Game Scene

    • How to Draw the Grid

    • How to Initialize and Reset the Game

    • How to Handle Player Input

    • How to Place Marks on the Board

    • How to Check for a Winner

    • How to Detect a Draw

    • How to Draw the Winning Line

    • Final Thoughts

    What is Phaser.js?

    Phaser.js is a free and open-source JavaScript game framework. It helps developers create HTML5 games that work across web browsers. Phaser handles things like rendering graphics, detecting input, and running the game loop.

    You can use Phaser to make simple games like Pong and Tic-Tac-Toe or advanced platformers and role playing games. It supports both Canvas and WebGL rendering, so your games will run smoothly on most devices.

    Project Setup

    Create a folder for your project and add two files: index.html and game.js. The HTML file loads Phaser and the JavaScript file contains the game logic. Here is the repository with the finished code.

    Here’s what the index.html file should look like:

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="utf-8" />
      <title>Tic Tac Toe — Phaser 3</title>
      <meta name="viewport" content="width=device-width,initial-scale=1" />
      <style>
        html, body { height: 100%; margin: 0; background: #0f172a; display: grid; place-items: center; }
        #game { box-shadow: 0 10px 30px rgba(0,0,0,.35); border-radius: 12px; overflow: hidden; }
        .hint { color: #e2e8f0; margin-top: 10px; font-size: 14px; text-align: center; opacity: .85; }
      </style>
      <script src="https://cdn.jsdelivr.net/npm/phaser@3.60.0/dist/phaser.min.js"></script>
    </head>
    <body>
      <div id="game"></div>
      <div class="hint">Click a cell to play. Tap “Restart” to start over.</div>
      <script src="./game.js"></script>
    </body>
    </html>
    

    This sets up a simple HTML page, loads Phaser from a CDN, and points to your game.js file. The #game container is where Phaser will insert the game canvas.

    How to Set Up the Game Configuration

    Phaser games are built from a configuration object that defines things like width, height, background color, and which functions to call for loading, creating, and updating the game.

    (() => {
      const GRID = 3;
      const CELL = 120;
      const BOARD = GRID * CELL;
      const HUD = 72;
      const WIDTH = BOARD;
      const HEIGHT = BOARD + HUD;
    
      let scene;
      let board;
      let currentPlayer;
      let gameOver;
    
      let gridGfx;
      let overlayGfx;
      let marks = [];
      let statusText;
      let restartText;
    
      const config = {
        type: Phaser.AUTO,
        parent: "game",
        width: WIDTH,
        height: HEIGHT,
        backgroundColor: "#ffffff",
        scale: { mode: Phaser.Scale.FIT, autoCenter: Phaser.Scale.CENTER_BOTH },
        scene: { preload, create, update }
      };
    
      new Phaser.Game(config);
    

    We start by defining constants for the grid size and cell size. The config object tells Phaser to create a game with these dimensions and use the preload, create, and update functions we will define.

    How to Preload Assets

    Since we are drawing everything with Phaser’s graphics and text tools, we do not need to load any external images or sounds.

      function preload() {
        // No assets to load
      }
    

    This is a placeholder that lets Phaser call preload before the game starts.

    How to Create the Game Scene

    The create function runs once at the start of the scene. Here we draw the grid, set up the initial state, and add UI elements.

      function create() {
        scene = this;
    
        gridGfx = scene.add.graphics({ lineStyle: { width: 4, color: 0x000000 } });
        overlayGfx = scene.add.graphics();
        drawGrid();
    
        initGame();
    
        statusText = scene.add.text(WIDTH / 2, BOARD + 12, "Player X's turn", {
          fontSize: "20px",
          color: "#111",
          fontFamily: "Arial, Helvetica, sans-serif"
        }).setOrigin(0.5, 0);
    
        restartText = scene.add.text(WIDTH / 2, BOARD + 38, "Restart", {
          fontSize: "18px",
          color: "#2563eb",
          fontFamily: "Arial, Helvetica, sans-serif"
        }).setOrigin(0.5, 0).setInteractive({ useHandCursor: true });
    
        restartText.on("pointerup", hardReset);
    
        scene.input.on("pointerdown", onPointerDown, scene);
      }
    

    We created two Graphics objects: one for the static grid and another for the win line. Then we called drawGrid() and initGame() to set up the game board. The status text and restart button are placed below the grid. We also listened for clicks on the board with pointerdown.

    How to Draw the Grid

    The grid is made up of two vertical and two horizontal lines.

      function drawGrid() {
        gridGfx.strokeLineShape(new Phaser.Geom.Line(CELL, 0, CELL, BOARD));
        gridGfx.strokeLineShape(new Phaser.Geom.Line(CELL * 2, 0, CELL * 2, BOARD));
        gridGfx.strokeLineShape(new Phaser.Geom.Line(0, CELL, BOARD, CELL));
        gridGfx.strokeLineShape(new Phaser.Geom.Line(0, CELL * 2, BOARD, CELL * 2));
      }
    

    We use Phaser.Geom.Line to define the start and end points for each line and then draw them with strokeLineShape.

    How to Initialize and Reset the Game

    The initGame function sets up a new game, and hardReset is called when the restart button is clicked.

      function initGame() {
        board = Array.from({ length: GRID }, () => Array(GRID).fill(""));
        currentPlayer = "X";
        gameOver = false;
        overlayGfx.clear();
        for (const t of marks) t.destroy();
        marks = [];
        setStatus("Player X's turn");
      }
    
      function hardReset() {
        initGame();
      }
    
      function setStatus(msg) {
        statusText && statusText.setText(msg);
      }
    

    The board is represented by a 2D array filled with empty strings. The current player starts as X, and the marks array keeps track of text objects so we can clear them on reset.

    How to Handle Player Input

    When the player clicks a cell, we determine its row and column and check if the move is valid.

      function onPointerDown(pointer) {
        if (gameOver) return;
        if (pointer.y > BOARD) return;
    
        const col = Math.floor(pointer.x / CELL);
        const row = Math.floor(pointer.y / CELL);
        if (!inBounds(row, col)) return;
        if (board[row][col] !== "") return;
    
        placeMark(row, col, currentPlayer);
    
        const win = checkWin(board);
        if (win) {
          gameOver = true;
          drawWinLine(win);
          setStatus(`Player ${currentPlayer} wins!`);
          return;
        }
    
        if (isFull(board)) {
          gameOver = true;
          setStatus("Draw! No more moves.");
          return;
        }
    
        currentPlayer = currentPlayer === "X" ? "O" : "X";
        setStatus(`Player ${currentPlayer}'s turn`);
      }
    

    This ensures that we only act if the game is not over, the click is inside the board, and the chosen cell is empty. After placing a mark, we check for a win or draw before switching turns.

    How to Place Marks on the Board

    We display an X or O at the center of the clicked cell.

      function inBounds(r, c) {
        return r >= 0 && r < GRID && c >= 0 && c < GRID;
      }
    
      function placeMark(row, col, player) {
        board[row][col] = player;
        const cx = col * CELL + CELL / 2;
        const cy = row * CELL + CELL / 2;
        const t = scene.add.text(cx, cy, player, {
          fontSize: Math.floor(CELL * 0.66) + "px",
          color: "#111111",
          fontFamily: "Arial, Helvetica, sans-serif"
        }).setOrigin(0.5);
        marks.push(t);
      }
    

    The coordinates are calculated so the text is centered in the cell. We store the text object in the marks array so it can be removed when resetting.

    How to Check for a Winner

    We check rows, columns, and diagonals to see if the current player has three in a row.

      function checkWin(b) {
        for (let r = 0; r < GRID; r++) {
          if (b[r][0] && b[r][0] === b[r][1] && b[r][1] === b[r][2]) {
            return { kind: "row", index: r };
          }
        }
        for (let c = 0; c < GRID; c++) {
          if (b[0][c] && b[0][c] === b[1][c] && b[1][c] === b[2][c]) {
            return { kind: "col", index: c };
          }
        }
        if (b[0][0] && b[0][0] === b[1][1] && b[1][1] === b[2][2]) {
          return { kind: "diag" };
        }
        if (b[0][2] && b[0][2] === b[1][1] && b[1][1] === b[2][0]) {
          return { kind: "anti" };
        }
        return null;
      }
    

    If a win is found, we return an object describing the winning line so it can be drawn.

    How to Detect a Draw

    If every cell is filled and there is no winner, the game ends in a draw.

      function isFull(b) {
        for (let r = 0; r < GRID; r++) {
          for (let c = 0; c < GRID; c++) {
            if (b[r][c] === "") return false;
          }
        }
        return true;
      }
    

    This loops over every cell and returns false if any are empty.

    How to Draw the Winning Line

    A red line is drawn over the winning cells.

      function drawWinLine(res) {
        overlayGfx.clear();
        overlayGfx.lineStyle(6, 0xef4444, 1);
        const pad = 14;
        const half = CELL / 2;
    
        if (res.kind === "row") {
          const y = res.index * CELL + half;
          overlayGfx.strokeLineShape(new Phaser.Geom.Line(pad, y, BOARD - pad, y));
        } else if (res.kind === "col") {
          const x = res.index * CELL + half;
          overlayGfx.strokeLineShape(new Phaser.Geom.Line(x, pad, x, BOARD - pad));
        } else if (res.kind === "diag") {
          overlayGfx.strokeLineShape(new Phaser.Geom.Line(pad, pad, BOARD - pad, BOARD - pad));
        } else if (res.kind === "anti") {
          overlayGfx.strokeLineShape(new Phaser.Geom.Line(BOARD - pad, pad, pad, BOARD - pad));
        }
      }
    })();
    

    The coordinates are calculated based on the type of win to ensure the line passes through the correct cells.

    Great. Now open index.html and you can start playing the game!

    Final Game

    Final Thoughts

    You have now built a complete Tic Tac Toe game in Phaser.js. This includes a 3×3 grid, alternating turns, win detection with a highlight line, draw detection, and a restart button. The code uses core game development concepts like input handling, game state management, and rendering, which you can use in larger projects.

    If you enjoy online games, check out GameBoost, the ultimate marketplace for gamers. You can find Fortnite accounts with exclusive skins, along with options for other popular games like Grow a Garden, Clash of Clans, and more.

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

    Facebook Twitter Reddit Email Copy Link
    Previous ArticleHow to Set Up Firebase Crashlytics in a Flutter App (iOS and Android)
    Next Article How to Deploy a Flutter Web App to Firebase Hosting with GitHub Actions

    Related Posts

    Development

    Copy Errors as Markdown to Share With AI in Laravel 12.25

    August 21, 2025
    Artificial Intelligence

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

    August 21, 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-32878 – COROS PACE 3 TLS Certificate Validation Bypass

    Common Vulnerabilities and Exposures (CVEs)

    CVE-2025-30101 – Dell PowerScale OneFS TOCTOU Race Condition Vulnerability

    Common Vulnerabilities and Exposures (CVEs)

    CVE-2025-8611 – AOMEI Cyber Backup Remote Code Execution (RCE) Missing Authentication

    Common Vulnerabilities and Exposures (CVEs)

    Fortinet FortiWeb Instances Hacked With Webshells Following Public PoC Exploits

    Security

    Highlights

    CVE-2025-4648 – Centreon Web Reflected Cross-Site Scripting (XSS)

    May 13, 2025

    CVE ID : CVE-2025-4648

    Published : May 13, 2025, 10:15 a.m. | 29 minutes ago

    Description : Download of Code Without Integrity Check vulnerability in Centreon web allows Reflected XSS.
    A user with elevated privileges can inject XSS by altering the content of a SVG media during the submit request.
    This issue affects web: from 24.10.0 before 24.10.5, from 24.04.0 before 24.04.11, from 23.10.0 before 23.10.22, from 23.04.0 before 23.04.27, from 22.10.0 before 22.10.29.

    Severity: 8.4 | HIGH

    Visit the link for more details, such as CVSS details, affected products, timeline, and more…

    Identity – compare images and videos

    July 28, 2025

    CVE-2025-52726 – Pebas CouponXxL Custom Post Types Privilege Escalation Vulnerability

    June 27, 2025

    How Apple’s biggest potential acquisition ever could perplex AI rivals like Google

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

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