1

I have a problem with a project that uses React. It's a tic tac toe game and I am trying to implement the computer player.

When the human player clicks on one square (Field-component) this triggers the updateField() function in the Board-component. It also calls the computerMove() function which in turn calls the minimax() function.

The minimax function is recursivly defined. However, I get the following error message, when the code execution reaches the line where minimax(possibleGame) is called.

[ERROR] react-dom Uncaught TypeError: Cannot read property 'minimax' of undefined

Apparently the function is unknown, which I don't understand because I bind it to the Board scope.

class Field extends React.Component{

  render(){
    return(
      <button className='field' onClick={this.props.clickFunc}>{this.props.value}</button>
    );
  }
}

class Board extends React.Component{
  constructor(){
    super();
    this.state = {
      fields: Array(9).fill(null),
    };
    this.minimax = this.minimax.bind(this);
    this.updateField = this.updateField.bind(this);
    this.computerMove = this.computerMove.bind(this);
  }

  updateField(id){
    const fields = this.state.fields.slice();

    // only proceed if field hasn't been taken yet
    if (fields[id] == null && this.props.winner == null){
      fields[id] = this.props.playerSymbol;
      this.setState({fields: fields});

      this.computerMove(fields);
    } 
  }

// check if somebody won the game
  checkWinner(squares){
    const lines = [
    [0, 1, 2],
    [3, 4, 5],
    [6, 7, 8],
    [0, 3, 6],
    [1, 4, 7],
    [2, 5, 8],
    [0, 4, 8],
    [2, 4, 6],
  ];
  for (let i = 0; i < lines.length; i++) {
    const [a, b, c] = lines[i];
    if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
      return squares[a];
    }
  }
  return null;

  }

  computerMove(fields){
    var computerSymbol = (this.props.playerSymbol == "X" ? "O" : "X");
    var game = {fields: fields,turn:computerSymbol};
    var result = this.minimax(game);
    var newBoard = fields.slice();
    newBoard[result[1]] = computerSymbol;
    this.setState({fields: newBoard}); 

    }

  score(game){
    // check for winner
    var result = this.checkWinner(game.fields);
    if (result !== null){
      if (result == this.props.playerSymbol){
        return -10;
      } else{
        return 10;
      }
    }else{
      return 0;
    }
  }

  minimax(game) {
    var score = this.score(game);
    if (score !== 0){
      return score;
    }
    var scores = [];
    var moves = [];

    game.fields.forEach(function(value,index,arr){
      if (value==null){
        var newField = arr.slice();
        newField[index] = game.turn;

        var nextTurn = (game.turn == "X" ? "O" : "X");
        var possibleGame = {fields: newField,turn:nextTurn};
        var result = this.minimax(possibleGame);
        scores.push(result[0]);
        moves.push(index);
      }
    }); 
    if (game.turn == this.props.playerSymbol){
      var max_ind = scores.indexOf(Math.max(...scores));
      return [scores[max_ind],moves[max_ind]];
    } else{
      var min_ind = scores.indexOf(Math.min(...scores));
      return [scores[min_ind],moves[min_ind]];
    }   
  }

  render(){
    var fields = this.state.fields;

    // check if somebody won

    const winner = this.checkWinner(fields);
    if (winner){
      this.props.declareWinner(winner);
    }


    return (
      <div className="animated fadeInUp board-container">
        <div className='row'>
          <Field value={fields[0]} clickFunc={() => this.updateField(0)} />
          <Field value={fields[1]} clickFunc={() => this.updateField(1)}/>
          <Field value={fields[2]} clickFunc={() => this.updateField(2)}/>
        </div>
        <div className='row'>
          <Field value={fields[3]} clickFunc={() => this.updateField(3)} />
          <Field value={fields[4]} clickFunc={() => this.updateField(4)}/>
          <Field value={fields[5]} clickFunc={() => this.updateField(5)}/>
        </div>
        <div className='row'>
          <Field value={fields[6]} clickFunc={() => this.updateField(6)}/>
          <Field value={fields[7]} clickFunc={() => this.updateField(7)}/>
          <Field value={fields[8]} clickFunc={() => this.updateField(8)}/>
        </div>
      </div>
      )
  }
}

The whole code can be tested here: https://codepen.io/miga89/pen/pPqmra

I also tried to come up with a MWE (https://codepen.io/miga89/pen/XRQBjP) However, this example works for some reason.

Thank you for your help!

1
  • 2
    game.fields.forEach((value,index,arr) => { Commented May 28, 2017 at 11:58

2 Answers 2

2

The value of this depends on how and where the function is being called.

In most cases, the value of this is determined by how a function is called. It can't be set by assignment during execution, and it may be different each time the function is called. ES5 introduced the bind method to set the value of a function's this regardless of how it's called, and ES2015 introduced arrow functions whose this is lexically scoped (it is set to the this value of the enclosing execution context). https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/this

You need to explicitly bind this to be used consistently inside the function.

game.fields.forEach(function(value,index,arr){
  if (value==null){
    var newField = arr.slice();
    newField[index] = game.turn;

    var nextTurn = (game.turn == "X" ? "O" : "X");
    var possibleGame = {fields: newField,turn:nextTurn};
    var result = this.minimax(possibleGame);
    scores.push(result[0]);
    moves.push(index);
  }
}.bind(this));

or, use arrow functions:

game.fields.forEach((value,index,arr) => {
  if (value==null){
    var newField = arr.slice();
    newField[index] = game.turn;

    var nextTurn = (game.turn == "X" ? "O" : "X");
    var possibleGame = {fields: newField,turn:nextTurn};
    var result = this.minimax(possibleGame);
    scores.push(result[0]);
    moves.push(index);
  }
});

An arrow function expression has a shorter syntax than a function expression and does not bind its own this, arguments, super, or new.target. These function expressions are best suited for non-method functions, and they cannot be used as constructors. https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Functions/Arrow_functions

Sign up to request clarification or add additional context in comments.

Comments

0

Maintain the rules of recursion: suppose we have a parent child tree then in the bellow you will find an example

In the constructor function:

this.checkSlug = this.checkSlug.bind(this);
this.onViewToBar = this.onViewToBar.bind(this);

your recursive function example:

  checkSlug = (slug, parent) => {
    for (let i = 0; i < parent.length; i++) {
      if (parent[i].children && parent[i].slug !== slug) {
        const value = this.checkSlug(slug, parent[i].children);
        if (value){
          return value;
        }
      } else {
        if (parent[i].slug === slug) {
          return parent[i];
        }
      }
    }
  }

now my caller function

 onViewToBar({ slug }) {
    this.setState({ slug: slug });
    const { cursor, data } = this.state;

    let node =this.checkSlug(slug, data);
  }
```

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.