最新消息:Welcome to the puzzle paradise for programmers! Here, a well-designed puzzle awaits you. From code logic puzzles to algorithmic challenges, each level is closely centered on the programmer's expertise and skills. Whether you're a novice programmer or an experienced tech guru, you'll find your own challenges on this site. In the process of solving puzzles, you can not only exercise your thinking skills, but also deepen your understanding and application of programming knowledge. Come to start this puzzle journey full of wisdom and challenges, with many programmers to compete with each other and show your programming wisdom! Translated with DeepL.com (free version)

javascript - Uncaught TypeError: Cannot read property 'focus' of undefined, even though focus is being called -

matteradmin5PV0评论

I'm trying to make an AI for a puter that plays tic-tac-toe out of javascript, but I'm running into a console error that says that it can't read the property focus of undefined. My code:

window.addEventListener('load', () => {
  // Determine whether you are going first
  const humanTurnFirst = Math.random() >= 0.5;
  /**
   * Get an array of the text content of each of the tic-tac-toe buttons
   * @returns {Array} Array of the text content of each square, from top-left to bottom-right.
  */

  const getLayout = () => {
    // Array of buttons ordered from top-left to bottom right
    const buttons = [
      document.getElementsByClassName('corner-top-left')[0],
      document.getElementsByClassName('edge-top')[0],
      document.getElementsByClassName('corner-top-right')[0],
      document.getElementsByClassName('edge-left')[0],
      document.getElementsByClassName('center-button')[0],
      document.getElementsByClassName('edge-right')[0],
      document.getElementsByClassName('corner-bottom-left')[0],
      document.getElementsByClassName('edge-bottom')[0],
      document.getElementsByClassName('corner-bottom-right')[0],
    ];
    const layout = [];
    buttons.forEach(button => layout.push(button.innerText));
    return layout;
  };
  /**
   * Make the puter play a square
   * @param {Node} button The square to play
   */

  const autoClick = (button) => {
    console.log('button', button);
    const $turn = document.getElementsByClassName('turn')[0];
    $turn.innerText = 'Not your turn yet...';
    const $allButtons = [...document.getElementsByClassName('button')];
    const $allDisableableButtons = $allButtons
      .filter(
        element => element !== button
        && !element.disabled,
      );
    $allDisableableButtons.forEach((disableableButton) => {
      const thisButton = disableableButton;
      thisButton.disabled = true;
    });
    console.log('button', button);
    button.focus();
    setTimeout(() => {
      button.click();
      $allDisableableButtons.forEach((disableableButton) => {
        const thisButton = disableableButton;
        thisButton.disabled = false;
        $turn.innerText = 'Try clicking an empty space.';
      });
    }, 500);
  };
  /**
   * Calculate the best square for the puter to play.
   * @param {Array.<Node>} layout Array of the text of each square, from top-left to bottom right.
   * @param {Node|Boolean} previous The last move that you've made.
   */
  const puterTurn = (layout, previous, localHumanTurnFirst) => {
    const buttons = [
      document.getElementsByClassName('corner-top-left')[0],
      document.getElementsByClassName('edge-top')[0],
      document.getElementsByClassName('corner-top-right')[0],
      document.getElementsByClassName('edge-left')[0],
      document.getElementsByClassName('center-button')[0],
      document.getElementsByClassName('edge-right')[0],
      document.getElementsByClassName('corner-bottom-left')[0],
      document.getElementsByClassName('edge-bottom')[0],
      document.getElementsByClassName('corner-bottom-right')[0],
    ];
    const $corners = [...document.getElementsByClassName('corner')];
    // If there is no previous move, the puter goes first with a random corner.
    if (!previous) {
      const randomBelow4 = Math.floor(Math.random() * 4);
      const randomCorner = $corners[randomBelow4];
      autoClick(randomCorner);
      /* If the puter is going first,
        has already filled out a random corner,
        and there is nothing in the center,
        it will place another X in one of the adgacent corners.
      */
    } else if (
      !localHumanTurnFirst
      && layout.filter(element => element === 'X').length === 1
      && previous !== buttons[4]
    ) {
      const filledOutCorner = buttons.filter(element => element.innerText === 'X')[0];
      const diagonalCorner = document.getElementsByClassName(filledOutCorner.className
        .split(/\s+/)[2]
        .replace(/(left|right)/, match => (match === 'left' ? 'right' : 'left'))
        .replace(/(top|bottom)/, match => (match === 'top' ? 'bottom' : 'top')))[0];
      const emptyCorners = $corners.filter(corner => corner.innerText === 'Empty');
      const adjacentCorners = emptyCorners.filter(element => element !== diagonalCorner);
      const potentialCorners = adjacentCorners
        .filter(
          corner => document.getElementsByClassName(`edge-${corner.className.split(/\s+/)[2].split('-')[1]}`)[0].innerText === 'Empty'
            && document.getElementsByClassName(`edge-${corner.className.split(/\s+/)[2].split('-')[2]}`)[0].innerText === 'Empty',
        );
      const randomPotentialCorner = potentialCorners[
        Math.floor(
          Math.random()
          * potentialCorners.length,
        )
      ];
      autoClick(randomPotentialCorner);
    } else if (
      !localHumanTurnFirst
      && buttons.filter(button => button.innerText === 'X' && button.className.split(/\s+/).includes('corner')).length === 2
      && buttons.filter(button => button.innerText === 'O' && [...document.getElementsByClassName(`corner-${button.className.replace('button edge edge-', '')}`)].every(element => element.innerText === 'X')).length === 1
    ) {
      autoClick(buttons[4]);
    }
  };
  /**
   * Add event listener for squares
   * @param {Boolean} localHumanTurnFirst Whether you go first.
   */
  const squaresOnClick = (localHumanTurnFirst, isHumanTurn) => {
    const humanLetter = localHumanTurnFirst ? 'X' : 'O';
    const puterLetter = localHumanTurnFirst ? 'O' : 'X';
    const $squares = [...document.getElementsByClassName('button')];
    $squares.forEach((square) => {
      const thisSquare = square;
      square.addEventListener('click', () => {
        if (isHumanTurn) {
          thisSquare.innerText = humanLetter;
          puterTurn(getLayout(), thisSquare, localHumanTurnFirst);
          squaresOnClick(localHumanTurnFirst, false);
        } else {
          thisSquare.innerText = puterLetter;
          squaresOnClick(localHumanTurnFirst, true);
        }
        thisSquare.disabled = true;
      });
    });
  };
  /**
   * Turn the wele screen into the game screen.
   * @param {Boolean} localHumanTurnFirst Whether you go first.
   */
  const spawnSquares = (localHumanTurnFirst) => {
    const $turn = document.getElementsByClassName('turn')[0];
    const $mainGame = document.getElementsByClassName('main-game')[0];
    $turn.innerText = 'Try clicking an empty space.';
    $mainGame.className = 'main-game dp-4 tic-tac-toe';
    $mainGame.setAttribute('aria-label', 'Tic-tac-toe grid');
    $mainGame.innerHTML = `
      <button class="button corner corner-top-left corner-top corner-left">Empty</button>
      <button class="button edge edge-top">Empty</button>
      <button class="button corner corner-top-right corner-top corner-right">Empty</button>
      <button class="button edge edge-left">Empty</button>
      <button class="button center-button">Empty</button>
      <button class="button edge edge-right">Empty</button>
      <button class="button corner corner-bottom-left corner-bottom corner-left">Empty</button>
      <button class="button edge edge-bottom">Empty</button>
      <button class="button corner corner-bottom-right corner-bottom corner-right">Empty</button>
    `;
    squaresOnClick(localHumanTurnFirst, localHumanTurnFirst);
    if (!localHumanTurnFirst) {
      puterTurn(getLayout(), false, localHumanTurnFirst);
    }
  };
  /**
   * Create the button that starts the game.
   */
  const weleButton = (localHumanTurnFirst) => {
    const $weleButton = document.getElementsByClassName('start-button')[0];
    $weleButton.addEventListener('click', () => spawnSquares(localHumanTurnFirst));
  };
  /**
   * Turn the main game into the wele screen.
   * @param {Boolean} localHumanTurnFirst Whether you go first.
   */
  const wele = (localHumanTurnFirst) => {
    const $mainGame = document.getElementsByClassName('main-game')[0];
    const $turn = document.getElementsByClassName('turn')[0];
    $turn.innerText = 'Wele!';
    $mainGame.className = 'main-game dp-4 wele center';
    $mainGame.innerHTML = `
    <section class="wele-section">
      <h2 class="wele-heading">Wele to unbeatable tic-tac-toe!</h2>
      <p class="wele-text">
        According to random chance, your turn has already been chosen
        as ${localHumanTurnFirst ? 'first (with an X)' : 'second (with an O)'}, which 
        means that the puter is going 
        ${localHumanTurnFirst ? 'second (with an O)' : 'first (with an X)'}. <strong>
          Press the start button to start the game!</strong
        >
      </p>
    </section>
    <button class="start-button button">Start</button>
  `;
    weleButton(localHumanTurnFirst);
  };
  wele(humanTurnFirst);
});

