Advertisement
Guest User

Untitled

a guest
Mar 22nd, 2019
108
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. "use strict";
  2.  
  3. const GRID_SIZE_ROWS = 3;
  4. const GRID_SIZE_COLUMNS = 3;
  5.  
  6. class Status {
  7.   constructor (element) {
  8.     this.element = element;
  9.   }
  10.  
  11.   clear() {
  12.     while (this.element.firstChild) {
  13.       this.element.removeChild(this.element.firstChild);
  14.     }
  15.   }
  16.  
  17.   appendText(text, style) {
  18.     let textNode = document.createElement('span');
  19.     textNode.innerText = text;
  20.     for (let property in style) {
  21.       textNode.style[property] = style[property];
  22.     }
  23.     this.element.appendChild(textNode);
  24.   }
  25.  
  26.   appendMany(array) {
  27.     for (let item of array) {
  28.       this.appendText(item.text, item.style);
  29.     }
  30.   }
  31. }
  32.  
  33. class Grid {
  34.   constructor(element) {
  35.     this.element = element;
  36.     this.callbacks = [];
  37.     this.build();
  38.   }
  39.  
  40.   reset() {
  41.     while (this.element.firstChild) {
  42.       this.element.removeChild(this.element.firstChild);
  43.     }
  44.     this.build();
  45.   }
  46.  
  47.   build() {
  48.     // Create row elements
  49.     this.rows = [];
  50.     for (let rowIndex = 0; rowIndex < GRID_SIZE_ROWS; rowIndex++) {
  51.       // Create the object representing a row
  52.       let row = {
  53.         grid: this,
  54.         index: rowIndex,
  55.         element: document.createElement('div'),
  56.         cells: []
  57.       };
  58.       this.rows.push(row);
  59.  
  60.       // Configure the row element
  61.       row.element.className = 'row';
  62.       this.element.appendChild(row.element);
  63.  
  64.       // Create the cells for this row
  65.       for (let colIndex = 0; colIndex < GRID_SIZE_COLUMNS; colIndex++) {
  66.         let cell = {
  67.           row: row,
  68.           index: colIndex,
  69.           element: document.createElement('div'),
  70.           piece: {
  71.             element: document.createElement('div'),
  72.  
  73.             get type() {
  74.               let classes = this.element.className.split(' ');
  75.               if (classes.length >= 2)
  76.                 return classes.find(i => i != 'piece');
  77.               return 'empty';
  78.             },
  79.  
  80.             set type(piece) {
  81.               this.element.className = `${piece} piece`;
  82.             }
  83.           },
  84.  
  85.           get highlighted() {
  86.             return this.element.className.startsWith('highlighted');
  87.           },
  88.  
  89.           set highlighted(value) {
  90.             if (value)
  91.               this.element.className = 'highlighted cell';
  92.             else
  93.               this.element.className = 'cell';
  94.           }
  95.         };
  96.         row.cells.push(cell);
  97.         row.element.appendChild(cell.element);
  98.  
  99.         // Configure the cell element
  100.         cell.element.className = 'cell';
  101.         cell.element.addEventListener('click', (e) => {
  102.           cell.row.grid.onClickCell(cell);
  103.         });
  104.         cell.element.appendChild(cell.piece.element);
  105.  
  106.         // Configure the piece element
  107.         cell.piece.element.className = 'piece';
  108.       }
  109.     }
  110.   }
  111.  
  112.   attachClickCellHandler(callback) {
  113.     this.callbacks.push(callback);
  114.   }
  115.  
  116.   onClickCell(cell) {
  117.     for (let callback of this.callbacks)
  118.       callback(cell);
  119.   }
  120.  
  121.   * getLine(startX, startY, stepX, stepY) {
  122.     let x = startX;
  123.     let y = startY;
  124.     while (((y >= 0) && (y < this.rows.length)) &&
  125.            ((x >= 0) && (x < this.rows[y].cells.length)))
  126.     {
  127.       yield this.rows[y].cells[x];
  128.       x += stepX;
  129.       y += stepY;
  130.     }
  131.   }
  132. }
  133.  
  134. class TicTacToe {
  135.   constructor(gridNode, statusNode) {
  136.     // Create an array of "players" to control turns
  137.     this.players = [
  138.       {
  139.         piece: 'nought',
  140.         displayText: {
  141.           single: 'Player One',
  142.           possessive: 'Player One\'s'
  143.         },
  144.         color: 'green'
  145.       },
  146.       {
  147.         piece: 'cross',
  148.         displayText: {
  149.           single: 'Player Two',
  150.           possessive: 'Player Two\'s'
  151.         },
  152.         color: 'red'
  153.       }
  154.     ];
  155.     this.currentPlayerIndex = 0;
  156.     this.finished = false;
  157.  
  158.     // Create the status text
  159.     this.status = new Status(statusNode);
  160.  
  161.     // Create the grid, and attach a "click cell" handler
  162.     this.grid = new Grid(gridNode);
  163.     this.grid.attachClickCellHandler(this.onClickCell.bind(this));
  164.  
  165.     // Begin the first turn
  166.     this.beginTurn();
  167.   }
  168.  
  169.   get currentPlayer() {
  170.     return this.players[this.currentPlayerIndex];
  171.   }
  172.  
  173.   beginTurn() {
  174.     this.status.clear();
  175.     this.status.appendMany([
  176.       { text: 'It is ' },
  177.       {
  178.         text: this.currentPlayer.displayText.possessive,
  179.         style: {
  180.           color: this.currentPlayer.color
  181.         }
  182.       },
  183.       { text: ' turn!' }
  184.     ]);
  185.   }
  186.  
  187.   nextTurn() {
  188.     // Move to the next player
  189.     this.currentPlayerIndex += 1;
  190.     if (this.currentPlayerIndex >= this.players.length)
  191.       this.currentPlayerIndex = 0;
  192.     this.beginTurn();
  193.   }
  194.  
  195.   onClickCell(cell) {
  196.     // Has the game been won?
  197.     if (this.finished) {
  198.       this.reset();
  199.       return;
  200.     }
  201.  
  202.     // Did the user click on a cell that was already occupied?
  203.     if (cell.piece.type != 'empty') {
  204.       this.status.clear();
  205.       this.status.appendText('That space is already occupied!');
  206.       return;
  207.     }
  208.  
  209.     // Add their piece to the board and check for a win condition
  210.     cell.piece.type = this.currentPlayer.piece;
  211.     this.checkForWinCondition(cell);
  212.  
  213.     // If the game is not finished, go to the next turn
  214.     if (!this.finished)
  215.       this.nextTurn();
  216.   }
  217.  
  218.   checkForWinCondition(cell) {
  219.     // Only check lines that are worth checking
  220.     let lines = [
  221.       // The horizontal line
  222.       [ 0, cell.row.index, 1, 0 ],
  223.  
  224.       // The vertical line
  225.       [ cell.index, 0, 0, 1 ]
  226.     ];
  227.    
  228.     // Check if either of the diagonals are worth checking
  229.     if (cell.index == cell.row.index)
  230.       lines.push([ 0, 0, 1, 1 ]);
  231.     if (Math.abs(cell.index - 2) == cell.row.index)
  232.       lines.push([ 2, 0, -1, 1 ]);
  233.  
  234.     // Go through each line
  235.     for (let lineParams of lines) {
  236.       // Check is this line is a win for the current player
  237.       let line = this.grid.getLine.apply(this.grid, lineParams);
  238.       let path = [];
  239.       while (true) {
  240.         // Get the next "step" in this line
  241.         let step = line.next();
  242.         if (step.done)
  243.           // There are no more steps, break out of this loop
  244.           break;
  245.  
  246.         // If this cell in the line contributes to a win, then add it to
  247.         // the path
  248.         if (step.value.piece.type === this.currentPlayer.piece)
  249.           path.push(step.value);
  250.         else
  251.           // This line is not a win for the current player, so
  252.           // break out of this loop
  253.           break;
  254.       };
  255.  
  256.       // If the line we just checked is a win, finish the game
  257.       if (path.length == 3) {
  258.         this.playerWonGame(path);
  259.         break;
  260.       }
  261.     }
  262.  
  263.     // If none of the lines were a win, make sure the game is still winnable
  264.     if (!this.finished)
  265.       this.checkForUnwinnableCondition();
  266.   }
  267.  
  268.   checkForUnwinnableCondition() {
  269.     let allOccupied = true;
  270.     for (let column = 0; column < GRID_SIZE_COLUMNS; column++) {
  271.       let line = this.grid.getLine(column, 0, 0, 1);
  272.       while (true) {
  273.         // Get the next "step" in this line
  274.         let step = line.next();
  275.         if (step.done)
  276.           // There are no more steps, break out of this loop
  277.           break;
  278.         if (step.value.piece.type === 'empty') {
  279.           allOccupied = false;
  280.           break;
  281.         }
  282.       };
  283.      
  284.       if (!allOccupied)
  285.         break;
  286.     };
  287.  
  288.     // If all spaces are occupied, and nobody has won, then
  289.     // the game is over.
  290.     if (allOccupied) {
  291.       this.status.clear();
  292.       this.status.appendText('Nobody wins!');
  293.       this.finished = true;
  294.     }
  295.   }
  296.  
  297.   playerWonGame(path) {
  298.     // Highlight the winning line
  299.     for (let cell of path) {
  300.       cell.highlighted = true;
  301.     }
  302.  
  303.     // Show a victory message
  304.     this.status.clear();
  305.     this.status.appendMany([
  306.       {
  307.         text: this.currentPlayer.displayText.single,
  308.         style: {
  309.           color: this.currentPlayer.color
  310.         },
  311.       },
  312.       { text: ' is the winner!' }
  313.     ]);
  314.  
  315.     // Finish the game
  316.     this.finished = true;
  317.   }
  318.  
  319.   reset() {
  320.     this.status.clear();
  321.     this.grid.reset();
  322.     this.currentPlayerIndex = 0;
  323.     this.finished = false;
  324.     this.beginTurn();
  325.   }
  326. };
  327.  
  328. window.addEventListener('load', (e) => {
  329.   // Create a new TicTacToe object
  330.   let game = new TicTacToe(
  331.     document.querySelector('#game .grid'),
  332.     document.querySelector('#game .status')
  333.   );
  334. });
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement