Click here to Skip to main content
16,004,507 members
Articles / Game Development

Creating Browser Based Word Games

Rate me:
Please Sign up or sign in to vote.
1.44/5 (2 votes)
15 Aug 2024CPOL15 min read 1.9K   17   2   3
This article describes my approach to writing a word game that imitates Wordle and further games that use the same techniques.

Introduction

This article describes the development of word games that depend on dictionaries and finding words. It uses a clone of Wordle as the primary target but also covers other games built using the same technology. It will show how to store dictionaries, how to look up words quickly, how to use HTML to define boards, tiles and on screen keyboards, how to uses CSS and W3 to style for different screen resolutions, how to use JavaScript events, and how to use jQuery, especially its support for animation. My clone of Wordle is nothing like Wordle under the hood. It uses different dictionaries, has a different look and feel, has more options, such as four-letter and six-letter alternatives, and is more fun to play. I call my version GuessWord. I will also discuss Griddle, an interlocking word game that is more challenging than Wordle , and Word Pyramid, a word game that is based on finding anagrams.

Structuring the HTML page

Guessword is self-contained on a single HTML page. It does not communicate with a server or other apps once the page has been loaded into a browser window. The initial screen has a header bar containing a simple menu built using W3 styles.

Play starts a new game.

About hides the body of the page and shows a panel containing information about the game and instructions on how to play the game.

GuessWord Why & How

History displays a page showing statistics on games played and turns taken to win.

Player's record'

Games gives you a choice of 4-letter, 5-letter and 6-letter games.

Games Menu

Creating the playing board and on-screen keyboard

It is best to use HTML to define the elements but leave all the styling to CSS and the manipulation to JavaScript and jQuery. To that end, every HTML element has unique ID and is assigned a class. Here is the layout of the letter entry grid in HTML in GuessWord.

HTML
<div id="boardleftmargin" class="fivecolmargin">
    <div id="column row0">
        <span id="0_0" class="gridletter empty delay0 w3-cell"></span>
        <span id="0_1" class="gridletter empty delay1 w3-cell"></span>
        <span id="0_2" class="gridletter empty delay2 w3-cell"></span>
        <span id="0_3" class="gridletter empty delay3 w3-cell"></span>
        <span id="0_4" class="gridletter empty delay4 w3-cell"></span>
        <span id="0_5" class="gridletter empty delay5 w3-cell"></span>
    </div>
    <div id="column row1">
        <span id="1_0" class="gridletter empty delay0 w3-cell"></span>
        <span id="1_1" class="gridletter empty delay1 w3-cell"></span>
        <span id="1_2" class="gridletter empty delay2 w3-cell"></span>
        <span id="1_3" class="gridletter empty delay3 w3-cell"></span>
        <span id="1_4" class="gridletter empty delay4 w3-cell"></span>
        <span id="1_5" class="gridletter empty delay5 w3-cell"></span>
    </div>
    <div id="column row2">
        <span id="2_0" class="gridletter empty delay0 w3-cell"></span>
        <span id="2_1" class="gridletter empty delay1 w3-cell"></span>
        <span id="2_2" class="gridletter empty delay2 w3-cell"></span>
        <span id="2_3" class="gridletter empty delay3 w3-cell"></span>
        <span id="2_4" class="gridletter empty delay4 w3-cell"></span>
        <span id="2_5" class="gridletter empty delay5 w3-cell"></span>
    </div>
    <div id="column row3">
        <span id="3_0" class="gridletter empty delay0 w3-cell"></span>
        <span id="3_1" class="gridletter empty delay1 w3-cell"></span>
        <span id="3_2" class="gridletter empty delay2 w3-cell"></span>
        <span id="3_3" class="gridletter empty delay3 w3-cell"></span>
        <span id="3_4" class="gridletter empty delay4 w3-cell"></span>
        <span id="3_5" class="gridletter empty delay5 w3-cell"></span>
    </div>
    <div id="column row4">
        <span id="4_0" class="gridletter empty delay0 w3-cell"></span>
        <span id="4_1" class="gridletter empty delay1 w3-cell"></span>
        <span id="4_2" class="gridletter empty delay2 w3-cell"></span>
        <span id="4_3" class="gridletter empty delay3 w3-cell"></span>
        <span id="4_4" class="gridletter empty delay4 w3-cell"></span>
        <span id="4_5" class="gridletter empty delay5 w3-cell"></span>
    </div>
    <div id="column row5">
        <span id="5_0" class="gridletter empty delay0 w3-cell"></span>
        <span id="5_1" class="gridletter empty delay1 w3-cell"></span>
        <span id="5_2" class="gridletter empty delay2 w3-cell"></span>
        <span id="5_3" class="gridletter empty delay3 w3-cell"></span>
        <span id="5_4" class="gridletter empty delay4 w3-cell"></span>
        <span id="5_5" class="gridletter empty delay5 w3-cell"></span>
    </div>
