Build a HTML5 game like Wordle using Phaser – Step 1: keyboard input and result management

Read all posts about "" game

If you don’t live on Mars, you probably know Wordle game. Developed in October 2021 by Josh Wardle, after a couple of months it was acquired by the New York Times “for an undisclosed price in the low-seven figures.”

The gameplay is very easy: Every day, a five-letter word is chosen which players aim to guess within six tries.

After every guess, each letter is marked as either green, yellow or gray: green indicates that letter is correct and in the correct position, yellow means it is in the answer but not in the right position, while gray indicates it is not in the answer at all.

Multiple instances of the same letter in a guess, such as the “o”s in “robot”, will be colored green or yellow only if the letter also appears multiple times in the answer; otherwise, excess repeating letters will be colored gray.

Around the web you can find a lot of Wordle clones and tutorials, so it’s time to publish my take on the game.

In this first step, I am going to show you how to manage user input and handle results.

First of all, let’s see the prototype:

In yellow, the word you have to guess, randomly picked from the official list of Wordle words.

In cyan, the word you want to write. Use the keyboard to write the word. You can use Backspace to delete the last letter, Enter to submit the word or Space to restart the game with a new randomly picked word.

The prototype handles the keyboard input and is able to recognize when a word is incomplete (shorter than five characters) or invalid (not included in the word list).

For each letter, I also say if it’s wrong, correct or perfect.

Let’s have a look at the source code, split into one html file and three TypeScript files.

index.html

The web page which hosts the game, to be run inside thegame element.

<!DOCTYPE html>
<html>
	<head>
        <style type = "text/css">
            * {
                padding: 0;
                margin: 0;
            }
            body{
                background: #000;
            }
            canvas {
                touch-action: none;
                -ms-touch-action: none;
            }
        </style>
        <script src = "main.js"></script>
    </head>
	<body>
        <div id = "thegame"></div>
	</body>
</html>

main.ts

This is where the game is created, with all Phaser related options.

// MAIN GAME FILE

// modules to import
import Phaser from 'phaser';
import { PreloadAssets } from './preloadAssets';
import { PlayGame } from './playGame';

// object to initialize the Scale Manager
const scaleObject : Phaser.Types.Core.ScaleConfig = {
    mode : Phaser.Scale.FIT,
    autoCenter : Phaser.Scale.CENTER_BOTH,
    parent : 'thegame',
    width : 600,
    height : 280
}

// game configuration object
const configObject : Phaser.Types.Core.GameConfig = {
    type : Phaser.AUTO,
    backgroundColor : 0x222222,
    scale : scaleObject,
    scene : [PreloadAssets, PlayGame]
}

// the game itself
new Phaser.Game(configObject);

preloadAssets.ts

Here we preload all assets to be used in the game, at the moment only the JSON object with all words.

// CLASS TO PRELOAD ASSETS

// this class extends Scene class
export class PreloadAssets extends Phaser.Scene {

    // constructor    
    constructor() {
        super({
            key : 'PreloadAssets'
        });
    }

    // method to be execute during class preloading
    preload(): void {

        // this is how we preload a JSON object
        this.load.json('words', 'assets/words.json');
	}

    // method to be called once the instance has been created
	create(): void {

        // call PlayGame class
        this.scene.start('PlayGame');
	}
}

playGame.ts

Main game file, where we handle keyboard input and result management.

// THE GAME ITSELF

// possible word states:
// perfect, when the letter is in the right position
// correct, when the letter exists but it's not in the right position
// wrong, when the letter does not exist
enum letterState {
    PERFECT,
    CORRECT,
    WRONG  
}

// this class extends Scene class
export class PlayGame extends Phaser.Scene {

    // array with all possible words
    words : string[];

    // text object to show the word we are writing
    wordText : Phaser.GameObjects.Text;

    // text object to display the result
    resultText : Phaser.GameObjects.Text;

    // string where to store the current word
    currentWord : string;

    // string where to store the word to guess
    wordToGuess : string;

    // constructor
    constructor() {
        super({
            key: 'PlayGame'
        });
    }

