Do you like my tutorials?

Then consider supporting me on Ko-fi

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

I hope you are enjoying the series about Wordle game.

In first step I showed you how to handle keyboard input and result management.

In second step we saw how to add a virtual keyboard.

In third step I added the game board and improved virtual keyboard layout.

In fourth step I added Bootstrap DOM elements to create an in-game menu to select game mode and display statistics.

Now, we are going to try another way to display Bootstrap – or any other UI framework – elements in a Phaser game.

What if we build a page with a Bootstrap toolbar and the game, with the game having listeners on Bootstrap menu items?

Look at the example:

First, let me explain what you are seeing:

The top black toolbar with the red dropdown menu are outside Phaser game, which starts right below the toolbar and fills the 100% of the remaining vertical space.

Use the keyboard to write the word. You can play with your physical keyboard, using Backspace to delete the last letter, Enter to submit the word or Space to restart the game with a new randomly picked word.

Also, the big menu allows you to play with the word of the day, with a random word or to visit my site. Stats option opens a modal which is outside Phaser game.

If you are reading this post with a mobile device, you can see it in action directly from this link.

Doing this way rather than embedding the DOM directly into Phaser game allows you to keep things separated, and there isn’t a best way in my opinion, but it’s good to see both ways.

Let’s have a look at the assets used in the prototype:

bigfont.fnt and bigfont.png: respectively the XML information and the image of the big font.

bigkey.png: a sprite sheet containing Enter and Delete keys.

box.png: sprite sheet the box where to write the each letter of the word.

font.fnt and font.png: respectively the XML information and the image of the small font.

key.png: sprite sheeet of the virtual keyboard key.

words.json: the list of allowed words.

And now, the completely commented source code, which is made up by one html file, one css file and 11 TypeScript files.

index.html

The web page which hosts the game, to be run inside thegame element, with also all Bootstrap UI including the toolbar, the dropdown menu and the modal box.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<!DOCTYPE html>
<html>
    <head>
        <meta name="viewport" content="initial-scale=1, maximum-scale=1">
        <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha256-4+XzXVhsDmqanXGHaHvgh1gMQKX40OUvDEBTu8JcmNs=" crossorigin="anonymous"></script>
        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous">
        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-ygbV9kiqUc6oa4msXn9868pTtWMgiQaeYH7/t7LECLbyPA2x65Kgf80OJFdroafW" crossorigin="anonymous"></script>
        <link rel = 'stylesheet' href = 'style.css'>
        <script src = 'main.js'></script>
    </head>
    <body>
        <div class="wrapper">
            <div>
                <nav class="navbar navbar-expand navbar-dark bg-dark">
                    <div class="container-fluid" id="contentcontainer">
                        <ul class="navbar-nav me-auto mb-2 mb-lg-0">
                            <li class="nav-item dropdown">
                                <button type="button" class="btn btn-danger dropdown-toggle rounded-0" data-bs-toggle="dropdown">
                                    Menu
                                </button>
                                <ul class="dropdown-menu">
                                    <li><a href="javascript:void(0);" class="dropdown-item" id="day">Word of the Day</a></li>
                                    <li><a href="javascript:void(0);" class="dropdown-item" id="rnd">Random Word</a></li>
                                    <li><a href="javascript:void(0);" class="dropdown-item" id="stats" data-bs-toggle="modal" data-bs-target="#statsmodal">Stats</a></li>
                                    <li><hr class="dropdown-divider"></li>
                                    <li><a class="dropdown-item" href="https://emanueleferonato.com/">Developed by Emanuele Feronato</a></li>
                                </ul>
                            </li>
                        </ul>
                        <span class="navbar-text">
                            Bootstrap toolbar
                        </span>
                    </div>
                </nav>
            </div>
            <div id="thegame"></div>
        </div>
        <div class="modal fade" id="statsmodal" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1">
            <div class="modal-dialog">
                <div class="modal-content">
                    <div class="modal-header">
                        <h5 class="modal-title" id="staticBackdropLabel">Game stats</h5>
                        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
                    </div>
                    <div class="modal-body" id="modalbody">
                    </div>
                    <div class="modal-footer">
                        <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
                    </div>
                </div>
            </div>
        </div>
    </body>
</html>

style.css

The style sheet of the main web page, look how we use a flex display with column direction to make Phaser game cover the remaining area.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
* {
    padding : 0;
    margin : 0;
}
 
canvas {
    touch-action : none;
    -ms-touch-action : none;
}
 
.wrapper, html, body {
    height : 100%;
    margin : 0;
}
 
.wrapper {
    display : flex;
    flex-direction : column;
}
 
.navbar-dark .navbar-text {
    color : rgba(255, 255, 255, 1);
}

gameOptions.ts

Configurable game options.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// CONFIGURABLE GAME OPTIONS
 
// possible game modes
// random word: a word randomly picked from words array
// word of the day: a word picked according to current date
export enum gameMode {
    RANDOM_WORD,
    WORD_OF_THE_DAY   
}
  
export const GameOptions = {
 
    // number of rows, that is the amount of tries to guess the word
    rows : 6,
 
    // vertical position of the first row, starting from the top of the screen
    firstRowY : 150
}

main.ts

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// MAIN GAME FILE
 
// modules to import
import Phaser from 'phaser';
import { PreloadAssets } from './preloadAssets';
import { PlayGame } from './playGame';
import { Container } from './container';
import { MenuHandler } from './menuHandler';
 
// object to initialize the Scale Manager
const scaleObject : Phaser.Types.Core.ScaleConfig = {
    mode : Phaser.Scale.FIT,
    autoCenter : Phaser.Scale.CENTER_BOTH,
    parent : 'thegame',
    width : 700,
    height : 1244
}
 
// game configuration object
const configObject : Phaser.Types.Core.GameConfig = {
    type : Phaser.AUTO,
    backgroundColor : 0xffffff,
    scale : scaleObject,
    scene : [PreloadAssets, Container, MenuHandler, PlayGame]
}
 
// the game itself
new Phaser.Game(configObject);

preloadAssets.ts

Here we preload all assets to be used in the game, such as the JSON object with all words, the images and the bitmap fonts.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 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');
 
        // preload images
        this.load.spritesheet('key', 'assets/key.png', {
            frameWidth: 70,
            frameHeight: 90
        });
        this.load.spritesheet('bigkey', 'assets/bigkey.png', {
            frameWidth: 105,
            frameHeight: 90
        });
        this.load.spritesheet('box', 'assets/box.png', {
            frameWidth: 100,
            frameHeight: 100
        });
 
        // this is how we preload a bitmap font
        this.load.bitmapFont('font', 'assets/font.png', 'assets/font.fnt');
        this.load.bitmapFont('bigfont', 'assets/bigfont.png', 'assets/bigfont.fnt');
    }
 
    // method to be called once the instance has been created
    create(): void {
 
        // call PlayGame class
        this.scene.start('Container');
    }
}

container.ts

This scene is the main scene and acts as a container for two subscenes: one with the game itself and one with the code to handle external Bootstrap events.

It’s a good idea to keep these two scripts separated, just in case you decide to remove the Bootstrap part and change with some other framework.

Another example about combining more scenes can be found reading the post Understanding Phaser capability of running multiple Scenes simoultaneously with a real world example: continous particle scrolling background while main Scene restarts.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// CLASS TO CONTAIN THE GAME
 
// this class extends Scene class
export class Container extends Phaser.Scene {
 
     // constructor   
     constructor() {
        super({
            key : 'Container'
        });
    }
 
    // method to be called once the scene has been created
    create(): void {
 
        // this is how we launch a scene
        this.scene.launch('PlayGame');
        this.scene.launch('MenuHandler');
    }
}

menuHandler.ts

All the code to handle Bootstrap events is placed here, as well as a snippet to horizontally resize the navbar to match the width of the Phaser game.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
// CLASS TO HANDLE EXTERNAL MENU
 
import { gameMode } from './gameOptions';
 
// this class extends Scene class
export class MenuHandler extends Phaser.Scene {
 
    // constructor   
    constructor() {
       super({
           key : 'MenuHandler'
       });
    }
 
    // method to be called once the scene has been created
    create(): void {
 
        // resize upper Boostrap menu
        this.resizeMenu();
 
        // this is how we manage clicks on "Word of the Day" button
        let wordOfTheDayButton : HTMLElement = document.getElementById('day') as HTMLElement;
        wordOfTheDayButton.addEventListener('click', this.startWordOfTheDay.bind(this));
 
        // this is how we manage clicks on "Random Word" button
        let randomWordButton : HTMLElement = document.getElementById('rnd') as HTMLElement;
        randomWordButton.addEventListener('click', this.startRandom.bind(this));
 
        // this is how we manage clicks on "Stats" button
        let statsButton : HTMLElement = document.getElementById('stats') as HTMLElement;
        statsButton.addEventListener('click', this.displayStats.bind(this));
 
        // resize listener, to call resizeMenu method each time the game is resized
        this.scale.on('resize', this.resizeMenu, this);
    }
 
