commit 83f00df29221a97729eaff028549520d54d20409 Author: emanuel Date: Wed Dec 17 17:43:55 2025 +0100 Initial commit diff --git a/Work in progress/auth.js b/Work in progress/auth.js new file mode 100755 index 0000000..6edbb8b --- /dev/null +++ b/Work in progress/auth.js @@ -0,0 +1,16 @@ +export const CORRECT_PASSWORD = 'Yatzy'; + +export function checkPassword(passwordInput, passwordError, passwordModal, appContainer) { + const enteredPassword = passwordInput.value; + if (enteredPassword === CORRECT_PASSWORD) { + sessionStorage.setItem('yatzy-authenticated', 'true'); + passwordModal.classList.add('hidden'); + appContainer.classList.remove('hidden'); + return true; + } else { + passwordError.textContent = 'Fel lösenord. Försök igen.'; + passwordInput.value = ''; + return false; + } +} + diff --git a/Work in progress/gameLogic.js b/Work in progress/gameLogic.js new file mode 100755 index 0000000..9042a7d --- /dev/null +++ b/Work in progress/gameLogic.js @@ -0,0 +1,152 @@ +export const SCORE_CATEGORIES = { + upper: [ + { id: 'ones', name: 'Ettor', maxCount: 5, multiple: 1, inputType: 'count' }, + { id: 'twos', name: 'Tvåor', maxCount: 5, multiple: 2, inputType: 'count' }, + { id: 'threes', name: 'Treor', maxCount: 5, multiple: 3, inputType: 'count' }, + { id: 'fours', name: 'Fyror', maxCount: 5, multiple: 4, inputType: 'count' }, + { id: 'fives', name: 'Femmor', maxCount: 5, multiple: 5, inputType: 'count' }, + { id: 'sixes', name: 'Sexor', maxCount: 5, multiple: 6, inputType: 'count' } + ], + lower: [ + { id: 'pair', name: 'Ett par', inputType: 'options', options: [2, 4, 6, 8, 10, 12] }, + { id: 'twoPair', name: 'Två par', min: 6, max: 22, inputType: 'score' }, + { id: 'threeOfAKind', name: 'Tretal', inputType: 'options', options: [3, 6, 9, 12, 15, 18] }, + { id: 'fourOfAKind', name: 'Fyrtal', inputType: 'options', options: [4, 8, 12, 16, 20, 24] }, + { id: 'smallStraight', name: 'Liten stege', fixed: 15, inputType: 'score' }, + { id: 'largeStraight', name: 'Stor stege', fixed: 20, inputType: 'score' }, + { id: 'fullHouse', name: 'Kåk', min: 7, max: 28, inputType: 'score' }, + { id: 'chance', name: 'Chans', min: 5, max: 30, inputType: 'score' }, + { id: 'yatzee', name: 'Yatzy', fixed: 50, inputType: 'score' } + ] +}; + +export const ALL_CATEGORIES = [...SCORE_CATEGORIES.upper, ...SCORE_CATEGORIES.lower]; + +// Hjälpfunktion för att räkna förekomsten av varje tärningsvärde +function countDice(dice) { + const counts = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0 }; + dice.forEach(die => { + counts[die]++; + }); + return counts; +} + +// Hjälpfunktion för att beräkna poäng för en specifik kategori +function calculateCategoryScore(dice, categoryId) { + const counts = countDice(dice); + + switch (categoryId) { + case 'ones': + return counts[1] * 1; + case 'twos': + return counts[2] * 2; + case 'threes': + return counts[3] * 3; + case 'fours': + return counts[4] * 4; + case 'fives': + return counts[5] * 5; + case 'sixes': + return counts[6] * 6; + case 'pair': + for (let i = 6; i >= 1; i--) { + if (counts[i] >= 2) return i * 2; + } + return 0; + case 'twoPair': + const pairs = []; + for (let i = 6; i >= 1; i--) { + if (counts[i] >= 2) pairs.push(i * 2); + } + if (pairs.length >= 2) { + return pairs[0] + pairs[1]; + } else if (pairs.length === 1) { + // Om det bara finns ett par, ta det högsta möjliga paret och lägg till högsta enkeltärning + const highestSingle = Math.max(...dice.filter(die => counts[die] === 1)); + return pairs[0] + (highestSingle || 0); + } + return 0; + case 'threeOfAKind': + for (let i = 6; i >= 1; i--) { + if (counts[i] >= 3) return i * 3; + } + return 0; + case 'fourOfAKind': + for (let i = 6; i >= 1; i--) { + if (counts[i] >= 4) return i * 4; + } + return 0; + case 'smallStraight': + const hasSmallStraight = dice.some(die => { + const has1234 = dice.includes(1) && dice.includes(2) && dice.includes(3) && dice.includes(4); + const has2345 = dice.includes(2) && dice.includes(3) && dice.includes(4) && dice.includes(5); + const has3456 = dice.includes(3) && dice.includes(4) && dice.includes(5) && dice.includes(6); + return has1234 || has2345 || has3456; + }); + return hasSmallStraight ? 15 : 0; + case 'largeStraight': + const has12345 = dice.includes(1) && dice.includes(2) && dice.includes(3) && dice.includes(4) && dice.includes(5); + const has23456 = dice.includes(2) && dice.includes(3) && dice.includes(4) && dice.includes(5) && dice.includes(6); + return (has12345 || has23456) ? 20 : 0; + case 'fullHouse': + const hasThreeOfAKind = Object.values(counts).some(count => count >= 3); + const hasPair = Object.values(counts).some(count => count >= 2); + return (hasThreeOfAKind && hasPair) ? dice.reduce((sum, die) => sum + die, 0) : 0; + case 'chance': + return dice.reduce((sum, die) => sum + die, 0); + case 'yatzee': + return Object.values(counts).some(count => count === 5) ? 50 : 0; + default: + return 0; + } +} + +// Funktion för att beräkna möjliga poäng för en kategori +export function calculatePossibleScores(gameData, playerId, categoryId) { + // Hämta de senast kastade tärningarna för spelaren (detta måste implementeras i main.js) + const currentDice = gameData.currentDice || [1, 1, 1, 1, 1]; // Exempelvärden, ersätt med riktiga tärningar + + const category = ALL_CATEGORIES.find(cat => cat.id === categoryId); + if (!category) return []; + + const score = calculateCategoryScore(currentDice, categoryId); + + if (category.inputType === 'count') { + return [score]; + } else if (category.inputType === 'options') { + return category.options.includes(score) ? [score] : [0]; + } else if (category.inputType === 'score') { + if (category.fixed) { + return [category.fixed]; + } else if (category.min !== undefined && category.max !== undefined) { + return score >= category.min && score <= category.max ? [score] : [0]; + } else { + return [score]; + } + } + + return [score]; +} + +export function calculateTotals(gameData) { + const playerTotals = {}; + gameData.players.forEach(p => { + const pScores = gameData.scores[p.id] || {}; + const upperSum = SCORE_CATEGORIES.upper.reduce((sum, cat) => sum + (pScores[cat.id] || 0), 0); + const upperBonus = upperSum >= 63 ? 50 : 0; + const upperTotal = upperSum + upperBonus; + const lowerTotal = SCORE_CATEGORIES.lower.reduce((sum, cat) => sum + (pScores[cat.id] || 0), 0); + const grandTotal = upperTotal + lowerTotal; + playerTotals[p.id] = { upperSum, upperBonus, upperTotal, lowerTotal, grandTotal }; + }); + return playerTotals; +} + +export function isGameComplete(gameData) { + if (!gameData || !gameData.players || !gameData.scores) return false; + return gameData.players.every(p => { + return ALL_CATEGORIES.every(cat => { + return gameData.scores[p.id]?.[cat.id] !== null && gameData.scores[p.id]?.[cat.id] !== undefined; + }); + }); +} diff --git a/Work in progress/index.html b/Work in progress/index.html new file mode 100755 index 0000000..6069c92 --- /dev/null +++ b/Work in progress/index.html @@ -0,0 +1,183 @@ + + + + + + Yatzy Poängkort + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Work in progress/main.js b/Work in progress/main.js new file mode 100755 index 0000000..13bfbfc --- /dev/null +++ b/Work in progress/main.js @@ -0,0 +1,534 @@ +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) => ` +
+
${die}
+
+ `).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 = '

Inga spelare hittades. Lägg till en ovan.

'; + } else { + playerListContainer.innerHTML = players.map(p => ` + + `).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 = `

