🎲 Ćwiczenie: Gra w Kości - JavaScript

Naucz się programować gry i losowości w JavaScript

🎯 Cel ćwiczenia

Gra w kości to doskonały projekt do nauki JavaScript! Nauczysz się generowania liczb losowych, manipulacji DOM, obsługi zdarzeń, przechowywania stanów gry i tworzenia logiki gier.

Czego się nauczysz:

  • Generowania liczb losowych z Math.random()
  • Manipulacji elementami DOM
  • Obsługi zdarzeń (kliknięcia, naciśnięcia klawiszy)
  • Zarządzania stanem gry (punkty, tury, gracze)
  • Tworzenia animacji i efektów wizualnych
  • Walidacji reguł gry i logiki biznesowej

🎲 Część 1: Podstawy - Losowanie Kości

Generowanie liczb losowych

W JavaScript używamy Math.random() do generowania liczb losowych.

Przykład 1: Prosta kostka (1-6)

JavaScript:

// Math.random() zwraca liczbę od 0 (włącznie) do 1 (wyłącznie) // np. 0.0, 0.234, 0.999... function rollDice() { // Math.random() * 6 daje: 0 do 5.999... // Math.floor() zaokrągla w dół: 0, 1, 2, 3, 4, 5 // +1 daje końcowy wynik: 1, 2, 3, 4, 5, 6 const result = Math.floor(Math.random() * 6) + 1; return result; } // Użycie: const diceValue = rollDice(); console.log("Wyrzuciłeś: " + diceValue);
Wzór ogólny: Aby losować od MIN do MAX:
Math.floor(Math.random() * (MAX - MIN + 1)) + MIN

Przykład 2: Wyświetlanie wyniku na stronie

HTML:

<div id="dice-result">?</div> <button onclick="throwDice()">Rzuć kostką</button>

JavaScript:

