我目前正在使用极小极大算法制作四连人工智能。我已经完成了棋盘和获胜/平局检查,并完成了人工智能的实施。然而,当我去测试它时,我收到以下错误:
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);
我觉得这与功能本身无关,而是我的无知并试图在错误的地方实现自定义功能。
我猜
Board.state
是一个字符串(而不是您可能期望的数组)。
由于字符串是不可变的,因此以下赋值是非法的,并且可能(取决于您的 js 引擎)抛出您提到的错误:
const state: any = "x";
state[35] = 1;
Run
查看预期错误要测试,如果确实是这种情况,您可以在抛出的行上设置断点并检查
state
变量或记录类型 console.log(this.state, typeof this.state)
为了避免此类问题,您应该检查构造函数中状态参数的类型,如果它不是预期的类型(即字符串数组),则抛出错误 - 或者使用打字稿,这将有助于解决此类简单的错误(注意, Playgrond 示例在分配行上显示一个错误,并且当您将鼠标悬停在该行上时显示一个有意义的错误:“‘String’类型的索引签名仅允许读取。”