Get the full commented source code of

HTML5 Suika Watermelon Game

Talking about Wordle game, Game development, HTML5, Javascript, Phaser and TypeScript.

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' && 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) && 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) && 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.

Never miss an update! Subscribe, and I will bother you by email only when a new game or full source code comes out.