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
localStorageto 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:
- Player places a bet
- Clicks Spin
- Credits decrease
- Reels animate and stop randomly
- Game checks results
- Winnings (if any) are added
- Jackpot grows each spin
- 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