Video Tutorial | 'Merge Words' Game With AI
This is a fun game that we create using AI where users will be given a grid with parts that from words.. Ex: 'to', 'co', 'fo', 'il', 'ed' makes 'coil', 'foil', 'foiled', and 'toil'
The Tutorial
Do Subscribe To The YouTube Channel If You Haven’t Already And Drop A ❤️
The Recap
Previously, we covered how to use AI to create basic maths and word games. It had some cool features like,
functionality to solve equations
race against a timer
functionality to read out the rules
background music
voice for correct, incorrect, and repeated words
results
cookie to save game data on the browser (for 24 hours) in case you refresh
The Context
→ In the next game, we are going to create a 3x3 grid containing one-letter, two-letter and three-letter prefixes, bases and suffixes like
re
un
out
dis
er
s
y
en
ly
→ The valid words can be - ‘rower, borer, rows, mos, toes, rowers, borers, reborrow, rerowed, rerowing, outrow, outrowed, outrowing, unto, toy, rowen, moly‘.
→ There will be a big square box in the page with 9 mini square boxes.
→ Each square box will have a prefix, base or suffix.
→ We will refer to these small squares as ‘tiles’.
→ User is expected to tap on a tile, highlighting it and considering it selected. Then selects other, and they get added to the initial ones, creating a word which the user can then submit by pressing enter or clicking submit on mobile.
→ As a user selects a tile, it contributes to the creation of a word.
→ An input field shows the selected tiles.
→ A ‘Rules’ Box will show the rules to the user.
The Structure
As we progress into the series of game creation, we will get into more detailed structuring for AI to give us an optimal response. As the games get complex, the structuring also needs to be more detailed.
Help me create an HTML ‘Merge Words‘ game.
The following combination of suffixes, bases, and prefixes will be distributed into a 3x3 grid. Each tile will hold one.
The list is as follows
row
bor
er
s
mo
toe
re
ing
ed
The valid words allowed are - ‘bored, borer, borers, boring, borrow, borrowed, borrower, borrowing, borrows, mo, mos, reborers, rebored, reboring, reborrowed, reborrowing, reborrows, reed, rerow, rerowed, rerowers, rerowing, rerows, retoed, retoeing, retoes, row, rowed, rower, rowers, rowing, rows, toe, toed, toeing, toes, more‘ | I was able to get this list from Grok (Note For Us: For this task, it out performed ChatGPT and Perplexity)
There is an input field which takes a tile post, it is ‘Tapped’ Or ‘Clicked’.
The ‘Submit‘ button or the ‘Enter Key’ submits the word.
It is matched against the valid words list. If it is correct, the user sees a message which picks one of these adjectives - ‘awesome‘, ‘amazing‘, ‘fantastic‘, ‘nice’, ‘good’, ‘great job’, ‘correct’, ‘you did it’, ‘that right’, ‘good job’, ‘marvellous’ at random followed by a + sign followed by the number of points scored. If it is wrong, then consider the submission, but show text ‘Invalid Word‘
Each correct tile equals the number of letters it has. So, ‘ed‘ is 2 points and ‘bor‘ is 3. Similarly, ‘s’ is 1 point.
The ‘Rule‘ box will contain the following rules
Create as many words as possible using these tiles
Each tile has points relative to the letters in it
Tap or click on a tile to select it
Add a 10-minute countdown for the user to solve this puzzle. Add a cookie that saves ‘time remaining’ and ‘word created by the user’ and refreshes every 6 hours. Show the user a message on top that says ‘Game Refreshes In’, followed by the time remaining for the cookie to refresh. If a cookie is not created, show the user a message that ‘Your Progress Will Be Saved‘.
The sequence of the items on the page will be
The game title, which displays as ‘Merge Words’
The ‘Game Refreshes‘ or ‘Your Progress Will Be Saved‘ message, depending on the status of cookie.
A 10-minute countdown timer for the user to solve the game.
The input field takes the input from tiles being selected
The square 3x3 grid containing 9 tiles with one suffix, base, or prefix in each tile from the given list
‘Submit’ and ‘Backspace‘ buttons should be spaced and aligned in the same row.
Adjective, Invalid Word, Repeated Word Message After Submitting
‘Rule’ Box containing the rules mentioned above
‘Word List’ showcasing the words created
Pick the below sound paths. I will be saving these sounds be these names on the local system in a folder where other files are.
background-music.mp3 (for background music)
correct-word.mp3 (whenever a correct word is created)
incorrect-word.mp3 (whenever an incorrect word is created)
Word-already-exists.mp3 (whenever a word exists and is already created by the user)
Whenever you pick an adjective and show the points gained by the user, speak it to the user with the adjective, a pause, and then the points gained. For wrong words, speak ‘Invalid Word‘.
Make sure to give the user an amazing UX/UI experience.
Add a button on the top to play and pause the background music.
Make the background dynamic and have bubbles like a lava lamp. Make it move slowly so it is not distracting for the user, but it should look cool.
Add a toggle for ‘Light Mode‘ and ‘Dark Mode’ of the game.
Additional Rules (Only for Demo)
On desktop devices, add a div on the left of the game containing the input points given to you.
Show the created word list on the right of the game.
Add a toggle to collapse all input rules and change the body overflow to hidden.
The Output (Without Dark Mode and Demo Rules)
This is the code I received. You can use the same code or create your own. Do not worry if you are not happy with the version you get. Rework the prompt with the things you would like it to focus and it will improve the UI for you.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Merge Words</title>
<link rel="icon" type="image/x-icon" href="/favicon.ico"> <!-- Added favicon -->
<style>
body {
font-family: 'Arial', sans-serif;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
background: #1a1a1a;
color: #fff;
overflow: auto;
}
.container {
text-align: center;
max-width: 800px;
padding: 20px;
}
h1 {
font-size: 2.5em;
margin-bottom: 10px;
color: #00ffcc;
text-shadow: 0 0 10px #00ffcc;
}
#message {
font-size: 1.2em;
margin-bottom: 10px;
color: #ffcc00;
}
#timer {
font-size: 1.5em;
margin-bottom: 20px;
color: #ff6666;
}
#input-field {
width: 80%;
padding: 10px;
font-size: 1.2em;
margin-bottom: 20px;
border: none;
border-radius: 5px;
background: #333;
color: #fff;
}
.grid {
display: grid;
grid-template-columns: repeat(3, 100px);
gap: 10px;
margin: 0 auto 20px;
justify-content: center;
}
.tile {
width: 100px;
height: 100px;
background: #444;
display: flex;
justify-content: center;
align-items: center;
font-size: 1.5em;
cursor: pointer;
border-radius: 10px;
transition: transform 0.2s, background 0.2s;
}
.tile:hover {
background: #666;
transform: scale(1.1);
}
.tile.selected {
background: #00ffcc;
color: #1a1a1a;
}
.buttons {
display: flex;
justify-content: center;
gap: 20px;
margin-bottom: 20px;
}
button {
padding: 10px 20px;
font-size: 1.2em;
border: none;
border-radius: 5px;
cursor: pointer;
background: #00ffcc;
color: #1a1a1a;
transition: background 0.2s;
}
button:hover {
background: #00cc99;
}
#feedback {
font-size: 1.2em;
margin-bottom: 20px;
min-height: 24px;
}
#rules, #word-list {
background: #333;
padding: 15px;
border-radius: 10px;
margin-bottom: 20px;
text-align: left;
}
#rules h2, #word-list h2 {
margin-top: 0;
color: #00ffcc;
}
#music-control {
position: fixed;
top: 20px;
right: 20px;
padding: 10px;
background: #00ffcc;
color: #1a1a1a;
border-radius: 5px;
cursor: pointer;
}
canvas {
position: fixed;
top: 0;
left: 0;
z-index: -1;
opacity: 0.5;
}
</style>
</head>
<body>
<button id="music-control">Play Music</button>
<canvas id="background"></canvas>
<div class="container">
<h1>Merge Words</h1>
<div id="message">Your Progress Will Be Saved</div>
<div id="timer">Time Remaining: 10:00</div>
<input type="text" id="input-field" readonly placeholder="Select tiles to form a word">
<div class="grid" id="grid"></div>
<div class="buttons">
<button id="submit">Submit</button>
<button id="backspace">Backspace</button>
</div>
<div id="feedback"></div>
<div id="rules">
<h2>Rules</h2>
<ul>
<li>Create as many words as possible using these tiles</li>
<li>Each tile has points relative to the letters in it</li>
<li>Tap or click on a tile to select it</li>
</ul>
</div>
<div id="word-list">
<h2>Words Created</h2>
<ul id="words"></ul>
</div>
</div>
<script>
const tiles = ['row', 'bor', 'er', 's', 'mo', 'toe', 're', 'ing', 'ed'];
const validWords = ['bored', 'borer', 'borers', 'boring', 'borrow', 'borrowed', 'borrower', 'borrowing', 'borrows', 'mo', 'mos', 'reborers', 'rebored', 'reboring', 'reborrowed', 'reborrowing', 'reborrows', 'reed', 'rerow', 'rerowed', 'rerowers', 'rerowing', 'rerows', 'retoed', 'retoeing', 'retoes', 'row', 'rowed', 'rower', 'rowers', 'rowing', 'rows', 'toe', 'toed', 'toeing', 'toes', 'more'];
const adjectives = ['awesome', 'amazing', 'fantastic', 'nice', 'good', 'great job', 'correct', 'you did it', 'that right', 'good job', 'marvellous'];
let selectedTiles = [];
let createdWords = [];
let timeLeft = 600; // 10 minutes in seconds
const refreshTime = 6 * 60 * 60 * 1000; // 6 hours in milliseconds
let timerInterval, refreshInterval;
const grid = document.getElementById('grid');
const inputField = document.getElementById('input-field');
const submitBtn = document.getElementById('submit');
const backspaceBtn = document.getElementById('backspace');
const feedback = document.getElementById('feedback');
const wordList = document.getElementById('words');
const message = document.getElementById('message');
const timerDisplay = document.getElementById('timer');
const musicControl = document.getElementById('music-control');
const backgroundMusic = new Audio('background-music.mp3');
backgroundMusic.loop = true;
let isMusicPlaying = false; // Initially false to prevent autoplay
// Shuffle tiles
function shuffle(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
return array;
}
// Initialize grid
function initGrid() {
grid.innerHTML = '';
shuffle(tiles).forEach(tile => {
const div = document.createElement('div');
div.classList.add('tile');
div.textContent = tile;
div.addEventListener('click', () => selectTile(div, tile));
grid.appendChild(div);
});
}
// Select tile
function selectTile(div, tile) {
if (!div.classList.contains('selected')) {
div.classList.add('selected');
selectedTiles.push(tile);
inputField.value = selectedTiles.join('');
}
}
// Backspace
function backspace() {
if (selectedTiles.length > 0) {
selectedTiles.pop();
inputField.value = selectedTiles.join('');
const tilesElements = document.querySelectorAll('.tile');
tilesElements.forEach((tile, index) => {
if (index === tiles.indexOf(selectedTiles[selectedTiles.length - 1]) && selectedTiles.length > 0) {
tile.classList.add('selected');
} else {
tile.classList.remove('selected');
}
});
}
}
// Submit word
function submitWord() {
const word = inputField.value;
if (!word) return;
if (createdWords.includes(word)) {
feedback.textContent = 'Repeated Word';
new Audio('Word-already-exists.mp3').play();
speak('Word already exists');
} else if (validWords.includes(word)) {
const points = word.length;
const adjective = adjectives[Math.floor(Math.random() * adjectives.length)];
feedback.textContent = `${adjective} +${points}`;
new Audio('correct-word.mp3').play();
speak(`${adjective}... ${points} points`);
createdWords.push(word);
const li = document.createElement('li');
li.textContent = `${word} (+${points})`;
wordList.appendChild(li);
saveProgress();
} else {
feedback.textContent = 'Invalid Word';
new Audio('incorrect-word.mp3').play();
speak('Invalid Word');
}
selectedTiles = [];
inputField.value = '';
document.querySelectorAll('.tile').forEach(tile => tile.classList.remove('selected'));
}
// Text-to-speech
function speak(text) {
const utterance = new SpeechSynthesisUtterance(text);
utterance.rate = 1;
speechSynthesis.speak(utterance);
}
// Timer
function startTimer() {
timerInterval = setInterval(() => {
timeLeft--;
const minutes = Math.floor(timeLeft / 60);
const seconds = timeLeft % 60;
timerDisplay.textContent = `Time Remaining: ${minutes}:${seconds < 10 ? '0' : ''}${seconds}`;
saveProgress();
if (timeLeft <= 0) {
clearInterval(timerInterval);
feedback.textContent = 'Time’s up!';
document.querySelectorAll('.tile').forEach(tile => tile.style.pointerEvents = 'none');
submitBtn.disabled = true;
backspaceBtn.disabled = true;
}
}, 1000);
}
// Cookie handling
function setCookie(name, value, hours) {
const date = new Date();
date.setTime(Date.now() + hours * 60 * 60 * 1000); // Fixed typo
const expires = "expires=" + date.toUTCString();
document.cookie = name + "=" + JSON.stringify(value) + ";" + expires + ";path=/";
}
function getCookie(name) {
const nameEQ = name + "=";
const ca = document.cookie.split(';');
for (let i = 0; i < ca.length; i++) {
let c = ca[i].trim();
if (c.indexOf(nameEQ) === 0) {
return JSON.parse(c.substring(nameEQ.length, c.length));
}
}
return null;
}
function saveProgress() {
setCookie('gameProgress', { timeLeft, createdWords, timestamp: Date.now() }, 6);
}
function loadProgress() {
const progress = getCookie('gameProgress');
if (progress && progress.timeLeft > 0 && progress.createdWords) {
timeLeft = progress.timeLeft;
createdWords = progress.createdWords;
createdWords.forEach(word => {
const points = word.length;
const li = document.createElement('li');
li.textContent = `${word} (+${points})`;
wordList.appendChild(li);
});
const minutes = Math.floor(timeLeft / 60);
const seconds = timeLeft % 60;
timerDisplay.textContent = `Time Remaining: ${minutes}:${seconds < 10 ? '0' : ''}${seconds}`;
updateRefreshMessage(progress.timestamp);
} else {
message.textContent = 'Your Progress Will Be Saved';
saveProgress(); // Initialize cookie
}
}
function updateRefreshMessage(startTime) {
message.textContent = 'Game Refreshes In: calculating...';
refreshInterval = setInterval(() => {
const now = Date.now();
const timeToRefresh = startTime + refreshTime - now;
if (timeToRefresh <= 0) {
clearInterval(refreshInterval);
document.cookie = 'gameProgress=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
window.location.reload();
} else {
const hours = Math.floor(timeToRefresh / (1000 * 60 * 60));
const minutes = Math.floor((timeToRefresh % (1000 * 60 * 60)) / (1000 * 60));
message.textContent = `Game Refreshes In: ${hours}h ${minutes}m`;
}
}, 1000);
}
// Music control
musicControl.addEventListener('click', () => {
if (isMusicPlaying) {
backgroundMusic.pause();
musicControl.textContent = 'Play Music';
isMusicPlaying = false;
} else {
backgroundMusic.play().then(() => {
isMusicPlaying = true;
musicControl.textContent = 'Pause Music';
}).catch(err => console.error('Music play failed:', err));
}
});
// Trigger music on first user interaction
document.addEventListener('click', () => {
if (!isMusicPlaying) {
backgroundMusic.play().then(() => {
isMusicPlaying = true;
musicControl.textContent = 'Pause Music';
}).catch(err => console.error('Music play failed:', err));
}
}, { once: true });
// Event listeners
submitBtn.addEventListener('click', submitWord);
backspaceBtn.addEventListener('click', backspace);
document.addEventListener('keydown', (e) => {
if (e.key === 'Enter') submitWord();
if (e.key === 'Backspace') backspace();
});
// Lava lamp background
const canvas = document.getElementById('background');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const ctx = canvas.getContext('2d');
const bubbles = [];
class Bubble {
constructor() {
this.x = Math.random() * canvas.width;
this.y = canvas.height + 50;
this.radius = Math.random() * 50 + 20;
this.speed = Math.random() * 0.5 + 0.2;
this.color = `hsl(${Math.random() * 360}, 70%, 50%)`;
}
update() {
this.y -= this.speed;
if (this.y < -this.radius) this.y = canvas.height + this.radius;
}
draw() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.fill();
}
}
for (let i = 0; i < 10; i++) bubbles.push(new Bubble());
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
bubbles.forEach(bubble => {
bubble.update();
bubble.draw();
});
requestAnimationFrame(animate);
}
animate();
window.addEventListener('resize', () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
});
// Initialize
initGrid();
loadProgress();
startTimer();
</script>
</body>
</html>
To modify it further, you can use the code above and feed it to ‘Grok‘, ‘Perplexity’, and ‘ChatGPT’.
Previous Newsletters