JSFiddle - React, Tailwind, and code Playground

HTML

<body>
  <div id="canvas">
    <div class="top row">
      <div class="left"></div>
      <div class="middle"></div>
      <div class="right"></div>
    </div>
    <div class="middle row">
      <div class="left"></div>
      <div class="middle"></div>
      <div class="right"></div>
    </div>
    <div class="bottom row">
      <div class="left"></div>
      <div class="middle"></div>
      <div class="right"></div>
    </div>
  </div>
  <div id="winner">
    WINNER!
  </div>
  <div id="draw">
    DRAW!
  </div>
</body>

CSS

body {
  font-size: 32px;
  font-family: sans-serif;
  width: 295px;
  margin: 1em auto;
}

#canvas {
  width: 296px;
  float: left;
}

.row {
  width: 296px;
  float: left;
}

.row div {
  width: 95px;
  padding-top: 25px;
  height: 61px;
  float: left;
  text-align: center;
}

.top {
  border-bottom: 5px solid black;
}

.bottom {
  border-top: 5px solid black;
}

.left {
  border-right: 5px solid black;
}

.right {
  border-left: 5px solid black;
}

#draw, #winner {
  display: none;
  text-align: center;
  padding-top: 0.5em;
  clear: both;
}

JavaScript

const X = "X";
const O = "O";

const WINNING_STATE = {
  NONE: 0,
  X: 1,
  O: 2,
  DRAW: 3,
}

class Model {
  constructor() {
    this.gameState = [];
    this.isXTurn = true;
    this.moveCount = 0;
    let i;
    for (i = 0; i < 3; i++) {
      this.gameState[i] = [];
    }
  }

  getSquare(row, col) {
    return this.gameState[col][row]
  }

  selectX(row, col) {
    this.selectSquare(X, row, col);
  }

  selectO(row, col) {
    this.selectSquare(O, row, col);
  }

  selectSquare(type, row, col) {
    if (type != X && type != O) {
      throw new TypeError("Invalid type: " + type)
    }

    if (row < 0 || row > 2 || col < 0 || col > 2) {
      throw new RangeError("Invalid range: " + row + ", " + col)
    }

    this.gameState[col][row] = type;
    this.moveCount++;

    // this.printGameState();
  }

  printGameState() {
    let i;
    for (i = 0; i < 3; i++) {
      console.log(this.gameState[i]);
    }
  }
}

class Controller {
  constructor() {
    this.model = new Model();
    this.winningState = WINNING_STATE.NONE;
    this.squares = document.querySelectorAll(".row div");
    this.bindAllSquareClicks();
  }

  winningStateForXAtPosition(row, col) {
    return this.winningStateForTypeAtPosition(X, row, col);
  }

  winningStateForOAtPosition(row, col) {
    return this.winningStateForTypeAtPosition(O, row, col);
  }

  winningStateForTypeAtPosition(type, row, col) {
    let gameState = this.model.gameState // Convenience

    // Check horizontally
    let col_;
    for (col_ = 0; col_ < 3; col_++) {
      if (type != this.model.getSquare(row, col_)) {
        break;
      }
    }
    if (col_ == 3) {
      return WINNING_STATE[type]
    }

    // Check vertically
    let row_;
    for (row_ = 0; row_ < 3; row_++) {
      if (type != this.model.getSquare(row_, col)) {
        break;
      }
    }
    if (row_ == 3) {
      return WINNING_STATE[type]
    }

    // Check diagonal
    if (row == col) {
      let rowcol;
      for (rowcol = 0; rowcol < 3;...