Coin flipping with CSS Animation
A Pen by Gurjeet Singh on CodePen.
Coin flipping with CSS Animation
A Pen by Gurjeet Singh on CodePen.
<div class='coin-flip'> | |
<!-- The tail starts out hidden; we don't want to wait until CSS rule evaluation to hide this image --> | |
<div id='coin-tails' style='display:none'> | |
<img src='https://upload.wikimedia.org/wikipedia/commons/thumb/d/da/US_One_Cent_Rev.png/240px-US_One_Cent_Rev.png'> | |
</div> | |
<div id='coin-heads'> | |
<img src='https://upload.wikimedia.org/wikipedia/commons/thumb/2/2e/US_One_Cent_Obv.png/240px-US_One_Cent_Obv.png'> | |
</div> | |
</div> | |
<div id='controls'> | |
Balance <div id='div-balance'></div> | |
<div id='buttons'> | |
<button id='btn-heads' onclick=flip('heads')>Heads</button> | |
<button id='btn-tails' onclick=flip('tails')>Tails</button> | |
</div> | |
<div id='div-result'></div> | |
</div> | |
<div id='div-debuginfo'></div> |
/* | |
DONE: | |
1. Build basic UI for a coin-flipping game | |
1. Buy honestcasino.com | |
========================================================== | |
TODO: | |
1. Customer feedback mechanism | |
1. Announce on forums (Reddit/poker, Show HN, others) | |
1. NoSleep for phone browsers | |
0. Keyboard shortcuts (h,H=>Heads, t,T=> Tails) | |
2. Flipping Sound | |
3. Winning sound | |
*/ | |
var result = 'heads'; | |
var chosen = result; | |
var headClass = 'coin-heads'; | |
var tailClass = 'coin-tails'; | |
var balance; | |
var coinState; | |
const betUnit = 25; | |
const DGEBI = document.getElementById.bind(document); | |
var imgHead = DGEBI(headClass); | |
var imgTail = DGEBI(tailClass); | |
var btnHeads = DGEBI('btn-heads'); | |
var btnTails = DGEBI('btn-tails'); | |
var divResult = DGEBI('div-result'); | |
var divBalance = DGEBI('div-balance'); | |
var divDebugInfo = DGEBI('div-debuginfo'); | |
/* Init game state; this function is intentionally unnamed, executed using IIFE */ | |
(function(){ | |
balance = 2500; | |
coinState = 'stopped'; | |
divBalance.innerHTML = balance; | |
})(); | |
function serverGetRandomByte(callback) { | |
var val = randomIntInc(0,255); // mimic NodeJS' `crypto.randomBytes(1)` | |
setTimeout(function() {callback(val)}, 1000 * randomIntInc(1,3)) | |
} | |
// Returns a random number in range [low,high]; that is, both ends of the range are inclusive | |
function randomIntInc (low, high) { | |
return Math.floor(Math.random() * (high - low + 1) + low); | |
} | |
function flip(p) { | |
chosen = p | |
startSpinning(); | |
} | |
/*function toggleSpinning(){if(coinState==='spinning')stopSpinning();}else{startSpinning();}}*/ | |
document.onkeypress = function(e){detectKeyPress(e)}; | |
function detectKeyPress(e) { | |
const key = e.key; | |
if (key === 'h' || key === 'H') { | |
flip('heads') | |
} else if (key === 't' || key === 'T') { | |
flip('tails') | |
} else if (key === 'd') { | |
divDebugInfo.style.display = 'none'; | |
} else if (key === 'D') { | |
divDebugInfo.style.display = 'block'; | |
} | |
} | |
function updateResult() { | |
divResult.innerHTML = result; | |
if (debug) { | |
divDebugInfo.innerHTML = debugText; | |
} else { | |
divDebugInfo.innerHTML = ''; | |
} | |
} | |
function startSpinning() { | |
if (coinState === 'spinning') | |
return; | |
coinState = 'spinning'; | |
balance = balance - betUnit; | |
result = '?' | |
updateResult(); | |
divBalance.innerHTML = balance; | |
btnHeads.disabled = btnTails.disabled = true; | |
imgHead.classList.add(headClass); | |
imgTail.classList.add(tailClass); | |
imgHead.style.display = imgTail.style.display = 'block'; | |
serverGetRandomByte(stopSpinning); | |
} | |
function stopSpinning(val) { | |
if (coinState === 'stopped') | |
return; | |
coinState = 'stopped'; | |
btnHeads.disabled = btnTails.disabled = false; | |
imgHead.classList.remove(headClass); | |
imgTail.classList.remove(tailClass); | |
if (val % 51 === 0) { | |
/* The coin landed on edge; This is also the house-edge. */ | |
result = "edge"; | |
} else if (val % 2 === 0) { | |
result = "heads"; | |
} else { | |
result = "tails"; | |
} | |
if (result === "edge") { | |
divResult.innerHTML = 'Edge!!!'; | |
} else if (result == chosen) { | |
balance = balance + (2 * betUnit); | |
divResult.innerHTML = 'You won!!!'; | |
} else { | |
divResult.innerHTML = 'You lost :-('; | |
} | |
divBalance.innerHTML = balance; | |
if (result === 'edge') { | |
imgTail.style.display = 'none'; | |
imgHead.style.display = 'none'; | |
} else if (result === "heads") { | |
imgTail.style.display = 'none'; | |
} else { | |
imgHead.style.display = 'none'; | |
} | |
// Debugging code | |
if (debug) { | |
iter = iter + 1; | |
if (balance < lowBalance) lowBalance = balance; | |
if (balance > highBalance) highBalance = balance; | |
if (result === 'edge') edges = edges + 1; | |
else if (result === 'heads') heads = heads + 1; | |
else if (result === 'tails') tails = tails + 1; | |
if (result === chosen) correctGuesses = correctGuesses +1; | |
else wrongGuesses = wrongGuesses +1; | |
debugText = `<table id='debuginfo'>` | |
+`<tr><td>Random value</td><td>${val}</td></tr>` | |
+`<tr><td>Total flips</td><td>${iter}</td></tr>` | |
+`<tr><td>Landed on edge</td><td>${edges}</td></tr>` | |
+`<tr><td>Landed on heads</td><td>${heads}</td></tr>` | |
+`<tr><td>Landed on tails</td><td>${tails}</td></tr>` | |
+`<tr><td>Correct guesses</td><td>${correctGuesses}</td></tr>` | |
+`<tr><td>Wrong guesses</td><td>${wrongGuesses}</td></tr>` | |
+`<tr><td>Low balance</td><td>${lowBalance}</td></tr>` | |
+`<tr><td>High balance</td><td>${highBalance}</td></tr>` | |
+`</table>`; | |
updateResult(); | |
//setTimeout(function(){btnTails.click()}, 500) | |
} | |
} | |
var debug = false; | |
var debugText = ''; | |
var iter = 0; | |
var edges = 0; | |
var heads = 0; | |
var tails = 0; | |
var correctGuesses = 0; | |
var wrongGuesses = 0; | |
var lowBalance = balance; | |
var highBalance = balance; |
body { | |
/* Seerat picked this colour (using arbitrary values for RGB), and I like it! */ | |
background-color: rgb(255,100,90); | |
overflow: hidden; | |
} | |
.coin-flip { | |
/* border: 1px solid red; */ | |
position: absolute; | |
margin: auto; | |
width: 100px; | |
height: 100px; | |
left: 0; | |
right: 0; | |
transform-style: preserve-3d; | |
animation: - 1.5s ease-in-out 0s infinite alternate; | |
} | |
#controls { | |
/* border: 1px solid blue; */ | |
position: absolute; | |
text-align: center; | |
margin: auto; | |
top: 110px; | |
/* TODO: Figure out how to auto-center this div, rather than the 3-line hack that follows */ | |
width: 130px; | |
left: 0; | |
right: 0; | |
} | |
#div-debuginfo{ | |
top: 170px; | |
position: absolute; | |
margin: auto; | |
width: 250px; | |
left: 0; | |
right: 0; | |
} | |
#div-balance { | |
display: inline-block; | |
} | |
.coin-flip img { | |
width: 100px; | |
} | |
.coin-heads, .coin-tails { | |
backface-visibility: hidden; | |
position: absolute; | |
top: 0; | |
left: 0; | |
} | |
.coin-heads{ | |
animation: flip-head .1s linear 0s infinite alternate; | |
} | |
.coin-tails{ | |
animation: flip-tail .1s linear 0s infinite alternate; | |
} | |
@keyframes fly { | |
0% { top: 100%; } | |
100% { top: 0%; } | |
} | |
@keyframes flip-head { | |
0%, 100% { transform: rotateX(0deg); } | |
50% { transform: rotateX(180deg); } | |
} | |
@keyframes flip-tail { | |
0%, 100% { transform: rotateX(180deg); } | |
50% { transform: rotateX(0deg); } | |
} |