what the heck? - you also reverted to the wrong star colour - and i can not actually use a star at all on the next level? you seem to have broken working logic - as i am missing letters from words - and the stars do not work on level 2 - but did level 1 and 3 - also the original version - words were collected twice - level 7 star did not work - level 8 - i could make tīkanga on the first move - but not all letters were blue. one short. whenua had the a missing - using a star to get tīkanga - and also one letter short and - level 9 - rohe - only 3 letters showing the 'e' is missing - but all good when used star (that up-sized but not white) - i like this code and hope we can fix this
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Kupu Rapu - Te Reo Māori Word Game</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap');
body {
font-family: 'Inter', sans-serif;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
color: #fff;
padding: 20px;
touch-action: none;
overflow-y: auto;
}
.container {
background-color: rgba(255, 255, 255, 0.1);
border-radius: 24px;
padding: 24px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
backdrop-filter: blur(10px);
width: 100%;
max-width: 960px;
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
}
.title {
font-size: 2.5rem;
font-weight: 700;
color: #fff;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
margin-bottom: 0.5rem;
}
.subtitle {
font-size: 1.25rem;
color: #e0e0e0;
margin-bottom: 1.5rem;
}
.main-content {
display: flex;
flex-direction: column;
width: 100%;
gap: 24px;
}
@media (min-width: 768px) {
.main-content {
flex-direction: row;
align-items: flex-start;
justify-content: center;
}
}
.game-section {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
.word-builder {
display: flex;
justify-content: center;
min-height: 48px;
gap: 4px;
margin-bottom: 1rem;
background-color: rgba(255, 255, 255, 0.2);
border-radius: 12px;
padding: 8px;
width: 100%;
text-transform: uppercase;
}
.word-builder .built-letter {
width: 32px;
height: 32px;
display: flex;
justify-content: center;
align-items: center;
background: #fff;
color: #000;
font-weight: 700;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
animation: popIn 0.2s cubic-bezier(0.68, -0.55, 0.27, 1.55);
}
@keyframes popIn {
0% { transform: scale(0); opacity: 0; }
100% { transform: scale(1); opacity: 1; }
}
.grid {
display: grid;
grid-template-columns: repeat(6, 1fr);
gap: 8px;
width: 100%;
max-width: 480px;
aspect-ratio: 1 / 1;
margin-bottom: 1rem;
position: relative;
}
.tile {
position: relative;
background-color: rgba(255, 255, 255, 0.2);
color: black;
font-size: clamp(0.9rem, 3.5vw, 1.8rem);
font-weight: 700;
border-radius: 12px;
cursor: pointer;
user-select: none;
display: flex;
justify-content: center;
align-items: center;
transition: transform 0.2s, box-shadow 0.2s, opacity 0.3s, top 0.4s ease-out;
text-transform: uppercase;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.tile.hidden-tile {
opacity: 0;
}
.tile:hover {
transform: translateY(-2px);
box-shadow: 0 6px 10px rgba(0, 0, 0, 0.2);
}
.selected {
transform: scale(1.05);
outline: 3px solid white;
outline-offset: -3px;
}
.message-box {
background-color: rgba(0, 0, 0, 0.3);
color: white;
padding: 12px;
border-radius: 12px;
margin-bottom: 16px;
font-weight: 700;
width: 100%;
}
.win-message {
font-size: 2rem;
font-weight: 700;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
margin-bottom: 1rem;
}
.win-message span {
display: block;
font-size: 1.5rem;
font-weight: 400;
}
button {
background-color: #6a1a7c;
color: white;
padding: 12px 24px;
border-radius: 9999px;
font-weight: 700;
transition: transform 0.2s, background-color 0.2s;
cursor: pointer;
border: none;
outline: none;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
button:hover {
transform: translateY(-2px);
background-color: #7d2693;
}
.word-list-section {
background-color: rgba(0, 0, 0, 0.2);
border-radius: 12px;
padding: 16px;
width: 100%;
}
.word-list-heading {
font-size: 1.2rem;
font-weight: 700;
margin-bottom: 12px;
}
.word-list {
display: flex;
flex-direction: column;
justify-content: flex-start;
gap: 8px;
align-items: flex-start;
}
.word-item {
border-radius: 6px;
padding: 4px 8px;
font-weight: 500;
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
transition: background-color 0.3s;
}
.word-item-text {
font-size: 1.2rem;
font-weight: 700;
color: black;
}
.word-item-count {
font-size: 1.1rem;
font-weight: 400;
color: black;
}
.word-item-count .tick {
font-size: 1.5em;
line-height: 0.8;
font-weight: bold;
display: inline-block;
vertical-align: top;
margin-right: -0.1em;
}
.word-item.bg-red-400 { background-color: #f87171; }
.tile.bg-red-400 { background-color: #f87171; }
.word-item.bg-orange-400 { background-color: #fb923c; }
.tile.bg-orange-400 { background-color: #fb923c; }
.word-item.bg-yellow-400 { background-color: #facc15; }
.tile.bg-yellow-400 { background-color: #facc15; }
.word-item.bg-green-400 { background-color: #4ade80; }
.tile.bg-green-400 { background-color: #4ade80; }
.word-item.bg-blue-400 { background-color: #60a5fa; }
.tile.bg-blue-400 { background-color: #60a5fa; }
.word-item.bg-purple-400 { background-color: #a78bfa; }
.tile.bg-purple-400 { background-color: #a78bfa; }
.text-black { color: black; }
.star-container {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 8px;
margin-top: 16px;
width: 100%;
max-width: 240px;
justify-content: center;
margin-left: auto;
margin-right: auto;
}
.star {
width: 32px;
height: 32px;
background-color: #a78bfa;
clip-path: polygon(
50% 0%, 61% 35%, 98% 35%, 68% 57%, 79% 91%,
50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%
);
cursor: pointer;
transition: transform 0.2s, background-color 0.2s;
}
.star.selected {
background-color: #ffffff;
transform: scale(1.2);
}
.star.used {
background-color: #000000;
cursor: default;
}
</style>
</head>
<body>
<div class="container">
<h1 class="title">Kupu Rapu</h1>
<p class="subtitle">Drag to connect adjacent letters to find words!</p>
<div class="main-content">
<div id="game-container" class="game-section">
<div id="word-builder" class="word-builder"></div>
<div id="grid-container" class="grid"></div>
<div id="message-box" class="message-box opacity-0 transition-opacity duration-300"></div>
</div>
<div id="win-screen" class="hidden flex-col items-center justify-center">
<div class="win-message">
Ka Pai! <span>You found all the words!</span>
</div>
<button id="play-again-btn">Play Again</button>
</div>
<div class="word-list-section md:block hidden">
<div class="word-list-heading">Words to find:</div>
<div id="word-list" class="word-list"></div>
<div id="star-container" class="star-container"></div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
const gridContainer = document.getElementById('grid-container');
const messageBox = document.getElementById('message-box');
const winScreen = document.getElementById('win-screen');
const gameContainer = document.getElementById('game-container');
const playAgainBtn = document.getElementById('play-again-btn');
const wordListEl = document.getElementById('word-list');
const wordBuilderEl = document.getElementById('word-builder');
const starContainer = document.getElementById('star-container');
const GRID_SIZE = 6;
const TOTAL_WORDS = 5;
const FIND_COUNT_PER_WORD = 2;
let gameBoard = [];
let selectedTiles = [];
let foundWordCounts = {};
let wordPlacementCounts = {};
let wordsToFind = [];
let wordColors = {};
let isDragging = false;
let lastTile = null;
let stars = [];
let isStarSelected = false;
const letters = ['a', 'e', 'h', 'i', 'k', 'm', 'n', 'o', 'p', 'r', 't', 'u', 'w'];
const assignedColors = ['bg-yellow-400', 'bg-red-400', 'bg-orange-400', 'bg-blue-400', 'bg-green-400'];
const fullWordList = {
'pipi': 'shellfish',
'moana': 'sea',
'mahi': 'work',
'manu': 'bird',
'muri': 'after',
'whana': 'kick',
'kete': 'basket',
'hāpu': 'pregnant, sub-tribe',
'teina': 'younger sibling',
'wairua': 'spirit',
'whānau': 'family',
'aroha': 'love',
'rākau': 'tree, stick',
'tamariki': 'children',
'kōrero': 'speak, story',
'tūpuna': 'ancestors',
'whare': 'house',
'waiata': 'song',
'tāne': 'man, husband',
'wahine': 'woman, wife',
'māmā': 'mother',
'pāpā': 'father',
'kuia': 'elderly woman',
'koro': 'elderly man',
'tīkanga': 'customs, protocols',
'iwi': 'tribe',
'rohe': 'region, area',
'taonga': 'treasure',
'mātauranga': 'knowledge',
'karakia': 'prayer, incantation',
'whenua': 'land, placenta',
'ngākau': 'heart, feelings',
'mārama': 'moon, clarity',
'rongoā': 'medicine, remedy',
'hīkoi': 'walk, march',
'tākaro': 'play, game',
'pounamu': 'greenstone',
'kākahu': 'clothing',
'mōkai': 'pet'
};
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;
}
function getRandomWords(count) {
const words = Object.keys(fullWordList).filter(word => word.length >= 4 && word.length <= GRID_SIZE);
shuffle(words);
return words.slice(0, count);
}
function createTile(row, col, letter, color) {
const tile = document.createElement('div');
tile.classList.add('tile', color, 'text-black');
tile.dataset.row = row;
tile.dataset.col = col;
tile.dataset.letter = letter;
tile.innerHTML = `<span>${letter.toUpperCase()}</span>`;
return tile;
}
function createStar(index) {
const star = document.createElement('div');
star.classList.add('star');
star.dataset.index = index;
star.addEventListener('click', () => handleStarClick(index));
starContainer.appendChild(star);
return star;
}
function displayWordList() {
wordListEl.innerHTML = '';
wordsToFind.forEach(word => {
const count = foundWordCounts[word] || 0;
const wordItem = document.createElement('div');
wordItem.classList.add('word-item', wordColors[word] || 'bg-purple-400');
const wordText = document.createElement('span');
wordText.classList.add('word-item-text');
wordText.textContent = word.toUpperCase();
const wordCount = document.createElement('span');
wordCount.classList.add('word-item-count');
let ticks = '';
for (let i = 0; i < count; i++) {
ticks += '<span class="tick">✓</span>';
}
wordCount.innerHTML = ticks;
wordItem.appendChild(wordText);
wordItem.appendChild(wordCount);
wordListEl.appendChild(wordItem);
});
}
// placeWord: tries to place a word on provided board (board param)
function placeWord(word, board) {
const directions = [[0, 1], [0, -1], [1, 0], [-1, 0], [1, 1], [1, -1], [-1, 1], [-1, -1]];
let placed = false;
let attempts = 0;
while (!placed && attempts < 200) {
const startR = Math.floor(Math.random() * GRID_SIZE);
const startC = Math.floor(Math.random() * GRID_SIZE);
const path = [{ r: startR, c: startC }];
const visited = new Set([`${startR},${startC}`]);
const findPath = (r, c, wordIndex) => {
if (wordIndex === word.length) return true;
const currentLetter = word[wordIndex];
shuffle(directions);
for (const [dr, dc] of directions) {
const newR = r + dr;
const newC = c + dc;
const newCoord = `${newR},${newC}`;
if (newR >= 0 && newR < GRID_SIZE && newC >= 0 && newC < GRID_SIZE && !visited.has(newCoord)) {
// Can step into a null cell, or a cell already showing same letter for same word
const cell = board[newR][newC];
if (cell === null || cell === undefined || cell.word === null || (cell.letter === word[wordIndex] && cell.word === word)) {
visited.add(newCoord);
path.push({ r: newR, c: newC });
if (findPath(newR, newC, wordIndex + 1)) return true;
path.pop();
visited.delete(newCoord);
}
}
}
return false;
};
// Validate starting position
const startCell = board[startR][startC];
if (startCell === null || startCell === undefined || startCell.word === null || (startCell.letter === word[0] && startCell.word === word)) {
const found = findPath(startR, startC, 1);
if (found && path.length >= word.length) {
for (let i = 0; i < word.length; i++) {
const { r, c } = path[i];
board[r][c] = { letter: word[i], word: word, color: wordColors[word] || 'bg-purple-400' };
}
placed = true;
wordPlacementCounts[word] = (wordPlacementCounts[word] || 0) + 1;
}
}
attempts++;
}
return placed;
}
// refreshBoard: RE-PLACES ALL wordsToFind on a fresh board (keeps foundWordCounts intact)
function refreshBoard() {
// Attempt several times to create a board that contains ALL words
const MAX_OVERALL_ATTEMPTS = 40;
let overallAttempt = 0;
let success = false;
const words = [...wordsToFind];
while (!success && overallAttempt < MAX_OVERALL_ATTEMPTS) {
// build empty board
const localBoard = Array.from({ length: GRID_SIZE }, () => Array(GRID_SIZE).fill(null));
// Shuffle placement order a bit so overlaps vary
shuffle(words);
let allPlaced = true;
for (const w of words) {
const placed = placeWord(w, localBoard);
if (!placed) {
allPlaced = false;
break;
}
}
if (allPlaced) {
// fill empties with random letters
for (let r = 0; r < GRID_SIZE; r++) {
for (let c = 0; c < GRID_SIZE; c++) {
if (localBoard[r][c] === null) {
localBoard[r][c] = { letter: getRandomLetter(), word: null, color: 'bg-purple-400' };
}
}
}
gameBoard = localBoard;
success = true;
break;
}
overallAttempt++;
}
if (!success) {
// Fallback: place as many as possible, then fill rest (should be rare)
gameBoard = Array.from({ length: GRID_SIZE }, () => Array(GRID_SIZE).fill(null));
for (const w of words) {
placeWord(w, gameBoard); // try, ignore failures
}
for (let r = 0; r < GRID_SIZE; r++) {
for (let c = 0; c < GRID_SIZE; c++) {
if (gameBoard[r][c] === null) {
gameBoard[r][c] = { letter: getRandomLetter(), word: null, color: 'bg-purple-400' };
}
}
}
}
renderBoard();
displayWordList();
}
function initBoard() {
// Called at the start/new-game. This resets counts.
foundWordCounts = {};
wordPlacementCounts = {};
wordsToFind = getRandomWords(TOTAL_WORDS);
wordColors = {};
shuffle(assignedColors);
wordsToFind.forEach((word, index) => {
foundWordCounts[word] = 0;
wordPlacementCounts[word] = 0;
wordColors[word] = assignedColors[index] || 'bg-purple-400';
});
initStars();
refreshBoard();
}
function renderBoard() {
// ensure board has valid cells
for (let r = 0; r < GRID_SIZE; r++) {
if (!gameBoard[r]) gameBoard[r] = Array(GRID_SIZE).fill(null);
for (let c = 0; c < GRID_SIZE; c++) {
if (!gameBoard[r][c]) gameBoard[r][c] = { letter: getRandomLetter(), word: null, color: 'bg-purple-400' };
}
}
gridContainer.innerHTML = '';
gameBoard.forEach((row, r) => {
row.forEach((cell, c) => {
const tile = createTile(r, c, cell.letter, cell.color);
tile.addEventListener('click', () => handleTileClick(r, c));
gridContainer.appendChild(tile);
cell.element = tile;
});
});
}
function initStars() {
starContainer.innerHTML = '';
stars = [];
// Keep 10 stars like originally
for (let i = 0; i < 10; i++) {
const star = createStar(i);
stars.push({ element: star, used: false });
}
}
function getRandomLetter() {
return letters[Math.floor(Math.random() * letters.length)];
}
function getSelectedWord() {
return selectedTiles.map(tile => gameBoard[tile.row][tile.col].letter).join('');
}
function isValidAdjacency(newTile) {
if (selectedTiles.length === 0) return true;
const lastTile = selectedTiles[selectedTiles.length - 1];
const dx = Math.abs(newTile.row - lastTile.row);
const dy = Math.abs(newTile.col - lastTile.col);
return (dx <= 1 && dy <= 1 && (dx > 0 || dy > 0));
}
function showMessage(text, duration = 1500) {
messageBox.textContent = text;
messageBox.classList.remove('opacity-0');
setTimeout(() => {
messageBox.classList.add('opacity-0');
}, duration);
}
function clearHighlights() {
document.querySelectorAll('.tile.selected').forEach(tile => tile.classList.remove('selected'));
selectedTiles = [];
wordBuilderEl.innerHTML = '';
}
function handleStarClick(index) {
if (stars[index].used || isDragging) return;
// Toggle selection: if already selected, deselect; else select only this
const thisStar = stars[index].element;
if (thisStar.classList.contains('selected')) {
thisStar.classList.remove('selected');
isStarSelected = false;
} else {
stars.forEach(s => s.element.classList.remove('selected'));
thisStar.classList.add('selected');
isStarSelected = true;
}
}
function getAdjacentTiles(row, col) {
const tiles = [];
const directions = [[0, 0], [0, 1], [0, -1], [1, 0], [-1, 0], [1, 1], [1, -1], [-1, 1], [-1, -1]];
for (const [dr, dc] of directions) {
const newR = row + dr;
const newC = col + dc;
if (newR >= 0 && newR < GRID_SIZE && newC >= 0 && newC < GRID_SIZE) {
tiles.push({ row: newR, col: newC });
}
}
return tiles;
}
// If a star is selected and the user clicks any tile, we use the star to REFRESH the whole board.
function handleTileClick(row, col) {
// If a star is selected, use it to refresh the board (keeps found counts)
if (isStarSelected) {
const selectedStarIdx = stars.findIndex(s => s.element.classList.contains('selected'));
if (selectedStarIdx >= 0) {
stars[selectedStarIdx].used = true;
stars[selectedStarIdx].element.classList.remove('selected');
stars[selectedStarIdx].element.classList.add('used');
isStarSelected = false;
// Full board refresh: place ALL wordsToFind again (keeps foundWordCounts)
refreshBoard();
showMessage('Board refreshed!', 1500);
return;
}
}
// Otherwise, tile clicks are ignored for selecting letters (we use drag selection)
}
function handleStart(e) {
if (isStarSelected) return; // disable drag if using star
isDragging = true;
handleMove(e);
}
function handleMove(e) {
if (!isDragging) return;
const clientX = e.clientX !== undefined ? e.clientX : (e.touches && e.touches[0] && e.touches[0].clientX);
const clientY = e.clientY !== undefined ? e.clientY : (e.touches && e.touches[0] && e.touches[0].clientY);
const target = document.elementFromPoint(clientX, clientY);
const tile = target && target.closest('.tile');
if (tile && tile !== lastTile) {
const row = parseInt(tile.dataset.row);
const col = parseInt(tile.dataset.col);
const newTile = { row, col };
const isAlreadySelected = selectedTiles.some(t => t.row === row && t.col === col);
if (!isAlreadySelected && isValidAdjacency(newTile)) {
selectedTiles.push(newTile);
tile.classList.add('selected');
lastTile = tile;
const builtLetterDiv = document.createElement('div');
builtLetterDiv.classList.add('built-letter');
builtLetterDiv.textContent = gameBoard[row][col].letter.toUpperCase();
wordBuilderEl.appendChild(builtLetterDiv);
}
}
}
function handleEnd() {
if (!isDragging) return;
isDragging = false;
lastTile = null;
const word = getSelectedWord().toLowerCase();
const foundWord = wordsToFind.find(w => w === word);
if (foundWord && word.length >= 4) {
foundWordCounts[foundWord] = (foundWordCounts[foundWord] || 0) + 1;
showMessage('Mīharo - you got it!');
// remove letters used and let others drop + refill
dropLettersAndRefill(selectedTiles);
displayWordList();
} else if (word.length > 1) {
showMessage('That is not a valid word.');
}
clearHighlights();
checkWinCondition();
}
function tryToPlaceNewWord() {
const wordsOnGrid = new Set();
for (let r = 0; r < GRID_SIZE; r++) {
for (let c = 0; c < GRID_SIZE; c++) {
if (gameBoard[r][c] && gameBoard[r][c].word) {
wordsOnGrid.add(gameBoard[r][c].word);
}
}
}
const wordsRemaining = wordsToFind.filter(w => foundWordCounts[w] < FIND_COUNT_PER_WORD);
const wordsNotOnGrid = wordsRemaining.filter(w => !wordsOnGrid.has(w));
if (wordsNotOnGrid.length > 0) {
shuffle(wordsNotOnGrid);
let placed = false;
for (const word of wordsNotOnGrid) {
if (placeWord(word, gameBoard)) {
placed = true;
break;
}
}
return placed;
}
return false;
}
function replaceRandomLetters() {
const nonWordTiles = [];
for (let r = 0; r < GRID_SIZE; r++) {
for (let c = 0; c < GRID_SIZE; c++) {
if (!gameBoard[r][c].word) {
nonWordTiles.push({ r, c });
}
}
}
shuffle(nonWordTiles);
const tilesToReplace = nonWordTiles.slice(0, Math.min(20, nonWordTiles.length));
tilesToReplace.forEach(({ r, c }) => {
gameBoard[r][c] = null;
});
const wordsOnGrid = new Set();
for (let r = 0; r < GRID_SIZE; r++) {
for (let c = 0; c < GRID_SIZE; c++) {
if (gameBoard[r][c] && gameBoard[r][c].word) {
wordsOnGrid.add(gameBoard[r][c].word);
}
}
}
const wordsRemaining = wordsToFind.filter(w => foundWordCounts[w] < FIND_COUNT_PER_WORD);
const wordsNotOnGrid = wordsRemaining.filter(w => !wordsOnGrid.has(w));
shuffle(wordsNotOnGrid);
for (const word of wordsNotOnGrid) {
placeWord(word, gameBoard);
}
for (let r = 0; r < GRID_SIZE; r++) {
for (let c = 0; c < GRID_SIZE; c++) {
if (gameBoard[r][c] === null) {
gameBoard[r][c] = { letter: getRandomLetter(), word: null, color: 'bg-purple-400' };
}
}
}
renderBoard();
}
function dropLettersAndRefill(tilesToRemove) {
// tilesToRemove is array of {row, col}
const newGameBoard = Array.from({ length: GRID_SIZE }, () => Array(GRID_SIZE).fill(null));
const removeSet = new Set(tilesToRemove.map(t => `${t.row},${t.col}`));
// For each column, collect existing tiles (from bottom up) that are NOT removed.
for (let c = 0; c < GRID_SIZE; c++) {
const columnStack = [];
for (let r = GRID_SIZE - 1; r >= 0; r--) {
const coord = `${r},${c}`;
if (!removeSet.has(coord)) {
columnStack.push(gameBoard[r][c]);
}
}
// Put them back from bottom to top
let fillRow = GRID_SIZE - 1;
for (let i = 0; i < columnStack.length; i++) {
newGameBoard[fillRow][c] = columnStack[i];
fillRow--;
}
// rest remain null for now
}
gameBoard = newGameBoard;
// Try to ensure missing target words are placed again
const wordsOnGrid = new Set();
for (let r = 0; r < GRID_SIZE; r++) {
for (let c = 0; c < GRID_SIZE; c++) {
if (gameBoard[r][c] && gameBoard[r][c].word) {
wordsOnGrid.add(gameBoard[r][c].word);
}
}
}
const wordsToFindStillRemaining = wordsToFind.filter(w => foundWordCounts[w] < FIND_COUNT_PER_WORD);
if (wordsToFindStillRemaining.length > 0) {
let attempts = 0;
while (wordsOnGrid.size < wordsToFindStillRemaining.length && attempts < 100) {
const successfullyPlaced = tryToPlaceNewWord();
if (!successfullyPlaced) {
replaceRandomLetters();
}
wordsOnGrid.clear();
for (let r = 0; r < GRID_SIZE; r++) {
for (let c = 0; c < GRID_SIZE; c++) {
if (gameBoard[r][c] && gameBoard[r][c].word) {
wordsOnGrid.add(gameBoard[r][c].word);
}
}
}
attempts++;
}
if (wordsOnGrid.size < wordsToFindStillRemaining.length) {
// As a last-resort ensure all words are present by refreshing full board
refreshBoard();
}
if (wordsOnGrid.size === 0) {
showMessage("Whakatika! - New words added!", 2000);
}
}
// Fill any remaining null cells
for (let r = 0; r < GRID_SIZE; r++) {
for (let c = 0; c < GRID_SIZE; c++) {
if (gameBoard[r][c] === null) {
gameBoard[r][c] = { letter: getRandomLetter(), word: null, color: 'bg-purple-400' };
}
}
}
renderBoard();
}
function checkWinCondition() {
const allFoundTwice = wordsToFind.every(word => foundWordCounts[word] >= FIND_COUNT_PER_WORD);
if (allFoundTwice) {
gameContainer.classList.add('hidden');
winScreen.classList.remove('hidden');
winScreen.classList.add('flex');
}
}
function resetGame() {
gameContainer.classList.remove('hidden');
winScreen.classList.add('hidden');
winScreen.classList.remove('flex');
initBoard();
}
function attachEventListeners() {
gridContainer.addEventListener('mousedown', handleStart);
gridContainer.addEventListener('mousemove', handleMove);
gridContainer.addEventListener('mouseup', handleEnd);
gridContainer.addEventListener('mouseleave', handleEnd);
gridContainer.addEventListener('touchstart', (e) => handleStart(e.touches[0]));
gridContainer.addEventListener('touchmove', (e) => handleMove(e.touches[0]));
gridContainer.addEventListener('touchend', handleEnd);
playAgainBtn.addEventListener('click', resetGame);
}
initBoard();
attachEventListeners();
});
</script>
</body>
</html>
Below is the result - i think i ran out of free-time during the creation of the code! Now no words to find!