TicTacToe
Minimal and easily extendable tic-tac-toe in a responsive viewport
by Federico Tibaldo
HTML
<div id="announcement"></div>
<div class="game-board-container">
<svg viewBox="0 0 17 17" version="1.1" xmlns="http://www.w3.org/2000/svg">
<g class="game-board">
<line x1="1" y1="6" x2="16" y2="6"/>
<line x1="1" y1="11" x2="16" y2="11"/>
<line x1="6" y1="1" x2="6" y2="16"/>
<line x1="11" y1="1" x2="11" y2="16"/>
</g>
</svg>
<div id="win-stroke"></div>
<div class="game-board">
<div class="game-box"></div>
<div class="game-box"></div>
<div class="game-box"></div>
<div class="game-box"></div>
<div class="game-box"></div>
<div class="game-box"></div>
<div class="game-box"></div>
<div class="game-box"></div>
<div class="game-box"></div>
</div>
</div>
SCSS
* {
box-sizing: inherit;
}
html {
box-sizing: border-box;
}
body {
display: flex;
margin: 0;
align-content: center;
justify-content: center;
}
#announcement {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: white;
z-index: 10;
opacity: 0;
visibility: hidden;
transition: opacity 0.5s ease-in 0.15s;
&.visible {
visibility: visible;
opacity: 0.8;
}
}
.game-board-container {
position: relative;
width: 50%;
}
.game-board-container:after {
content: "";
display: block;
padding-bottom: 100%;
}
.game-board-container>* {
position: absolute;
width: 100%;
height: 100%;
}
.game-board {
display: flex;
flex-wrap: wrap;
padding: 4%;
.game-box {
width: 33%;
height: 33%;
}
}
/*************************/
/* SVG */
/*************************/
/* General */
line {
stroke-width: 1;
stroke-linecap: round;
}
circle {
fill: none;
}
/* Colors */
.x {
stroke: #f44336;
}
.o {
stroke: #2196F3;
}
/* Groups */
.game-board {
stroke: #607D8B;
}
.x-group {
stroke-dasharray: 6;
stroke-dashoffset: 7;
}
.o-group {
transform: rotate(-150deg);
transform-origin: center;
stroke-dasharray: 13;
stroke-dashoffset: 13.25;
}
.win-stroke {
stroke-dasharray: 15.5;
stroke-dashoffset: 15.75;
}
.win-stroke-diagonal {
stroke-dasharray: 21.5;
stroke-dashoffset: 21.75;
}
/* Animation */
.dash {
animation: dash .15s linear forwards;
}
.dash-long {
animation: dash .25s ease-in forwards;
}
.dash-delay {
animation: dash .15s linear forwards .15s;
}
.dash-delay-long {
animation: dash .2s linear forwards .5s;
}
@keyframes dash {
to {
stroke-dashoffset: 0;
}
}
JavaScript
var tictactoe = tictactoe || (function() {
var turn = 1
var _winStrokeContainer
var _announcementContainer
var _boxes = []
var gameBoard = []
/**
* Displays an X in the given element
*/
function printX(el) {
el.innerHTML = '<svg viewBox="0 0 7 7" version="1.1" xmlns="http://www.w3.org/2000/svg"><g class="x-group"><line class="x dash" x1="2" y1="2" x2="5" y2="5"/><line class="x dash-delay" x1="2" y1="5" x2="5" y2="2"/></g></svg>'
}
/**
* Displays an O in the given element
*/
function printO(el) {
el.innerHTML = '<svg viewBox="0 0 7 7" version="1.1" xmlns="http://www.w3.org/2000/svg"><g class="o-group"><circle class="o dash-long" cx="3.5" cy="3.5" r="2"/></g></svg>'
}
function announce(el, msg) {
let p = document.createElement('p')
p.textContent = msg
el.appendChild(p)
el.classList.toggle('visible')
}
function strokeRow(index) {
let y = 3.25 * (index + 1) + 2 * index
let color = getTurn()
_winStrokeContainer.innerHTML = `<svg viewBox="0 0 17 17" version="1.1" xmlns="http://www.w3.org/2000/svg"><line class="win-stroke ${color} dash-delay-long" x1="1" y1="${y}" x2="16" y2="${y}"/></svg>`
}
function strokeColumn(index) {
let x = 3.25 * (index + 1) + 2 * index
let color = getTurn()
_winStrokeContainer.innerHTML = `<svg viewBox="0 0 17 17" version="1.1" xmlns="http://www.w3.org/2000/svg"><g class="win-stroke"><line class="${color} dash-delay-long" x1="${x}" y1="1" x2="${x}" y2="16"/></g></svg>`
}
function strokeDiagonal(index) {
let f = (x) => (x - 8.5) * index + 8.5
let color = getTurn()
_winStrokeContainer.innerHTML = `<svg viewBox="0 0 17 17" version="1.1" xmlns="http://www.w3.org/2000/svg"><g class="win-stroke-diagonal"><line class="${color} dash-delay-long" x1="1" y1="${f(1)}" x2="16" y2="${f(16)}"/></g></svg>`
}
function changeTurn() {
turn ^= 1
}
function getTurn() {
return turn ? 'x' : 'o'
}
function update(index) {
if (legal(index)) {
gameBoard[index] = getTurn()
let el =...