Search code examples
angulartypescriptchesschessboard.js

Ai chess.js on angular onDrop ugly


Cannot read property 'moves' of undefine

hello, i am currently making a chess game with Angular

hello I can't make artificial intelligence work, the piece stays stuck in the mause

I currently use the chess.js and chessboard library but the problem is that the AI is mainly written in jquery

if I comment thats lines, this working but Ai no working

onDrop: onDrop, onMouseoutSquare: onMouseoutSquare, onMouseoverSquare: onMouseoverSquare, onSnapEnd: onSnapEnd,

Angular code

import { Component, OnInit } from '@angular/core';
import * as ChessBoard from 'chessboardjs/www/js/chessboard';
declare var $: any;

declare var ChessBoard: any;
declare var Chess: any;
@Component({
  selector: 'app-chess',
  templateUrl: './chess.component.html',
  styleUrls: ['./chess.component.scss']
})
export class ChessComponent implements OnInit {
  board: any;
  game: any;

  constructor() { }



  ngOnInit() {
////////////////////////// 7


      let minimaxDepth = 2;
      initGame();

    function setDepth(depth){
      console.log("este es el "+depth);
      document.getElementById('difficulty').style.display = 'none';
      document.getElementById('chessboard').style.display = 'block';
      document.getElementById('restart').style.display = 'block';
      console.log(depth);
      minimaxDepth = depth;
    }

      function initGame(){
       //document.getElementById('chessboard').style.display = 'none';
        document.getElementById('gameover').style.display = 'none';
        document.getElementById('restart').style.display = 'none';
        document.getElementById('difficulty').style.display = 'block';

        $('.easy').click(function(){
          setDepth(0);
        });
        $('.medium').click(function(){
          setDepth(1);
        });
        $('.hard').click(function(){
          setDepth(2);
        });


      }


      $('.restartGame').click(function(){
        this.board.clear();
        this.board.start();
        this.game.reset();
        initGame();
      });



        // game = new Chess();
    this.game = new Chess();

      let removeGreySquares = function() {
        $('#chessboard .square-55d63').css('background', '');
      };


      let greySquare = function(square) {
        let squareEl = $('#chessboard .square-' + square);

        let background = '#a9a9a9';
        if (squareEl.hasClass('black-3c85d') === true) {
          background = '#696969';
        }

        squareEl.css('background', background);
      };




      // uses the minimax algorithm with alpha beta pruning to caculate the best move
      let calculateBestMove = function() {

        let possibleNextMoves = this.game.moves();
        let bestMove = -9999;
        let bestMoveFound;

        for (let i = 0; i < possibleNextMoves.length; i++) {
          let possibleNextMove = possibleNextMoves[i];
          this.game.move(possibleNextMove);
          let value = minimax(minimaxDepth, -10000, 10000, false);
          this.game.undo();
          if (value >= bestMove) {
            bestMove = value;
            bestMoveFound = possibleNextMove;
          }
        }
        return bestMoveFound;
      };


      // minimax with alhpha-beta pruning and search depth d = 3 levels
      let minimax = function(depth, alpha, beta, isMaximisingPlayer) {
        if (depth === 0) {
          return -evaluateBoard(this.game.board());
        }

        let possibleNextMoves = this.game.moves();
        let numPossibleMoves = possibleNextMoves.length;
        let bestMove = -9999;
        if (isMaximisingPlayer) {

          for (let i = 0; i < numPossibleMoves; i++) {
            this.game.move(possibleNextMoves[i]);
            bestMove = Math.max(bestMove, minimax(depth - 1, alpha, beta, !isMaximisingPlayer));
            this.game.undo();
            alpha = Math.max(alpha, bestMove);
            if (beta <= alpha){
              return bestMove;
            }
          }

        } else {
          let bestMove = 9999;
          for (let i = 0; i < numPossibleMoves; i++) {
            this.game.move(possibleNextMoves[i]);
            bestMove = Math.min(bestMove, minimax(depth - 1, alpha, beta, !isMaximisingPlayer));
            this.game.undo();
            beta = Math.min(beta, bestMove);
            if (beta <= alpha){
              return bestMove;
            }
          }
        }

        return bestMove;
      };


      // the evaluation function for minimax
      let evaluateBoard = function(board) {
        let totalEvaluation = 0;
        for (let i = 0; i < 8; i++) {
          for (let j = 0; j < 8; j++) {
            totalEvaluation = totalEvaluation + getPieceValue(board[i][j], i, j);
          }
        }
        return totalEvaluation;
      };


      let reverseArray = function(array) {
        return array.slice().reverse();
      };

      let whitePawnEval =
        [
          [0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0],
          [5.0,  5.0,  5.0,  5.0,  5.0,  5.0,  5.0,  5.0],
          [1.0,  1.0,  2.0,  3.0,  3.0,  2.0,  1.0,  1.0],
          [0.5,  0.5,  1.0,  2.5,  2.5,  1.0,  0.5,  0.5],
          [0.0,  0.0,  0.0,  2.0,  2.0,  0.0,  0.0,  0.0],
          [0.5, -0.5, -1.0,  0.0,  0.0, -1.0, -0.5,  0.5],
          [0.5,  1.0,  1.0,  -2.0, -2.0,  1.0,  1.0,  0.5],
          [0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0]
        ];

      let blackPawnEval = reverseArray(whitePawnEval);

      let knightEval =
        [
          [-5.0, -4.0, -3.0, -3.0, -3.0, -3.0, -4.0, -5.0],
          [-4.0, -2.0,  0.0,  0.0,  0.0,  0.0, -2.0, -4.0],
          [-3.0,  0.0,  1.0,  1.5,  1.5,  1.0,  0.0, -3.0],
          [-3.0,  0.5,  1.5,  2.0,  2.0,  1.5,  0.5, -3.0],
          [-3.0,  0.0,  1.5,  2.0,  2.0,  1.5,  0.0, -3.0],
          [-3.0,  0.5,  1.0,  1.5,  1.5,  1.0,  0.5, -3.0],
          [-4.0, -2.0,  0.0,  0.5,  0.5,  0.0, -2.0, -4.0],
          [-5.0, -4.0, -3.0, -3.0, -3.0, -3.0, -4.0, -5.0]
        ];

      let whiteBishopEval = [
        [ -2.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -2.0],
        [ -1.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0, -1.0],
        [ -1.0,  0.0,  0.5,  1.0,  1.0,  0.5,  0.0, -1.0],
        [ -1.0,  0.5,  0.5,  1.0,  1.0,  0.5,  0.5, -1.0],
        [ -1.0,  0.0,  1.0,  1.0,  1.0,  1.0,  0.0, -1.0],
        [ -1.0,  1.0,  1.0,  1.0,  1.0,  1.0,  1.0, -1.0],
        [ -1.0,  0.5,  0.0,  0.0,  0.0,  0.0,  0.5, -1.0],
        [ -2.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -2.0]
      ];

      let blackBishopEval = reverseArray(whiteBishopEval);

      let whiteRookEval = [
        [  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0],
        [  0.5,  1.0,  1.0,  1.0,  1.0,  1.0,  1.0,  0.5],
        [ -0.5,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0, -0.5],
        [ -0.5,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0, -0.5],
        [ -0.5,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0, -0.5],
        [ -0.5,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0, -0.5],
        [ -0.5,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0, -0.5],
        [  0.0,   0.0, 0.0,  0.5,  0.5,  0.0,  0.0,  0.0]
      ];

      let blackRookEval = reverseArray(whiteRookEval);

      let evalQueen = [
        [ -2.0, -1.0, -1.0, -0.5, -0.5, -1.0, -1.0, -2.0],
        [ -1.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0, -1.0],
        [ -1.0,  0.0,  0.5,  0.5,  0.5,  0.5,  0.0, -1.0],
        [ -0.5,  0.0,  0.5,  0.5,  0.5,  0.5,  0.0, -0.5],
        [  0.0,  0.0,  0.5,  0.5,  0.5,  0.5,  0.0, -0.5],
        [ -1.0,  0.5,  0.5,  0.5,  0.5,  0.5,  0.0, -1.0],
        [ -1.0,  0.0,  0.5,  0.0,  0.0,  0.0,  0.0, -1.0],
        [ -2.0, -1.0, -1.0, -0.5, -0.5, -1.0, -1.0, -2.0]
      ];

      let whiteKingEval = [

        [ -3.0, -4.0, -4.0, -5.0, -5.0, -4.0, -4.0, -3.0],
        [ -3.0, -4.0, -4.0, -5.0, -5.0, -4.0, -4.0, -3.0],
        [ -3.0, -4.0, -4.0, -5.0, -5.0, -4.0, -4.0, -3.0],
        [ -3.0, -4.0, -4.0, -5.0, -5.0, -4.0, -4.0, -3.0],
        [ -2.0, -3.0, -3.0, -4.0, -4.0, -3.0, -3.0, -2.0],
        [ -1.0, -2.0, -2.0, -2.0, -2.0, -2.0, -2.0, -1.0],
        [  2.0,  2.0,  0.0,  0.0,  0.0,  0.0,  2.0,  2.0 ],
        [  2.0,  3.0,  1.0,  0.0,  0.0,  1.0,  3.0,  2.0 ]
      ];

      let blackKingEval = reverseArray(whiteKingEval);


      let getPieceValue = function(piece, x, y) {
        if (piece === null) {
          return 0;
        }

        let absoluteValue = getAbsoluteValue(piece, piece.color === 'w', x , y);

        if (piece.color === 'w'){
          return absoluteValue;
        } else {
          return -absoluteValue;
        }
      };


      let getAbsoluteValue = function(piece, isWhite, x , y) {
        if (piece.type === 'p') {
          return 10 + ( isWhite ? whitePawnEval[y][x] : blackPawnEval[y][x] );
        } else if (piece.type === 'r') {
          return 50 + ( isWhite ? whiteRookEval[y][x] : blackRookEval[y][x] );
        } else if (piece.type === 'n') {
          return 30 + knightEval[y][x];
        } else if (piece.type === 'b') {
          return 30 + ( isWhite ? whiteBishopEval[y][x] : blackBishopEval[y][x] );
        } else if (piece.type === 'q') {
          return 90 + evalQueen[y][x];
        } else if (piece.type === 'k') {
          return 900 + ( isWhite ? whiteKingEval[y][x] : blackKingEval[y][x] );
        }
      };


      let makeAImove = function() {
        let bestMove = calculateBestMove();
        this.game.move(bestMove);
        this.board.position(this.game.fen());
      };


      let onDrop = function(source, target) {
        removeGreySquares();

        // see if the move is legal
        var move = this.game.move({
          from: source,
          to: target,
          promotion: 'q'
        });

        // illegal move
        if (move === null) { return 'snapback'; }

        // make legal move for black AI player
        window.setTimeout(makeAImove, 250);
      };


    var onMouseoverSquare = function(square, piece) {
      // get list of possible moves for this square
      var moves = this.game.moves({
        square: square,
        verbose: true
      });

      // exit if there are no moves available for this square
      if (moves.length === 0) return;

      // highlight the square they moused over
      greySquare(square);

      // highlight the possible squares for this piece
      for (var i = 0; i < moves.length; i++) {
        greySquare(moves[i].to);
      }
    };

      let onMouseoutSquare = function(square, piece) {
        removeGreySquares();
      };


      // update the board position after the piece snap
      // for castling, en passant, pawn promotion
      let onSnapEnd = function() {
        this.board.position(this.game.fen());
      };

     /* var cfg = {
        draggable: true,
        position: 'start',
        onDragStart: onDragStart,
        onDrop: onDrop,
        onMouseoutSquare: onMouseoutSquare,
        onMouseoverSquare: onMouseoverSquare,
        onSnapEnd: onSnapEnd
      };
      board = ChessBoard('board', cfg);*/


///////////////////////////////
      let config = {
      orientation: 'white',
      draggable: true,
      position: 'start',
      moveSpeed: 'fast',
      snapbackSpeed: 100,
      snapSpeed: 100,
      pieceTheme: 'img/chesspieces/wikipedia/{piece}.png',
      showNotation: false,
        onDrop: onDrop,
        onMouseoutSquare: onMouseoutSquare,
        onMouseoverSquare: onMouseoverSquare,
        onSnapEnd: onSnapEnd,
      onDragStart: this.onDragStart.bind( this )
    };


      this.board = new ChessBoard( 'chessboard', config );

      //this.game = new Chess();

      console.log('color of g5: ' + this.game.square_color('g5'));

     //this.board.move('e2-e4');

      this.updateStatus();

  }


