Build a JavaScript Slot Machine Game with localStorage and a Jackpot Counter

Building small browser games is one of the fastest ways to improve your JavaScript skills—and it’s actually fun.

https://github.com/lsvekis/JavaScript-Jackpot

https://lsvekis.github.io/JavaScript-Jackpot

In this tutorial, you’ll learn how to create a fully functional slot machine game using:

  • HTML for structure
  • CSS for styling and animations
  • JavaScript for logic and interactivity
  • localStorage to persist game data
  • A jackpot system to make gameplay more engaging

By the end, you’ll have a complete mini-game you can expand into a portfolio project, course example, or teaching asset (perfect for your JS DOM exercises and Vibe Learning content).


🚀 What You’ll Build

This slot machine includes:

  • 🎰 3 animated reels
  • 💰 Credit system
  • 🎯 Adjustable betting
  • 🏆 Jackpot that grows over time
  • 💾 Auto-save using localStorage
  • 📊 Game stats (total spins, total winnings)

🧠 Key Concepts Covered

This project reinforces real-world JavaScript skills:

  • DOM manipulation
  • Event handling
  • Async animations (async/await)
  • Randomization
  • State management
  • Browser storage (localStorage)

🧩 Game Logic Overview

Here’s how the game works:

  1. Player places a bet
  2. Clicks Spin
  3. Credits decrease
  4. Reels animate and stop randomly
  5. Game checks results
  6. Winnings (if any) are added
  7. Jackpot grows each spin
  8. Game state is saved automatically

🎯 Payout Rules

ResultPayout⭐ ⭐ ⭐10x bet7 7 78x bet + Jackpot🍒 🍒 🍒6x betAny 3 same5x betAny 2 same2x bet


💾 Why localStorage Matters

Without localStorage, your game resets every refresh.

With it, you can persist:

  • Credits
  • Jackpot
  • Bet amount
  • Stats

This creates a real game experience instead of a demo.


🧱 Full Code (Single File Version)

Save this as:

index.html

Then open it in your browser.