</div>

<div id="6_0" class="bottomspacer">

</div>

If we want to refer to the third letter in the second row, using jQuery, we could simply write $("#1_2"). In practice, we would use methods and variables to generate the reference. Each cell has three CSS classes assigned to them. w3-cell leverages the styling provide by the W3 style sheet. gridletter tells the browser how to render a letter and empty indicates the cell has not been populated. Classes can be manipulated at run time using JavaScript and jQuery.

The onscreen keyboard follows the same principles.

HTML
<div id="keyboard">
    <div class="firstkeyfirstrow">
        <div id="q" class="key delay5 empty w3-cell">Q</div>
        <div id="w" class="key delay5 empty w3-cell">W</div>
        <div id="e" class="key delay5 empty w3-cell">E</div>
        <div id="r" class="key delay5 empty w3-cell">R</div>
        <div id="t" class="key delay5 empty w3-cell">T</div>
        <div id="y" class="key delay5 empty w3-cell">Y</div>
        <div id="u" class="key delay5 empty w3-cell">U</div>
        <div id="i" class="key delay5 empty w3-cell">I</div>
        <div id="o" class="key delay5 empty w3-cell">O</div>
        <div id="p" class="key delay5 empty w3-cell">P</div>
    </div>
    <div class="firstkeysecondrow">
        <div id="a" class="key delay5 empty w3-cell">A</div>
        <div id="s" class="key delay5 empty w3-cell">S</div>
        <div id="d" class="key delay5 empty w3-cell">D</div>
        <div id="f" class="key delay5 empty w3-cell">F</div>
        <div id="g" class="key delay5 empty w3-cell">G</div>
        <div id="h" class="key delay5 empty w3-cell">H</div>
        <div id="j" class="key delay5 empty w3-cell">J</div>
        <div id="k" class="key delay5 empty w3-cell">K</div>
        <div id="l" class="key delay5 empty w3-cell">L</div>
    </div>
    <div class="firstkeythirdrow">
        <div id="z" class="key delay5 empty w3-cell">Z</div>
        <div id="x" class="key delay5 empty w3-cell">X</div>
        <div id="c" class="key delay5 empty w3-cell">C</div>
        <div id="v" class="key delay5 empty w3-cell">V</div>
        <div id="b" class="key delay5 empty w3-cell">B</div>
        <div id="n" class="key delay5 empty w3-cell">N</div>
        <div id="m" class="key delay5 empty w3-cell">M</div>
    </div>
    <div id="buttonrow" class="firstkeyfirstrow">
        <div id="backspace" class="backspacekey delay5 empty w3-cell">
            ⌫ Backspace
        </div>
        <div id="enter" class="enterkey delay5 empty w3-cell">
            Enter ⏎
        </div>
    </div>
</div>

At run time, they look like this:

Layout And Keyboard

Layout and on-screen keyboard

Styling the playing board and on-screen keyboard

These are the CSS styles that govern the appearance of the letters and keyboard depending on their state. For example, letters in the correct position have the empty class replaced by the green class.

CSS
.gridletter {
    border: 1px solid black;
    box-shadow: 3px 3px 5px #999;
    cursor: pointer;
    text-align: center;
}

.key, .backspacekey, .enterkey {
    border: 1px solid;
    text-shadow: 1px 0 0 black;
    cursor: pointer;
    text-align: center;
    vertical-align: middle;
}

.highlight {
    border: 3px dashed red;
    padding: 0;
}

@keyframes rotate {
    0% {
        transform: rotateY(90deg);
    }
}

.empty {
    background: linear-gradient(135deg, rgb(245,245,245), rgb(250,235,215));
}

.green {
    background: linear-gradient(135deg, chartreuse, seagreen);
    animation-duration: 1.5s;
    animation-name: rotate;
    animation-fill-mode: forwards;
}

