PHP Classes

File: public_html/webdoors/hangman/js/hangman.js

Recommend this page to a friend!
  Packages of Matthew Asham   Binkterm PHP   public_html/webdoors/hangman/js/hangman.js   Download  
File: public_html/webdoors/hangman/js/hangman.js
Role: Auxiliary data
Content type: text/plain
Description: Auxiliary data
Class: Binkterm PHP
Bulletin board system based on the Web
Author: By
Last change:
Date: 5 days ago
Size: 10,528 bytes
 

Contents

Class file image Download
// -------------------- External dictionary loading -------------------- const WORDS_JSON_URL = "words.json"; // relative to hangman/ folder const MAX_WRONG = 6; // Fallback minimal list (used only if words.json fails to load) const FALLBACK_WORDS = { easy: [ { word: "APPLE", category: "Food" }, { word: "HOUSE", category: "General" }, { word: "RIVER", category: "Nature" } ], normal: [ { word: "JAVASCRIPT", category: "Tech" }, { word: "VANCOUVER", category: "Places" }, { word: "TELESCOPE", category: "Science" } ], hard: [ { word: "INTEROPERABILITY", category: "Tech" }, { word: "THERMODYNAMICS", category: "Science" }, { word: "JURISPRUDENCE", category: "Law" } ] }; // Loaded dictionary in this normalized form: // { easy: [{word,category}...], normal: [...], hard: [...] } let DICTIONARY = null; function normalizeWord(raw) { // Keep A-Z only (optionally allow hyphens/spaces if you want later) const cleaned = String(raw).toUpperCase().replace(/[^A-Z]/g, ""); return cleaned.length ? cleaned : null; } function difficultyForWord(word) { const n = word.length; if (n <= 6) return "easy"; if (n <= 9) return "normal"; return "hard"; } async function loadDictionary() { try { const res = await fetch(WORDS_JSON_URL, { cache: "no-store" }); if (!res.ok) throw new Error(`HTTP ${res.status}`); const data = await res.json(); // Expect: { version: 1, categories: { "Cat": ["WORD", ...], ... } } const categories = data?.categories; if (!categories || typeof categories !== "object") throw new Error("Invalid words.json format"); const dict = { easy: [], normal: [], hard: [] }; for (const [category, words] of Object.entries(categories)) { if (!Array.isArray(words)) continue; for (const raw of words) { const w = normalizeWord(raw); if (!w) continue; const diff = difficultyForWord(w); dict[diff].push({ word: w, category }); } } // Must have at least a little content const total = dict.easy.length + dict.normal.length + dict.hard.length; if (total < 20) throw new Error("Dictionary too small (need more words)"); DICTIONARY = dict; return true; } catch (err) { console.warn("Failed to load words.json, using fallback list:", err); DICTIONARY = FALLBACK_WORDS; return false; } } // -------------------- WebDoor API wrapper assumed in js/webdoor.js -------------------- // WebDoor.load(), WebDoor.loadSave(), WebDoor.save({state}), WebDoor.deleteSave() // -------------------- Game state -------------------- let state = null; // { // difficulty: "easy"|"normal"|"hard", // target: { word: "STRING", category: "STRING" }, // guessed: ["A","B"], // wrong: 0, // startedAt: epoch_ms, // finished: null | { outcome: "win"|"lose"|"forfeit", endedAt: epoch_ms } // } function setStatus(msg = "") { const el = document.getElementById("status"); if (el) el.textContent = msg; } function setResult(text, show) { const el = document.getElementById("result"); if (!el) return; el.textContent = text; el.classList.toggle("hidden", !show); } function randomPick(arr) { return arr[Math.floor(Math.random() * arr.length)]; } function newGame(difficulty) { const pool = DICTIONARY?.[difficulty] || FALLBACK_WORDS[difficulty]; const target = randomPick(pool); state = { difficulty, target, guessed: [], wrong: 0, startedAt: Date.now(), finished: null }; } function normalizeLetter(ch) { const up = ch.toUpperCase(); return /^[A-Z]$/.test(up) ? up : null; } function getMaskedWord() { const w = state.target.word; const guessed = new Set(state.guessed); return [...w].map(c => (guessed.has(c) ? c : "_")).join(" "); } function isWin() { return !getMaskedWord().includes("_"); } function isOver() { return !!state.finished || state.wrong >= MAX_WRONG || isWin(); } function finish(outcome) { state.finished = { outcome, endedAt: Date.now() }; } function autoSave() { // Save opaque JSON: include everything we need WebDoor.save({ state }).catch(() => {}); } function addGuess(letter) { if (!letter || isOver()) return; if (state.guessed.includes(letter)) { setStatus(`You already guessed ${letter}.`); return; } state.guessed.push(letter); if (!state.target.word.includes(letter)) { state.wrong += 1; setStatus(`${letter} is not in the word.`); } else { setStatus(`Nice ? ${letter} is in the word.`); } if (isWin()) { finish("win"); setResult("? You win!", true); setStatus(`Solved: ${state.target.word}`); } else if (state.wrong >= MAX_WRONG) { finish("lose"); setResult("? Game over.", true); setStatus(`The word was: ${state.target.word}`); } render(); autoSave(); } function hint() { if (isOver()) return; const w = state.target.word; const guessed = new Set(state.guessed); const remaining = [...new Set([...w].filter(c => !guessed.has(c)))]; if (!remaining.length) { setStatus("No hints available."); return; } // Cost 1 wrong guess state.wrong = Math.min(MAX_WRONG, state.wrong + 1); const reveal = randomPick(remaining); state.guessed.push(reveal); setStatus(`Hint revealed: ${reveal} (cost 1 wrong guess).`); if (isWin()) { finish("win"); setResult("? You win!", true); setStatus(`Solved: ${state.target.word}`); } else if (state.wrong >= MAX_WRONG) { finish("lose"); setResult("? Game over.", true); setStatus(`The word was: ${state.target.word}`); } render(); autoSave(); } function forfeit() { if (isOver()) return; finish("forfeit"); setResult("?? Forfeit.", true); setStatus(`The word was: ${state.target.word}`); render(); autoSave(); } // -------------------- Rendering -------------------- function drawHangman() { const c = document.getElementById("gallows"); const ctx = c.getContext("2d"); ctx.clearRect(0, 0, c.width, c.height); ctx.lineWidth = 6; ctx.strokeStyle = "rgba(231,238,252,0.9)"; // gallows ctx.beginPath(); ctx.moveTo(40, 290); ctx.lineTo(160, 290); ctx.moveTo(90, 290); ctx.lineTo(90, 40); ctx.lineTo(230, 40); ctx.lineTo(230, 80); ctx.stroke(); const w = state.wrong; if (w >= 1) { ctx.beginPath(); ctx.arc(230, 105, 25, 0, Math.PI * 2); ctx.stroke(); } if (w >= 2) { ctx.beginPath(); ctx.moveTo(230, 130); ctx.lineTo(230, 205); ctx.stroke(); } if (w >= 3) { ctx.beginPath(); ctx.moveTo(230, 150); ctx.lineTo(200, 175); ctx.stroke(); } if (w >= 4) { ctx.beginPath(); ctx.moveTo(230, 150); ctx.lineTo(260, 175); ctx.stroke(); } if (w >= 5) { ctx.beginPath(); ctx.moveTo(230, 205); ctx.lineTo(205, 245); ctx.stroke(); } if (w >= 6) { ctx.beginPath(); ctx.moveTo(230, 205); ctx.lineTo(255, 245); ctx.stroke(); } } function renderLetters() { const letters = document.getElementById("letters"); letters.innerHTML = ""; const guessed = new Set(state.guessed); for (let i = 65; i <= 90; i++) { const ch = String.fromCharCode(i); const b = document.createElement("button"); b.textContent = ch; const used = guessed.has(ch) || isOver(); if (used) b.classList.add("used"); b.disabled = used; b.onclick = () => addGuess(ch); letters.appendChild(b); } } function render() { document.getElementById("word").textContent = getMaskedWord(); document.getElementById("wrongCount").textContent = String(state.wrong); document.getElementById("maxWrong").textContent = String(MAX_WRONG); document.getElementById("category").textContent = state.target.category || "General"; const guessedList = document.getElementById("guessedList"); guessedList.innerHTML = ""; state.guessed.forEach(ch => { const span = document.createElement("span"); span.className = "badge"; span.textContent = ch; guessedList.appendChild(span); }); drawHangman(); renderLetters(); if (!state.finished) setResult("", false); else { const o = state.finished.outcome; setResult(o === "win" ? "? You win!" : o === "lose" ? "? Game over." : "?? Forfeit.", true); } } // -------------------- Boot + events -------------------- async function start() { await WebDoor.load(); // Load dictionary first (so new games have the big pool) const loaded = await loadDictionary(); // Load saved state if present const saved = await WebDoor.loadSave(); if (saved?.state) { state = saved.state; // If dictionary changed and word is missing category, keep playing anyway if (!state.target?.word) { newGame("normal"); } } else { newGame("normal"); } // Sync UI controls const difficulty = document.getElementById("difficulty"); difficulty.value = state.difficulty || "normal"; difficulty.onchange = () => { newGame(difficulty.value); setStatus(`New ${difficulty.value} game started. (${loaded ? "Large dictionary" : "Fallback list"})`); setResult("", false); render(); autoSave(); }; document.getElementById("new").onclick = () => { newGame(difficulty.value); setStatus(`New game started. (${loaded ? "Large dictionary" : "Fallback list"})`); setResult("", false); render(); autoSave(); }; document.getElementById("hint").onclick = hint; document.getElementById("forfeit").onclick = forfeit; document.getElementById("save").onclick = async () => { await WebDoor.save({ state }); setStatus("Saved."); }; // Keyboard input window.addEventListener("keydown", (e) => { if (e.ctrlKey || e.metaKey || e.altKey) return; const letter = normalizeLetter(e.key); if (!letter) return; addGuess(letter); }); render(); setStatus(`Type a letter or click buttons to guess. (${loaded ? "Loaded words.json" : "Using fallback list"})`); autoSave(); } start();