    create() : void {

        // store JSON loaded words into words array
        this.words = this.cache.json.get('words');

        // at the beginning, current word is empty
        this.currentWord = '';

        // pick a random word to guess
        this.wordToGuess = this.words[Phaser.Math.Between(0, this.words.length - 1)].toUpperCase();

        // just a static text
        this.add.text(10, 10, 'Word to guess: ' + this.wordToGuess, {
            font : '32px arial',
            color : '#ffff00'
        }); 

        // another static text
        let staticText : Phaser.GameObjects.Text = this.add.text(10, 50, 'Your word: ', {
            font : '32px arial',
            color : '#00ffff'
        });

        // this is the text we are going to write
        this.wordText = this.add.text(staticText.getBounds().right, 50, '', {
            font : '32px arial'
        });

        // this is the text where to display the result
        this.resultText = this.add.text(10, 90, '', {
            font : '32px arial',
            color : '#ff00ff'
        });

        // keydown linster to call updateWord method
        this.input.keyboard.on('keydown', this.updateWord, this);
    }
    
    // method to be called each time we need to update a word
    updateWord(e : KeyboardEvent) : void {

        // store key pressed in key variable
        var key : string = e.key;
        
        // if the key is space, restart the game
        if (key == ' ') {
            this.scene.start('PlayGame');
            return;
        }

        // if the key is backspace and the word has at least one character,
        // remove the last character
        if (key == 'Backspace' &amp;&amp; this.currentWord.length > 0) {
            this.currentWord = this.currentWord.slice(0, -1);
            this.wordText.setText(this.currentWord);
            return;
        }

        // regular expression saying "I want one letter"
        const regex = /^[a-zA-Z]{1}$/;

        // if the key is a letter and the word has less than 5 characters,
        // add the character
        if (regex.test(key) &amp;&amp; this.currentWord.length < 5) {
            this.currentWord += e.key.toUpperCase();
            this.wordText.setText(this.currentWord);
            return;
        }

        // if the key is Enter, it's time to check the result
        if (key == 'Enter') {

            // if the word is 5 characters long, proceed to verify the result
            if (this.currentWord.length == 5) {

                // if the word is a valid word, proceed to verify the result
                if (this.words.includes(this.currentWord.toLowerCase())) {

                    // at the beginning we se the result as a series of wrong characters
                    let result : number[] = Array(5).fill(letterState.WRONG);

                    // creation of a temp string with the word to guess
                    let tempWord : string = this.wordToGuess;

                    // loop through all word characters
                    for (let i : number = 0; i < 5; i ++) {

                        // do i-th char of the current word and i-th car of the word to guess match?
                        if (this.currentWord.charAt(i) == tempWord.charAt(i)) {

                            // this is a PERFECT result
                            result[i] = letterState.PERFECT;

                            // remove the i-th character from temp word
                            tempWord = this.removeChar(tempWord, i);
                        }

                        // i-th char of the current word and i-th car of the word to guess do not match
                        else {

                            // loop through all character of the word to guess
                            for (let j : number = 0; j < 5; j ++) {

                                // do i-th char of the current word and j-th car of the word to guess match,
                                // and don't j-th char of the current word and j-th car of the word to guess match?
                                if (this.currentWord.charAt(i) == this.wordToGuess.charAt(j) &amp;&amp; this.currentWord.charAt(j) != this.wordToGuess.charAt(j)) {
                                    
                                    // this is a correct result
                                    result[i] = letterState.CORRECT;

                                    // remove the i-th character from temp word
                                    tempWord = this.removeChar(tempWord, j);
                                    break;    
                                }
                            }   
                        }    
                    }

                    // time to show the result
                    let resultString : string = '';

                    // loop through all result items and compose result string accordingly
                    result.forEach((element : number, index : number) => {
                        resultString += this.currentWord.charAt(index) + ' : ';
                        switch (element) {
                            case letterState.WRONG :
                                resultString += 'wrong letter';
                                break;
                            case letterState.CORRECT :
                                resultString += 'right letter in wrong position';
                                break;
                            case letterState.PERFECT : 
                                resultString += 'PERFECT letter';
                        }
                        resultString += '\n';
                    });

                    // display result string
                    this.resultText.setText(resultString);
                }

                // if the word is not a valid word, display the error message
                else {
                    this.resultText.setText('Not a valid word');    
                }
            }

            // if the word is not a 5 letters word, display the error message
            else {
                this.resultText.setText('Not a 5 letters word');
            }
        }    
    }

    // simple method to change the index-th character of a string with '_'
    // just to have an unmatchable character
    removeChar(initialString : string, index : number) : string {
        return initialString.substring(0, index) + '_' + initialString.substring(index + 1);
    }
}

Keyboard input and result management has been done in just a few lines, so in next step we can start building the virtual keyboard to be used with tap/click. Download the source code of the entire project, word list included.

Get the most popular Phaser 3 book

Through 202 pages, 32 source code examples and an Android Studio project you will learn how to build cross platform HTML5 games and create a complete game along the way.

Get the book

214 GAME PROTOTYPES EXPLAINED WITH SOURCE CODE
// 1+2=3
// 100 rounds
// 10000000
// 2 Cars
// 2048
// A Blocky Christmas
// A Jumping Block
// A Life of Logic
// Angry Birds
// Angry Birds Space
// Artillery
// Astro-PANIC!
// Avoider
// Back to Square One
// Ball Game
// Ball vs Ball
// Ball: Revamped
// Balloon Invasion
// BallPusher
// Ballz
// Bar Balance
// Bejeweled
// Biggification
// Block it
// Blockage
// Bloons
// Boids
// Bombuzal
// Boom Dots
// Bouncing Ball
// Bouncing Ball 2
// Bouncy Light
// BoxHead
// Breakout
// Bricks
// Bubble Chaos
// Bubbles 2
// Card Game
// Castle Ramble
// Chronotron
// Circle Chain
// Circle Path
// Circle Race
// Circular endless runner
// Cirplosion
// CLOCKS - The Game
// Color Hit
// Color Jump
// ColorFill
// Columns
// Concentration
// Crossy Road
// Crush the Castle
// Cube Jump
// CubesOut
// Dash N Blast
// Dashy Panda
// Deflection
// Diamond Digger Saga
// Don't touch the spikes
// Dots
// Down The Mountain
// Drag and Match
// Draw Game
// Drop Wizard
// DROP'd
// Dudeski
// Dungeon Raid
// Educational Game
// Elasticity
// Endless Runner
// Erase Box
// Eskiv
// Farm Heroes Saga
// Filler
// Flappy Bird
// Fling
// Flipping Legend
// Floaty Light
// Fuse Ballz
// GearTaker
// Gem Sweeper
// Globe
// Goat Rider
// Gold Miner
// Grindstone
// GuessNext
// Helicopter
// Hero Emblems
// Hero Slide
// Hexagonal Tiles
// HookPod
// Hop Hop Hop Underwater
// Horizontal Endless Runner
// Hundreds
// Hungry Hero
// Hurry it's Christmas
// InkTd
// Iromeku
// Jet Set Willy
// Jigsaw Game
// Knife Hit
// Knightfall
// Legends of Runeterra
// Lep's World
// Line Rider
// Lumines
// Magick
// MagOrMin
// Mass Attack
// Math Game
// Maze
// Meeblings
// Memdot
// Metro Siberia Underground
// Mike Dangers
// Mikey Hooks
// Nano War
// Nodes
// o:anquan
// One Button Game
// One Tap RPG
// Ononmin
// Pacco
// Perfect Square!
// Perfectionism
// Phyballs
// Pixel Purge
// PixelField
// Planet Revenge
// Plants Vs Zombies
// Platform
// Platform game
// Plus+Plus
// Pocket Snap
// Poker
// Pool
// Pop the Lock
// Pop to Save
// Poux
// Pudi
// Pumpkin Story
// Puppet Bird
// Pyramids of Ra
// qomp
// Quick Switch
// Racing
// Radical
// Rebuild Chile
// Renju
// Rise Above
// Risky Road
// Roguelike
// Roly Poly
// Run Around
// Rush Hour
// SameGame
// SamePhysics
// Save the Totem
// Security
// Serious Scramblers
// Shrink it
// Sling
// Slingy
// Snowflakes
// Sokoban
// Space Checkers
// Space is Key
// Spellfall
// Spinny Gun
// Splitter
// Spring Ninja
// Sproing
// Stabilize!
// Stack
// Stick Hero
// String Avoider
// Stringy
// Sudoku
// Super Mario Bros
// Surfingers
// Survival Horror
// Talesworth Adventure
// Tetris
// The Impossible Line
// The Moops - Combos of Joy
// The Next Arrow
// Threes
// Tic Tac Toe
// Timberman
// Tiny Wings
// Tipsy Tower
// Toony
// Totem Destroyer
// Tower Defense
// Trick Shot
// Tunnelball
// Turn
// Turnellio
// TwinSpin
// vvvvvv
// Warp Shift
// Way of an Idea
// Whack a Creep
// Wheel of Fortune
// Where's my Water
// Wish Upon a Star
// Word Game
// Wordle
// Worms
// Yanga
// Yeah Bunny
// Zhed
// zNumbers