mirror of
https://github.com/tiennm99/loldle.git
synced 2026-10-11 03:13:42 +00:00
Vanilla JS web game with modular architecture: - data-loader: champion data fetching, seeded random selection - game-engine: mode-agnostic state machine with localStorage persistence - classic-mode: 7-attribute comparison logic (gender, genre, range, resource, region, lane, year) - ui-renderer: search autocomplete, colored feedback grid, stats display - Dark theme responsive UI with Data Dragon CDN champion images
197 lines
5.6 KiB
JavaScript
197 lines
5.6 KiB
JavaScript
// Bootstrap: wire data, engine, classic mode, and UI together
|
|
|
|
import { loadChampions, getAllChampions, getRandomChampion, getTodaySeed } from "./data-loader.js";
|
|
import { createGame, submitGuess, getGuessedNames, clearUnlimitedState, saveUnlimitedStats, loadUnlimitedStats } from "./game-engine.js";
|
|
import { compareChampions } from "./classic-mode.js";
|
|
import {
|
|
initSearch,
|
|
updateSearchCallbacks,
|
|
renderGuessRow,
|
|
renderGridHeader,
|
|
renderGameOver,
|
|
updateGuessCounter,
|
|
setSearchEnabled,
|
|
renderStats,
|
|
} from "./ui-renderer.js";
|
|
|
|
const UNLIMITED_SEED_KEY = "loldle_unlimited_seed";
|
|
|
|
let currentGame = null;
|
|
let currentMode = "daily";
|
|
|
|
async function init() {
|
|
try {
|
|
await loadChampions();
|
|
} catch (err) {
|
|
document.getElementById("grid").textContent = "Failed to load champion data. Please refresh.";
|
|
return;
|
|
}
|
|
|
|
const champions = getAllChampions();
|
|
if (!champions.length) {
|
|
document.getElementById("grid").textContent = "No champion data available.";
|
|
return;
|
|
}
|
|
|
|
// Initialize search once with champion list
|
|
const searchContainer = document.querySelector(".search-container");
|
|
initSearch(searchContainer, champions);
|
|
|
|
// Mode toggle buttons
|
|
document.getElementById("mode-daily").addEventListener("click", () => switchMode("daily"));
|
|
document.getElementById("mode-unlimited").addEventListener("click", () => switchMode("unlimited"));
|
|
|
|
// Start with daily mode
|
|
startGame("daily");
|
|
}
|
|
|
|
function switchMode(mode) {
|
|
currentMode = mode;
|
|
|
|
// Update toggle UI
|
|
document.querySelectorAll(".mode-btn").forEach((btn) => btn.classList.remove("active"));
|
|
document.getElementById(`mode-${mode}`).classList.add("active");
|
|
|
|
startGame(mode);
|
|
}
|
|
|
|
function startGame(mode) {
|
|
const gridContainer = document.getElementById("grid");
|
|
const gameOverContainer = document.getElementById("game-over");
|
|
const statsContainer = document.getElementById("stats");
|
|
const searchContainer = document.querySelector(".search-container");
|
|
const guessCounter = document.getElementById("guess-counter");
|
|
|
|
// Clear previous state
|
|
gridContainer.innerHTML = "";
|
|
gameOverContainer.innerHTML = "";
|
|
|
|
// Show/hide stats
|
|
if (mode === "unlimited") {
|
|
renderStats(statsContainer, loadUnlimitedStats());
|
|
} else {
|
|
statsContainer.innerHTML = "";
|
|
}
|
|
|
|
// Pick target champion
|
|
let target;
|
|
let seed = "";
|
|
if (mode === "daily") {
|
|
seed = getTodaySeed();
|
|
target = getRandomChampion(seed);
|
|
} else {
|
|
// Unlimited: reuse persisted seed if game is in progress, else create new
|
|
seed = getOrCreateUnlimitedSeed();
|
|
target = getRandomChampion(seed);
|
|
}
|
|
|
|
// Create game
|
|
const maxGuesses = mode === "daily" ? 6 : 0;
|
|
currentGame = createGame({
|
|
target,
|
|
compareFn: compareChampions,
|
|
maxGuesses,
|
|
mode,
|
|
seed,
|
|
});
|
|
|
|
// Render grid header
|
|
renderGridHeader(gridContainer);
|
|
|
|
// Restore previous guesses (from localStorage)
|
|
if (currentGame.guesses.length > 0) {
|
|
currentGame.results.forEach((result, i) => {
|
|
renderGuessRow(gridContainer, currentGame.guesses[i], result);
|
|
});
|
|
}
|
|
|
|
// Update counter
|
|
updateGuessCounter(guessCounter, currentGame.guesses.length, currentGame.maxGuesses);
|
|
|
|
// Handle already-finished game (restored from storage)
|
|
if (currentGame.isOver) {
|
|
setSearchEnabled(searchContainer, false);
|
|
renderGameOver(gameOverContainer, currentGame.isWon, currentGame.target, currentGame.guesses.length);
|
|
if (mode === "unlimited") {
|
|
addNewGameButton(gameOverContainer);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Enable search and update callbacks for this game instance
|
|
setSearchEnabled(searchContainer, true);
|
|
const input = searchContainer.querySelector("#search-input");
|
|
input.placeholder = "Type a champion name...";
|
|
input.value = "";
|
|
input.focus();
|
|
|
|
updateSearchCallbacks(
|
|
(champion) => handleGuess(champion, gridContainer, gameOverContainer, searchContainer, guessCounter, statsContainer),
|
|
() => getGuessedNames(currentGame),
|
|
);
|
|
}
|
|
|
|
function handleGuess(champion, gridContainer, gameOverContainer, searchContainer, guessCounter, statsContainer) {
|
|
const result = submitGuess(currentGame, champion);
|
|
if (!result) return;
|
|
|
|
renderGuessRow(gridContainer, champion, result);
|
|
updateGuessCounter(guessCounter, currentGame.guesses.length, currentGame.maxGuesses);
|
|
|
|
if (currentGame.isOver) {
|
|
setSearchEnabled(searchContainer, false);
|
|
renderGameOver(gameOverContainer, currentGame.isWon, currentGame.target, currentGame.guesses.length);
|
|
|
|
if (currentGame.mode === "unlimited") {
|
|
saveUnlimitedStats(currentGame);
|
|
renderStats(statsContainer, loadUnlimitedStats());
|
|
addNewGameButton(gameOverContainer);
|
|
}
|
|
} else {
|
|
// Refocus input for next guess
|
|
searchContainer.querySelector("#search-input").focus();
|
|
}
|
|
}
|
|
|
|
/** Get or create a persistent seed for unlimited mode */
|
|
function getOrCreateUnlimitedSeed() {
|
|
try {
|
|
const saved = localStorage.getItem(UNLIMITED_SEED_KEY);
|
|
if (saved) return saved;
|
|
} catch {
|
|
// Ignore
|
|
}
|
|
return createNewUnlimitedSeed();
|
|
}
|
|
|
|
/** Create and persist a new unlimited seed */
|
|
function createNewUnlimitedSeed() {
|
|
const seed = `unlimited_${Date.now()}_${Math.random()}`;
|
|
try {
|
|
localStorage.setItem(UNLIMITED_SEED_KEY, seed);
|
|
} catch {
|
|
// Ignore
|
|
}
|
|
return seed;
|
|
}
|
|
|
|
function addNewGameButton(container) {
|
|
const btn = document.createElement("button");
|
|
btn.className = "new-game-btn";
|
|
btn.textContent = "New Game";
|
|
btn.addEventListener("click", () => {
|
|
clearUnlimitedState();
|
|
// Clear the persisted seed so a new one is generated
|
|
try {
|
|
localStorage.removeItem(UNLIMITED_SEED_KEY);
|
|
} catch {
|
|
// Ignore
|
|
}
|
|
startGame("unlimited");
|
|
});
|
|
container.appendChild(btn);
|
|
}
|
|
|
|
// Start the app
|
|
init();
|