JSFiddle - React, Tailwind, and code Playground
by Travis Almand
HTML
<button id="startBtn">Start</button>
SCSS
@import url('https://fonts.googleapis.com/css?family=Permanent+Marker');
html {
box-sizing: border-box;
font-size: 10px;
}
*, *:before, *:after {
box-sizing: inherit;
}
body {
align-items: center;
display: flex;
flex-direction: column;
font-family: 'Permanent Marker';
height: 100vh;
justify-content: center;
}
#startBtn {
background: white;
border: 2px solid black;
border-radius: 5px;
cursor: pointer;
font-family: 'Permanent Marker';
font-size: 2rem;
margin: 0 1rem;
padding: 10px 40px;
transition: transform 0.25s;
&:active {
transform: scale3d(.75, 0.75, 1);
}
}
#scoreboard {
display: flex;
margin-bottom: 4rem;
width: 50vh;
// display isn't necessary for pseudo elements if parent is flex
// nice
&::before {
// here's a reason for all the dataset changes in js
// free UI updates with state changes
content: 'x: ' attr(data-x-score);
font-size: 3rem;
width: 50%;
}
&::after {
content: 'o: ' attr(data-o-score);
font-size: 3rem;
text-align: right;
width: 50%;
}
}
#gameboard {
border: 1px solid black;
display: flex;
flex-wrap: wrap;
height: 50vh;
min-height: 200px;
min-width: 200px;
width: 50vh;
&::before {
font-size: 3rem;
position: absolute;
text-align: center;
transform: translate3d(0, -110%, 0);
width: 50vh;
}
// more dataset changes in js causing UI changes
&[data-message="x"]::before {
content: 'x turn';
}
&[data-message="o"]::before {
content: 'o turn';
}
&[data-message="winner"]::before {
color: green;
content: 'winner!';
}
&[data-message="tie"]::before {
color: red;
content: 'tie!';
}
// sometimes sub-pixel math causes gaps between borders
// a more clever border layout would solve that
.square {
align-items: center;
background-color: white;
border: 1px solid black;
cursor: pointer;
display: flex;
height: 33.33%;
justify-content: center;
...
JavaScript
console.clear();
var startGame = (function () {
var _startButton = document.querySelector('#startBtn');
var _startClickHandler = function () {
_startButton.remove();
window.setTimeout(function () {
scoreboard.createScoreboard();
}, 250);
window.setTimeout(function () {
gameboard.createGameboard();
gameboard.setMessage('x');
}, 500);
window.setTimeout(function () {
buttonPanel.createPanel();
}, 750);
};
_startButton.addEventListener('click', _startClickHandler);
})();
var scoreboard = (function () {
var public = {};
var _scoreboard;
var _score = {
x: 0,
o: 0
};
public.createScoreboard = function () {
document.body.insertAdjacentHTML('afterbegin', '<div id="scoreboard"></div>');
_scoreboard = document.querySelector('#scoreboard')
_scoreboard.dataset.xScore = _score.x
_scoreboard.dataset.oScore = _score.o;
};
public.updateScore = function (winner) {
_score[winner]++;
_scoreboard.dataset.xScore = _score.x;
_scoreboard.dataset.oScore = _score.o;
};
return public;
})();
var gameboard = (function () {
var public = {};
var _gameboard;
var _squaresHTML = '';
public.createGameboard = function () {
document.body.insertAdjacentHTML('beforeend', '<div id="gameboard"></div>');
_gameboard = document.querySelector('#gameboard');
for (var i = 0; i < 9; i++) {
_squaresHTML += '<div class="square" data-square="' + i + '"></div>';
}
_gameboard.insertAdjacentHTML('afterbegin', _squaresHTML);
_gameboard.addEventListener('click', function (e) {
if (e.target.id !== "gameboard" && !gameplay.getWinner() && !e.target.dataset.player) {
e.target.dataset.player = _gameboard.dataset.message;
gameplay.setMove(e.target.dataset.square, _gameboard.dataset.message);
gameplay.checkForWin();
if (AI.hasAI() && _gameboard.dataset.message === 'o') {
...