Add tic tac toe

main
Elbert Alias 4 years ago
parent 3f88e0d595
commit ecba4f1cba

@ -198,10 +198,14 @@ a, a:focus, a:hover {
.empty {
background: #fff;
height: calc(100% - 4.5rem);
padding: 4.5rem 1.5rem 5rem 1.5rem;
padding: 2.5rem;
text-align: center;
}
.empty__text {
margin-bottom: 1.5rem;
}
.empty--hidden {
display: none;
}
@ -377,6 +381,7 @@ body.dynamic-icon .category__heading:hover .category__pin {
}
.options {
background: white;
padding: 1.5rem 1.5rem 1rem 1.5rem;
}
@ -389,6 +394,52 @@ body.dynamic-icon .category__heading:hover .category__pin {
margin-top: 1rem;
}
.ttt-grid {
background: var(--color-primary-lighten);
border-radius: 4px;
line-height: 0;
margin: auto;
width: calc(6rem + 2px);
}
.ttt-cell {
border: 1px solid var(--color-primary);
border-width: 1px 0 0 1px;
display: inline-block;
padding: .2rem;
width: 2rem;
height: 2rem;
}
.ttt-row:first-child .ttt-cell {
border-top: none;
}
.ttt-cell:first-child {
border-left: none;
}
.ttt-icon {
display: none;
height: 100%;
width: 100%;
}
.ttt-cell .ttt-icon {
color: var(--color-primary);
display: block;
}
.ttt-blink .ttt-icon {
animation: blink 250ms step-end 0s 3;
}
@keyframes blink {
50% {
opacity: 0;
}
}
/* Dark mode */
.dark {

@ -44,7 +44,29 @@
</svg>
</div>
<div class="empty" data-i18n="noAppsDetected"></div>
<div class="empty">
<div class="empty__text" data-i18n="noAppsDetected">&nbsp;</div>
<div class="ttt-grid">
<div class="ttt-row">
<div class="ttt-cell"></div><div class="ttt-cell"></div><div class="ttt-cell"></div>
</div>
<div class="ttt-row">
<div class="ttt-cell"></div><div class="ttt-cell"></div><div class="ttt-cell"></div>
</div>
<div class="ttt-row">
<div class="ttt-cell"></div><div class="ttt-cell"></div><div class="ttt-cell"></div>
</div>
</div>
<svg class="ttt-icon ttt-icon-x" viewBox="0 0 24 24">
<path fill="currentColor" d="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z" />
</svg>
<svg class="ttt-icon ttt-icon-o" viewBox="0 0 24 24">
<path fill="currentColor" d="M12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4A8,8 0 0,1 20,12A8,8 0 0,1 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z" />
</svg>
</div>
<div class="detections detections--hidden"></div>
@ -125,5 +147,7 @@
</a>
</div>
</div>
<script src="../js/tictactoe.js"></script>
</body>
</html>

@ -0,0 +1,175 @@
'use strict'
/* eslint-env browser */
/* eslint-disable no-labels */
const grid = document.body.querySelector('.ttt-grid')
const icons = {
x: document.body.querySelector('.ttt-icon-x'),
o: document.body.querySelector('.ttt-icon-o'),
}
let paused = true
const cells = {}
const axes = ['y', 'x']
const players = ['x', 'o']
function fill(cell, player) {
cell.value = player
cell.el.appendChild(icons[player].cloneNode(true))
}
function reset() {
for (let y = 1; y <= 3; y++) {
for (let x = 1; x <= 3; x++) {
const cell = cells[y][x]
cell.el.classList.remove('ttt-blink')
cell.el.firstChild && cell.el.removeChild(cell.el.firstChild)
cell.value = ''
}
}
const { empty } = check()
play(empty)
}
function checkLine(line, complete) {
for (const player of players) {
if (line[player].length === 3) {
complete.player = player
complete.cells.push(...line[player])
}
}
}
function check(dryrun) {
const empty = []
const complete = {
player: null,
cells: [],
}
for (const axis of axes) {
const diagonal = { o: [], x: [] }
for (let a = 1; a <= 3; a++) {
const y = a
const x = axis === 'y' ? y : 4 - y
const cell = cells[y][x]
cell.value && diagonal[cell.value].push(cell)
const straight = { o: [], x: [] }
for (let b = 1; b <= 3; b++) {
const y = axis === 'y' ? a : b
const x = axis === 'y' ? b : a
const cell = cells[y][x]
cell.value ? straight[cell.value].push(cell) : empty.push(cell)
}
checkLine(straight, complete)
}
checkLine(diagonal, complete)
}
if (!dryrun) {
if (complete.player) {
complete.cells.forEach(({ el }) => el.classList.add('ttt-blink'))
setTimeout(() => {
reset()
}, 1200)
} else if (!empty.length) {
setTimeout(() => {
reset()
}, 1200)
}
}
return { winner: complete.player, empty: [...new Set(empty)] }
}
function play(cells) {
setTimeout(() => {
let found = false
search: for (const cell of cells) {
for (const player of players) {
cell.value = player
const { winner, empty } = check(true)
if (winner || !empty) {
found = true
fill(cell, 'x')
break search
} else {
cell.value = ''
}
}
}
if (!found) {
const cell = cells[Math.round(Math.random() * (cells.length - 1))]
fill(cell, 'x')
}
const { winner, empty } = check()
if (!winner && empty) {
paused = false
}
}, 400)
}
for (let y = 1; y <= 3; y++) {
for (let x = 1; x <= 3; x++) {
const el = grid.querySelector(
`.ttt-row:nth-child(${y}) .ttt-cell:nth-child(${x})`
)
el.addEventListener('click', () => {
if (paused) {
return
}
const cell = cells[y][x]
if (!cell.value) {
paused = true
fill(cell, 'o')
const { winner, empty } = check()
!winner && play(empty)
}
})
cells[y] = cells[y] || {}
cells[y][x] = {
x,
y,
el,
value: '',
}
}
}
reset()