  // do not pick up pieces if the game is over
  // only pick up pieces for White
/*
  var onDragStart = (source, piece, position, orientation) => {
    if (this.game.in_checkmate() === true || this.game.in_draw() === true || this.game.game_over() === true ) {
      $('#gameover').show();
      $("#gameover").html('Game over!');
      return false;
    }
  };
*/



  onDragStart(source, piece, position, orientation) {
    if (this.game.in_checkmate() === true || this.game.in_draw() === true || this.game.game_over() === true ) {
      document.getElementById('gameover').style.display="block";
      $('#gameover').html('Game over!');
      return false;
    }
    /*// do not pick up pieces if the game is over
    if (this.game.game_over()) {return false};

    // only pick up pieces for the side to move
    if ((this.game.turn() === 'w' && piece.search(/^b/) !== -1) ||
      (this.game.turn() === 'b' && piece.search(/^w/) !== -1)) {
      return false
    };

    return true;*/
  }


  updateStatus() {
    let status = '';

    let moveColor = 'White';
    if (this.game.turn() === 'b') {
      moveColor = 'Black';
    }

    // checkmate?
    if (this.game.in_checkmate()) {
      status = 'Game over, ' + moveColor + ' is in checkmate.';
    }

    // draw?
    else if (this.game.in_draw()) {
      status = 'Game over, drawn position';
    }

    // game still on
    else {
      status = moveColor + ' to move';

      // check?
      if (this.game.in_check()) {
        status += ', ' + moveColor + ' is in check';
      }
    }

    console.log(status);
  }




/*
 dropOffBoard: 'snapback', // this is the default
      position: 'start'
* */
}
<div class=" alert alerta-game alert-warning alert-dismissible fade show" role="alert" >

  <p>
    <strong>Attention: </strong>this game is in development.
  </p>
</div>



<div class="menu" id="difficulty">
  <br>
  <h1>Select Difficulty:</h1>
  <hr><br>
  <button type="button" class="btn easy btn-success btn-xlarge">Easy</button>
  <br><br>
  <button type="button" class="btn medium btn-warning btn-xlarge">Medium</button>
  <br><br>
  <button type="button" class="btn hard btn-danger btn-xlarge">Hard</button>
</div>

<div id="chessboard" role="main"style="margin:0px auto; max-width: 600px;"></div>

<div id="gameover" class="alert alert-primary hide" role="alert"><strong></strong></div>
<div id="restart" class="restartGame">
  <button type="button" class="btn medium btn-danger btn-xlarge">Restart</button>
</div>


Solution

  • Seems you have just copy-pasted a Javascript file inside a component...and well...that is no going to work at all.

    1. Try to avoid “getElementById”, these are Vanilla JS while you could be using a Reactive Form (Angular way)

    2. You are handling “click” event as vanilla JS you should use an Angular approach, through a method attached to the element which is executed when the user performs a “click” in the view.

    3. Same happens with your “onDrag” and “orDrop”...etc.

    4. Dont use anonymous functions but use Fat Arrow “=>”, it avoids the need of binding this (most of the times).

    TLTR; Vanilla JS wont work if you just throw its code in the ngOnInit method..You need to adapt the code, mainly the logic about Events controlling and View redrawing. Seems you are trying to port/wrap a JS library which is mainly Event Controlling and View rendering....(aside of 2-3 AI methods) so this is not going to be easy.