未捕获类型错误:无法在字符串“”上创建属性“35”

问题描述 投票:0回答:1

我目前正在使用极小极大算法制作四连人工智能。我已经完成了棋盘和获胜/平局检查,并完成了人工智能的实施。然而,当我去测试它时,我收到以下错误:

Uncaught TypeError: Cannot create property '35' on string ''
    at Board.insert (board.js:394:26)
    at player.js:29:15
    at Array.forEach (<anonymous>)
    at Player.getBestMove (player.js:27:33)
    at script.js:8:20

我已经浏览了我能找到的所有类似问题,但谷歌并没有提供更多帮助。我的大部分功能都基于 这个 Tic-Tac-Toe AI 教程,但

getLowestEmptyCell()
方法是我自己的。

board.js:

export default class Board {
  constructor(state = ["", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""]) {
    this.state = state;
  }


  printFormattedBoard() {
    let formattedString = '';
    this.state.forEach((cell, index) => {
      formattedString += cell ? ` ${cell} |` : `   |`;
      if ((index + 1) % 7 === 0) {
        formattedString = formattedString.slice(0, -1);
        if (index < 41) formattedString += '\n\u2015\u2015\u2015 \u2015\u2015\u2015 \u2015\u2015\u2015 \u2015\u2015\u2015 \u2015\u2015\u2015 \u2015\u2015\u2015 \u2015\u2015\u2015\n'
      }
    });

    console.log('%c' + formattedString, 'color: #c11dd4; font-size: 16px;');
  }


  isEmpty() {
    return this.state.every(cell => !cell);
  }

  isFull() {
    return this.state.every(cell => cell);
  }

  isTerminal() {
    if (this.isEmpty()) return false;


    /* 320 lines of winning combinations */



    if (this.isFull()) {
      return { 'winner': 'draw' };
    }

    return false;
  }


  getLowestEmptyCell(index) {
    if (index > 41 || index < 0 || this.state[index]) return NaN;

    let i = 0;

    if (index >= 0) i = 35;
    if (index >= 7) i = 28;
    if (index >= 14) i = 21;
    if (index >= 21) i = 14;
    if (index >= 28) i = 7;
    if (index >= 35) i = 0;

    for (i; i > -1; i -= 7) {
      if (!this.state[index + i]) return index + i;
    }
  }


  insert(symbol, position) {
    if (![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41].includes(position)) throw new Error('Cell index does not exist or is not possible!');

    if(!['r', 'y'].includes(symbol)) throw new Error('The symbol can only be an r or a y!');

    if (this.state[position]) return false;

    position = this.getLowestEmptyCell(position);
    this.state[position] = symbol; // error thrown here
    return true;
  }


  getAvailableMoves() {
    let moves = [];
    
    for (let i = 0; i < 7; i++) {
      if (!this.state[i]) moves.push(this.getLowestEmptyCell(i));
    }

    return moves;
  }
}

player.js:

import Board from './board.js';

export default class Player {
  constructor(maxDepth =  -1) {
    this.maxDepth = maxDepth;
    this.nodesMap = new Map();
  }


  getBestMove(board, maximising = true, callback = () => {}, depth = 0) {
    if (depth === 0) this.nodesMap.clear();

    if (board.isTerminal() || depth === this.maxDepth) {
      if (board.isTerminal().winner === 'r') {
        return 100 - depth;
      } else if (board.isTerminal().winner === 'y') {
        return -100 + depth;
      }

      return 0;
    }


    if (maximising) {
      let best = -100;

      board.getAvailableMoves().forEach(index => {
        const child = new Board([...board.state]);
        child.insert('r', index);

        const nodeValue = this.getBestMove(child, false, callback, depth + 1);
        best = Math.max(best, nodeValue);

        if (depth === 0) {
          const moves = this.nodesMap.has(nodeValue) ? `${this.nodesMap.get(nodeValue)},${index}` : index;
          this.nodesMap.set(nodeValue, moves);
        }
      });


      if (depth === 0) {
        let returnValue;
        if (typeof this.nodesMap.get(best) === 'string') {
          const arr = this.nodesMap.get(best).split(',');
          returnValue = arr[Math.floor(Math.random() * arr.length)];
        } else {
          returnValue = this.nodesMap.get(best);
        }

        callback(returnValue);
        return returnValue;
      }

      return best;
    }


    if (!maximising) {
      let best = 100;

      board.getAvailableMoves().forEach(index => {
        const child = new Board([...board.state]);
        child.insert('y', index);

        const nodeValue = this.getBestMove(child, false, callback, depth + 1);
        best = Math.max(best, nodeValue);

        if (depth === 0) {
          const moves = this.nodesMap.has(nodeValue) ? `${this.nodesMap.get(nodeValue)},${index}` : index;
          this.nodesMap.set(nodeValue, moves);
        }
      });


      if (depth === 0) {
        let returnValue;
        if (typeof this.nodesMap.get(best) === 'string') {
          const arr = this.nodesMap.get(best).split(',');
          returnValue = arr[Math.floor(Math.random() * arr.length)];
        } else {
          returnValue = this.nodesMap.get(best);
        }

        callback(returnValue);
        return returnValue;
      }

      return best;
    }
  }
}

script.js:

import Board from './classes/board.js';
import Player from './classes/player.js';

const board = new Board(["", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""]);
const player = new Player();
console.log(player.getBestMove(board));
board.printFormattedBoard();
//console.log(player.nodesMap);

我觉得这与功能本身无关,而是我的无知并试图在错误的地方实现自定义功能。

javascript minimax
1个回答
0
投票

我猜

Board.state
是一个字符串(而不是您可能期望的数组)。

由于字符串是不可变的,因此以下赋值是非法的,并且可能(取决于您的 js 引擎)抛出您提到的错误:

const state: any = "x";
state[35] = 1;

游乐场示例

  • 单击
    Run
    查看预期错误

要测试,如果确实是这种情况,您可以在抛出的行上设置断点并检查

state
变量或记录类型
console.log(this.state, typeof this.state)

为了避免此类问题,您应该检查构造函数中状态参数的类型,如果它不是预期的类型(即字符串数组),则抛出错误 - 或者使用打字稿,这将有助于解决此类简单的错误(注意, Playgrond 示例在分配行上显示一个错误,并且当您将鼠标悬停在该行上时显示一个有意义的错误:“‘String’类型的索引签名仅允许读取。”

© www.soinside.com 2019 - 2024. All rights reserved.