Tic Tac Toe

Allows to people to play a game of Tic Tac Toe.

by Perry Kaufman

HTML

<h1>Tic Tac Toe</h1>
<h4>By Perry Kaufman</h4>
<div id="tic-tac-toe">
  <div data-type="grid" class="tic-tac-toe-grid">
    <div class="tic-tac-toe-cell" data-row="1" data-col="1" data-type="cell"></div>
    <div class="tic-tac-toe-cell" data-row="1" data-col="2" data-type="cell"></div>
    <div class="tic-tac-toe-cell" data-row="1" data-col="3" data-type="cell"></div>
    <div class="tic-tac-toe-cell" data-row="2" data-col="1" data-type="cell"></div>
    <div class="tic-tac-toe-cell" data-row="2" data-col="2" data-type="cell"></div>
    <div class="tic-tac-toe-cell" data-row="2" data-col="3" data-type="cell"></div>
    <div class="tic-tac-toe-cell" data-row="3" data-col="1" data-type="cell"></div>
    <div class="tic-tac-toe-cell" data-row="3" data-col="2" data-type="cell"></div>
    <div class="tic-tac-toe-cell" data-row="3" data-col="3" data-type="cell"></div>
  </div>

  <div class="tic-tac-toe-symbols" data-type="controls">
    <label class="tic-tac-toe-label">Player 1</label>
    <div id="cross" class="symbol" data-type="symbol" data-symbol="cross">
      <div class="cross"></div>
    </div>
    <label class="tic-tac-toe-label">Player 2</label>
    <div id="nought" class="symbol" data-type="symbol" data-symbol="nought">
      <div class="nought"></div>
    </div>
  </div>

  <span data-type="message" class="tic-tac-toe-message"></span>
  <button data-type="reset" class="tic-tac-toe-button">Restart</button>
</div>

CSS

:root {
  --fore-color: #001564;
  --back-color: #ededef;
}

* {
  box-sizing: inherit;
}

html,
body {
  background: var(--back-color);
  box-sizing: border-box;
  color: var(--fore-color);
  font-family: sans-serif;
  font-size: 20px;
}

h1, h4 {
  margin: 0;
  text-align: center;
}

h4 {
  margin-bottom: 25px;
}

#tic-tac-toe {
  display: grid;
  grid-template-areas: "tr tl" "bl br";
  justify-content: center;
}

.tic-tac-toe-grid {
  display: grid;
  grid-area: tr;
  grid-template-columns: 100px 100px 100px;
  grid-template-rows: 100px 100px 100px;
}

.tic-tac-toe-cell {
  display: flex;
  align-items: center;
  justify-content: center;
}

[data-row="1"] {
  border-bottom: 3px solid var(--fore-color);
}

[data-row="2"] {
  border-bottom: 3px solid var(--fore-color);
  border-top: 3px solid var(--fore-color);
}

[data-row="3"] {
  border-top: 3px solid var(--fore-color);
}

[data-col="1"] {
  border-right: 3px solid var(--fore-color);
}

[data-col="2"] {
  border-right: 3px solid var(--fore-color);
  border-left: 3px solid var(--fore-color);
}

[data-col="3"] {
  border-left: 3px solid var(--fore-color);
}

.tic-tac-toe-label {
  font-weight: bold;
}

.tic-tac-toe-symbols {
  align-items: center;
  border: 3px solid var(--fore-color);
  border-radius: 5px;
  display: flex;
  flex-flow: column nowrap;
  grid-area: tl;
  justify-content: space-around;
  padding: 5px;
  margin: 10px;
}

.symbol {
  align-items: center;
  cursor: default;
  display: flex;
  height: 80px;
  justify-content: center;
  transition: all .25s ease-in-out;
  width: 80px;
}

.symbol.small {
  height: 10px !important;
  width: 10px !important;
}

.cross {
  height: 10px;
  position: relative;
  width: 80px;
}

.cross::before,
.cross::after {
  background-color: var(--fore-color);
  border-radius: 5px;
  content: "";
  height: 10px;
  position: absolute;
  width: 100%;
}

.cross::before {
  transform: rotate(45deg);
}

.cross::after {
  transform: rotate(-45deg);
}

.nought {
 ...

JavaScript

/*
 * Can be added to an object to provide a simple listener interface.
 */
const listenerMixin = {
  setListener(name, callback) {
    if (typeof callback !== 'function') {
      throw new Error('Error: callback must be a function');
    }
    if (!this._listeners) {
      this._listeners = {};
    }
    this._listeners[name] = callback;
  },
  _triggerListener(name, ...args) {
    if (this._listeners && this._listeners[name]) {
      this._listeners[name].apply(this, args);
    }
  }
}

/*
 * User Interface for a game of Tic Tac Toe.
 */
class TicTacToeView {
  constructor() {
    let container = document.querySelector('#tic-tac-toe');

    let board = container.querySelector('[data-type="grid"]');
    this._cells = board.querySelectorAll('[data-type="cell"]');

    let selection = container.querySelector('[data-type="controls"]');
    this._symbols = selection.querySelectorAll('[data-type="symbol"]');
    
    this._message = container.querySelector('[data-type="message"]');
    this._reset = container.querySelector('[data-type="reset"]');

    this._init();
  }
  _init() {
    //Initialize cells
    this._cells.forEach((currentValue) => {
      currentValue.ondragover = (event) => {
        this._dragOverHandler(event);
      };
      currentValue.ondrop = (event) => {
        this._dropHandler(event);
      };
    });
    //Initialize symbols
    this._symbols.forEach((currentValue) => {
      currentValue.draggable = true;
      currentValue.ondragstart = (event) => {
        this._dragHandler(event);
      };
    });
    this._reset.addEventListener('click', () => {
      this._triggerListener('reset');
    });
  }
  _dragHandler(event) {
    event.dataTransfer.setData('text', event.target.id);
  }
  _dragOverHandler(event) {
    event.preventDefault();
  }
  _dropHandler(event) {
    event.preventDefault();
    let id = event.dataTransfer.getData('text')
    this._triggerListener('move', id, event.target.dataset.row, event.target.dataset.col)
  }
 ...