When I call puterTurn when the first elseif condition is true, I get this weird output in the console:

The problem is in these lines, I think:

    } else if (
  !localHumanTurnFirst
  && layout.filter(element => element === 'X').length === 1
  && previous !== buttons[4]
) {
  const filledOutCorner = buttons.filter(element => element.innerText === 'X')[0];
  const diagonalCorner = document.getElementsByClassName(filledOutCorner.className
    .split(/\s+/)[2]
    .replace(/(left|right)/, match => (match === 'left' ? 'right' : 'left'))
    .replace(/(top|bottom)/, match => (match === 'top' ? 'bottom' : 'top')))[0];
  const emptyCorners = $corners.filter(corner => corner.innerText === 'Empty');
  const adjacentCorners = emptyCorners.filter(element => element !== diagonalCorner);
  const potentialCorners = adjacentCorners
    .filter(
      corner => document.getElementsByClassName(`edge-${corner.className.split(/\s+/)[2].split('-')[1]}`)[0].innerText === 'Empty'
        && document.getElementsByClassName(`edge-${corner.className.split(/\s+/)[2].split('-')[2]}`)[0].innerText === 'Empty',
    );
  const randomPotentialCorner = potentialCorners[
    Math.floor(
      Math.random()
      * potentialCorners.length,
    )
  ];
  autoClick(randomPotentialCorner);
} else if (
  !localHumanTurnFirst
  && buttons.filter(button => button.innerText === 'X' && button.className.split(/\s+/).includes('corner')).length === 2
  && buttons.filter(button => button.innerText === 'O' && [...document.getElementsByClassName(`corner-${button.className.replace('button edge edge-', '')}`)].every(element => element.innerText === 'X')).length === 1
) {
  autoClick(buttons[4]);
}

