Skip to content

Instantly share code, notes, and snippets.

@NightSling
Last active September 15, 2024 16:10
Show Gist options
  • Save NightSling/e170d2fc0434cb14f9c8f02bb7b1edd5 to your computer and use it in GitHub Desktop.
Save NightSling/e170d2fc0434cb14f9c8f02bb7b1edd5 to your computer and use it in GitHub Desktop.
Proof-of-concept for a grid word-finding game.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Interactive Word Search Puzzle</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
line-height: 1.6;
}
.container {
margin-bottom: 20px;
}
#puzzleContainer,
#answerContainer {
display: inline-block;
margin-right: 20px;
}
.grid {
display: inline-grid;
gap: 1px;
background-color: #f0f0f0;
padding: 10px;
user-select: none;
}
.cell {
width: 30px;
height: 30px;
display: flex;
justify-content: center;
align-items: center;
background-color: white;
border: 1px solid #ddd;
font-weight: bold;
cursor: pointer;
}
.highlighted {
background-color: rgba(0, 0, 255, 0.2);
}
.found {
font-weight: bold;
}
#wordList {
margin-top: 20px;
}
.word {
display: inline-block;
margin-right: 10px;
padding: 5px;
border-radius: 3px;
}
input,
button {
margin: 5px;
padding: 5px;
}
</style>
</head>
<body>
<h1>Interactive Word Search Puzzle</h1>
<div>
<input type="text" id="wordsInput" placeholder="Enter words (comma-separated)">
<input type="number" id="gridSizeInput" placeholder="Grid size (optional)">
<button id="generateButton">Generate Puzzle</button>
<button id="showAnswerButton">Show Answer</button>
</div>
<div class="container">
<div id="puzzleContainer"></div>
<div id="answerContainer"></div>
</div>
<div id="wordList"></div>
<script>
class WordSearchGenerator {
constructor(words, size = null) {
this.words = words.map(word => word.toUpperCase());
this.size = size || this.calculateMinGridSize();
this.grid = null;
this.solution = null;
this.wordColors = {};
}
calculateMinGridSize() {
const maxWordLength = Math.max(...this.words.map(word => word.length));
const totalLetters = this.words.join('').length;
return Math.max(maxWordLength, Math.ceil(Math.sqrt(totalLetters)));
}
createGrid(size) {
return Array(size).fill().map(() => Array(size).fill(' '));
}
wordFits(grid, word, row, col, dRow, dCol) {
const size = grid.length;
if (row + dRow * (word.length - 1) < 0 || row + dRow * (word.length - 1) >= size) return false;
if (col + dCol * (word.length - 1) < 0 || col + dCol * (word.length - 1) >= size) return false;
for (let i = 0; i < word.length; i++) {
if (![' ', word[i]].includes(grid[row + i * dRow][col + i * dCol])) return false;
}
return true;
}
placeWord(grid, word) {
const size = grid.length;
const directions = [[0, 1], [1, 0], [1, 1], [-1, 1]];
let attempts = 0;
while (attempts < 100) {
const [dRow, dCol] = directions[Math.floor(Math.random() * directions.length)];
const row = Math.floor(Math.random() * size);
const col = Math.floor(Math.random() * size);
if (this.wordFits(grid, word, row, col, dRow, dCol)) {
for (let i = 0; i < word.length; i++) {
grid[row + i * dRow][col + i * dCol] = word[i];
}
return true;
}
attempts++;
}
return false;
}
fillEmptySpaces(solution) {
const grid = JSON.parse(JSON.stringify(solution));
for (let i = 0; i < grid.length; i++) {
for (let j = 0; j < grid.length; j++) {
if (grid[i][j] === ' ') {
grid[i][j] = String.fromCharCode(65 + Math.floor(Math.random() * 26));
}
}
}
return grid;
}
createWordSearch() {
this.solution = this.createGrid(this.size);
for (const word of this.words) {
if (!this.placeWord(this.solution, word)) {
console.log(`Couldn't place word: ${word}`);
}
this.wordColors[word] = this.getRandomColor();
}
this.grid = this.fillEmptySpaces(this.solution);
}
getRandomColor() {
const hue = Math.floor(Math.random() * 360);
return `hsl(${hue}, 70%, 80%)`;
}
generate() {
this.createWordSearch();
return {
grid: this.grid,
solution: this.solution,
words: this.words,
wordColors: this.wordColors
};
}
}
let currentPuzzle = null;
let isMouseDown = false;
let startCell = null;
let highlightedCells = [];
function generatePuzzle() {
const wordsInput = document.getElementById('wordsInput').value;
const gridSizeInput = document.getElementById('gridSizeInput').value;
const words = wordsInput.split(',').map(word => word.trim()).filter(word => word.length > 0);
const size = gridSizeInput ? parseInt(gridSizeInput) : null;
if (words.length === 0) {
alert('Please enter at least one word.');
return;
}
const generator = new WordSearchGenerator(words, size);
currentPuzzle = generator.generate();
renderPuzzle(currentPuzzle.grid, 'puzzleContainer');
renderWordList(currentPuzzle.words, currentPuzzle.wordColors);
document.getElementById('answerContainer').innerHTML = '';
}
function renderPuzzle(grid, containerId) {
const container = document.getElementById(containerId);
container.innerHTML = '';
const gridElement = document.createElement('div');
gridElement.className = 'grid';
gridElement.style.gridTemplateColumns = `repeat(${grid.length}, 30px)`;
for (let i = 0; i < grid.length; i++) {
for (let j = 0; j < grid.length; j++) {
const cell = document.createElement('div');
cell.className = 'cell';
cell.textContent = grid[i][j];
cell.dataset.row = i;
cell.dataset.col = j;
gridElement.appendChild(cell);
}
}
container.appendChild(gridElement);
if (containerId === 'puzzleContainer') {
gridElement.addEventListener('mousedown', startHighlight);
gridElement.addEventListener('mouseover', continueHighlight);
gridElement.addEventListener('mouseup', endHighlight);
}
}
function renderWordList(words, wordColors) {
const wordList = document.getElementById('wordList');
wordList.innerHTML = '';
words.forEach(word => {
const wordElement = document.createElement('span');
wordElement.className = 'word';
wordElement.textContent = word;
wordElement.style.backgroundColor = wordColors[word];
wordList.appendChild(wordElement);
});
}
function startHighlight(e) {
if (e.target.classList.contains('cell')) {
isMouseDown = true;
startCell = e.target;
highlightedCells = [startCell];
startCell.classList.add('highlighted');
}
}
function continueHighlight(e) {
if (isMouseDown && e.target.classList.contains('cell')) {
const currentCell = e.target;
const startRow = parseInt(startCell.dataset.row);
const startCol = parseInt(startCell.dataset.col);
const currentRow = parseInt(currentCell.dataset.row);
const currentCol = parseInt(currentCell.dataset.col);
const rowDiff = currentRow - startRow;
const colDiff = currentCol - startCol;
const maxDiff = Math.max(Math.abs(rowDiff), Math.abs(colDiff));
// Clear previous highlighting
document.querySelectorAll('.cell.highlighted').forEach(cell => cell.classList.remove('highlighted'));
highlightedCells = [startCell];
if (maxDiff === 0) {
// Only highlight the start cell if there's no movement
startCell.classList.add('highlighted');
} else {
for (let i = 0; i <= maxDiff; i++) {
const row = startRow + Math.round(rowDiff * i / maxDiff);
const col = startCol + Math.round(colDiff * i / maxDiff);
const cell = document.querySelector(`.cell[data-row="${row}"][data-col="${col}"]`);
if (cell) {
cell.classList.add('highlighted');
highlightedCells.push(cell);
}
}
}
}
}
function endHighlight() {
if (isMouseDown) {
isMouseDown = false;
const word = highlightedCells.map(cell => cell.textContent).join('').substr(1);
const reversedWord = word.split('').reverse().join('').substr(1);
console.log(word, reversedWord);
if (currentPuzzle.words.includes(word) || currentPuzzle.words.includes(reversedWord)) {
const color = currentPuzzle.wordColors[word] || currentPuzzle.wordColors[reversedWord];
highlightedCells.forEach(cell => {
cell.classList.remove('highlighted');
cell.classList.add('found');
cell.style.backgroundColor = color;
});
} else {
highlightedCells.forEach(cell => cell.classList.remove('highlighted'));
}
highlightedCells = [];
}
}
function showAnswer() {
if (currentPuzzle) {
renderPuzzle(currentPuzzle.solution, 'answerContainer');
const answerGrid = document.querySelector('#answerContainer .grid');
currentPuzzle.words.forEach(word => {
const color = currentPuzzle.wordColors[word];
const result = findWordInGrid(word);
if (result) {
highlightWord(answerGrid, result.startRow, result.startCol, result.endRow, result.endCol, color);
}
});
}
}
function wordStartsAt(word, row, col) {
const directions = [[0, 1], [1, 0], [1, 1], [-1, 1], [0, -1], [-1, 0], [-1, -1], [1, -1]];
for (const [dRow, dCol] of directions) {
if (checkWordInDirection(word, row, col, dRow, dCol)) {
return true;
}
}
return false;
}
function checkWordInDirection(word, row, col, dRow, dCol) {
if (row + dRow * (word.length - 1) < 0 || row + dRow * (word.length - 1) >= currentPuzzle.solution.length) return false;
if (col + dCol * (word.length - 1) < 0 || col + dCol * (word.length - 1) >= currentPuzzle.solution.length) return false;
for (let i = 0; i < word.length; i++) {
if (currentPuzzle.solution[row + i * dRow][col + i * dCol] !== word[i]) {
return false;
}
}
return true;
}
function findWordInGrid(word) {
const directions = [
[0, 1], [1, 0], [1, 1], [-1, 1],
[0, -1], [-1, 0], [-1, -1], [1, -1]
];
for (let row = 0; row < currentPuzzle.solution.length; row++) {
for (let col = 0; col < currentPuzzle.solution.length; col++) {
for (const [dRow, dCol] of directions) {
if (checkWordInDirection(word, row, col, dRow, dCol)) {
return {
startRow: row,
startCol: col,
endRow: row + dRow * (word.length - 1),
endCol: col + dCol * (word.length - 1)
};
}
}
}
}
return null;
}
function highlightWord(grid, startRow, startCol, endRow, endCol, color) {
const rowDiff = endRow - startRow;
const colDiff = endCol - startCol;
const maxDiff = Math.max(Math.abs(rowDiff), Math.abs(colDiff));
for (let i = 0; i <= maxDiff; i++) {
const row = startRow + Math.round(rowDiff * i / maxDiff);
const col = startCol + Math.round(colDiff * i / maxDiff);
const cell = grid.children[row * currentPuzzle.solution.length + col];
cell.style.backgroundColor = color;
}
}
document.getElementById('generateButton').addEventListener('click', generatePuzzle);
document.getElementById('showAnswerButton').addEventListener('click', showAnswer);
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment