535 lines
19 KiB
JavaScript
Executable File
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');
|
|
}
|
|
})();
|