It appears like autoClick is being called thrice. This doesn't appear to be affecting my actual code, but I think console errors are pretty annoying, and I want to get to the bottom of this. Thanks so much for your time, and I'm sorry if I'm being really vague.

EDIT: console.log('button', button)

I'm trying to make an AI for a puter that plays tic-tac-toe out of javascript, but I'm running into a console error that says that it can't read the property focus of undefined. My code:

window.addEventListener('load', () => {
  // Determine whether you are going first
  const humanTurnFirst = Math.random() >= 0.5;
  /**
   * Get an array of the text content of each of the tic-tac-toe buttons
   * @returns {Array} Array of the text content of each square, from top-left to bottom-right.
  */

  const getLayout = () => {
    // Array of buttons ordered from top-left to bottom right
    const buttons = [
      document.getElementsByClassName('corner-top-left')[0],
      document.getElementsByClassName('edge-top')[0],
      document.getElementsByClassName('corner-top-right')[0],
      document.getElementsByClassName('edge-left')[0],
      document.getElementsByClassName('center-button')[0],
      document.getElementsByClassName('edge-right')[0],
      document.getElementsByClassName('corner-bottom-left')[0],
      document.getElementsByClassName('edge-bottom')[0],
      document.getElementsByClassName('corner-bottom-right')[0],
    ];
    const layout = [];
    buttons.forEach(button => layout.push(button.innerText));
    return layout;
  };
  /**
   * Make the puter play a square
   * @param {Node} button The square to play
   */

  const autoClick = (button) => {
    console.log('button', button);
    const $turn = document.getElementsByClassName('turn')[0];
    $turn.innerText = 'Not your turn yet...';
    const $allButtons = [...document.getElementsByClassName('button')];
    const $allDisableableButtons = $allButtons
      .filter(
        element => element !== button
        && !element.disabled,
      );
    $allDisableableButtons.forEach((disableableButton) => {
      const thisButton = disableableButton;
      thisButton.disabled = true;
    });
    console.log('button', button);
    button.focus();
    setTimeout(() => {
      button.click();
      $allDisableableButtons.forEach((disableableButton) => {
        const thisButton = disableableButton;
        thisButton.disabled = false;
        $turn.innerText = 'Try clicking an empty space.';
      });
    }, 500);
  };
  /**
   * Calculate the best square for the puter to play.
   * @param {Array.<Node>} layout Array of the text of each square, from top-left to bottom right.
   * @param {Node|Boolean} previous The last move that you've made.
   */
  const puterTurn = (layout, previous, localHumanTurnFirst) => {
    const buttons = [
      document.getElementsByClassName('corner-top-left')[0],
      document.getElementsByClassName('edge-top')[0],
      document.getElementsByClassName('corner-top-right')[0],
      document.getElementsByClassName('edge-left')[0],
      document.getElementsByClassName('center-button')[0],
      document.getElementsByClassName('edge-right')[0],
      document.getElementsByClassName('corner-bottom-left')[0],
      document.getElementsByClassName('edge-bottom')[0],
      document.getElementsByClassName('corner-bottom-right')[0],
    ];
    const $corners = [...document.getElementsByClassName('corner')];
    // If there is no previous move, the puter goes first with a random corner.
    if (!previous) {
      const randomBelow4 = Math.floor(Math.random() * 4);
      const randomCorner = $corners[randomBelow4];
      autoClick(randomCorner);
      /* If the puter is going first,
        has already filled out a random corner,
        and there is nothing in the center,
        it will place another X in one of the adgacent corners.
      */
    } else if (
      !localHumanTurnFirst
      && layout.filter(element => element === 'X').length === 1
      && previous !== buttons[4]
    ) {
      const filledOutCorner = buttons.filter(element => element.innerText === 'X')[0];
      const diagonalCorner = document.getElementsByClassName(filledOutCorner.className
        .split(/\s+/)[2]
        .replace(/(left|right)/, match => (match === 'left' ? 'right' : 'left'))
        .replace(/(top|bottom)/, match => (match === 'top' ? 'bottom' : 'top')))[0];
      const emptyCorners = $corners.filter(corner => corner.innerText === 'Empty');
      const adjacentCorners = emptyCorners.filter(element => element !== diagonalCorner);
      const potentialCorners = adjacentCorners
        .filter(
          corner => document.getElementsByClassName(`edge-${corner.className.split(/\s+/)[2].split('-')[1]}`)[0].innerText === 'Empty'
            && document.getElementsByClassName(`edge-${corner.className.split(/\s+/)[2].split('-')[2]}`)[0].innerText === 'Empty',
        );
      const randomPotentialCorner = potentialCorners[
        Math.floor(
          Math.random()
          * potentialCorners.length,
        )
      ];
      autoClick(randomPotentialCorner);
    } else if (
      !localHumanTurnFirst
      && buttons.filter(button => button.innerText === 'X' && button.className.split(/\s+/).includes('corner')).length === 2
      && buttons.filter(button => button.innerText === 'O' && [...document.getElementsByClassName(`corner-${button.className.replace('button edge edge-', '')}`)].every(element => element.innerText === 'X')).length === 1
    ) {
      autoClick(buttons[4]);
    }
  };
  /**
   * Add event listener for squares
   * @param {Boolean} localHumanTurnFirst Whether you go first.
   */
  const squaresOnClick = (localHumanTurnFirst, isHumanTurn) => {
    const humanLetter = localHumanTurnFirst ? 'X' : 'O';
    const puterLetter = localHumanTurnFirst ? 'O' : 'X';
    const $squares = [...document.getElementsByClassName('button')];
    $squares.forEach((square) => {
      const thisSquare = square;
      square.addEventListener('click', () => {
        if (isHumanTurn) {
          thisSquare.innerText = humanLetter;
          puterTurn(getLayout(), thisSquare, localHumanTurnFirst);
          squaresOnClick(localHumanTurnFirst, false);
        } else {
          thisSquare.innerText = puterLetter;
          squaresOnClick(localHumanTurnFirst, true);
        }
        thisSquare.disabled = true;
      });
    });
  };
  /**
   * Turn the wele screen into the game screen.
   * @param {Boolean} localHumanTurnFirst Whether you go first.
   */
  const spawnSquares = (localHumanTurnFirst) => {
    const $turn = document.getElementsByClassName('turn')[0];
    const $mainGame = document.getElementsByClassName('main-game')[0];
    $turn.innerText = 'Try clicking an empty space.';
    $mainGame.className = 'main-game dp-4 tic-tac-toe';
    $mainGame.setAttribute('aria-label', 'Tic-tac-toe grid');
    $mainGame.innerHTML = `
      <button class="button corner corner-top-left corner-top corner-left">Empty</button>
      <button class="button edge edge-top">Empty</button>
      <button class="button corner corner-top-right corner-top corner-right">Empty</button>
      <button class="button edge edge-left">Empty</button>
      <button class="button center-button">Empty</button>
      <button class="button edge edge-right">Empty</button>
      <button class="button corner corner-bottom-left corner-bottom corner-left">Empty</button>
      <button class="button edge edge-bottom">Empty</button>
      <button class="button corner corner-bottom-right corner-bottom corner-right">Empty</button>
    `;
    squaresOnClick(localHumanTurnFirst, localHumanTurnFirst);
    if (!localHumanTurnFirst) {
      puterTurn(getLayout(), false, localHumanTurnFirst);
    }
  };
  /**
   * Create the button that starts the game.
   */
  const weleButton = (localHumanTurnFirst) => {
    const $weleButton = document.getElementsByClassName('start-button')[0];
    $weleButton.addEventListener('click', () => spawnSquares(localHumanTurnFirst));
  };
  /**
   * Turn the main game into the wele screen.
   * @param {Boolean} localHumanTurnFirst Whether you go first.
   */
  const wele = (localHumanTurnFirst) => {
    const $mainGame = document.getElementsByClassName('main-game')[0];
    const $turn = document.getElementsByClassName('turn')[0];
    $turn.innerText = 'Wele!';
    $mainGame.className = 'main-game dp-4 wele center';
    $mainGame.innerHTML = `
    <section class="wele-section">
      <h2 class="wele-heading">Wele to unbeatable tic-tac-toe!</h2>
      <p class="wele-text">
        According to random chance, your turn has already been chosen
        as ${localHumanTurnFirst ? 'first (with an X)' : 'second (with an O)'}, which 
        means that the puter is going 
        ${localHumanTurnFirst ? 'second (with an O)' : 'first (with an X)'}. <strong>
          Press the start button to start the game!</strong
        >
      </p>
    </section>
    <button class="start-button button">Start</button>
  `;
    weleButton(localHumanTurnFirst);
  };
  wele(humanTurnFirst);
});