function throwDice() { // Losuj liczbę const result = Math.floor(Math.random() * 6) + 1; // Znajdź element na stronie const diceElement = document.getElementById('dice-result'); // Zaktualizuj jego zawartość diceElement.textContent = result; }

🎨 Część 2: Wizualizacja Kości

Przykład 3: Kostka z kropkami (Unicode)

Możemy użyć znaków Unicode do reprezentacji kości:

function getDiceEmoji(value) { const diceEmojis = { 1: '⚀', 2: '⚁', 3: '⚂', 4: '⚃', 5: '⚄', 6: '⚅' }; return diceEmojis[value]; } function throwDice() { const result = Math.floor(Math.random() * 6) + 1; const emoji = getDiceEmoji(result); document.getElementById('dice-result').textContent = emoji; }

Przykład 4: Kostka HTML/CSS z kropkami

HTML:

<div class="dice" id="dice"> <div class="dot dot1"></div> <div class="dot dot2"></div> <div class="dot dot3"></div> <div class="dot dot4"></div> <div class="dot dot5"></div> <div class="dot dot6"></div> </div>

CSS:

.dice { width: 100px; height: 100px; background: white; border: 3px solid #333; border-radius: 15px; position: relative; } .dot { width: 15px; height: 15px; background: #333; border-radius: 50%; position: absolute; display: none; /* Domyślnie ukryte */ } /* Pozycje kropek */ .dot1 { top: 20px; left: 20px; } .dot2 { top: 20px; right: 20px; } .dot3 { top: 42.5px; left: 42.5px; } .dot4 { bottom: 20px; left: 20px; } .dot5 { bottom: 20px; right: 20px; } .dot6 { top: 42.5px; right: 42.5px; } /* Klasy dla różnych wartości */ .dice.show-1 .dot3 { display: block; } .dice.show-2 .dot1, .dice.show-2 .dot5 { display: block; } .dice.show-3 .dot1, .dice.show-3 .dot3, .dice.show-3 .dot5 { display: block; } .dice.show-4 .dot1, .dice.show-4 .dot2, .dice.show-4 .dot4, .dice.show-4 .dot5 { display: block; } .dice.show-5 .dot1, .dice.show-5 .dot2, .dice.show-5 .dot3, .dice.show-5 .dot4, .dice.show-5 .dot5 { display: block; } .dice.show-6 .dot1, .dice.show-6 .dot2, .dice.show-6 .dot6, .dice.show-6 .dot4, .dice.show-6 .dot5 { display: block; }

JavaScript:

function showDiceValue(value) { const dice = document.getElementById('dice'); // Usuń wszystkie poprzednie klasy dice.className = 'dice'; // Dodaj nową klasę dla aktualnej wartości dice.classList.add('show-' + value); } function throwDice() { const result = Math.floor(Math.random() * 6) + 1; showDiceValue(result); }

🎮 Część 3: Prosta Gra - "Wyścig do 50"

📋 Zasady gry:

  • Dwóch graczy na zmianę rzuca kostką
  • Wyrzucony wynik dodaje się do punktów gracza
  • Ale! Jeśli wyrzucisz 1, tracisz wszystkie punkty z tej tury
  • Gracz może zdecydować, czy rzucić jeszcze raz, czy zatrzymać się
  • Pierwszy gracz, który osiągnie 50 punktów, wygrywa!

Przykład 5: Kompletna gra "Wyścig do 50"

HTML:

<div class="game-container"> <h2>Wyścig do 50 punktów</h2> <div class="players"> <div class="player active" id="player1"> <h3>Gracz 1</h3> <p>Punkty ogółem: <span id="score1">0</span></p> <p>Punkty w turze: <span id="current1">0</span></p> </div> <div class="player" id="player2"> <h3>Gracz 2</h3> <p>Punkty ogółem: <span id="score2">0</span></p> <p>Punkty w turze: <span id="current2">0</span></p> </div> </div> <div class="dice-display"> <div id="dice">?</div> </div> <div class="controls"> <button id="roll-btn">Rzuć kostką</button> <button id="hold-btn">Zatrzymaj</button> <button id="new-btn">Nowa gra</button> </div> </div>

JavaScript:

// Stan gry let scores = [0, 0]; let currentScore = 0; let activePlayer = 0; let playing = true; // Elementy DOM const player1El = document.getElementById('player1'); const player2El = document.getElementById('player2'); const score1El = document.getElementById('score1'); const score2El = document.getElementById('score2'); const current1El = document.getElementById('current1'); const current2El = document.getElementById('current2'); const diceEl = document.getElementById('dice'); const rollBtn = document.getElementById('roll-btn'); const holdBtn = document.getElementById('hold-btn'); const newBtn = document.getElementById('new-btn'); // Rzut kostką rollBtn.addEventListener('click', function() { if (!playing) return; // Losuj liczbę 1-6 const dice = Math.floor(Math.random() * 6) + 1; diceEl.textContent = dice; if (dice !== 1) { // Dodaj do punktów w turze currentScore += dice; document.getElementById(`current${activePlayer + 1}`).textContent = currentScore; } else { // Wyrzucono 1 - koniec tury switchPlayer(); } }); // Zatrzymaj (zapisz punkty) holdBtn.addEventListener('click', function() { if (!playing) return; // Dodaj punkty z tury do ogólnych punktów scores[activePlayer] += currentScore; document.getElementById(`score${activePlayer + 1}`).textContent = scores[activePlayer]; // Sprawdź, czy gracz wygrał if (scores[activePlayer] >= 50) { playing = false; alert(`Gracz ${activePlayer + 1} wygrał!`); } else { // Zmień gracza switchPlayer(); } }); // Zmiana gracza function switchPlayer() { // Wyzeruj punkty w turze currentScore = 0; document.getElementById(`current${activePlayer + 1}`).textContent = 0; // Zmień aktywnego gracza activePlayer = activePlayer === 0 ? 1 : 0; // Zaktualizuj wizualnie player1El.classList.toggle('active'); player2El.classList.toggle('active'); } // Nowa gra newBtn.addEventListener('click', function() { scores = [0, 0]; currentScore = 0; activePlayer = 0; playing = true; score1El.textContent = 0; score2El.textContent = 0; current1El.textContent = 0; current2El.textContent = 0; diceEl.textContent = '?'; player1El.classList.add('active'); player2El.classList.remove('active'); });

✨ Część 4: Animacje i Efekty

Przykład 6: Animacja rzutu kostką

CSS:

@keyframes shake { 0%, 100% { transform: rotate(0deg); } 25% { transform: rotate(-15deg); } 75% { transform: rotate(15deg); } } .dice.rolling { animation: shake 0.5s; }

JavaScript:

function throwDiceWithAnimation() { const dice = document.getElementById('dice'); // Dodaj animację dice.classList.add('rolling'); // Symuluj losowanie podczas animacji let counter = 0; const interval = setInterval(() => { const tempValue = Math.floor(Math.random() * 6) + 1; dice.textContent = tempValue; counter++; if (counter > 10) { clearInterval(interval); // Pokaż ostateczny wynik const finalValue = Math.floor(Math.random() * 6) + 1; dice.textContent = finalValue; dice.classList.remove('rolling'); } }, 50); }

✏️ ZADANIA PRAKTYCZNE

Zadanie 1: Prosta Kostka

ŁATWY

Cel: Stwórz prostą stronę z kostką, którą można rzucić.

Wymagania:

  • Przycisk "Rzuć kostką"
  • Wyświetlanie wyniku (1-6) w dużym elemencie
  • Licznik rzutów (ile razy rzucono kostką)
  • Przycisk "Reset" do zerowania licznika
  • Stylowanie CSS (kolorowa kostka)

Wskazówka: Użyj zmiennej globalnej do przechowywania liczby rzutów.

Zadanie 2: Statystyki Rzutów

ŁATWY

Cel: Rozszerz zadanie 1 o statystyki.

Wymagania:

  • Zliczaj ile razy wypadła każda liczba (1-6)
  • Wyświetlaj statystyki w tabeli lub listach
  • Pokaż procentowy udział każdej liczby
  • Znajdź i wyświetl najczęściej wyrzucaną liczbę
  • Przycisk do czyszczenia statystyk

Wskazówka: Użyj tablicy lub obiektu do przechowywania liczby wystąpień każdej wartości.

Zadanie 3: Gra "Zgadnij Liczbę"

ŚREDNI

Cel: Komputer losuje liczbę 1-6, gracz musi zgadnąć przed rzutem.

Wymagania:

  • 6 przycisków do wyboru liczby (1-6)
  • Po wybraniu, kostka rzuca automatycznie
  • Komunikat "Trafione!" lub "Pudło!"
  • Licznik trafień i pudłów
  • Pokaż serię trafień (streak)
  • Dodaj dźwięk przy trafieniu (opcjonalnie)

Wskazówka: Użyj new Audio('sound.mp3').play() dla dźwięku.

Zadanie 4: Gra dla 2 graczy "Wyścig do 100"

ŚREDNI

Cel: Zaawansowana wersja gry "Wyścig do 50".

Wymagania:

  • Cel: 100 punktów (zamiast 50)
  • Rzut 1 = strata wszystkich punktów w turze
  • Rzut podwójnej szóstki (dwa razy 6 pod rząd) = bonus +10 punktów
  • Przycisk "Rzuć 2 kostkami" - rzut dwiema kostkami jednocześnie
  • Historia ostatnich 5 rzutów dla każdego gracza
  • Zapisz wynik najlepszej gry (localStorage)
  • Animacja przy zmianie gracza

Wskazówka: Użyj localStorage.setItem() i localStorage.getItem().

Zadanie 5: Gra "Generał" (Yahtzee)

TRUDNY

Cel: Uproszczona wersja gry Generał z 5 kostkami.

Wymagania:

  • 5 kostek - wszystkie rzucają się jednocześnie
  • Możliwość "trzymania" wybranych kostek (nie rzucają się ponownie)
  • 3 rzuty na turę
  • Punktacja:
    • Para (dwie takie same) = suma tych kostek
    • Dwie pary = suma wszystkich
    • Trójka = suma wszystkich
    • Kareta = suma wszystkich
    • Full (para + trójka) = 25 punktów
    • Mały strit (4 kolejne) = 30 punktów
    • Duży strit (5 kolejnych) = 40 punktów
    • Generał (5 takich samych) = 50 punktów
  • Tabela wyników z możliwością wyboru kategorii
  • Suma końcowa po wypełnieniu wszystkich kategorii

Wskazówka: To zaawansowane zadanie - podziel na mniejsze części!

Zadanie 6: Gra "Świnka" (Pig Game) z AI

TRUDNY

Cel: Gra przeciwko komputerowi z prostą sztuczną inteligencją.

Wymagania:

  • Tryb gry: Gracz vs Komputer
  • AI podejmuje decyzje:
    • Rzuca kostką, jeśli punkty w turze < 20
    • Zatrzymuje się, jeśli punkty w turze ≥ 20
    • Gra agresywniej, jeśli jest w tyle
  • Opóźnienia dla ruchów AI (żeby było realistyczne)
  • Animacja "myślenia" AI
  • 3 poziomy trudności: Łatwy, Średni, Trudny
  • Statystyki zwycięstw gracza vs AI
  • Możliwość włączenia trybu dla 2 graczy

Wskazówka: Użyj setTimeout() dla opóźnień ruchów AI.

💡 WSKAZÓWKI I NAJLEPSZE PRAKTYKI

1. Organizacja kodu

Dla bardziej złożonych gier, użyj obiektów do organizacji kodu:

const game = { players: [ { name: 'Gracz 1', score: 0, currentScore: 0 }, { name: 'Gracz 2', score: 0, currentScore: 0 } ], activePlayer: 0, playing: true, rollDice() { return Math.floor(Math.random() * 6) + 1; }, switchPlayer() { this.activePlayer = this.activePlayer === 0 ? 1 : 0; }, reset() { this.players.forEach(player => { player.score = 0; player.currentScore = 0; }); this.activePlayer = 0; this.playing = true; } };

2. Debugowanie gier losowych

Podczas testowania, możesz tymczasowo zastąpić losowanie stałymi wartościami:

// Tryb debugowania const DEBUG = true; function rollDice() { if (DEBUG) { return 6; // Zawsze zwraca 6 do testów } return Math.floor(Math.random() * 6) + 1; }

3. Walidacja stanów gry

Zawsze sprawdzaj, czy gra jest w trakcie przed wykonaniem akcji:

rollBtn.addEventListener('click', function() { if (!playing) { alert('Gra się skończyła! Kliknij "Nowa gra"'); return; } // ... reszta kodu });

4. LocalStorage dla zapisywania gier

// Zapisz stan gry function saveGame() { const gameState = { scores: scores, activePlayer: activePlayer, currentScore: currentScore }; localStorage.setItem('diceGame', JSON.stringify(gameState)); } // Wczytaj stan gry function loadGame() { const saved = localStorage.getItem('diceGame'); if (saved) { const gameState = JSON.parse(saved); scores = gameState.scores; activePlayer = gameState.activePlayer; currentScore = gameState.currentScore; } }

Częste błędy do uniknięcia:

  • ❌ Zapominanie o użyciu Math.floor() - bez tego dostaniesz liczby zmiennoprzecinkowe
  • ❌ Nieprawidłowy zakres: Math.random() * 6 daje 0-5, musisz dodać +1
  • ❌ Brak walidacji stanu gry - przyciski działają nawet po zakończeniu
  • ❌ Niezerowanie zmiennych przy nowej grze
  • ❌ Brak zabezpieczenia przed szybkim klikaniem podczas animacji

🎨 Część 5: Ulepszenia wizualne

Style CSS dla lepszego wyglądu gry

/* Kostka 3D */ .dice-3d { width: 100px; height: 100px; background: linear-gradient(145deg, #fff, #e6e6e6); border-radius: 20px; box-shadow: 0 10px 30px rgba(0,0,0,0.3), inset 0 -2px 5px rgba(0,0,0,0.1); transition: transform 0.3s; } .dice-3d:hover { transform: scale(1.05); } /* Aktywny gracz */ .player { padding: 30px; border-radius: 15px; transition: all 0.3s; opacity: 0.5; } .player.active { opacity: 1; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; box-shadow: 0 10px 30px rgba(102, 126, 234, 0.5); } /* Animacja wygranej */ @keyframes winner { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.1); } } .winner { animation: winner 0.5s ease-in-out 3; background: gold !important; } /* Przyciski */ .btn { padding: 15px 30px; font-size: 18px; border: none; border-radius: 10px; cursor: pointer; transition: all 0.3s; box-shadow: 0 5px 15px rgba(0,0,0,0.2); } .btn:hover { transform: translateY(-2px); box-shadow: 0 7px 20px rgba(0,0,0,0.3); } .btn:active { transform: translateY(0); } .btn-primary { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; } .btn-success { background: linear-gradient(135deg, #56ab2f 0%, #a8e063 100%); color: white; }

🎮 Przykładowe rozszerzenia gier

Pomysły na dodatkowe funkcje:

  • Tryb turnieju: Najlepszy z 3/5 gier
  • Tabela wyników: Top 10 najlepszych wyników
  • Efekty dźwiękowe: Dźwięki rzutu, wygranej, przegranej
  • Motywy: Jasny/ciemny tryb, różne kolory kostek
  • Osiągnięcia: Odznaki za specjalne wyczyny
  • Multiplayer online: Gra przez internet (zaawansowane)
  • Różne warianty kostek: 4, 8, 10, 12, 20 ścian
  • Power-upy: Specjalne karty zmieniające zasady
  • Tryb story: Kampania z misjami

🔧 Przykład kodu pomocniczego

Funkcje użytkowe (utility functions)

// Rzut wieloma kostkami function rollMultipleDice(count) { const results = []; for (let i = 0; i < count; i++) { results.push(Math.floor(Math.random() * 6) + 1); } return results; } // Suma kostek function sumDice(diceArray) { return diceArray.reduce((sum, dice) => sum + dice, 0); } // Sprawdź czy są pary function hasPair(diceArray) { const counts = {}; diceArray.forEach(dice => { counts[dice] = (counts[dice] || 0) + 1; }); return Object.values(counts).some(count => count >= 2); } // Sprawdź strit (kolejne liczby) function isStraight(diceArray) { const sorted = [...diceArray].sort((a, b) => a - b); for (let i = 0; i < sorted.length - 1; i++) { if (sorted[i + 1] - sorted[i] !== 1) { return false; } } return true; } // Formatuj czas (dla czasomierza) function formatTime(seconds) { const mins = Math.floor(seconds / 60); const secs = seconds % 60; return `${mins}:${secs.toString().padStart(2, '0')}`; } // Odtwórz dźwięk function playSound(soundFile) { const audio = new Audio(soundFile); audio.play().catch(err => console.log('Nie można odtworzyć dźwięku')); } // Zapisz wynik do localStorage function saveHighScore(score) { const highScore = localStorage.getItem('highScore') || 0; if (score > highScore) { localStorage.setItem('highScore', score); return true; // Nowy rekord! } return false; }

🎓 Test Wiedzy

Pytania kontrolne - sprawdź swoją wiedzę!

  1. Jaki zakres zwraca Math.random()? (Odp: 0 do 1, wyłącznie 1)
  2. Jak uzyskać losową liczbę od 1 do 6? (Odp: Math.floor(Math.random() * 6) + 1)
  3. Co robi Math.floor()? (Odp: Zaokrągla w dół do najbliższej liczby całkowitej)
  4. Jak zmienić zawartość elementu HTML? (Odp: element.textContent lub element.innerHTML)
  5. Jak dodać nasłuchiwacz zdarzenia kliknięcia? (Odp: element.addEventListener('click', function))
  6. Jak zapisać dane w localStorage? (Odp: localStorage.setItem('key', 'value'))
  7. Czym różni się textContent od innerHTML? (Odp: textContent to tylko tekst, innerHTML może zawierać HTML)
  8. Co to jest callback function? (Odp: Funkcja przekazana jako argument do innej funkcji)

🎉 PODSUMOWANIE

Gratulacje! Po ukończeniu tego ćwiczenia powinieneś umieć:

  • ✅ Generować liczby losowe w JavaScript
  • ✅ Manipulować elementami DOM
  • ✅ Obsługiwać zdarzenia (kliknięcia, naciśnięcia klawiszy)
  • ✅ Zarządzać stanem gry
  • ✅ Tworzyć logikę gier
  • ✅ Dodawać animacje i efekty wizualne
  • ✅ Przechowywać dane w localStorage
  • ✅ Debugować kod JavaScript

Następny krok: Spróbuj stworzyć własną grę! Połącz wiedzę z tego ćwiczenia z kreatywnością i stwórz coś unikalnego.

Powodzenia w tworzeniu gier! 🎮🚀

💼 Dlaczego warto umieć tworzyć gry?

  • Gry uczą logicznego myślenia i rozwiązywania problemów
  • Są świetnym sposobem na praktykę programowania
  • Pokazują umiejętności w portfolio
  • Rozwijają kreatywność i wyobraźnię
  • Uczą zarządzania stanem i strukturami danych
  • Są fajne i motywujące do nauki! 😊

🏆 Wyzwanie finalne

Stwórz swoją własną unikalną grę w kości!

Pomysły na inspirację:

  • Gra RPG z walkami na kostki
  • Symulator kasyna z różnymi grami
  • Gra planszowa online
  • Gra edukacyjna do nauki matematyki
  • Gra przygodowa z elementami losowymi

Wykorzystaj wszystko, czego się nauczyłeś i dodaj własne pomysły. Powodzenia! 🌟

przykład działającego rozwiązania