    // method to be called each time the menu is resized
    resizeMenu() : void {
 
        // get actual game width
        let actualGameWidth : number = this.game.scale.displaySize.width;
 
        // get navbar element by its id
        let navbar : HTMLElement = document.getElementById('contentcontainer') as HTMLElement;
 
        // resize navbar
        navbar.style.width = Math.max(300, actualGameWidth).toString() + 'px';
    }
 
    // method to start the game in "word of the day" mode
    startWordOfTheDay() : void {
 
        // this is how we can pass parameters to a scene
        this.scene.start('PlayGame', {
            game : gameMode.WORD_OF_THE_DAY
        });
    }   
     
    // method to start the game in "random" mode
    startRandom() : void {
 
        // this is how we can pass parameters to a scene
        this.scene.start('PlayGame', {
            game : gameMode.RANDOM_WORD
        });
    }
 
    // method to display statistics in the Bootstrap modal
    displayStats() : void {
 
        // get inner modal element by its id
        let modalElement : HTMLElement = document.getElementById('modalbody') as HTMLElement;
 
        // inject html
        modalElement.innerHTML = 'Here we\'ll place the stats<br />This content has been injected by Phaser';
    }
}

playGame.ts

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
// THE GAME ITSELF
 
import KeyboardKey from './keyboardKey';
import { GameOptions, gameMode } from './gameOptions';
import { GameGrid } from './gameGrid';
 
// 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 {
    WRONG = 1,
    CORRECT,
    PERFECT 
}
 
// keyboard layout, as a string array, each item is a row of keys
// > represents Enter
// < represents Backspace
const keyboardLayout : string[] = ['QWERTYUIOP','ASDFGHJKL','>ZXCVBNM<'];
 
// this class extends Scene class
export class PlayGame extends Phaser.Scene {
 
    // array with all possible words
    words : string[];
 
    // string where to store the current word
    currentWord : string;
 
    // string where to store the word to guess
    wordToGuess : string;
 
    // variable where to store game width
    gameWidth : number;
 
    // main game grid
    gameGrid : GameGrid;
 
    // virtual keyboard, as an array of KeyboardKey instances
    virtualKeyboard : KeyboardKey[][];
    
    // constructor
    constructor() {
        super({
            key: 'PlayGame'
        });
    }
 
    // method to be executed when the scene initialises
    init(mode : any) : void {
 
        // store JSON loaded words into words array
        this.words = this.cache.json.get('words');
 
        // different actions to do according to game mode
        // I am using a "switch" rather than an "if...then" because I plan to add more game modes
        switch (mode.game) {
 
            // no game mode defined, or "word of the day" mode
            case undefined :
            case gameMode.WORD_OF_THE_DAY :
 
                // set the random seed according to current date
                let seed : string[] = Array(new Date().toISOString().split("T")[0]);
                 
                // set Phaser seed
                Phaser.Math.RND.sow(seed);
 
                // pick a random word, with the known seed
                this.wordToGuess = this.words[Phaser.Math.RND.between(0, this.words.length - 1)].toUpperCase();
                break;
 
            // "random word" mode   
            case gameMode.RANDOM_WORD :
                this.wordToGuess = Phaser.Utils.Array.GetRandom(this.words).toUpperCase();
        }
    }
 
    // method to be executed when the scene has been created
    create() : void {   
         
        // set gameWidth to actual game width
        this.gameWidth = this.game.config.width as number;
 
        // at the beginning, current word is empty
        this.currentWord = '';
 
        // let's display somewhere the word to guess
        console.log(this.wordToGuess);
 
        // initialize virtual keyboard
        this.virtualKeyboard = [];
 
        // loop through keyboardLayout array
        keyboardLayout.forEach((row : string, index : number) => {
 
            // initialize virtual keyboard row
            this.virtualKeyboard[index] = [];
             
            // determine position of key sprites
            // some values are still hardcoded, and need to be optimized
            let rowWidth : number = 70 * row.length;
            let firstKeyPosition : number = (this.game.config.width as number - rowWidth) / 2;
 
            // loop through string
            for (let i : number = 0; i < row.length; i ++) {
 
                // get the i-th character
                let letter : string = row.charAt(i);
 
                // add the keyboard key
                this.virtualKeyboard[index][i] = new KeyboardKey(this, firstKeyPosition + i * 70 - (letter == '>' ? 35 : 0), 900 + index * 90, row.charAt(i));
            }
        });
 
        // add the game grid
        this.gameGrid = new GameGrid(this, GameOptions.rows, (this.gameWidth - 540) / 2, GameOptions.firstRowY);
 
        // waiting for keyboard input
        this.input.keyboard.on('keydown', this.onKeyDown, this);
    }
     