Kunde inte ladda spelare: ${error.message}

`; + } +} + +// 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 => ` + + `).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'); + } +})(); diff --git a/Work in progress/styles.css b/Work in progress/styles.css new file mode 100644 index 0000000..22f6749 --- /dev/null +++ b/Work in progress/styles.css @@ -0,0 +1,91 @@ +:root { + /* Catppuccin Latte (Light Theme) */ + --bg-base: #EFF1F5; + --bg-mantle: #E6E9EF; + --bg-surface: #FFFFFF; + --text-primary: #4C4F69; + --text-secondary: #6C6F84; + --border-color: #CCD0DA; + --accent-color: #8839EF; + --accent-text: #FFFFFF; + --success-color: #40A02B; + --error-color: #D20F39; + --subtle-hover: #DCE0E8; +} + +html.dark { + /* Catppuccin Mocha (Dark Theme) */ + --bg-base: #1E1E2E; + --bg-mantle: #181825; + --bg-surface: #11111B; + --text-primary: #CDD6F4; + --text-secondary: #A6ADC8; + --border-color: #45475A; + --accent-color: #CBA6F7; + --accent-text: #1E1E2E; + --success-color: #A6E3A1; + --error-color: #F38BA8; + --subtle-hover: #313244; +} + +body { + font-family: 'Inter', sans-serif; + background-color: var(--bg-base); + color: var(--text-primary); +} + +/* Component Styles */ +.card { + background-color: var(--bg-surface); + border: 1px solid var(--border-color); +} +.header-title { color: var(--accent-color); } +.header-subtitle { color: var(--text-secondary); } +.btn-primary { background-color: var(--success-color); color: var(--bg-base); } +.btn-secondary { background-color: var(--accent-color); color: var(--accent-text); } +.btn-neutral { background-color: var(--bg-mantle); color: var(--text-primary); border: 1px solid var(--border-color); } +.btn-neutral:hover { background-color: var(--subtle-hover); } +.btn-danger { background-color: var(--error-color); color: var(--bg-base); } + +.score-cell { + width: 100%; height: 100%; min-height: 44px; display: flex; + align-items: center; justify-content: center; cursor: pointer; + border-radius: 8px; transition: background-color 0.2s ease-in-out; +} +.score-cell:hover { background-color: var(--subtle-hover); } +.score-cell.filled { + font-weight: 600; + color: var(--accent-color); +} +.score-cell-disabled { cursor: not-allowed; } +.score-cell-disabled:hover { background-color: transparent; } + +.calculated-field { font-weight: 600; background-color: var(--bg-mantle); } +.grand-total { font-weight: 700; font-size: 1.125rem; background-color: var(--bg-mantle); color: var(--accent-color); } +.table-header th { background-color: var(--accent-color); color: var(--accent-text); font-weight: 600; position: sticky; top: 0; z-index: 10; } +.category-col { position: sticky; left: 0; z-index: 5; background-color: var(--bg-surface); width: 100px; } +@media (min-width: 640px) { + .category-col { width: 150px; } +} +.current-player-col { background-color: var(--subtle-hover) !important; } + +/* Modal styles */ +.modal-backdrop { + position: fixed; top: 0; left: 0; width: 100%; height: 100%; + background-color: rgba(0, 0, 0, 0.5); display: flex; + justify-content: center; align-items: center; z-index: 50; +} +.modal-content { width: 90%; max-width: 500px; background-color: var(--bg-surface); } + +.score-input { + background-color: var(--bg-mantle); border: 1px solid var(--border-color); +} +.player-list-item { + background-color: var(--bg-surface); border: 1px solid var(--border-color); +} +.player-list-item:hover { + background-color: var(--subtle-hover); +} +.error-text { + color: var(--error-color); +} diff --git a/Work in progress/supabase.js b/Work in progress/supabase.js new file mode 100755 index 0000000..6a7d583 --- /dev/null +++ b/Work in progress/supabase.js @@ -0,0 +1,64 @@ +import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'; + +const SUPABASE_URL = 'https://db.tvalen.lol'; +const SUPABASE_ANON_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlzcyI6InN1cGFiYXNlIiwiaWF0IjoxNzUzOTk5MjAwLCJleHAiOjE5MTE3NjU2MDB9.rqUu8VzDYqPs28uxZUCqmK55abGvB_zMdS-LCb8KA8c'; + +export const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, { + realtime: { + params: { + eventsPerSecond: 10, + }, + endpoint: 'realtime/v1', + }, +}); + + +// Funktion för att hämta spelare +export async function getPlayers() { + const { data, error } = await supabase.from('players').select('*'); + if (error) { + console.error("Error fetching players:", error); + return []; + } + return data; +} + +// Funktion för att lägga till en spelare +export async function addPlayer(name, wins = 0) { + const { error } = await supabase.from('players').insert([{ name, wins }]); + if (error) { + console.error("Error adding player:", error); + throw error; + } +} + +// Funktion för att hämta de senaste spelen +export async function getRecentGames() { + const { data, error } = await supabase + .from('games') + .select('*') + .eq('isFinished', true) + .order('finishedAt', { ascending: false }) + .limit(10); + if (error) { + console.error("Error fetching recent games:", error); + return []; + } + return data; +} + +// Funktion för att hämta de högsta poängen +export async function getHighScores() { + const { data, error } = await supabase + .from('games') + .select('*') + .eq('isFinished', true) + .order('winnerScore', { ascending: false }) + .limit(3); + if (error) { + console.error("Error fetching high scores:", error); + return []; + } + return data; +} + diff --git a/Work in progress/ui.js b/Work in progress/ui.js new file mode 100755 index 0000000..708ccc3 --- /dev/null +++ b/Work in progress/ui.js @@ -0,0 +1,158 @@ +import { SCORE_CATEGORIES, ALL_CATEGORIES, calculateTotals, isGameComplete } from './gameLogic.js'; +import { getPlayers, getRecentGames, getHighScores } from './supabase.js'; + +// Funktion för att rendera poängkortet +export function renderScorecard(gameData, scorecardHead, scorecardBody) { + const players = gameData.players; + const scores = gameData.scores; + const isFinished = gameData.isFinished; + const currentPlayerId = isFinished ? null : gameData.players[gameData.currentPlayerIndex]?.id; + + scorecardHead.innerHTML = ` + + Kategori + ${players.map(p => `${p.name}`).join('')} + + `; + + const playerTotals = calculateTotals(gameData); + + let bodyHtml = ''; + + const renderCategoryRow = (cat) => { + return ` + ${cat.name} + ${players.map(p => { + const score = scores[p.id]?.[cat.id]; + const scoreText = (score === null || score === undefined) ? '' : score; + const isFilled = (score !== null && score !== undefined); + const filledClass = isFilled ? 'filled' : ''; + const disabledClass = isFinished ? 'score-cell-disabled' : ''; + const currentColClass = p.id === currentPlayerId ? 'current-player-col' : ''; + + return ` +
+ ${scoreText} +
+ `; + }).join('')} + `; + }; + + SCORE_CATEGORIES.upper.forEach(cat => bodyHtml += renderCategoryRow(cat)); + + bodyHtml += ` + Summa${players.map(p => `${playerTotals[p.id].upperSum}`).join('')} + Bonus${players.map(p => `${playerTotals[p.id].upperBonus}`).join('')} + Övre Summa${players.map(p => `${playerTotals[p.id].upperTotal}`).join('')} + `; + + SCORE_CATEGORIES.lower.forEach(cat => bodyHtml += renderCategoryRow(cat)); + + bodyHtml += `Nedre Summa${players.map(p => `${playerTotals[p.id].lowerTotal}`).join('')}`; + + if (isFinished) { + bodyHtml += `Totalsumma${players.map(p => `${playerTotals[p.id].grandTotal}`).join('')}`; + } + + scorecardBody.innerHTML = bodyHtml; + // Anropa funktionen för att koppla event listeners efter att poängkortet har renderats + if (typeof attachScorecardEventListeners === 'function') { + attachScorecardEventListeners(); + } +} + +// Funktion för att rendera topplistan för spelare +export async function renderPlayerLeaderboard() { + const container = document.getElementById('player-leaderboard'); + if (!container) return; + + try { + const players = await getPlayers(); + players.sort((a, b) => (b.wins || 0) - (a.wins || 0)); + + if (players.length === 0) { + container.innerHTML = '

Inga spelare har lagts till än.

'; + } else { + let html = ''; + container.innerHTML = html; + } + } catch (error) { + console.error("Error rendering leaderboard:", error); + container.innerHTML = `

Kunde inte ladda topplistan.

`; + } +} + +// Funktion för att rendera topplistan för högsta poäng +export async function renderHighScores() { + const container = document.getElementById('high-score-list'); + if (!container) return; + + try { + const games = await getHighScores(); + if (games.length === 0) { + container.innerHTML = '

Inga avslutade spel än.

'; + } else { + let html = ''; + container.innerHTML = html; + } + } catch (error) { + console.error("Error rendering high scores:", error); + container.innerHTML = `

Kunde inte ladda topplistan.

`; + } +} + +// Funktion för att rendera de senaste spelen +export async function renderRecentGames() { + const container = document.getElementById('recent-games-list'); + if (!container) return; + + try { + const games = await getRecentGames(); + if (games.length === 0) { + container.innerHTML = '

Inga avslutade spel än.

'; + } else { + let html = ''; + container.innerHTML = html; + } + } catch (error) { + console.error("Error rendering recent games:", error); + container.innerHTML = `

Kunde inte ladda senaste spel.

`; + } +} + diff --git a/index.html b/index.html new file mode 100755 index 0000000..ef43210 --- /dev/null +++ b/index.html @@ -0,0 +1,1067 @@ + + + + + + Yatzy Poängkort + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +