GPT-5.6 Sol × slots
8.3Vegas slot machine — reels, pay table, betting, win animations
correctness 9.0quality 9.0documentation 7.096667ms
$ cat slots.prompt — what the model was asked
Build a fully playable Vegas-style slot machine in a single self-contained HTML file with no external libraries and no external assets.
## Reels
- 3 reels, each with these 7 symbols: 🍒 Cherry, 🍋 Lemon, 🍊 Orange, 🍇 Grape, 🔔 Bell, ⭐ Star, 7️⃣ Seven
- Spin animation: each reel spins independently (reel 1 stops ~800ms, reel 2 ~1200ms, reel 3 ~1600ms), showing symbols scrolling at ~12 symbols/sec before snapping to the result
- SPIN button disabled while spinning
## Pay table (multipliers × bet)
- 7️⃣ 7️⃣ 7️⃣ → 100×
- ⭐ ⭐ ⭐ → 50×
- 🔔 🔔 🔔 → 20×
- 🍇 🍇 🍇 → 15×
- 🍊 🍊 🍊 → 10×
- 🍋 🍋 🍋 → 5×
- 🍒 🍒 🍒 → 3×
- Any two 🍒 in first two positions → 2×
- All other → 0× (loss)
## Betting & credits
- Start with 100 credits
- Bet buttons: 1, 5, 10 credits (highlighted active; default: 1)
- Bet deducted immediately on spin; win adds bet × multiplier
- GAME OVER at 0 credits with 'Play Again' button that resets to 100
## Win feedback
- Win: flash matching symbols, show '+[amount] credits' in gold, display win name ('JACKPOT!', 'THREE BELLS', etc.)
- Loss: shake animation on reels
- Credit balance count-up animation over 400ms
## Pay table display
- Visible panel on page (not a modal) listing all combinations
- Highlight winning row when that combination hits
## Code quality
- Single HTML file; all JS in <script>, all CSS in <style>
- Game state: { credits, bet, spinning, reels, result }
- Named functions: initState(), spin(), checkWin(), animateReels(), updateUI()
- RNG: Math.random() only
- No inline onclick= handlers; use addEventListenerrun 1 of 1
8.3correctness 9.0quality 9.0documentation 7.096667ms
# judge: The game is complete and mechanically correct, including payout precedence, reel order, state handling, effects, and credit animation, but reel timings and animation intervals are embedded as literals rather than named constants and the code has little explicit documentation.
$ cat codex-cli/gpt-5.6-sol.slots.r1.response
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Lucky Sevens</title>
<style>
:root {
--gold: #ffd45c;
--deep-gold: #a86b00;
--red: #b80f22;
--dark-red: #510711;
--cream: #fff4c7;
--panel: #18070b;
}
* {
box-sizing: border-box;
}
body {
min-height: 100vh;
margin: 0;
display: grid;
place-items: center;
padding: 24px;
color: var(--cream);
font-family: Impact, Haettenschweiler, "Arial Narrow Bold", sans-serif;
background:
radial-gradient(circle at 50% 15%, #8d1522 0, #35070d 36%, #100205 72%),
#100205;
}
button {
font: inherit;
}
.casino {
width: min(940px, 100%);
border: 5px solid var(--gold);
border-radius: 28px;
padding: 22px;
background: linear-gradient(145deg, #860e1e, #37050c);
box-shadow:
0 0 0 8px #7b4200,
0 0 35px rgba(255, 196, 47, .5),
inset 0 0 30px rgba(0, 0, 0, .65);
}
h1 {
margin: 0;
text-align: center;
color: var(--gold);
font-size: clamp(2.5rem, 8vw, 5.4rem);
line-height: .9;
letter-spacing: .08em;
text-shadow: 0 4px #7a0a18, 0 0 18px #ffb000;
}
.tagline {
margin: 10px 0 20px;
text-align: center;
letter-spacing: .25em;
color: white;
}
.layout {
display: grid;
grid-template-columns: minmax(0, 1fr) 250px;
gap: 20px;
}
.machine,
.paytable {
border: 3px solid #d39118;
border-radius: 18px;
background: linear-gradient(#25070d, #100205);
box-shadow: inset 0 0 18px #000;
}
.machine {
padding: 18px;
}
.win-name {
height: 38px;
display: grid;
place-items: center;
color: var(--gold);
font-size: clamp(1.25rem, 4vw, 2rem);
letter-spacing: .08em;
text-shadow: 0 0 12px #ff9d00;
}
.reels-frame {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
padding: 13px;
border: 5px solid var(--gold);
border-radius: 18px;
background: #8a4e05;
box-shadow: inset 0 0 15px #2b1000;
}
.reel {
height: clamp(112px, 19vw, 170px);
overflow: hidden;
border: 4px solid #28040a;
border-radius: 12px;
background: linear-gradient(#d7d7d7, #fff 35%, #fff 65%, #c6c6c6);
box-shadow: inset 0 0 22px rgba(0, 0, 0, .4);
}
.strip {
height: 100%;
will-change: transform;
}
.symbol {
height: 100%;
display: grid;
place-items: center;
font-family: system-ui, sans-serif;
font-size: clamp(3.4rem, 10vw, 7rem);
line-height: 1;
user-select: none;
}
.symbol.winner {
animation: winnerFlash .22s ease-in-out 6 alternate;
}
.reels-frame.shake {
animation: shake .35s ease-in-out;
}
@keyframes winnerFlash {
from { filter: brightness(1); transform: scale(1); }
to {
filter: brightness(1.5) drop-shadow(0 0 15px #ffd600);
transform: scale(1.12);
background: rgba(255, 222, 0, .42);
}
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
20% { transform: translateX(-9px) rotate(-1deg); }
40% { transform: translateX(8px) rotate(1deg); }
60% { transform: translateX(-6px); }
80% { transform: translateX(4px); }
}
.status {
min-height: 39px;
margin: 13px 0 5px;
display: grid;
place-items: center;
color: #ddd;
font-family: Arial, sans-serif;
font-size: 1.15rem;
font-weight: 800;
}
.status.win {
color: var(--gold);
animation: pulse .5s ease-in-out 2 alternate;
text-shadow: 0 0 12px #ff9700;
}
@keyframes pulse {
to { transform: scale(1.12); }
}
.credits {
margin: 8px 0 16px;
text-align: center;
color: white;
font-size: clamp(1.5rem, 5vw, 2.4rem);
}
#creditValue {
display: inline-block;
min-width: 3ch;
color: var(--gold);
font-variant-numeric: tabular-nums;
}
.controls {
display: flex;
align-items: center;
justify-content: center;
gap: 16px;
flex-wrap: wrap;
}
.bets {
display: flex;
gap: 7px;
}
.bet-btn,
.spin-btn,
.again-btn {
border: 3px solid var(--gold);
color: white;
cursor: pointer;
box-shadow: 0 4px 0 #542600;
transition: transform .12s, filter .12s;
}
.bet-btn {
min-width: 48px;
padding: 8px 12px;
border-radius: 9px;
background: #5d0b15;
}
.bet-btn.active {
color: #2a0800;
background: var(--gold);
transform: translateY(2px);
box-shadow: 0 2px 0 #542600, 0 0 12px #ffc400;
}
button:not(:disabled):active {
transform: translateY(3px);
}
button:disabled {
cursor: not-allowed;
filter: grayscale(1) brightness(.55);
}
.spin-btn {
min-width: 155px;
padding: 13px 25px;
border-radius: 999px;
background: linear-gradient(#ff354d, #a90018);
font-size: 1.65rem;
letter-spacing: .08em;
}
.paytable {
padding: 13px;
}
.paytable h2 {
margin: 2px 0 10px;
color: var(--gold);
text-align: center;
letter-spacing: .1em;
}
.pay-row {
display: grid;
grid-template-columns: 1fr auto;
gap: 8px;
align-items: center;
margin: 5px 0;
padding: 8px;
border: 1px solid #72202a;
border-radius: 8px;
font-family: Arial, sans-serif;
background: rgba(255,255,255,.04);
}
.pay-row span:first-child {
font-size: 1.13rem;
white-space: nowrap;
}
.pay-row strong {
color: var(--gold);
}
.pay-row.hit {
color: #270500;
background: var(--gold);
animation: rowGlow .4s ease-in-out 4 alternate;
}
.pay-row.hit strong {
color: #7b0012;
}
@keyframes rowGlow {
to { box-shadow: 0 0 18px var(--gold); }
}
.game-over {
position: fixed;
inset: 0;
z-index: 10;
display: grid;
place-items: center;
padding: 20px;
background: rgba(8, 0, 2, .88);
}
.game-over[hidden] {
display: none;
}
.game-over-card {
padding: 38px;
border: 5px solid var(--gold);
border-radius: 22px;
text-align: center;
background: #650b18;
box-shadow: 0 0 35px #ffae00;
}
.game-over h2 {
margin: 0 0 22px;
color: var(--gold);
font-size: clamp(3rem, 12vw, 6rem);
}
.again-btn {
padding: 13px 24px;
border-radius: 10px;
background: #198a36;
font-size: 1.35rem;
}
@media (max-width: 720px) {
body { padding: 10px; }
.casino { padding: 13px; }
.layout { grid-template-columns: 1fr; }
.paytable { order: 2; }
.pay-row { margin: 3px 0; padding: 6px 8px; }
}
</style>
</head>
<body>
<main class="casino">
<h1>LUCKY 7s</h1>
<p class="tagline">VEGAS SLOTS</p>
<div class="layout">
<section class="machine" aria-label="Slot machine">
<div class="win-name" id="winName" aria-live="polite"></div>
<div class="reels-frame" id="reelsFrame">
<div class="reel"><div class="strip" id="reel0"></div></div>
<div class="reel"><div class="strip" id="reel1"></div></div>
<div class="reel"><div class="strip" id="reel2"></div></div>
</div>
<div class="status" id="status" aria-live="polite">PLACE YOUR BET</div>
<div class="credits">CREDITS: <span id="creditValue">100</span></div>
<div class="controls">
<div class="bets" aria-label="Choose bet">
<button class="bet-btn active" data-bet="1" type="button">BET 1</button>
<button class="bet-btn" data-bet="5" type="button">BET 5</button>
<button class="bet-btn" data-bet="10" type="button">BET 10</button>
</div>
<button class="spin-btn" id="spinButton" type="button">SPIN</button>
</div>
</section>
<aside class="paytable" aria-label="Pay table">
<h2>PAY TABLE</h2>
<div class="pay-row" data-win="seven"><span>7️⃣ 7️⃣ 7️⃣</span><strong>100×</strong></div>
<div class="pay-row" data-win="star"><span>⭐ ⭐ ⭐</span><strong>50×</strong></div>
<div class="pay-row" data-win="bell"><span>🔔 🔔 🔔</span><strong>20×</strong></div>
<div class="pay-row" data-win="grape"><span>🍇 🍇 🍇</span><strong>15×</strong></div>
<div class="pay-row" data-win="orange"><span>🍊 🍊 🍊</span><strong>10×</strong></div>
<div class="pay-row" data-win="lemon"><span>🍋 🍋 🍋</span><strong>5×</strong></div>
<div class="pay-row" data-win="cherry"><span>🍒 🍒 🍒</span><strong>3×</strong></div>
<div class="pay-row" data-win="two-cherries"><span>🍒 🍒 ANY</span><strong>2×</strong></div>
</aside>
</div>
</main>
<div class="game-over" id="gameOver" hidden>
<div class="game-over-card">
<h2>GAME OVER</h2>
<button class="again-btn" id="playAgain" type="button">PLAY AGAIN</button>
</div>
</div>
<script>
const symbols = ["🍒", "🍋", "🍊", "🍇", "🔔", "⭐", "7️⃣"];
const wins = {
"7️⃣": { multiplier: 100, name: "JACKPOT!", key: "seven" },
"⭐": { multiplier: 50, name: "THREE STARS", key: "star" },
"🔔": { multiplier: 20, name: "THREE BELLS", key: "bell" },
"🍇": { multiplier: 15, name: "THREE GRAPES", key: "grape" },
"🍊": { multiplier: 10, name: "THREE ORANGES", key: "orange" },
"🍋": { multiplier: 5, name: "THREE LEMONS", key: "lemon" },
"🍒": { multiplier: 3, name: "THREE CHERRIES", key: "cherry" }
};
let state;
let displayedCredits = 100;
const reelElements = [...document.querySelectorAll(".strip")];
const betButtons = [...document.querySelectorAll(".bet-btn")];
const spinButton = document.getElementById("spinButton");
const creditValue = document.getElementById("creditValue");
const statusElement = document.getElementById("status");
const winName = document.getElementById("winName");
const reelsFrame = document.getElementById("reelsFrame");
const gameOver = document.getElementById("gameOver");
function initState() {
state = {
credits: 100,
bet: 1,
spinning: false,
reels: ["🍒", "🍋", "7️⃣"],
result: null
};
displayedCredits = 100;
gameOver.hidden = true;
statusElement.textContent = "PLACE YOUR BET";
statusElement.className = "status";
winName.textContent = "";
clearEffects();
renderStaticReels();
updateUI();
}
function randomSymbol() {
return symbols[Math.floor(Math.random() * symbols.length)];
}
function renderStaticReels() {
state.reels.forEach((symbol, index) => {
reelElements[index].style.transition = "none";
reelElements[index].style.transform = "translateY(0)";
reelElements[index].innerHTML =
'<div class="symbol">' + symbol + "</div>";
});
}
function checkWin(result) {
if (result[0] === result[1] && result[1] === result[2]) {
return wins[result[0]];
}
if (result[0] === "🍒" && result[1] === "🍒") {
return {
multiplier: 2,
name: "DOUBLE CHERRIES",
key: "two-cherries"
};
}
return null;
}
function animateReels(result) {
const promises = reelElements.map((strip, index) => {
const duration = [800, 1200, 1600][index] +
Math.floor(Math.random() * 161) - 80;
const steps = Math.max(8, Math.round(duration / 83));
const sequence = [state.reels[index]];
for (let i = 1; i < steps; i++) {
sequence.push(randomSymbol());
}
sequence.push(result[index]);
strip.style.transition = "none";
strip.style.transform = "translateY(0)";
strip.innerHTML = sequence.map(symbol =>
'<div class="symbol">' + symbol + "</div>"
).join("");
return new Promise(resolve => {
requestAnimationFrame(() => {
requestAnimationFrame(() => {
strip.style.transition =
"transform " + duration + "ms linear";
strip.style.transform =
"translateY(-" + ((sequence.length - 1) * 100) + "%)";
});
});
setTimeout(resolve, duration);
});
});
return Promise.all(promises);
}
function animateCredits(from, to) {
displayedCredits = from;
const start = performance.now();
const duration = 400;
function frame(now) {
const progress = Math.min((now - start) / duration, 1);
const eased = 1 - Math.pow(1 - progress, 3);
displayedCredits = Math.round(from + (to - from) * eased);
creditValue.textContent = displayedCredits;
if (progress < 1) {
requestAnimationFrame(frame);
} else {
displayedCredits = to;
creditValue.textContent = to;
}
}
requestAnimationFrame(frame);
}
function clearEffects() {
reelsFrame.classList.remove("shake");
document.querySelectorAll(".winner").forEach(element =>
element.classList.remove("winner")
);
document.querySelectorAll(".pay-row.hit").forEach(row =>
row.classList.remove("hit")
);
}
function showWin(win, amount) {
winName.textContent = win.name;
statusElement.textContent = "+" + amount + " CREDITS";
statusElement.className = "status win";
const winningCount = win.key === "two-cherries" ? 2 : 3;
reelElements.forEach((strip, index) => {
const visibleSymbol = strip.lastElementChild;
if (index < winningCount && visibleSymbol) {
visibleSymbol.classList.add("winner");
}
});
const row = document.querySelector(
'.pay-row[data-win="' + win.key + '"]'
);
if (row) row.classList.add("hit");
}
function showLoss() {
winName.textContent = "";
statusElement.textContent = "NO WIN — TRY AGAIN";
statusElement.className = "status";
void reelsFrame.offsetWidth;
reelsFrame.classList.add("shake");
}
async function spin() {
if (state.spinning || state.credits < state.bet) return;
clearEffects();
state.spinning = true;
state.result = null;
winName.textContent = "";
statusElement.textContent = "GOOD LUCK!";
statusElement.className = "status";
state.credits -= state.bet;
displayedCredits = state.credits;
creditValue.textContent = state.credits;
updateUI();
const result = [randomSymbol(), randomSymbol(), randomSymbol()];
await animateReels(result);
state.reels = result;
state.result = checkWin(result);
if (state.result) {
const beforePrize = state.credits;
const prize = state.bet * state.result.multiplier;
state.credits += prize;
showWin(state.result, prize);
animateCredits(beforePrize, state.credits);
} else {
showLoss();
}
state.spinning = false;
updateUI();
if (state.credits === 0) {
setTimeout(() => {
gameOver.hidden = false;
}, 650);
}
}
function updateUI() {
betButtons.forEach(button => {
const value = Number(button.dataset.bet);
button.classList.toggle("active", value === state.bet);
button.disabled = state.spinning || value > state.credits;
});
spinButton.disabled =
state.spinning || state.credits < state.bet || state.credits === 0;
spinButton.textContent = state.spinning ? "SPINNING…" : "SPIN";
if (!state.spinning && displayedCredits === state.credits) {
creditValue.textContent = state.credits;
}
}
betButtons.forEach(button => {
button.addEventListener("click", () => {
const selectedBet = Number(button.dataset.bet);
if (!state.spinning && selectedBet <= state.credits) {
state.bet = selectedBet;
updateUI();
}
});
});
spinButton.addEventListener("click", spin);
document.getElementById("playAgain").addEventListener("click", initState);
initState();
</script>
</body>
</html>