When I call puterTurn when the first elseif condition is true, I get this weird output in the console:

The problem is in these lines, I think:

    } else if (
  !localHumanTurnFirst
  && layout.filter(element => element === 'X').length === 1
  && previous !== buttons[4]
) {
  const filledOutCorner = buttons.filter(element => element.innerText === 'X')[0];
  const diagonalCorner = document.getElementsByClassName(filledOutCorner.className
    .split(/\s+/)[2]
    .replace(/(left|right)/, match => (match === 'left' ? 'right' : 'left'))
    .replace(/(top|bottom)/, match => (match === 'top' ? 'bottom' : 'top')))[0];
  const emptyCorners = $corners.filter(corner => corner.innerText === 'Empty');
  const adjacentCorners = emptyCorners.filter(element => element !== diagonalCorner);
  const potentialCorners = adjacentCorners
    .filter(
      corner => document.getElementsByClassName(`edge-${corner.className.split(/\s+/)[2].split('-')[1]}`)[0].innerText === 'Empty'
        && document.getElementsByClassName(`edge-${corner.className.split(/\s+/)[2].split('-')[2]}`)[0].innerText === 'Empty',
    );
  const randomPotentialCorner = potentialCorners[
    Math.floor(
      Math.random()
      * potentialCorners.length,
    )
  ];
  autoClick(randomPotentialCorner);
} else if (
  !localHumanTurnFirst
  && buttons.filter(button => button.innerText === 'X' && button.className.split(/\s+/).includes('corner')).length === 2
  && buttons.filter(button => button.innerText === 'O' && [...document.getElementsByClassName(`corner-${button.className.replace('button edge edge-', '')}`)].every(element => element.innerText === 'X')).length === 1
) {
  autoClick(buttons[4]);
}

