238 lines
6.3 KiB
JavaScript
238 lines
6.3 KiB
JavaScript
const STORAGE_KEY = "euchre-scoreboard";
|
||
|
||
const defaultState = () => ({
|
||
teamUsName: "Us",
|
||
teamThemName: "Them",
|
||
teamUsScore: 0,
|
||
teamThemScore: 0,
|
||
targetScore: 10,
|
||
loneEnabled: true,
|
||
history: [],
|
||
gameOver: false,
|
||
});
|
||
|
||
let state = loadState();
|
||
|
||
const els = {
|
||
teamUsName: document.getElementById("teamUsName"),
|
||
teamThemName: document.getElementById("teamThemName"),
|
||
teamUsScore: document.getElementById("teamUsScore"),
|
||
teamThemScore: document.getElementById("teamThemScore"),
|
||
targetScore: document.getElementById("targetScore"),
|
||
loneEnabled: document.getElementById("loneEnabled"),
|
||
historyList: document.getElementById("historyList"),
|
||
handCount: document.getElementById("handCount"),
|
||
undoBtn: document.getElementById("undoBtn"),
|
||
resetBtn: document.getElementById("resetBtn"),
|
||
settingsBtn: document.getElementById("settingsBtn"),
|
||
settingsPanel: document.getElementById("settingsPanel"),
|
||
closeSettings: document.getElementById("closeSettings"),
|
||
winnerOverlay: document.getElementById("winnerOverlay"),
|
||
winnerText: document.getElementById("winnerText"),
|
||
winnerScore: document.getElementById("winnerScore"),
|
||
newGameBtn: document.getElementById("newGameBtn"),
|
||
teamUsCard: document.getElementById("teamUsCard"),
|
||
teamThemCard: document.getElementById("teamThemCard"),
|
||
};
|
||
|
||
function loadState() {
|
||
try {
|
||
const saved = localStorage.getItem(STORAGE_KEY);
|
||
if (saved) {
|
||
return { ...defaultState(), ...JSON.parse(saved) };
|
||
}
|
||
} catch {
|
||
/* ignore corrupt storage */
|
||
}
|
||
return defaultState();
|
||
}
|
||
|
||
function saveState() {
|
||
localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
|
||
}
|
||
|
||
function teamLabel(team) {
|
||
return team === "us" ? state.teamUsName : state.teamThemName;
|
||
}
|
||
|
||
function addScore(team, points, label) {
|
||
if (state.gameOver) return;
|
||
|
||
const entry = {
|
||
team,
|
||
points,
|
||
label,
|
||
teamUsScore: state.teamUsScore,
|
||
teamThemScore: state.teamThemScore,
|
||
timestamp: Date.now(),
|
||
};
|
||
|
||
if (team === "us") {
|
||
state.teamUsScore += points;
|
||
} else {
|
||
state.teamThemScore += points;
|
||
}
|
||
|
||
state.history.push(entry);
|
||
checkWinner();
|
||
saveState();
|
||
render();
|
||
}
|
||
|
||
function undoLast() {
|
||
if (!state.history.length) return;
|
||
|
||
const last = state.history.pop();
|
||
state.teamUsScore = last.teamUsScore;
|
||
state.teamThemScore = last.teamThemScore;
|
||
state.gameOver = false;
|
||
saveState();
|
||
render();
|
||
}
|
||
|
||
function resetGame(skipConfirm = false) {
|
||
if (
|
||
!skipConfirm &&
|
||
state.history.length &&
|
||
!confirm("Start a new game? Current scores will be lost.")
|
||
) {
|
||
return;
|
||
}
|
||
state = defaultState();
|
||
state.teamUsName = els.teamUsName.value.trim() || "Us";
|
||
state.teamThemName = els.teamThemName.value.trim() || "Them";
|
||
state.targetScore = Number(els.targetScore.value);
|
||
state.loneEnabled = els.loneEnabled.checked;
|
||
saveState();
|
||
render();
|
||
}
|
||
|
||
function checkWinner() {
|
||
const target = state.targetScore;
|
||
state.gameOver = state.teamUsScore >= target || state.teamThemScore >= target;
|
||
}
|
||
|
||
function renderHistory() {
|
||
const count = state.history.length;
|
||
els.handCount.textContent = `${count} hand${count === 1 ? "" : "s"}`;
|
||
|
||
if (!count) {
|
||
els.historyList.innerHTML = '<li class="history-empty">No hands played yet</li>';
|
||
return;
|
||
}
|
||
|
||
els.historyList.innerHTML = state.history
|
||
.slice()
|
||
.reverse()
|
||
.map((entry, i) => {
|
||
const handNum = count - i;
|
||
const name = teamLabel(entry.team);
|
||
return `<li>
|
||
<span>#${handNum} — ${name}: ${entry.label}</span>
|
||
<span class="history-points">+${entry.points}</span>
|
||
</li>`;
|
||
})
|
||
.join("");
|
||
}
|
||
|
||
function renderWinner() {
|
||
if (!state.gameOver) {
|
||
els.winnerOverlay.hidden = true;
|
||
els.teamUsCard.classList.remove("winner");
|
||
els.teamThemCard.classList.remove("winner");
|
||
return;
|
||
}
|
||
|
||
const usWins = state.teamUsScore >= state.targetScore;
|
||
const winner = usWins ? state.teamUsName : state.teamThemName;
|
||
const winnerScore = usWins ? state.teamUsScore : state.teamThemScore;
|
||
const loserScore = usWins ? state.teamThemScore : state.teamUsScore;
|
||
|
||
els.winnerText.textContent = `${winner} wins!`;
|
||
els.winnerScore.textContent = `${winnerScore} – ${loserScore}`;
|
||
els.winnerOverlay.hidden = false;
|
||
|
||
if (usWins) {
|
||
els.teamUsCard.classList.add("winner");
|
||
els.teamThemCard.classList.remove("winner");
|
||
} else {
|
||
els.teamThemCard.classList.add("winner");
|
||
els.teamUsCard.classList.remove("winner");
|
||
}
|
||
}
|
||
|
||
function updateLoneButtons() {
|
||
document.querySelectorAll(".lone-btn").forEach((btn) => {
|
||
btn.classList.toggle("hidden", !state.loneEnabled);
|
||
});
|
||
}
|
||
|
||
function render() {
|
||
els.teamUsName.value = state.teamUsName;
|
||
els.teamThemName.value = state.teamThemName;
|
||
els.teamUsScore.textContent = state.teamUsScore;
|
||
els.teamThemScore.textContent = state.teamThemScore;
|
||
els.targetScore.value = String(state.targetScore);
|
||
els.loneEnabled.checked = state.loneEnabled;
|
||
|
||
els.undoBtn.disabled = !state.history.length;
|
||
updateLoneButtons();
|
||
renderHistory();
|
||
renderWinner();
|
||
|
||
document.querySelectorAll(".btn-score, .btn-euchre").forEach((btn) => {
|
||
btn.disabled = state.gameOver;
|
||
});
|
||
}
|
||
|
||
document.querySelectorAll(".btn-score, .btn-euchre").forEach((btn) => {
|
||
btn.addEventListener("click", () => {
|
||
addScore(btn.dataset.team, Number(btn.dataset.points), btn.dataset.label);
|
||
});
|
||
});
|
||
|
||
els.undoBtn.addEventListener("click", undoLast);
|
||
els.resetBtn.addEventListener("click", resetGame);
|
||
els.newGameBtn.addEventListener("click", () => resetGame(true));
|
||
|
||
els.settingsBtn.addEventListener("click", () => {
|
||
els.settingsPanel.hidden = !els.settingsPanel.hidden;
|
||
});
|
||
|
||
els.closeSettings.addEventListener("click", () => {
|
||
state.targetScore = Number(els.targetScore.value);
|
||
state.loneEnabled = els.loneEnabled.checked;
|
||
saveState();
|
||
render();
|
||
els.settingsPanel.hidden = true;
|
||
});
|
||
|
||
els.teamUsName.addEventListener("change", () => {
|
||
state.teamUsName = els.teamUsName.value.trim() || "Us";
|
||
saveState();
|
||
renderHistory();
|
||
});
|
||
|
||
els.teamThemName.addEventListener("change", () => {
|
||
state.teamThemName = els.teamThemName.value.trim() || "Them";
|
||
saveState();
|
||
renderHistory();
|
||
});
|
||
|
||
els.targetScore.addEventListener("change", () => {
|
||
state.targetScore = Number(els.targetScore.value);
|
||
checkWinner();
|
||
saveState();
|
||
render();
|
||
});
|
||
|
||
els.loneEnabled.addEventListener("change", () => {
|
||
state.loneEnabled = els.loneEnabled.checked;
|
||
saveState();
|
||
render();
|
||
});
|
||
|
||
checkWinner();
|
||
saveState();
|
||
render();
|