title: Converting working functional Javascript Tic Tac Toe game to Class based to practice OOP
body: <p>I'm making an exercise for myself to better understand OOP design by taking a working Javascript functional Tic Tac Toe game with AI to a Class based one. I'm getting stuck on the usual issues with what to put where in classes, single source of truth, loose coupling, etc. Not looking for complete answers here but perhaps some hints on a better strategy? </p>
  
  <p>Here is the original working functional TTT: </p>
  
  <pre><code>import "./styles.css";
  // functional TIC TAC TOE
  // Human is 'O'
  // Player is 'X'
  
  let ttt = {
    board: [], // array to hold the current game
    reset: function() {
      // reset board array and get HTML container
      ttt.board = [];
      const container = document.getElementById("ttt-game"); // the on div declared in HTML file
      container.innerHTML = "";
  
      // redraw swuares
      // create a for loop to build board
      for (let i = 0; i &lt; 9; i++) {
        //  push board array with null
        ttt.board.push(null);
        // set square to create DOM element with 'div'
        let square = document.createElement("div");
        // insert "&amp;nbsp;" non-breaking space to square
        square.innnerHTML = "&amp;nbsp;";
  
        // set square.dataset.idx set to i of for loop
        square.dataset.idx = i;
  
        // build square id's with i from loop / 'ttt-' + i - concatnate iteration
        square.id = "ttt-" + i;
        // add click eventlistener to square to fire ttt.play();
        square.addEventListener("click", ttt.play);
        // appendChild with square (created element 'div') to container
        container.appendChild(square);
      }
    },
  
    play: function() {
      // ttt.play() : when the player selects a square
      // play is fired when player selects square
      // (A) Player's move - Mark with "O"
      // set move to this.dataset.idx
  
      let move = this.dataset.idx;
  
      // assign ttt.board array with move to 0
      ttt.board[move] = 0;
      // assign "O" to innerHTML for this
      this.innerHTML = "O";
      // add "Player" to a classList for this
      this.classList.add("Player");
      // remove the eventlistener 'click'  and fire ttt.play
      this.removeEventListener("click", ttt.play);
  
      // (B) No more moves available - draw
      // check to see if board is full
      if (ttt.board.indexOf(null) === -1) {
        // alert "No winner"
        alert("No Winner!");
        // ttt.reset();
        ttt.reset();
      } else {
        // (C) Computer's move - Mark with 'X'
        // capture move made with dumbAI or notBadAI
        move = ttt.dumbAI();
        // assign ttt.board array with move to 1
        ttt.board[move] = 1;
        // assign sqaure to AI move with id "ttt-" + move (concatenate)
        let square = document.getElementById("ttt-" + move);
        // assign "X" to innerHTML for this
        square.innerHTML = "X";
  
        // add "Computer" to a classList for this
        square.classList.add("Computer");
        // square removeEventListener click  and fire ttt.play
        square.removeEventListener("click", ttt.play);
  
        // (D) Who won?
        // assign win to null (null, "x", "O")
        let win = null;
        // Horizontal row checks
        for (let i = 0; i &lt; 9; i += 3) {
          if (
            ttt.board[i] != null &amp;&amp;
            ttt.board[i + 1] != null &amp;&amp;
            ttt.board[i + 2] != null
          ) {
            if (
              ttt.board[i] == ttt.board[i + 1] &amp;&amp;
              ttt.board[i + 1] == ttt.board[i + 2]
            ) {
              win = ttt.board[i];
            }
          }
          if (win !== null) {
            break;
          }
        }
        // Vertical row checks
        if (win === null) {
          for (let i = 0; i &lt; 3; i++) {
            if (
              ttt.board[i] !== null &amp;&amp;
              ttt.board[i + 3] !== null &amp;&amp;
              ttt.board[i + 6] !== null
            ) {
              if (
                ttt.board[i] === ttt.board[i + 3] &amp;&amp;
                ttt.board[i + 3] === ttt.board[i + 6]
              ) {
                win = ttt.board[i];
              }
              if (win !== null) {
                break;
              }
            }
          }
        }
        // Diaganal row checks
        if (win === null) {
          if (
            ttt.board[0] != null &amp;&amp;
            ttt.board[4] != null &amp;&amp;
            ttt.board[8] != null
          ) {
            if (ttt.board[0] == ttt.board[4] &amp;&amp; ttt.board[4] == ttt.board[8]) {
              win = ttt.board[4];
            }
          }
        }
        if (win === null) {
          if (
            ttt.board[2] != null &amp;&amp;
            ttt.board[4] != null &amp;&amp;
            ttt.board[6] != null
          ) {
            if (ttt.board[2] == ttt.board[4] &amp;&amp; ttt.board[4] == ttt.board[6]) {
              win = ttt.board[4];
            }
          }
        }
        // We have a winner
        if (win !== null) {
          alert("WINNER - " + (win === 0 ? "Player" : "Computer"));
          ttt.reset();
        }
      }
    },
  
    dumbAI: function() {
      // ttt.dumbAI() : dumb computer AI, randomly chooses an empty slot
  
      // Extract out all open slots
      let open = [];
      for (let i = 0; i &lt; 9; i++) {
        if (ttt.board[i] === null) {
          open.push(i);
        }
      }
  
      // Randomly choose open slot
      const random = Math.floor(Math.random() * (open.length - 1));
      return open[random];
    },
    notBadAI: function() {
      // ttt.notBadAI() : AI with a little more intelligence
  
      // (A) Init
      var move = null;
      var check = function(first, direction, pc) {
        // checkH() : helper function, check possible winning row
        // PARAM square : first square number
        //       direction : "R"ow, "C"ol, "D"iagonal
        //       pc : 0 for player, 1 for computer
  
        var second = 0,
          third = 0;
        if (direction === "R") {
          second = first + 1;
          third = first + 2;
        } else if (direction === "C") {
          second = first + 3;
          third = first + 6;
        } else {
          second = 4;
          third = first === 0 ? 8 : 6;
        }
  
        if (
          ttt.board[first] === null &amp;&amp;
          ttt.board[second] === pc &amp;&amp;
          ttt.board[third] === pc
        ) {
          return first;
        } else if (
          ttt.board[first] === pc &amp;&amp;
          ttt.board[second] === null &amp;&amp;
          ttt.board[third] === pc
        ) {
          return second;
        } else if (
          ttt.board[first] === pc &amp;&amp;
          ttt.board[second] === pc &amp;&amp;
          ttt.board[third] === null
        ) {
          return third;
        }
        return null;
      };
  
      // (B) Priority #1 - Go for the win
      // (B1) Check horizontal rows
      for (let i = 0; i &lt; 9; i += 3) {
        move = check(i, "R", 1);
        if (move !== null) {
          break;
        }
      }
      // (B2) Check vertical columns
      if (move === null) {
        for (let i = 0; i &lt; 3; i++) {
          move = check(i, "C", 1);
          if (move !== null) {
            break;
          }
        }
      }
      // (B3) Check diagonal
      if (move === null) {
        move = check(0, "D", 1);
      }
      if (move === null) {
        move = check(2, "D", 1);
      }
  
      // (C) Priority #2 - Block player from winning
      // (C1) Check horizontal rows
      for (let i = 0; i &lt; 9; i += 3) {
        move = check(i, "R", 0);
        if (move !== null) {
          break;
        }
      }
      // (C2) Check vertical columns
      if (move === null) {
        for (let i = 0; i &lt; 3; i++) {
          move = check(i, "C", 0);
          if (move !== null) {
            break;
          }
        }
      }
      // (C3) Check diagonal
      if (move === null) {
        move = check(0, "D", 0);
      }
      if (move === null) {
        move = check(2, "D", 0);
      }
      // (D) Random move if nothing
      if (move === null) {
        move = ttt.dumbAI();
      }
      return move;
    }
  };
  document.addEventListener("DOMContentLoaded", ttt.reset());
  </code></pre>
  
  <p>Here is what I have so far of my class based version: </p>
  
  <pre><code>import "./styles.css";
  
  class Gameboard {
    constructor() {
      this.board = [];
      this.container = document.getElementById("ttt-game");
      this.container.innerHTML = "";
    }
  
    reset() {
      this.board = [];
    }
  
    build() {
      for (let i = 0; i &lt; 9; i++) {
        this.board.push(null);
        const square = document.createElement("div");
        square.innerHTML = "&amp;nbsp;";
        square.dataset.idx = i;
        square.id = "ttt-" + i;
        square.addEventListener("click", () =&gt; {
          // What method do I envoke here? 
          console.log(square) 
        });
        this.container.appendChild(square);
      }
    }
  };
  
  class Game {
    constructor() {
      this.gameBoard = new Gameboard();
      this.player = new Player();
      this.computer = new Computer();
    }
  
    play() {
      this.gameBoard.build();
    }
  };
  
  class Player {
  
  };
  
  class Computer {
  
  };
  
  class DumbAI {
  
  };
  
  const game = new Game();
  
  document.addEventListener("DOMContentLoaded", game.play());
  </code></pre>
  
  <p>My HTML file is very simple with only a <code>&lt;div id="ttt-game"&gt;&lt;/div&gt;</code> to get started and CSS file is <code>grid</code>. </p>
  
  <p>The biggest issue I'm having is capturing the <code>squares</code> in <code>Game</code>. And where should I put <code>eventListeners</code> ? (my next project is to do a React version).</p>