It appears like autoClick is being called thrice. This doesn't appear to be affecting my actual code, but I think console errors are pretty annoying, and I want to get to the bottom of this. Thanks so much for your time, and I'm sorry if I'm being really vague.

EDIT: console.log('button', button)

Share Improve this question edited Jun 20, 2020 at 9:12 CommunityBot 11 silver badge asked Mar 2, 2019 at 17:11 fifn2fifn2 3822 gold badges6 silver badges16 bronze badges 6
  • can you check the line 43, are you calling the actual class name. Check for any typo – Beginner Commented Mar 2, 2019 at 17:17
  • basically the error is something you are trying to access in the dom but which is undefined, just check by putting a console.log() of the element, and not able to identify where are you calling the focus , can you post the line where you are getting error – Beginner Commented Mar 2, 2019 at 17:20
  • @DILEEPTHOMAS I put a console.log(button) in the autoClick function in various places. – fifn2 Commented Mar 2, 2019 at 17:26
  • what it is giving undefined or what console.log("button", button). Even if its undefined you will be able to understand easily – Beginner Commented Mar 2, 2019 at 17:29
  • @DILEEPTHOMAS I don't understand. Do you want me to put "button" before the button so that people don't get confused? – fifn2 Commented Mar 2, 2019 at 17:35
 |  Show 1 more ment

1 Answer 1

Reset to default 0

I've found out why this is.

I need to make my autoClick function return immediately if the button is falsy (or undefined) with this magical one liner:

 if (!button) { return; }

Articles related to this article

Post a comment

comment list (0)

  1. No comments so far