.grey {
    background: linear-gradient(135deg, rgb(220,220,220), rgb(119,136,153));
    animation-duration: 1.5s;
    animation-name: rotate;
    animation-fill-mode: forwards;
}

.yellow {
    background: linear-gradient(135deg, lightyellow, gold);
    animation-duration: 1.5s;
    animation-name: rotate;
    animation-fill-mode: forwards;
}

.red {
    background: linear-gradient(135deg, rgb(255,192,203),rgb(240,128,128));
    animation-duration: 1.5s;
    animation-name: rotate;
    animation-fill-mode: forwards;
}

.greengrad {
    background: linear-gradient(0deg, mediumseagreen, green);
    border: 1px solid;
    border-radius: 8px;
    box-shadow: 3px 3px 5px #999;
}

Note that the CSS provides a subtle background gradient and CSS animation properties. The animation rotates a letter tile when the class is changed. Here is what a completed game looks like.

Completed Game

CSS for letters and keys

Coding the game logic in JavaScript

GuessWord initializes the game when the document is "ready". It uses jQuery to determine readiness.

jquery
$(document).ready(function () {
//
// Load stylesheet based on screen size
LoadSizingStyleSheet();

const AttachEvents = function () {
    //
    // Add Events to Keyboard letter
    $(".key").on('click', function (e) {
        if ($(".key").prop('disabled')) {
            return;
        }
        let letter = e.target.id.toUpperCase();
        SetLetter(letter);
    });

    ...

    $(window).resize(function () {
        LoadSizingStyleSheet();
    });
}
document.addEventListener('keyup', function (e) {
    let key = e.key;
    let letter = "";

    ...

    switch (key) {
        case "Shift":
        case "Control":
        case "Alt":
            break;
        case "Backspace":
            ProcessBackspace();
            break;
        case "Enter":
            ProcessEnter();
            break;
        default:
            if ($(".key").prop('disabled')) {
                return;
            }
            if (g.EndOfGame) {
                return;
            }
            if (key >= "a" && key <= "z") {
                letter = key.toUpperCase();
            }
            else if (key >= "A" && key <= "Z") {
                letter = key;
            }
            if (letter != "") {
                SetLetter(letter);
            }
            break;
            }
        });
        AttachEvents();
        SetColunmVisibility(5, false);
        LocalStorageAvailable();
        SetEmailField();
        SetCopyRight();
        StartGame();
    });

GuessWord uses a single object to store global information and the state of the game. This is the declaration and instantiation.

JavaScript
const Global = function () {
    this.CurrentPos = 0;
    this.LastPos = -1;
    this.GridWidth = 5;
    this.CurrentRow = 0;
    this.ActiveCell = '';
    this.Positions = [
        "0_0", "0_1", "0_2", "0_3", "0_4", "0_5",
        "1_0", "1_1", "1_2", "1_3", "1_4", "1_5",
        "2_0", "2_1", "2_2", "2_3", "2_4", "2_5",
        "3_0", "3_1", "3_2", "3_3", "3_4", "3_5",
        "4_0", "4_1", "4_2", "4_3", "4_4", "4_5",
        "5_0", "5_1", "5_2", "5_3", "5_4", "5_5"
    ];
    this.Words = ['', '', '', '', '', ''];
    this.Mode = normalMode;
    this.LocalStorageAvailable = true;
    this.Alphabet = "abcdefghijklmnopqrstuvwxyz";
    this.Letter = " "
    this.CurrentPage = page_play;
    this.EndOfRow = false;
    this.EndOfGame = false;
    this.Won = false;
    this.TargetWord = "PRICE";
    this.KeyBoardState = kb_StartOfWord;
    this.ColorCodeQueue = [];
}
var g = new Global;

You can declare functions in many different ways, I chose this style.

JavaScript
const {function name} = function ({arguments}) {
   {body of function}
}

For example, this function returns the ID for a letter tile diven the row and column numbers to use in a jQuery reference to the tile.

JavaScript
const GetID = function (row, col) {
    return "#" + row.toString() + "_" + col.toString();
}

I used the same style to declare objects.

My JavaScript code was littered with dire warnings about how Substr was now deprecated. How dare they deprecate a function with such a long history! I've been using Substr ever since PL/1 was the next big thing that was going to replace Fortran and Cobol. It survived into Visual Basic and C# but now it is to be banished? So, I asked my good buddy ChatGTP to create a "mysubstr" method to replace "substr" when used as a method. Here's what it created.

