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.
History displays a page showing statistics on games played and turns taken to win.
Games gives you a choice of 4-letter, 5-letter and 6-letter games.
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.
<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.
<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 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.
.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.
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.
$(document).ready(function () {
LoadSizingStyleSheet();
const AttachEvents = function () {
$(".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.
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.
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.
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.
String.prototype.mysubstr = function (start, length) {
if (start < 0) {
start = this.length + start;
}
start = Math.max(start, 0);
let end = start + length;
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.
$(".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:
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:
var allElements = document.getElementsByTagName('*');
var keys = [];
for (var i = 0; i < allElements.length; i++) {
if (allElements[i].classList.contains('key')) {
keys.push(allElements[i]);
}
}
for (var i = 0; i < keys.length; i++) {
keys[i].addEventListener('click', function(e) {
var isDisabled = false;
for (var j = 0; j < keys.length; j++) {
if (keys[j].disabled) {
isDisabled = true;
break;
}
}
if (isDisabled) {
return;
}
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.
$(".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.
document.addEventListener('keyup', function (e) {
let key = e.which;
let letter = "";
let shift = e.shiftKey;
let ctrl = e.ctrlKey;
if (shift && ctrl) {
return;
}
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:
$("#" + 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 {
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);
QueueColorCode("aaaa", letterID);
}
let targetWord = g.TargetWord;
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, '!');
}
}
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);
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:
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 (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:
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:
@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.
.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:
Taking 6 turns instead of 4 (par)
Losing is worse.
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 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:
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.
$(document).ready(function () {
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.
$(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:
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.
<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.
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.
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.