    // method to process a key pressed
    onKeyDown(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', {
                gameMode : 2
            });
            return;
        }
 
        // backspace
        if (key == 'Backspace') {
            this.updateWord('<');
            return;
        }
 
        // regular expression saying "I want one letter"
        const regex = /^[a-zA-Z]{1}$/;
 
        // letter a-z or A-Z
        if (regex.test(key)) {
            this.updateWord(key);
            return;
        }
 
        // enter
        if (key == 'Enter') {
            this.updateWord('>');
        }
    }
 
    //method to be called each time we need to update a word
    updateWord(s : string) : void {
        switch(s) {
 
            // backsace
            case '<' :
 
                // if the word has at least one character, remove the last character
                if (this.currentWord.length > 0) {
 
                    // remove last current word character
                    this.currentWord = this.currentWord.slice(0, -1);
 
                    // call gameGrid's removeLetter method
                    this.gameGrid.removeLetter();
                }
                break;
 
            // enter
            case '>' :
                 
                // 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;   
                                    }
                                }  
                            }   
                        }
 
                        // loop through all result items and compose result string accordingly
                        result.forEach((element : number, index : number) => {
                             
                            // get letter position in our virtual keyboard
                            let position : Phaser.Math.Vector2 = this.getLetterPosition(this.currentWord.charAt(index));
 
                            // if the key of the virtual keyboard has not already been painted, then paint it.
                            if (parseInt(this.virtualKeyboard[position.x][position.y].frame.name) < element) {
                                this.virtualKeyboard[position.x][position.y].setFrame(element);
                            }
                        });
 
                        // reset current word
                        this.currentWord = '';
                       
                        // call gameGrid's showResult method
                        this.gameGrid.showResult(result);
                    }
                }
                break;
             
            // a-z or A-Z
            default :
 
                // if the word is less than 5 characters long, remove last character
                if (this.currentWord.length < 5) {
 
                    // add the letter
                    this.gameGrid.addLetter(s);
 
                    // update current word
                    this.currentWord += s.toUpperCase();
                    
                }               
        }
    }
 
    // method to get the position the virtual keyboard key, given a letter
    getLetterPosition(letter : string) : Phaser.Math.Vector2 {
         
        // set row to zero
        let row : number = 0;
 
        // set column to zero
        let column : number = 0;
 
        // loop though all keyboardLayout array
        keyboardLayout.forEach((currentRow : string, index : number) => {
             
            // does current row include the letter?
            if (currentRow.includes(letter)) {
 
                // set row to index
                row = index;
 
                // set column to letter position inside current row
                column = currentRow.indexOf(letter);  
            }
        })    
 
        // return the coordinate as a 2D vector
        return new Phaser.Math.Vector2(row, column);
    }
 
    // 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);
    }
}

keyboardKey.ts

Custom class for the virtual keyboard key.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// KEYBOARD KEY CLASS
 
import { PlayGame } from "./playGame";
import KeyboardLetter from "./keyboardLetter";
 
// this class extends Sprite class
export default class KeyboardKey extends Phaser.GameObjects.Sprite {
 
    // letter bound to the key
    boundLetter : string;
 
    // parent scene
    parentScene : PlayGame;
 
    constructor(scene : PlayGame, x : number, y : number, letter : string) {
 
        // different image key according if it's a letter character or '<' or '>'
        super(scene, x, y, '<>'.includes(letter) ? 'bigkey' : 'key');
 
        // assign parent scene
        this.parentScene = scene;
 
        // assign bound letter
        this.boundLetter = letter;
 
        // set sprite registration point to top, left
        this.setOrigin(0);
 
        // add the sprite to the scene
        scene.add.existing(this);
         
        // set the sprite interactive
        this.setInteractive();
 
        // listener for pointer down on the sprite, to call handlePointer callback
        this.on('pointerdown', this.handlePointer);
 
        // add a keyboard letter accoring to 'letter value
        switch(letter) {
 
            // backspace
            case '<' :
                this.setFrame(0);
                break;
             
            // enter
            case '>' :
                this.setFrame(1);
                break;
 
            // normal key
            default :
                new KeyboardLetter(scene, x + 10, y + 10, letter, 36);
        }
    }
 