JavaScript
String.prototype.mysubstr = function (start, length) {
    // If start is negative, calculate start from the end of the string
    if (start < 0) {
        start = this.length + start;
    }

    // Ensure start is not less than 0
    start = Math.max(start, 0);

    // Calculate the end index based on start and length
    let end = start + length;

    // Use slice to get the substring
    return this.slice(start, end);
};

Using jQuery to simplify the code

jQuery makes it easy to write code that references or modifies HTML elements. It does much more than that, of course, but for a simple application like GuessWord, just using that jQuery capability makes coding so much easier. For example, reacting to a key click on the on-screen keyboard takes very little code.

jquery
$(".key").on('click', function (e) {
    if ($(".key").prop('disabled')) {
        return;
    }
    let letter = e.target.id.toUpperCase();
    SetLetter(letter);
});

If we used pure JavaScript and we were good coders we might come up with this code to do the same thing:

JavaScript
const keys = document.querySelectorAll('.key');
keys.forEach(function(key) {
    key.addEventListener('click', function(e) {
        if (Array.from(keys).some(key => key.disabled)) {
            return;
        }
        let letter = e.target.id.toUpperCase();
        SetLetter(letter);
    });
});

A novice might code something like this:

// Get all elements with the class 'key'
var allElements = document.getElementsByTagName('*'); // Get all elements in the document
var keys = [];
for (var i = 0; i < allElements.length; i++) {
    if (allElements[i].classList.contains('key')) {
        keys.push(allElements[i]); // Add elements with class 'key' to the keys array
    }
}

// Add click event listener to each key
for (var i = 0; i < keys.length; i++) {
    keys[i].addEventListener('click', function(e) {
        // Check if any key is disabled
        var isDisabled = false;
        for (var j = 0; j < keys.length; j++) {
            if (keys[j].disabled) {
                isDisabled = true;
                break;
            }
        }
        if (isDisabled) {
            return;
        }

        // Get the letter from the clicked key's id and call SetLetter
        var letter = e.target.id.toUpperCase();
        SetLetter(letter);
    });
}

Playing the Game

Entering Letters

The player enters a word using a physical keyboard or the on-screen keyboard. As each letter is entered, the next available tile in the layout is selected and populated with the letter. Since we are not typing into an HTML text entry area, we need a way to wire up the tile with the keybooard. We use a jQuery selector to add an event to every key on the on-screen keyboard with this code.

jquery
//
// Add Events to Keyboard letter
$(".key").on('click', function (e) {
    if ($(".key").prop('disabled')) {
        return;
    }
    let letter = e.target.id.toUpperCase();
    SetLetter(letter);
});

If the user uses the keyboard then we use an event listener to process each key stroke. This is the code for the keyboard listener.

jquery
  document.addEventListener('keyup', function (e) {
    let key = e.which;
    let letter = "";
    let shift = e.shiftKey;
    let ctrl = e.ctrlKey;
    // Stop invoking debugger passing on "I".
    if (shift && ctrl) {
        return;
    }
    // Don't let copy/paste through
    if (ctrl && (key == 67 || key == 86)) {
        return;
    }
    switch (key) {
        case 8:
            ProcessBackspace();
            break;
        case 13:
            ProcessEnter();
            break;
        default:
            if ($(".key").prop('disabled')) {
                return;
            }
            if (g.EndOfGame) {
                return;
            }
            if (key >= 65 && key <= 90) {
                letter = g.Alphabet.mysubstr(key - 65, 1).toUpperCase();
            }
            else if (key >= 97 && key <= 122) {
                letter = g.Alphabet.mysubstr(key - 97, 1);
            }
            if (letter != "") {
                SetLetter(letter);
            }
            break;
    }
});

Whichever way the letter is entered, the SetLetter function is used to process the letter. The key statement inside SetLetter is:

jquery
$("#" + g.Positions[g.CurrentPos]).text(letter);

where g.Postions[g.CurrentPos] returns a key identifier, such as "4_2".

Processing a word

The Guessword help tab explains the rules.

Your task is to deduce the guess word that GuessWord has randomly selected from its dictionary of common words. You can enter your guess using the on-screen keyboard or a physical keyboard, if available.:

When you select "enter", GuessWord colors each letter in your guess using the following rules:

If the letter is not present in the guess word, it is colored grey.

If the letter is present in the guess word but is in the wrong position, it is colored yellow.

If the letter is present and in the right position, it is colored green.

If the guess is not in the GuessWord dictionary of almost all words, then all the letters in the guess are colored red.:

If your guess contains a letter that appears more than once in the target, then only a letter in the correct position will be colored green. Otherwise, the first match in the target will be colored yellow.

If your guess contains the same letter more than once, then only a letter in the correct position will be colored green. Otherwise, the first match in the guess will be colored yellow.

You can use the backspace key (⌫) to clear the guess. Alternatively, you can click or tap letters in the word and change them using either keyboard. When you do this, the letter you tap is highlighted with a red dashed border. After you enter a letter or simply click or tap the highlighted cell, the border is cleared. If the word is complete and you correct it, Guessword will remove the red, but only if the word is in its full dictionary.

The logic for implementing these rules is a little tricky. For me, the key was to separate searching for green letters from yellow letters. Here is the function that processes a guess.

        const WordAccepted = function (row) {
            g.ColorCodeQueue = [];
            let letter = '';
            let letterID = '';
            let originalguess = '';
            let guess = GetGuess(row);
            originalguess = guess;
            let found = ValidWord(guess.toLowerCase());
            if (!found) {
                for (let i = 0; i < g.GridWidth; i++) {
                    let letterID = GetID(row, i);
                    ChangeColorClass(letterID, red);
                }
                KeyBoardState(kb_BadWord);
                return false;
            }
            else {
                //
                // Set all letters in grid to grey (missing)
                for (let i = 0; i < g.GridWidth; i++) {
                    let letterID = GetID(row, i);
                    let letter = guess.mysubstr(i, 1);
                    let keyID = "#" + letter.toLowerCase();
                    SetKeyColorCode(greyCode, keyID);
                    // Force grey elements to front of pairs with same letterID
                    QueueColorCode("aaaa", letterID);
                }
                let targetWord = g.TargetWord;
                //
                // Loop through the target word looking for exact matches
                for (let i = 0; i < g.GridWidth; i++) {
                    let letter = guess.mysubstr(i, 1);
                    if (letter == targetWord.mysubstr(i, 1)) {
                        letterID = GetID(row, i);
                        let keyID = "#" + letter.toLowerCase();
                        SetKeyColorCode(greenCode, keyID);
                        QueueColorCode(green, letterID);
                        targetWord = HideMatch(targetWord, i, '?');
                        guess = HideMatch(guess, i, '!');
                    }
                }
                //
                // Loop through the target word looking for inexact matches
                for (let i = 0; i < g.GridWidth; i++) {
                    let letter = guess.mysubstr(i, 1);
                    let pos = targetWord.indexOf(letter);
                    if (pos >= 0) {
                        letterID = GetID(row, i);
                        let keyID = "#" + letter.toLowerCase();
                        SetKeyColorCode(yellowCode, keyID);
                        QueueColorCode(yellow, letterID);
                        targetWord = HideMatch(targetWord, pos, '?');
                        guess = HideMatch(guess, i, '!');
                    }
                }
                ProcessColorCodeQueue();
                g.TargetWord = g.TargetWord.toUpperCase();
                originalguess = originalguess.toUpperCase();
                g.Words[g.CurrentRow] = originalguess;
                g.Won = originalguess == g.TargetWord
                if (g.Won || g.CurrentRow == 5) {
                    g.EndOfGame = true;
                    KeyBoardState(kb_EndOfGame);
                    letterID = GetID(g.CurrentRow + 1, 0);
                    // Display finish message after tiles colored - note CurrentRow is incremented before timeout executes
                    setTimeout(function () { ShowMessage(GetMessage(g.CurrentRow + (g.Won ? -1 : 0)), letterID, DoNothing) }, 2000);
                }
                g.CurrentRow++;
                g.EndOfRow = false;
                KeyBoardState(kb_StartOfWord);
                return true;
            }
}

Dictionaries

The first question that pops into one's mind is how do you provide a browser application with a complete English dictionary without using a back-end server? The answer is that a file containing all 5 letter words is about 50KB. Browsers handle images that are 20MB or more, so 50KB is nothing. Guessword simply puts all the words into a JavaScript array. From peeking into Wordle in debug mode, I believe it does the same thing. So, our dictionary looks like this:

JavaScript
const _FullDict5 = [
    "aahed",
    "aalii",
    "aargh",
    ...
    "zowie",
    "zuzim",
    "zymes"
];

Guessword has 4 letter and 6 letter variants built in, so it also has dictionaries for all 4 letter and 6 letter words. If we allowed any word as the word to guess then we would have a lot of people asking "what is an aalii?" So, we need dictionaries of common words that can be used as the guess word. I used various sources on the Internet to cobble together dictionaries of common 4, 5 and 6 letter words. This table shows the number of words in each dictionary.

Dictionary Sizes

Dictionary Sizes (bytes)

Adding up all the characters gets us to 195,950 bytes.

We also need a way to choose the guess word for each game. I used Excel to give me a list of numbers from 1 up to the number of words in each target dictionary. Then I used =Rand() to create a random number for each number in each of the sequence columns. I sorted the three pairs of columns using the random number as the key. This gave me three list of numbers in random sequence, which I copied into the Guess Word dictionary file. The first time a users plays Guess Word, it generates a random number in the range 0 to the number of words in the dictionary. This number is the starting index and should be close to unique for each player. When a new game is started, Guess Word retrieves the starting index and uses that to select the corresponding word from a target dictionary. It then increments the starting index. The casual debugger would not be able to determine the current guess word just by looking at the Guess Word dictionaries.

Guess Word uses a binary search for checking whether a word is in a dictionary. These are the functions it uses:

JavaScript
const ValidWord = function (word) {
    let valid = true;
    switch (g.GridWidth) {
        case 4:
            if (BinSearch(_FullDict4, word) < 0) {
                valid = false;
            }
            break;
        case 5:
            if (BinSearch(_FullDict5, word) < 0) {
                valid = false;
            }
            break;
        case 6:
            if (BinSearch(_FullDict6, word) < 0) {
                valid = false;
            }
            break;
        default:
            alert('oops');
    }
    return valid;
}
const BinSearch = function (dictonary, word) {
    if (!dictonary.length) return -1;

    let high = dictonary.length - 1;
    let low = 0;
    let mid = 0;
    while (low <= high) {
        mid = parseInt((low + high) / 2);
        const element = dictonary[mid];
        if (element > word) {
            high = mid - 1;
        }
        else if (element < word) {
            low = mid + 1;
        }
        else {
            return mid;
        }
    }
    return -1;
};

Animation

Computer games need a little animation, even if they are just implementations of physical games like solitaire and scrabble. Guess Word uses CSS animation to change the color of tiles by rotating them to reveal the new color. The animation is initiated when a letter's CSS class is changed from empty to green (say). The CSS looks like this:

CSS
@keyframes rotate {
0% {
    transform: rotateY(90deg);
    }
}

.empty {
    background: linear-gradient(135deg, rgb(245,245,245), rgb(250,235,215));
}

.green {
    background: linear-gradient(135deg, chartreuse, seagreen);
    animation-duration: 1.5s;
    animation-name: rotate;
    animation-fill-mode: forwards;
}
etc.

If a completed word was animated by switching the letters to the computed class in a simple loop, then all the letters would rotate at the same time. This looked a bit silly, So we delay the animation start depending on the column with an additional style applied to each column. We also applied the delay5 style to the keyboard letters so they get recolored after the letters in the letter entry grid are recolored.

CSS
.delay0 {
    animation-delay: 0ms;
}
.delay1 {
    animation-delay: 400ms;
}
.delay2 {
    animation-delay: 800ms;
}
.delay3 {
    animation-delay: 1200ms;
}
.delay4 {
    animation-delay: 1600ms;
}
.delay5 {
    animation-delay: 2000ms;
}

Rewarding a Win (or not)

Guess Word displays a modal message when the player has won or lost. Note that the message is displayed after the tiles have been rotated. That introduces a little tension as the player waits to see if all the letters turn green. The message is made modal by the simple expedient of placing a semi transparent div over the board and under the message box. The messages are golf themed. This is an example for a narrow win:

Double Bogey

Taking 6 turns instead of 4 (par)

Losing is worse.

Lost

Losing

Four letter and Six letter versions of Guess Word

You can select between four, five and six letter versions. It would be easy to add even more variations, and some programmers published Wordle clones with up to 12 letters. After trying them out, four is as low as you can go before it becomes trivially easy to win, and going beyond six letters gets quite difficult because it is hard to think of words of seven letters or more fitting the constraints of the letters already identified and located.

The Daily challenge selects a 5 letter guess word identified by the date. This emulates Wordle which gives all players the same guess word on the same day.

Sizing using multiple CSS files

I used a somewhat unorthodox method to allow the game to be resized to suit the device that hosts it, be it a smart phones, mobile device, tablet, laptop or desktop computer. I have a style sheet that governs appearances and then a style sheet for each possible screen width from 312px wide to 1028px that governs dimensions. This seems like a horrible idea, having around 300 style sheets, one for each possible width +/- 2 pixels. The trick is to have a program that can generate those style sheets with minimal effort. At run time, Guess Word only downloads the style sheet that matches the current screen width; not all 300 versions.

We start with two style sheets called Guess WordFull and Guess WordBase. The first sizes the HTML elements for a screen width of 1024px. The second sizes them for a screen width of 316px. Here is how the size of a grid letter is set in the three style sheets.

Style Sheets

Style Sheet comparison

All the sizing styling is done in Guess WordFull and Guess WordBase. Any other styling, including sizing that doesn't need to be scaled, is done in Guess Word.css. It is critical that the two sizing stylesheets line up exactly. This is a constraint imposed by the program that interpolates betwwen the two stylesheets The program that creates them is a simple C# program that reads the two sizing sheets and creates style sheets called Guess Word312.css through Guess Word1028.css. The source for this program is included in the download. The user interface looks like this:

Style Sheets

CSS Resizer

 

The first thing that happens when the Guess Word.HTML web page has been loaded by the browser is the stylesheet reference in the HTML header is switched to match the size of the browser window.

