2025-12-17 17:43:55 +01:00

535 lines
19 KiB
JavaScript
Executable File

import { supabase, getPlayers, addPlayer, getRecentGames, getHighScores } from './supabase.js';
import { SCORE_CATEGORIES, ALL_CATEGORIES, calculateTotals, isGameComplete, calculatePossibleScores } from './gameLogic.js';
import { renderScorecard, renderPlayerLeaderboard, renderRecentGames, renderHighScores } from './ui.js';
import { checkPassword, CORRECT_PASSWORD } from './auth.js';
// DOM-element
const appContainer = document.getElementById('app');
const gameContainer = document.getElementById('game-container');
const startScreen = document.getElementById('start-screen');
const scorecardHead = document.getElementById('scorecard-head');
const scorecardBody = document.getElementById('scorecard-body');
const gameIdDisplay = document.getElementById('game-id-display');
const startGameModal = document.getElementById('start-game-modal');
const showStartModalBtn = document.getElementById('show-start-modal-btn');
const newGameBtn = document.getElementById('new-game-btn');
const backToStartBtn = document.getElementById('back-to-start-btn');
const endGameBtn = document.getElementById('end-game-btn');
const cancelStartGameBtn = document.getElementById('cancel-start-game-btn');
const addPlayerBtn = document.getElementById('add-player-btn');
const newPlayerNameInput = document.getElementById('new-player-name');
const newPlayerWinsInput = document.getElementById('new-player-wins');
const playerListContainer = document.getElementById('player-list');
const startGameBtn = document.getElementById('start-game-btn');
const alertModal = document.getElementById('alert-modal');
const alertModalTitle = document.getElementById('alert-modal-title');
const alertModalMessage = document.getElementById('alert-modal-message');
const scoreSelectModal = document.getElementById('score-select-modal');
const scoreModalTitle = document.getElementById('score-modal-title');
const scoreModalOptions = document.getElementById('score-modal-options');
const scoreModalCancelBtn = document.getElementById('score-modal-cancel-btn');
const themeToggle = document.getElementById('theme-toggle');
const themeIconLight = document.getElementById('theme-icon-light');
const themeIconDark = document.getElementById('theme-icon-dark');
const passwordModal = document.getElementById('password-modal');
const passwordInput = document.getElementById('password-input');
const passwordSubmitBtn = document.getElementById('password-submit-btn');
const passwordError = document.getElementById('password-error');
const confirmModal = document.getElementById('confirm-modal');
const diceContainer = document.getElementById('dice-container');
const rollDiceBtn = document.getElementById('roll-dice-btn');
// Global state
let currentGameId = null;
let currentGameData = null;
let gameUnsubscribe = null;
let selectedPlayersInOrder = [];
let currentDice = [1, 1, 1, 1, 1]; // Standardvärden för tärningar
let rollsLeft = 3; // Antal kast kvar
const appId = 'yatzy-app';
// Funktion för att visa meddelanden
function showAlert(message, title = 'Varning', onOkCallback = null) {
alertModalTitle.textContent = title;
alertModalMessage.innerHTML = message;
alertModal.classList.remove('hidden');
const oldBtn = document.getElementById('alert-modal-close-btn');
const newBtn = oldBtn.cloneNode(true);
oldBtn.parentNode.replaceChild(newBtn, oldBtn);
newBtn.addEventListener('click', () => {
alertModal.classList.add('hidden');
if (onOkCallback) onOkCallback();
}, { once: true });
}
// Funktion för att visa bekräftelsedialog
function showConfirm(onSave, onDelete) {
confirmModal.classList.remove('hidden');
const saveBtn = document.getElementById('confirm-save-btn');
const deleteBtn = document.getElementById('confirm-delete-btn');
const cancelBtn = document.getElementById('confirm-cancel-btn');
const close = () => confirmModal.classList.add('hidden');
saveBtn.onclick = () => { close(); onSave(); };
deleteBtn.onclick = () => { close(); onDelete(); };
cancelBtn.onclick = () => close();
}
// Funktion för att slå tärningar
function rollDice() {
if (rollsLeft <= 0) {
showAlert("Du har inga kast kvar!", "Inga kast kvar");
return;
}
const lockedDice = Array.from(document.querySelectorAll('.dice.locked'));
const lockedIndices = lockedDice.map(die => parseInt(die.dataset.index));
currentDice = currentDice.map((die, index) => {
if (lockedIndices.includes(index)) {
return die;
} else {
return Math.floor(Math.random() * 6) + 1;
}
});
rollsLeft--;
updateDiceUI();
updateRollButton();
console.log(`Tärningar efter kast: ${currentDice}, Kast kvar: ${rollsLeft}`);
}
// Funktion för att uppdatera tärnings-UI
function updateDiceUI() {
diceContainer.innerHTML = currentDice.map((die, index) => `
<div class="dice" data-index="${index}">
<div class="dice-face">${die}</div>
</div>
`).join('');
// Lägg till event listeners för att låsa/upplåsa tärningar
document.querySelectorAll('.dice').forEach(die => {
die.addEventListener('click', () => {
die.classList.toggle('locked');
});
});
}
// Funktion för att uppdatera kast-knappen
function updateRollButton() {
rollDiceBtn.textContent = `Slå tärningar (${rollsLeft} kvar)`;
if (rollsLeft <= 0) {
rollDiceBtn.disabled = true;
}
}
// Funktion för att återställa tärningar
function resetDice() {
currentDice = [1, 1, 1, 1, 1];
rollsLeft = 3;
updateDiceUI();
updateRollButton();
}
// Funktion för att lägga till spelare
async function addPlayerHandler() {
const name = newPlayerNameInput.value.trim();
const wins = newPlayerWinsInput.value;
const initialWins = parseInt(wins, 10) || 0;
if (!name) {
showAlert("Spelarens namn får inte vara tomt.", "Inmatningsfel");
return;
}
const { data } = await supabase.from('players').select('name').eq('name', name).single();
if (data) {
showAlert(`En spelare med namnet "${name}" finns redan.`, "Dubblett");
return;
}
try {
await addPlayer(name, initialWins);
newPlayerNameInput.value = '';
newPlayerWinsInput.value = '';
loadPlayersForSelection();
} catch (error) {
showAlert(`Kunde inte lägga till spelare: ${error.message}`, "Databasfel");
}
}
// Funktion för att ladda spelare
async function loadPlayersForSelection() {
try {
const players = await getPlayers();
players.sort((a, b) => (b.wins || 0) - (a.wins || 0));
if (players.length === 0) {
playerListContainer.innerHTML = '<p class="text-gray-500 dark:text-gray-400">Inga spelare hittades. Lägg till en ovan.</p>';
} else {
playerListContainer.innerHTML = players.map(p => `
<label class="flex items-center justify-between p-2 bg-white dark:bg-gray-600 rounded-md border dark:border-gray-500 cursor-pointer hover:bg-indigo-50 dark:hover:bg-indigo-900">
<div>
<input type="checkbox" class="h-5 w-5 rounded text-indigo-600 focus:ring-indigo-500 border-gray-300 dark:border-gray-500 bg-gray-200 dark:bg-gray-700" data-player-id="${p.id}" data-player-name="${p.name}">
<span class="ml-3 text-gray-700 dark:text-gray-200">${p.name}</span>
</div>
<span class="text-sm font-semibold text-indigo-600 dark:text-indigo-400">${p.wins || 0} vinster</span>
</label>
`).join('');
// Lägg till event listeners för checkboxarna
const checkboxes = playerListContainer.querySelectorAll('input[type="checkbox"]');
checkboxes.forEach(checkbox => {
checkbox.addEventListener('change', (e) => {
const playerId = e.target.dataset.playerId;
const playerName = e.target.dataset.playerName;
if (e.target.checked) {
if (!selectedPlayersInOrder.some(p => p.id === playerId)) {
selectedPlayersInOrder.push({ id: playerId, name: playerName });
}
} else {
selectedPlayersInOrder = selectedPlayersInOrder.filter(p => p.id !== playerId);
}
updateStartGameButtonState();
console.log("Valda spelare:", selectedPlayersInOrder);
});
});
}
} catch (error) {
console.error("Error loading players:", error);
playerListContainer.innerHTML = `<p class="text-red-500">Kunde inte ladda spelare: ${error.message}</p>`;
}
}
// Funktion för att uppdatera spelknappens status
function updateStartGameButtonState() {
console.log("Uppdaterar spelknappens status. Valda spelare:", selectedPlayersInOrder);
startGameBtn.disabled = selectedPlayersInOrder.length === 0;
}
// Funktion för att öppna/stänga modaler
function openStartGameModal() {
selectedPlayersInOrder = [];
loadPlayersForSelection();
startGameModal.classList.remove('hidden');
}
function closeStartGameModal() {
startGameModal.classList.add('hidden');
}
// Funktion för att applicera tema
function applyTheme(theme) {
if (theme === 'dark') {
document.documentElement.classList.add('dark');
themeIconLight.classList.add('hidden');
themeIconDark.classList.remove('hidden');
} else {
document.documentElement.classList.remove('dark');
themeIconLight.classList.remove('hidden');
themeIconDark.classList.add('hidden');
}
}
// Funktion för att återställa till startsidan
async function resetToStartScreen() {
if (gameUnsubscribe) {
gameUnsubscribe.unsubscribe();
gameUnsubscribe = null;
}
currentGameId = null;
currentGameData = null;
localStorage.removeItem(`yatzee-gameId-${appId}`);
gameContainer.classList.add('hidden');
startScreen.classList.remove('hidden');
await renderAllLeaderboards();
}
// Funktion för att rendera alla topplistor
async function renderAllLeaderboards() {
await Promise.all([
renderPlayerLeaderboard(),
renderRecentGames(),
renderHighScores()
]);
}
// Funktion för att lägga till standardspelare
async function addDefaultPlayers() {
const defaultPlayers = [
{ name: "Emanuel", wins: 580 },
{ name: "Mamma", wins: 555 },
{ name: "Pappa", wins: 556 }
];
for (const player of defaultPlayers) {
const { data, error } = await supabase
.from('players')
.select('name')
.eq('name', player.name)
.single();
if (!data) {
const { error: insertError } = await supabase
.from('players')
.insert([player]);
if (insertError) console.error(`Failed to add default player ${player.name}:`, insertError);
else console.log(`Default player "${player.name}" added.`);
}
}
}
// Funktion för att skapa ett nytt spel
async function createNewGame(selectedPlayers) {
if (selectedPlayers.length === 0) {
showAlert("Välj minst en spelare.", "Inmatningsfel");
return;
}
try {
const initialScores = {};
const playersForGame = [];
selectedPlayers.forEach(p => {
playersForGame.push({ id: p.id, name: p.name });
initialScores[p.id] = {
...Object.fromEntries(ALL_CATEGORIES.map(c => [c.id, null])),
};
});
const { data, error } = await supabase
.from('games')
.insert([{
players: playersForGame,
scores: initialScores,
isFinished: false,
currentPlayerIndex: 0,
currentDice: currentDice,
rollsLeft: rollsLeft
}])
.select()
.single();
if (error) throw error;
setCurrentGame(data.id);
closeStartGameModal();
} catch (error) {
console.error("Error creating new game:", error);
showAlert(`Kunde inte skapa spel: ${error.message}`, "Databasfel");
}
}
// Funktion för att sätta det aktuella spelet
function setCurrentGame(gameId) {
if (gameUnsubscribe) {
gameUnsubscribe.unsubscribe();
gameUnsubscribe = null;
}
currentGameId = gameId;
localStorage.setItem(`yatzee-gameId-${appId}`, gameId);
if (gameId) {
gameUnsubscribe = supabase
.channel(`game:${gameId}`)
.on('postgres_changes', { event: 'UPDATE', schema: 'public', table: 'games', filter: `id=eq.${gameId}` }, payload => {
currentGameData = payload.new;
currentDice = payload.new.currentDice || [1, 1, 1, 1, 1];
rollsLeft = payload.new.rollsLeft || 3;
renderGame(currentGameData);
})
.subscribe();
// Initial fetch
supabase.from('games').select('*').eq('id', gameId).single().then(({ data, error }) => {
if (data) {
currentGameData = data;
currentDice = data.currentDice || [1, 1, 1, 1, 1];
rollsLeft = data.rollsLeft || 3;
renderGame(currentGameData);
} else {
resetToStartScreen();
}
});
} else {
resetToStartScreen();
}
}
// Funktion för att rendera spelet
function renderGame(gameData) {
gameContainer.classList.remove('hidden');
startScreen.classList.add('hidden');
gameIdDisplay.textContent = currentGameId;
if (isGameComplete(gameData) && !gameData.isFinished) {
endGameBtn.classList.remove('hidden');
} else {
endGameBtn.classList.add('hidden');
}
renderScorecard(gameData, scorecardHead, scorecardBody);
updateDiceUI();
updateRollButton();
}
// Funktion för att visa modalen för att välja poäng
function showScoreSelectModal(playerId, categoryId, possibleScores) {
console.log(`Visar poängval för spelare ${playerId}, kategori ${categoryId}, möjliga poäng:`, possibleScores);
const category = ALL_CATEGORIES.find(cat => cat.id === categoryId);
scoreModalTitle.textContent = `Välj poäng för ${category.name}`;
scoreModalOptions.innerHTML = possibleScores.map(score => `
<button class="score-option btn-secondary font-bold py-2 px-4 rounded-md m-1"
data-score="${score}"
data-player-id="${playerId}"
data-category-id="${categoryId}">
${score}
</button>
`).join('');
scoreSelectModal.classList.remove('hidden');
// Lägg till event listeners för poängalternativen
const scoreOptionButtons = document.querySelectorAll('.score-option');
scoreOptionButtons.forEach(button => {
button.addEventListener('click', async (e) => {
const score = e.currentTarget.dataset.score;
const playerId = e.currentTarget.dataset.playerId;
const categoryId = e.currentTarget.dataset.categoryId;
console.log(`Poäng vald: Spelare ${playerId}, Kategori ${categoryId}, Poäng ${score}`);
// Uppdatera poängen i databasen
await updateScore(playerId, categoryId, parseInt(score));
scoreSelectModal.classList.add('hidden');
});
});
}
// Funktion för att uppdatera poängen i databasen
async function updateScore(playerId, categoryId, score) {
try {
const updatedScores = { ...currentGameData.scores };
updatedScores[playerId][categoryId] = score;
const nextPlayerIndex = isGameComplete(currentGameData) ?
currentGameData.currentPlayerIndex :
(currentGameData.currentPlayerIndex + 1) % currentGameData.players.length;
const { error } = await supabase
.from('games')
.update({
scores: updatedScores,
currentPlayerIndex: nextPlayerIndex,
rollsLeft: 3,
currentDice: [1, 1, 1, 1, 1]
})
.eq('id', currentGameId);
if (error) {
console.error("Fel vid uppdatering av poäng:", error);
showAlert("Kunde inte uppdatera poängen. Försök igen.", "Fel");
}
} catch (error) {
console.error("Oväntat fel vid uppdatering av poäng:", error);
showAlert("Ett oväntat fel uppstod. Försök igen.", "Fel");
}
}
// Funktion för att hantera klick på poängfälten
function attachScorecardEventListeners() {
const scoreCells = document.querySelectorAll('.score-cell:not(.filled):not(.score-cell-disabled)');
scoreCells.forEach(cell => {
cell.addEventListener('click', async (e) => {
const playerId = e.currentTarget.dataset.playerId;
const currentPlayerId = currentGameData.players[currentGameData.currentPlayerIndex].id;
if (playerId !== currentPlayerId) {
showAlert("Det är inte din tur!", "Fel spelare");
return;
}
const categoryId = e.currentTarget.dataset.categoryId;
console.log(`Poängfält klickat: Spelare ${playerId}, Kategori ${categoryId}`);
// Hämta möjliga poäng för den valda kategorin
const possibleScores = calculatePossibleScores({...currentGameData, currentDice}, playerId, categoryId);
// Visa modal för att välja poäng
showScoreSelectModal(playerId, categoryId, possibleScores);
});
});
}
// Funktion för att koppla event listeners
function attachEventListeners() {
if (showStartModalBtn) showStartModalBtn.addEventListener('click', openStartGameModal);
if (newGameBtn) newGameBtn.addEventListener('click', openStartGameModal);
if (cancelStartGameBtn) cancelStartGameBtn.addEventListener('click', closeStartGameModal);
if (addPlayerBtn) addPlayerBtn.addEventListener('click', addPlayerHandler);
if (scoreModalCancelBtn) scoreModalCancelBtn.addEventListener('click', () => scoreSelectModal.classList.add('hidden'));
if (rollDiceBtn) rollDiceBtn.addEventListener('click', rollDice);
if (newPlayerNameInput) {
newPlayerNameInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') addPlayerHandler();
});
}
if (themeToggle) {
themeToggle.addEventListener('click', () => {
const isDark = document.documentElement.classList.toggle('dark');
const newTheme = isDark ? 'dark' : 'light';
localStorage.setItem('theme', newTheme);
applyTheme(newTheme);
});
}
if (passwordSubmitBtn) {
passwordSubmitBtn.addEventListener('click', () => {
checkPassword(passwordInput, passwordError, passwordModal, appContainer);
});
}
if (passwordInput) {
passwordInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
checkPassword(passwordInput, passwordError, passwordModal, appContainer);
}
});
}
if (startGameBtn) {
startGameBtn.addEventListener('click', () => {
console.log("Starta spel-knappen klickad! Valda spelare:", selectedPlayersInOrder);
createNewGame(selectedPlayersInOrder);
});
}
}
// Funktion för att starta appen
(function() {
const savedTheme = localStorage.getItem('theme') || 'light';
applyTheme(savedTheme);
attachEventListeners();
if (sessionStorage.getItem('yatzy-authenticated') === 'true') {
passwordModal.classList.add('hidden');
appContainer.classList.remove('hidden');
addDefaultPlayers().then(() => resetToStartScreen());
} else {
passwordModal.classList.remove('hidden');
appContainer.classList.add('hidden');
}
})();