    // method to be called when the user clicks or taps the letter
    handlePointer() : void {
 
        // call 'updateWord' method on parent scene
        this.parentScene.updateWord(this.boundLetter);
    }
}

keyboardLetter.ts

Custom class for the letter to be printed on the virtual keyboard key.

1
2
3
4
5
6
7
8
9
10
11
12
13
// KEYBOARD LETTER
 
import { PlayGame } from "./playGame";
 
// this class extends BitmapText class
export default class KeyboardLetter extends Phaser.GameObjects.BitmapText {
    constructor(scene : PlayGame, x : number, y : number, text : string, size : number) {
        super(scene, x, y, 'font', text, size);
         
        // add the keyboard letter to the scene
        scene.add.existing(this);
    }
}

gameGrid.ts

This class manages the grid where players write and try to guess the word.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
// GAME GRID
 
import BigLetterBox from './bigLetterBox';
 
export class GameGrid {
 
    // current row where to write the letter
    currentRow : number;
 
    // current column where to write the letter
    currentColumn : number;
 
    // array to store all letter boxes
    letterBox : BigLetterBox[][];
 
    constructor(scene : Phaser.Scene, rows : number, firstRowX : number, firstRowY : number) {
         
        // set current row to zero
        this.currentRow = 0;
 
        // set current column to zero
        this.currentColumn = 0;
 
        // initialize letterBox array
        this.letterBox = [];
 
        // loop from 0 to 4
        for (let i: number = 0; i < 5; i ++) {
             
            // initialize letterBox[i] array
            this.letterBox[i] = [];
 
            // loop through all rows
            for (let j : number = 0; j < rows; j ++) {
 
                // assign to letterBox[i][j] a new BigLetterBox instance
                this.letterBox[i][j] = new BigLetterBox(scene, firstRowX + i * 110, firstRowY + j * 110);
            
        
    }
 
    // method to add a latter
    addLetter(letter : string) : void {
 
        // set the letter at current row and column
        this.letterBox[this.currentColumn][this.currentRow].setLetter(letter);
 
        // increase current column
        this.currentColumn ++;
    }
 
    // method to remove a letter
    removeLetter() : void {
 
        // decrease current column
        this.currentColumn --;
 
        // unset the letter ant current row and column
        this.letterBox[this.currentColumn][this.currentRow].setLetter('');   
    }
 
    // show guess result
    showResult(result : number[]) : void {
 
        // loop through all result items
        result.forEach((element : number, index : number) => {
 
            // set letterBox frame according to element value
            this.letterBox[index][this.currentRow].setFrame(element);
 
            // paint the letter white
            this.letterBox[index][this.currentRow].letterToShow.setTint(0xffffff);
        });
 
        // increase current row
        this.currentRow ++;
 
        // set current column to zero
        this.currentColumn = 0;
    }
}

bigLetterBox.ts

Class to mange the big letter box inside game grid.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// BIG LETTER BOX
 
import BigLetter from "./bigLetter";
 
// this class extends Sprite class
export default class BigLetterBox extends Phaser.GameObjects.Sprite {
 
    // letter to show in the big letter box
    letterToShow : BigLetter;
 
    constructor(scene : Phaser.Scene, x : number, y : number) {
        super(scene, x, y, 'box');
 
        // set registration point to top left corner
        this.setOrigin(0);
 
        // add the letter box to the scene
        scene.add.existing(this);
 
        // calculate letter box bounds
        let bounds : Phaser.Geom.Rectangle = this.getBounds();
 
        // assign letterToShow an instance of BigLetter
        this.letterToShow = new BigLetter(scene, bounds.centerX, bounds.centerY + 8);
         
    }
 
    // method to set the letter
    setLetter(letter : string) : void {
 
        // tint letterToShow black
        this.letterToShow.setTint(0);
 
        // set letterToShow text according to "letter" argument
        this.letterToShow.setText(letter.toUpperCase());
    }
}

bigLetter.ts

Custom class to manage the big letter which appears in the big letter box.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// BIG LETTER
 
// this class extends BitmapText class
export default class BigLetter extends Phaser.GameObjects.BitmapText {
     
    constructor(scene : Phaser.Scene, x : number, y : number) {   
        super(scene, x, y, 'bigfont', '');
 
        // set registration point to center
        this.setOrigin(0.5);
 
        // add the letter to the scene
        scene.add.existing(this);
    }
}

As you can see, there are two ways to use DOM objects in Phaser games, they can be included in the game itself or kept outside the game and controlled by listeners.

Which one would you use? As usual, you can download the source code of the entire project.

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