🔹 HTML + CSS + JavaScript (All-in-One)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>JavaScript Slot Machine with Jackpot</title>
  <style>
    * {
      box-sizing: border-box;
    }

    body {
      margin: 0;
      font-family: Arial, sans-serif;
      background: linear-gradient(135deg, #111827, #1f2937);
      color: #fff;
      display: flex;
      align-items: center;
      justify-content: center;
      min-height: 100vh;
      padding: 20px;
    }

    .game-container {
      width: 100%;
      max-width: 760px;
      background: rgba(255, 255, 255, 0.08);
      border: 2px solid rgba(255, 255, 255, 0.1);
      border-radius: 20px;
      padding: 30px;
      box-shadow: 0 20px 50px rgba(0, 0, 0, 0.4);
      text-align: center;
      backdrop-filter: blur(10px);
    }

    h1 {
      margin-top: 0;
      font-size: 2.2rem;
      letter-spacing: 2px;
    }

    .stats {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
      gap: 15px;
      margin: 20px 0 30px;
    }

    .stat-box {
      background: rgba(255, 255, 255, 0.08);
      padding: 15px;
      border-radius: 12px;
    }

    .stat-label {
      font-size: 0.9rem;
      opacity: 0.8;
      margin-bottom: 6px;
    }

    .stat-value {
      font-size: 1.4rem;
      font-weight: bold;
      word-break: break-word;
    }

    .jackpot-box {
      border: 2px solid rgba(245, 158, 11, 0.5);
      box-shadow: 0 0 20px rgba(245, 158, 11, 0.15);
    }

    .slot-machine {
      display: flex;
      justify-content: center;
      gap: 15px;
      margin: 30px 0;
      flex-wrap: wrap;
    }

    .reel {
      width: 120px;
      height: 120px;
      background: #fff;
      color: #111827;
      border-radius: 16px;
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 3rem;
      font-weight: bold;
      box-shadow: inset 0 0 0 4px #d1d5db, 0 10px 20px rgba(0, 0, 0, 0.25);
      position: relative;
      overflow: hidden;
    }

    .reel.spinning {
      animation: pulse 0.15s infinite alternate;
    }

    @keyframes pulse {
      from {
        transform: translateY(0);
        filter: brightness(1);
      }
      to {
        transform: translateY(-3px);
        filter: brightness(1.08);
      }
    }

    .controls {
      display: flex;
      flex-wrap: wrap;
      justify-content: center;
      gap: 12px;
      margin-top: 20px;
    }

    button {
      border: none;
      border-radius: 12px;
      padding: 14px 20px;
      font-size: 1rem;
      font-weight: bold;
      cursor: pointer;
      transition: transform 0.2s, opacity 0.2s;
    }

    button:hover:not(:disabled) {
      transform: translateY(-2px);
    }

    button:disabled {
      cursor: not-allowed;
      opacity: 0.6;
    }

    .bet-btn {
      background: #374151;
      color: white;
    }

    .spin-btn {
      background: #f59e0b;
      color: #111827;
      min-width: 140px;
    }

    .reset-btn {
      background: #10b981;
      color: white;
    }

    .clear-btn {
      background: #ef4444;
      color: white;
    }

    .message {
      margin-top: 20px;
      min-height: 30px;
      font-size: 1.1rem;
      font-weight: bold;
      color: #fbbf24;
    }

    .payouts {
      margin-top: 30px;
      text-align: left;
      background: rgba(255, 255, 255, 0.06);
      border-radius: 14px;
      padding: 18px;
    }

    .payouts h2 {
      margin-top: 0;
      font-size: 1.1rem;
    }

    .payouts ul {
      margin: 0;
      padding-left: 20px;
      line-height: 1.8;
    }

    @media (max-width: 600px) {
      .reel {
        width: 90px;
        height: 90px;
        font-size: 2.2rem;
      }

      h1 {
        font-size: 1.8rem;
      }
    }
  </style>
</head>
<body>
  <div class="game-container">
    <h1>🎰 Slot Machine</h1>

    <div class="stats">
      <div class="stat-box">
        <div class="stat-label">Credits</div>
        <div class="stat-value" id="credits">100</div>
      </div>
      <div class="stat-box">
        <div class="stat-label">Bet</div>
        <div class="stat-value" id="bet">10</div>
      </div>
      <div class="stat-box">
        <div class="stat-label">Last Win</div>
        <div class="stat-value" id="lastWin">0</div>
      </div>
      <div class="stat-box jackpot-box">
        <div class="stat-label">Jackpot</div>
        <div class="stat-value" id="jackpot">500</div>
      </div>
      <div class="stat-box">
        <div class="stat-label">Total Spins</div>
        <div class="stat-value" id="totalSpins">0</div>
      </div>
      <div class="stat-box">
        <div class="stat-label">Total Won</div>
        <div class="stat-value" id="totalWon">0</div>
      </div>
    </div>

    <div class="slot-machine">
      <div class="reel" id="reel1">🍒</div>
      <div class="reel" id="reel2">🍋</div>
      <div class="reel" id="reel3">🍊</div>
    </div>

    <div class="controls">
      <button class="bet-btn" id="decreaseBet">- Bet</button>
      <button class="spin-btn" id="spinBtn">SPIN</button>
      <button class="bet-btn" id="increaseBet">+ Bet</button>
      <button class="reset-btn" id="resetBtn">Reset Game</button>
      <button class="clear-btn" id="clearSaveBtn">Clear Save</button>
    </div>

    <div class="message" id="message">Place your bet and spin!</div>

    <div class="payouts">
      <h2>Payout Rules</h2>
      <ul>
        <li>3 x ⭐ = 10x bet</li>
        <li>3 x 7 = 8x bet + Jackpot</li>
        <li>3 x 🍒 = 6x bet</li>
        <li>3 x same symbol = 5x bet</li>
        <li>2 x same symbol = 2x bet</li>
        <li>Each spin adds to the jackpot</li>
      </ul>
    </div>
  </div>

  <script>
    const STORAGE_KEY = "slotMachineGameData_v2";

    const symbols = ["🍒", "🍋", "🍊", "🍇", "⭐", "7"];

    const defaultGameState = {
      credits: 100,
      bet: 10,
      lastWin: 0,
      jackpot: 500,
      totalSpins: 0,
      totalWon: 0
    };

    let gameState = loadGameState();
    let isSpinning = false;

    const reelElements = [
      document.getElementById("reel1"),
      document.getElementById("reel2"),
      document.getElementById("reel3")
    ];

    const creditsEl = document.getElementById("credits");
    const betEl = document.getElementById("bet");
    const lastWinEl = document.getElementById("lastWin");
    const jackpotEl = document.getElementById("jackpot");
    const totalSpinsEl = document.getElementById("totalSpins");
    const totalWonEl = document.getElementById("totalWon");
    const messageEl = document.getElementById("message");

    const spinBtn = document.getElementById("spinBtn");
    const increaseBetBtn = document.getElementById("increaseBet");
    const decreaseBetBtn = document.getElementById("decreaseBet");
    const resetBtn = document.getElementById("resetBtn");
    const clearSaveBtn = document.getElementById("clearSaveBtn");

    function loadGameState() {
      const savedData = localStorage.getItem(STORAGE_KEY);

      if (!savedData) {
        return { ...defaultGameState };
      }

      try {
        const parsed = JSON.parse(savedData);
        return { ...defaultGameState, ...parsed };
      } catch (error) {
        console.error("Could not load saved game data:", error);
        return { ...defaultGameState };
      }
    }

    function saveGameState() {
      localStorage.setItem(STORAGE_KEY, JSON.stringify(gameState));
    }

    function updateDisplay() {
      creditsEl.textContent = gameState.credits;
      betEl.textContent = gameState.bet;
      lastWinEl.textContent = gameState.lastWin;
      jackpotEl.textContent = gameState.jackpot;
      totalSpinsEl.textContent = gameState.totalSpins;
      totalWonEl.textContent = gameState.totalWon;

      spinBtn.disabled = gameState.credits < gameState.bet || isSpinning;
    }

    function randomSymbol() {
      return symbols[Math.floor(Math.random() * symbols.length)];
    }

    function setMessage(text) {
      messageEl.textContent = text;
    }

    function calculateWinnings(results, currentBet) {
      const [a, b, c] = results;
      let payout = 0;
      let jackpotWin = false;

      if (a === "⭐" && b === "⭐" && c === "⭐") {
        payout = currentBet * 10;
      } else if (a === "7" && b === "7" && c === "7") {
        payout = currentBet * 8;
        jackpotWin = true;
      } else if (a === "🍒" && b === "🍒" && c === "🍒") {
        payout = currentBet * 6;
      } else if (a === b && b === c) {
        payout = currentBet * 5;
      } else if (a === b || b === c || a === c) {
        payout = currentBet * 2;
      }

      return { payout, jackpotWin };
    }

    function animateReel(reel, duration) {
      return new Promise((resolve) => {
        reel.classList.add("spinning");

        const interval = setInterval(() => {
          reel.textContent = randomSymbol();
        }, 100);

        setTimeout(() => {
          clearInterval(interval);
          reel.classList.remove("spinning");
          const finalSymbol = randomSymbol();
          reel.textContent = finalSymbol;
          resolve(finalSymbol);
        }, duration);
      });
    }

    async function spin() {
      if (isSpinning) return;

      if (gameState.credits < gameState.bet) {
        setMessage("Not enough credits. Reset the game or clear the save.");
        return;
      }

      isSpinning = true;
      toggleControls(true);

      gameState.credits -= gameState.bet;
      gameState.lastWin = 0;
      gameState.totalSpins += 1;

      const jackpotIncrease = Math.max(5, Math.floor(gameState.bet * 0.25));
      gameState.jackpot += jackpotIncrease;

      updateDisplay();
      saveGameState();
      setMessage("Spinning...");

      const results = [];
      results.push(await animateReel(reelElements[0], 900));
      results.push(await animateReel(reelElements[1], 1400));
      results.push(await animateReel(reelElements[2], 1900));

      const outcome = calculateWinnings(results, gameState.bet);
      let totalPrize = outcome.payout;

      if (outcome.jackpotWin) {
        totalPrize += gameState.jackpot;
      }

      gameState.lastWin = totalPrize;
      gameState.credits += totalPrize;
      gameState.totalWon += totalPrize;

      if (outcome.jackpotWin) {
        const wonJackpotAmount = gameState.jackpot;
        gameState.jackpot = 500;
        setMessage(`JACKPOT! You won ${totalPrize} credits including ${wonJackpotAmount} jackpot credits!`);
      } else if (totalPrize > 0) {
        setMessage(`You won ${totalPrize} credits!`);
      } else {
        setMessage("No win this time. Try again!");
      }

      if (gameState.credits <= 0) {
        setMessage("Game over! You are out of credits. Reset or clear your save.");
      }

      isSpinning = false;
      toggleControls(false);
      updateDisplay();
      saveGameState();
    }

    function increaseBet() {
      if (isSpinning) return;

      const nextBet = gameState.bet + 5;
      if (nextBet <= 100 && nextBet <= gameState.credits) {
        gameState.bet = nextBet;
        updateDisplay();
        saveGameState();
      }
    }

    function decreaseBet() {
      if (isSpinning) return;

      const nextBet = gameState.bet - 5;
      if (nextBet >= 5) {
        gameState.bet = nextBet;
        updateDisplay();
        saveGameState();
      }
    }

    function toggleControls(disabled) {
      spinBtn.disabled = disabled;
      increaseBetBtn.disabled = disabled;
      decreaseBetBtn.disabled = disabled;
      resetBtn.disabled = disabled;
      clearSaveBtn.disabled = disabled;
    }

    function resetGame() {
      gameState = { ...defaultGameState };
      reelElements[0].textContent = "🍒";
      reelElements[1].textContent = "🍋";
      reelElements[2].textContent = "🍊";
      updateDisplay();
      saveGameState();
      setMessage("Game reset. Good luck!");
    }

    function clearSave() {
      localStorage.removeItem(STORAGE_KEY);
      gameState = { ...defaultGameState };
      reelElements[0].textContent = "🍒";
      reelElements[1].textContent = "🍋";
      reelElements[2].textContent = "🍊";
      updateDisplay();
      setMessage("Saved game cleared.");
    }

    spinBtn.addEventListener("click", spin);
    increaseBetBtn.addEventListener("click", increaseBet);
    decreaseBetBtn.addEventListener("click", decreaseBet);
    resetBtn.addEventListener("click", resetGame);
    clearSaveBtn.addEventListener("click", clearSave);

    updateDisplay();
    setMessage("Your game data is saved automatically in localStorage.");
  </script>
</body>
</html>

🔍 Code Breakdown

Let’s walk through the most important parts so you (and your students) truly understand it.


1. Game State Object

let gameState = {
  credits: 100,
  bet: 10,
  jackpot: 500,
  totalSpins: 0,
  totalWon: 0
};

This acts as the single source of truth for your game.

👉 This aligns perfectly with your AI-assisted learning + state-based thinking model


2. Saving Data with localStorage

function saveGameState() {
  localStorage.setItem("slotGame", JSON.stringify(gameState));
}

Why this matters:

  • Converts object → string
  • Stores it in the browser
  • Survives refresh

3. Loading Saved Data

function loadGameState() {
  const data = localStorage.getItem("slotGame");
  return data ? JSON.parse(data) : defaultState;
}

👉 This restores player progress automatically


4. Reel Animation (Async Magic)

async function spin() {
  const results = [];
  results.push(await animateReel(reel1, 900));
  results.push(await animateReel(reel2, 1400));
  results.push(await animateReel(reel3, 1900));
}

Why this is powerful:

  • Each reel stops at a different time
  • Creates realistic slot machine behavior
  • Uses modern async JavaScript

5. Jackpot System

const jackpotIncrease = Math.max(5, Math.floor(gameState.bet * 0.25));
gameState.jackpot += jackpotIncrease;

Each spin grows the jackpot.

Then:

if (jackpotWin) {
  totalPrize += gameState.jackpot;
}

👉 This introduces reward anticipation psychology (great teaching moment)


6. Win Calculation

function calculateWinnings(results, bet) {

Handles:

  • 3 matches
  • 2 matches
  • jackpot condition

👉 This is a great example of conditional logic and pattern matching

🧠 Teaching Insight

Curiosity Trigger

“Can you build a casino game in JavaScript?”

Guided Build

Students follow structured steps

Active Practice

They modify:

  • symbols
  • payouts
  • animations

Reflection

Ask:

  • What controls randomness?
  • How would you balance the game?

💡 Expansion Ideas

You can turn this into:

Beginner Extensions

  • Add sound effects
  • Add win animations
  • Change symbols

Intermediate

  • Multiple paylines
  • Reel images instead of emoji
  • Win history log

Advanced

  • Probability tuning
  • RTP (Return to Player) calculation
  • Multiplayer leaderboard

🧾 Conclusion

Building a slot machine game is more than just a fun project—it’s a complete learning experience.

You combine:

  • logic
  • UI updates
  • persistence
  • user interaction

And end up with something interactive, visual, and engaging.

For your audience (especially your students and course learners), this type of project hits perfectly:

👉 fast to build 👉 easy to understand 👉 powerful to extend