JSFiddle - React, Tailwind, and code Playground

by Travis Almand

HTML

<!-- javascript exercise, no HTML! -->

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;
}

#score {
  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%;
  }
}

#container {
  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;
    overflow: hidden;
    transition: background-color 0.25s 0.25s;
    width: 33.33%;
    
    &:hover {
      // but a more clever border layout would makes this hard to handle, compromise
      border-color: green;
    }
    
    &::before {
      content: '';
      transform:...

JavaScript

console.clear();

// this makes heavy use of dataset attributes to control display and state changes

// basic HTML to insert into body
document.body.insertAdjacentHTML('afterbegin', '<div id="score"></div>');
document.body.insertAdjacentHTML('beforeend', '<div id="container"></div><div id="panel"><button id="aiBtn"></button><button id="resetBtn">reset</button></div>');

const $score = document.querySelector('#score');
const $container = document.querySelector('#container');
const $aiBtn = document.querySelector('#aiBtn');
// possible winning choices
const wins = [[0,1,2],[3,4,5],[6,7,8],[0,3,6],[1,4,7],[2,5,8],[0,4,8],[2,4,6]];

// various lets for creating board and keeping track of play
let squaresHTML = '';
let plays = [null, null, null, null, null, null, null, null, null];
let winner = null;
let score = {x: 0, o: 0};
let hasAI;

// after every click we check for winner
let checkForWin = () => {
	let winRow;
  
  // compare current plays against possible win combinations
  // this does rip through entire wins array without breaking on match, but it's only 8 items
  // could change it to for loop to have break, but bah
  wins.forEach((element) => {
    if (plays[element[0]] && plays[element[0]] === plays[element[1]] && plays[element[1]] === plays[element[2]]) {
    	// if winner is found id the winning player
      winner = plays[element[0]];
      // id the winning row to highlight
      winRow = element;
    }
  });
  
  if (winner) {
  	// update message and score
  	$container.dataset.message = 'winner';
    score[winner]++;
    $score.dataset.xScore = score.x;
    $score.dataset.oScore = score.o;
    winRow.forEach((element) => {
    	// highlight winning row
    	$container.childNodes[element].classList.add('win');
    });
  } else {
  	// no win so switch players in message
  	$container.dataset.message = $container.dataset.message === 'x' ? 'o' : 'x';
  }
};

// AI always assumes player is X
// idea: look for wins
// idea: choose certain move based on...