jquery
$(document).ready(function () {
    //
    // Load stylesheet based on screen size
    LoadSizingStyleSheet();
...
    const LoadSizingStyleSheet = function () {
        let screensize = $(window).width();
        let size = "Full";
        for (let screensizes = 320; screensizes <= 1024; screensizes += 4) {
            if (screensize <= screensizes) {
                size = screensizes;
                break;
            }
        }
        let version = $("#version").text().trim();
        if (version != "") {
            version = "_" + version.replace(".", "_");
        }

        $("#screensize").attr("href", "css/GuessWord" + size + version + ".css");
        setTimeout(function () {
            SetGameName();
        }, 100);
    }

Note that the JavaScript and CSS files in Guess Word have version numbers to ensure the game is updated when the server version is updated.

If the web page is resized, the CSS file is updated. This is done in the resize event.

jquery
$(window).resize(function () {
    LoadSizingStyleSheet();
});

Deployment

We need a way to copy the HTML, CSS, JS, and IMG files to the server. For simple apps this is usually done with file copy or an FTP transfer. However, browsers love to cache files they have seen before, and getting the browser to actually go back to the server and get the latest files requires the user to know how to clear the cache and do a hard reset.To address this issue, I added a version number to every JavaScript (.js) and style sheet (.ccs) file. I wrote a simple C# program to go plug the current version number into the HTML file and then append that version number to every file that constitutes the application. Third-party files, such as W3.css and the jQuery distributable are loaded from external sources and are not included in the versioning process. The C# program takes the application files, versionizes them and writes them to a separate folder called "Deploy". The application files can then be copied or FTP'd from the Deploy folder to the web server. The source code for this program is included in the source code download. The user interface looks like this:

GuessWord Why & How

W3

I wanted a simple framework that would work with mobile devices and larger screens. I selected W3 over alternatives like BootStrap because it appeared to be simpler and was purely CSS based. I used its classes to partially style most elements on the web page. The menu depends almost entirely on W3.css.

HTML
<div id="menu" class="w3-bar w3-show">
    <a id="play" href="javascript: StartGame();" class="w3-bar-item w3-button w3-left-align whitetext">🎬Play</a>
    <a id="about" href="javascript: ShowAbout();" class="w3-bar-item w3-button w3-left-align whitetext">🧐About</a>
    <a id="history" href="javascript: ShowHistory();" class="w3-bar-item w3-button w3-left-align whitetext">📜History</a>
    <div class="w3-dropdown-hover">
        <button class="w3-button whitetext" onclick="ShowDropDownBody('gamesdropdown');">🎲Games▾</button>
        <div id="gamesdropdown" class="w3-dropdown-content w3-bar-block w3-card-4 greengrad whitetext">
            <a id="link1" href="javascript: SetGridWidth(page_fourlettergame, normalMode);" class="w3-bar-item w3-button">4-letter game</a>
            <a id="link2" href="javascript: SetGridWidth(page_fivelettergame, normalMode);" class="w3-bar-item w3-button">5-letter game</a>
            <a id="link3" href="javascript: SetGridWidth(page_sixlettergame, normalMode);" class="w3-bar-item w3-button">6-letter game</a>
            <a id="link3" href="javascript: SetGridWidth(page_fivelettergame, dailyMode);" class="w3-bar-item w3-button">Daily challenge</a>
        </div>
    </div>
</div>

I was not able to get W3 to do the automatic resizing from mobile to desktop, but then, I didn't try very hard. If I had started using W3 from the beginning I would have had more success.

Further games

I created two more original word games using Guess Word as the base.

Griddle

Griddle shows a 5x5 grid with some letters filled in. The objective is to complete the grid so each word across matches each word down. It is surprisingly difficult. The start position and winning position are shown in these two screen shots.

Griddle Starting Position

Griddle Winning Game

Anagram Pyramid

Anagram Pyramid shows a pyramid of letters. The top two rows are filled in. The objective is to complete the pyramid by creating an anagram from the letters in the previous row plus one more letter. The start position and winning position are shown in these two screen shots.

Anagram Pyramid Starting Game

Anagram Pyramid Winning Game

Conclusion and Points of Interest

While Guess Word appears to be a clone of Wordle, it is an original application developed from scratch using CSS, HTML, W3.css, and jQuery. I used Visual Studio 2022 as the development platform and wrote a couple of support programs to support screen sizes ranging from a budget smart phone up to a desktop PC.

Guess Word removes the once-a-day restriction of Wordle and adds 4 letter and 6-letter versions. It is also server independent. Once the Guess Word web page has been loaded. the user can continue playing without an internet connection. My wife and I play Wordle every morning to see who can get the lower score. Playing Guess Word hones our skills so we rarely lose at Wordle.

Guess Word demonstrates an easy way to include dictionaries in a word game without using an external database or server. It shows how to use HTML and CSS to create a playing board for a word game. It uses JavaScript and jQuery to implement the game logic. While it is not an action game, Guess Word does animate some actions, such as flipping and recoloring letter tiles and keyboard keys using CSS animation.

The application can serve as a foundation for similar games using words and dictionaries. For example, you could use much of the code to create web based implementations of Scrabble and Boggle.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer Retired/Contractor
United States United States
I have been a software developer since 1969. I started writing Fortran programs for an IBM 1620 computer. That was followed by a few years writing commercial software for IBM 360/370 computers using PL/1. In 1979, my firm bought an Hewlett=Packard 3000 minicomputer. I developed a 4GL for this computer to make it easier to support pension fund applications.

In the 1990s, I moved to the US and worked as a consultant developing commercial applications using Visual Basic and various databases. I was the architect and lead developer for BETS, a VB6/Oracle application that administered healthcare benefits for Kaiser Permanente in Ohio. I later joined Kaiser Permanente and continued working on BETS.

When Microsoft stopped supporting VB6, the decision was made to convert BETS to a web based platform. Once again, I was the architect and lead developer, although we were fortunate to have a great web developer on the team. We converted 1.5 million lines of VB6 code to C# and javaScript. That project was very successful and Kaiser implemented BETS in four other regions. That was my last project before retiring.

I write games and personal applications for fun. Redback is the first project I've shared on Code Project. I have a checkers program written in C# and a web-based word program written in javaScript and jQuery in the works plus a couple of little image utilities that support my photography hobby.

Comments and Discussions

 
SuggestionOne suggestion Pin
Sergey Alexandrovich Kryukov16-Aug-24 3:58
mvaSergey Alexandrovich Kryukov16-Aug-24 3:58 
GeneralRe: One suggestion Pin
Pat Dooley16-Aug-24 9:33
professionalPat Dooley16-Aug-24 9:33 
AnswerRe: One suggestion Pin
Sergey Alexandrovich Kryukov16-Aug-24 15:11
mvaSergey Alexandrovich Kryukov16-Aug-24 15:11 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.