Anagram Survival
Table Of Contents
You can play the game here.
The idea for this project was to make a simple word game that anybody could play and hopefully have fun doing so. I used this project as a way to brush up on my reactjs skills as I had mostly dealt with react previously by adding custom react components to an existing site through Raisely. So I wanted to build a project from start to finish instead. I typically like to use vanilla JavaScript for small projects or frameworks like Svelte for more interactive ones, so this was new for me.
The Game
The basic idea is to try and come up with anagrams of the current word/letters. The catch is that once you submit a valid word, you can now only use the letters from that word. You get a point for each word you submit and the game ends when you have ran out of valid words.
For example, a game could go like this:
Turn 1
current letters: REACT
submitted word -> CARE
Turn 2
current letters: CARE
submitted word -> CAR
Turn 3
current letters: CAR
Building the UI
Colour Scheme
The colour scheme was created on coolors.co using some pastel colours to fit what I thought a modern word game might look like.
Fonts
I used Google Fonts to look for some aesthetically pleasing fonts that would suit the game.
I chose a serif font for displaying the remaining letters as this would give the best legibility (especially for capital letters). The font chosen was Rosarivo.
For the other text such as headings or instructions I chose the sans-serif font Reddit Mono for a modern look.
The fonts and font imports were optimised with Transfonter.
Layout and Styling
The rest of the styling such as layout, buttons, etc was done using plain CSS as this is the easiest option for me.
Valid words and starting words
Precomputing scores
Initially I thought it would be important to choose starting words that allowed for higher possible scores, and also for the user to know what the highest score is for that word. In order to have this information available I had to precompute the scores for all of the words in the word list (~25,000 words).
I used a simple recursive approach with memoization to find the highest possible score for each word. I started by finding all the anagrams for each word and stored this data. Then for each word, find which anagram to choose based on the highest score of each anagram, and then base the current word’s score of this. It involves a recursive function to find the score of each anagram and the solution comes together nicely.
In the end I chose the top 2,000 words with the highest possible scores and this set became the starting words. The text file containing the words and their corresponding highest scores is shown below.
CONVERSATIONALIST 26 CONVERSATIONAL 25 CONVERSATIONS 25 ENVIRONMENTALIST 25 EXTRATERRESTRIALS 25 OVERCOMPENSATING 25 RATTLESNAKES 25 REVOLUTIONARIES 25 UNCHARACTERISTICALLY 25 ALTERNATIVES 24
... 1990 more lines
And the text file containing the starting words and an optimal solution for each is shown below.
CONVERSATIONALIST CONVERSATIONALIST,CONVERSATIONAL,CONSERVATION,CONVERSATION,CONTAINERS,CREATIONS,REACTIONS,ANCESTOR,COASTER,CATERS,CRATES,REACTS,TRACES,RATES,STARE,TEARS,EAST,EATS,SEAT,TEAS,ATE,EAT,ETA,TAE,TEA,AT,TA CONVERSATIONAL CONVERSATIONAL,CONSERVATION,CONVERSATION,CONTAINERS,CREATIONS,REACTIONS,ANCESTOR,COASTER,CATERS,CRATES,REACTS,TRACES,RATES,STARE,TEARS,EAST,EATS,SEAT,TEAS,ATE,EAT,ETA,TAE,TEA,AT,TA CONVERSATIONS CONVERSATIONS,CONSERVATION,CONVERSATION,CONTAINERS,CREATIONS,REACTIONS,ANCESTOR,COASTER,CATERS,CRATES,REACTS,TRACES,RATES,STARE,TEARS,EAST,EATS,SEAT,TEAS,ATE,EAT,ETA,TAE,TEA,AT,TA ENVIRONMENTALIST ENVIRONMENTALIST,REVELATIONS,RELATIVES,VERSATILE,EARLIEST,RELATES,STEALER,ALERTS,ALTERS,SLATER,LEAST,SLATE,STALE,STEAL,TALES,EAST,EATS,SEAT,TEAS,ATE,EAT,ETA,TAE,TEA,AT,TA EXTRATERRESTRIALS EXTRATERRESTRIALS,EXTRATERRESTRIAL,TERRESTRIAL,RETAILERS,EARLIEST,RELATES,STEALER,ALERTS,ALTERS,SLATER,LEAST,SLATE,STALE,STEAL,TALES,EAST,EATS,SEAT,TEAS,ATE,EAT,ETA,TAE,TEA,AT,TA OVERCOMPENSATING OVERCOMPENSATING,CONSERVATION,CONVERSATION,CONTAINERS,CREATIONS,REACTIONS,ANCESTOR,COASTER,CATERS,CRATES,REACTS,TRACES,RATES,STARE,TEARS,EAST,EATS,SEAT,TEAS,ATE,EAT,ETA,TAE,TEA,AT,TA RATTLESNAKES RATTLESNAKES,RATTLESNAKE,ALTERNATES,TRANSLATE,RATTLES,STARLET,STARTLE,ALERTS,ALTERS,SLATER,LEAST,SLATE,STALE,STEAL,TALES,EAST,EATS,SEAT,TEAS,ATE,EAT,ETA,TAE,TEA,AT,TA REVOLUTIONARIES REVOLUTIONARIES,REVELATIONS,RELATIVES,VERSATILE,EARLIEST,RELATES,STEALER,ALERTS,ALTERS,SLATER,LEAST,SLATE,STALE,STEAL,TALES,EAST,EATS,SEAT,TEAS,ATE,EAT,ETA,TAE,TEA,AT,TA UNCHARACTERISTICALLY UNCHARACTERISTICALLY,UNREALISTIC,REALISTIC,ARTICLES,RECITALS,CARTELS,SCARLET,ALERTS,ALTERS,SLATER,LEAST,SLATE,STALE,STEAL,TALES,EAST,EATS,SEAT,TEAS,ATE,EAT,ETA,TAE,TEA,AT,TA ALTERNATIVES ALTERNATIVES,ALTERNATES,TRANSLATE,RATTLES,STARLET,STARTLE,ALERTS,ALTERS,SLATER,LEAST,SLATE,STALE,STEAL,TALES,EAST,EATS,SEAT,TEAS,ATE,EAT,ETA,TAE,TEA,AT,TA
... 1990 more lines
choosing starting words deterministically based on date
I wanted the game to be similar to Wordle in that a word would be chosen each day (or custom number of hours), and that anybody accessing the game in any location in the world would have the same starting word. This can be done on the client-side by using a deterministic function to take the date (the number of milliseconds elapsed since January 1, 1970, 00:00:00 UTC, the Unix epoch) and convert it into an index into the array of starting words. Example code is below. The hash function is used to randomise the order of starting words (deterministically), rather than cycling through the list since it was in alphabetical order.
const NUM_HOURS_FOR_NEW_WORD = 24;
function universalHash(key, size) {
const A = 13;
const B = 7;
const PRIME = 31;
let hash = 0;
for (let i = 0; i < key.length; i++) {
hash = (hash * A + key.charCodeAt(i)) % PRIME;
}
hash = (hash * B) % PRIME;
return hash % size;
}
const currentTime = Math.floor(
Date.now() / (NUM_HOURS_FOR_NEW_WORD * 60 * 60 * 1000)
);
const index = universalHash(currentTime.toString(), words.length);
Future Plans
- Adding hangman style clues for a few good words in each turn.
- Adding an option to pick between short, medium, and long words.
- Some kind of past statistics or global leaderboard.