Files
impstr/app.js

251 lines
7.2 KiB
JavaScript

const words = [
"Kekse",
"Schach",
"Giraffe",
"Spaghetti",
"Rakete",
"Blume",
"Kino",
"Drachen",
"Buch",
"Kaffee",
"Berg",
"Pizza",
"Wolke",
"Bus",
"Zitrone",
"Gitarre",
"Meer",
"Bananen",
"Loewe",
"Regen",
];
const state = {
players: [],
currentIndex: 0,
imposterIndex: -1,
secretWord: "",
revealed: false,
discussionIndex: 0,
};
const elements = {
playerInput: document.querySelector("#player-input"),
addPlayer: document.querySelector("#add-player"),
playerList: document.querySelector("#player-list"),
startGame: document.querySelector("#start-game"),
clearPlayers: document.querySelector("#clear-players"),
setupHint: document.querySelector("#setup-hint"),
setup: document.querySelector("#setup"),
round: document.querySelector("#round"),
discussion: document.querySelector("#discussion"),
done: document.querySelector("#done"),
roundCount: document.querySelector("#round-count"),
restart: document.querySelector("#restart"),
currentPlayer: document.querySelector("#current-player"),
roundScreen: document.querySelector("#round-screen"),
handoff: document.querySelector("#handoff"),
revealCard: document.querySelector("#reveal-card"),
revealWord: document.querySelector("#reveal-word"),
discussionCount: document.querySelector("#discussion-count"),
discussionPlayer: document.querySelector("#discussion-player"),
discussionNext: document.querySelector("#discussion-next"),
discussionRestart: document.querySelector("#discussion-restart"),
imposterFound: document.querySelector("#imposter-found"),
playAgain: document.querySelector("#play-again"),
editPlayers: document.querySelector("#edit-players"),
};
const MIN_PLAYERS = 3;
const STORAGE_KEY = "imposterPlayers";
const updatePlayerList = () => {
elements.playerList.innerHTML = "";
state.players.forEach((player, index) => {
const item = document.createElement("li");
const name = document.createElement("span");
name.textContent = player;
const remove = document.createElement("button");
remove.type = "button";
remove.textContent = "Entfernen";
remove.addEventListener("click", () => {
state.players.splice(index, 1);
updatePlayerList();
});
item.append(name, remove);
elements.playerList.appendChild(item);
});
const ready = state.players.length >= MIN_PLAYERS;
elements.startGame.disabled = !ready;
elements.setupHint.textContent = ready
? `${state.players.length} Spieler bereit.`
: `Mindestens ${MIN_PLAYERS} Spieler.`;
localStorage.setItem(STORAGE_KEY, JSON.stringify(state.players));
};
const addPlayer = () => {
const raw = elements.playerInput.value.trim();
if (!raw) {
return;
}
state.players.push(raw);
elements.playerInput.value = "";
updatePlayerList();
elements.playerInput.focus();
};
const startRound = () => {
state.currentIndex = 0;
state.imposterIndex = Math.floor(Math.random() * state.players.length);
state.secretWord = words[Math.floor(Math.random() * words.length)];
state.revealed = false;
elements.revealCard.classList.add("flip-card--instant");
elements.setup.classList.add("hidden");
elements.discussion.classList.add("hidden");
elements.done.classList.add("hidden");
elements.round.classList.remove("hidden");
updateRoundView();
requestAnimationFrame(() => {
elements.revealCard.classList.remove("flip-card--instant");
});
};
const updateRoundView = () => {
const total = state.players.length;
const current = state.currentIndex + 1;
elements.roundCount.textContent = `Phase 1: Zuweisung \u2014 Spieler ${current} / ${total}`;
elements.currentPlayer.textContent = state.players[state.currentIndex];
elements.roundScreen.classList.remove("hidden");
const isImposter = state.currentIndex === state.imposterIndex;
elements.revealWord.textContent = isImposter ? "IMPOSTER" : state.secretWord;
elements.revealCard.classList.remove("flipped");
state.revealed = false;
};
const toggleReveal = () => {
elements.revealCard.classList.toggle("flipped");
state.revealed = !state.revealed;
};
const handoffPlayer = () => {
elements.revealCard.classList.add("flip-card--instant");
elements.revealCard.classList.remove("flipped");
state.currentIndex += 1;
if (state.currentIndex >= state.players.length) {
startDiscussion();
return;
}
updateRoundView();
requestAnimationFrame(() => {
elements.revealCard.classList.remove("flip-card--instant");
});
};
const startDiscussion = () => {
state.discussionIndex = 0;
elements.round.classList.add("hidden");
elements.done.classList.add("hidden");
elements.discussion.classList.remove("hidden");
updateDiscussionView();
};
const updateDiscussionView = () => {
const total = state.players.length;
const current = state.discussionIndex + 1;
elements.discussionCount.textContent = `Phase 2: Runde \u2014 Spieler ${current} / ${total}`;
elements.discussionPlayer.textContent = state.players[state.discussionIndex];
};
const nextDiscussion = () => {
if (state.players.length === 0) {
return;
}
state.discussionIndex = (state.discussionIndex + 1) % state.players.length;
updateDiscussionView();
};
const endDiscussion = () => {
elements.discussion.classList.add("hidden");
elements.done.classList.remove("hidden");
};
const resetToSetup = () => {
elements.round.classList.add("hidden");
elements.discussion.classList.add("hidden");
elements.done.classList.add("hidden");
elements.setup.classList.remove("hidden");
};
const clearPlayers = () => {
state.players = [];
updatePlayerList();
};
const loadPlayers = () => {
const saved = localStorage.getItem(STORAGE_KEY);
if (!saved) {
return;
}
try {
const parsed = JSON.parse(saved);
if (Array.isArray(parsed)) {
state.players = parsed.filter((name) => typeof name === "string");
}
} catch (error) {
localStorage.removeItem(STORAGE_KEY);
}
};
elements.addPlayer.addEventListener("click", addPlayer);
elements.playerInput.addEventListener("keydown", (event) => {
if (event.key === "Enter") {
addPlayer();
}
});
elements.clearPlayers.addEventListener("click", clearPlayers);
elements.startGame.addEventListener("click", startRound);
elements.handoff.addEventListener("click", handoffPlayer);
elements.revealCard.addEventListener("click", toggleReveal);
elements.discussionNext.addEventListener("click", nextDiscussion);
elements.discussionRestart.addEventListener("click", startRound);
elements.imposterFound.addEventListener("click", endDiscussion);
elements.restart.addEventListener("click", startRound);
elements.playAgain.addEventListener("click", startRound);
elements.editPlayers.addEventListener("click", resetToSetup);
loadPlayers();
updatePlayerList();
if ("serviceWorker" in navigator) {
window.addEventListener("load", async () => {
const reg = await navigator.serviceWorker.register("./sw.js");
const showUpdate = () => {
document.querySelector("#update-banner").classList.remove("hidden");
document.querySelector("#update-btn").addEventListener("click", () => {
reg.waiting.postMessage("SKIP_WAITING");
});
};
if (reg.waiting) {
showUpdate();
}
reg.addEventListener("updatefound", () => {
const newSW = reg.installing;
newSW.addEventListener("statechange", () => {
if (newSW.state === "installed" && navigator.serviceWorker.controller) {
showUpdate();
}
});
});
navigator.serviceWorker.addEventListener("controllerchange", () => {
window.location.reload();
});
});
}