You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

无第三方库时加速JS数组运算:Connect4 AI启发函数优化求助

Connect4 AI启发函数优化问题

我在计算机科学课程中,基于Keith Galli的Python版Connect4 AI实现了JavaScript版本,但启发函数经优化后仍比原版慢(7-11ms对比<1ms)。核心差异是原版使用numpy进行多维数组运算,而我处于code.org AppLab工作环境,无法导入/安装任何第三方库。

以下是我用于评估位置优势的启发函数代码:

function score_position(board, piece){
  var start = Date.now();
  
  if (!score_position.cache) {
    score_position.cache = {};
  }

  var key = board.join("") + piece;

  if (score_position.cache.hasOwnProperty(key)) {
    var end = Date.now();
    console.log("Elapsed time (cached): " + (end - start) + "ms");
    return score_position.cache[key];
  }
  
  var score = 0;
  var center_count = 0;
  for(var i=0; i < board.length; i++){
    if (board[i][3] === piece) {
      center_count++;
    }
  }
  score += center_count * 3;

  var col_array = [];
  var diag1_array = [];
  var diag2_array = [];

  for(var r=0; r<ROW_COUNT; r++){
    for(var c=0; c<COLUMN_COUNT-3; c++){
      var win = board[r].slice(c, c+WINDOW_LENGTH);
      if (win.indexOf('') === -1) {
        score += evaluate_window(win, piece);
      }
    }
  }
  
  for(var r=0; r<ROW_COUNT; r++){
    for(var c=0; c<COLUMN_COUNT; c++){
      col_array[c] = board[r][c];
    }
    for(var ct=0; ct<ROW_COUNT-3; ct++){
      var win = col_array.slice(ct, ct+WINDOW_LENGTH);
      if (win.indexOf('') === -1) {
        score += evaluate_window(win, piece);
      }
    }
  }
  
  for(var r=0; r<ROW_COUNT-3; r++){
    for(var c=0; c<COLUMN_COUNT-3; c++){
      diag1_array[0] = board[r][c];
      diag1_array[1] = board[r+1][c+1];
      diag1_array[2] = board[r+2][c+2];
      diag1_array[3] = board[r+3][c+3];
      if(diag1_array.indexOf('') === -1) {
        score += evaluate_window(diag1_array, piece);
      }
    }
  }
  
  for(var r=0; r<ROW_COUNT-3; r++){
    for(var c=0; c<COLUMN_COUNT-3; c++){
      diag2_array[0] = board[r+3][c];
      diag2_array[1] = board[r+2][c+1];
      diag2_array[2] = board[r+1][c+2];
      diag2_array[3] = board[r][c+3];
      if(diag2_array.indexOf('') === -1) {
        score += evaluate_window(diag2_array, piece);
      }
    }
  }

  
  score_position.cache[key] = score;
  
  var end = Date.now();
  console.log("Elapsed time:" + (end-start) + "ms");
  
  return score;
  
}

该函数遍历棋盘的行、列和对角线,调用evaluate_window函数为其中的窗口评分:

function evaluate_window(win, piece){
  
  if(!evaluate_window.cache){
    evaluate_window.cache = {};
  }
  
  var key = win.join("") + piece;
  
  if (evaluate_window.cache.hasOwnProperty(key)) {
    return evaluate_window.cache[key];
  }
  
    var score = 0;
    var opp_piece = 1;
    if(piece == 1){
        opp_piece = 2;
    }

    if(win.count(piece) == 4){
        score += 100;
    } else if (win.count(piece) == 3 && win.count(0) == 1){
        score += 5;
    } else if (win.count(piece) == 2 && win.count(0) == 2){
        score += 2;
    }
    if (win.count(opp_piece) == 3 && win.count(0) == 1){
        score -= 4;
    }
    
    evaluate_window.cache[key] = score;

    return score;
}

我已在两个函数中使用记忆化缓存来减少重复状态的计算,同时会跳过空窗口的循环,但代码运行仍较慢,推测是数组运算的开销问题。请问在无第三方库的情况下,如何优化这段代码以提升运行速度?


优化方案

1. 避免数组切片与新数组创建

当前代码频繁使用slice生成新窗口数组,带来内存分配和拷贝开销。直接在原棋盘上遍历窗口内的位置并统计棋子数量,无需生成新数组,能大幅减少性能损耗。

2. 内联窗口评分逻辑

移除evaluate_window函数,将其评分逻辑直接整合到score_position中,消除函数调用和缓存字典查找的额外开销(小窗口的缓存收益远低于这些开销)。

3. 优化缓存键生成

原缓存键使用board.join("")会生成包含逗号、方括号的冗余字符串,改为将棋盘转为紧凑的一维字符串(如每一行的数字直接拼接),缩短键长度,加快字典查找速度。

4. 移除不必要的空窗口检查

原代码跳过空窗口的逻辑与原版numpy实现不符,且额外增加了数组遍历开销。直接处理所有窗口,在计数时自然区分空位情况,同时保留原有评分规则。

5. 替换自定义计数方法

替换win.count(piece)这类自定义扩展方法,直接在窗口遍历过程中统计我方棋子、对手棋子和空位的数量,避免额外函数调用。


优化后示例代码

function score_position(board, piece) {
  var start = Date.now();
  
  if (!score_position.cache) {
    score_position.cache = {};
  }

  // 生成紧凑缓存键:将二维棋盘转为无冗余字符的一维字符串
  var boardKey = '';
  for (var r = 0; r < ROW_COUNT; r++) {
    boardKey += board[r].join('');
  }
  var key = boardKey + piece;

  if (score_position.cache.hasOwnProperty(key)) {
    var end = Date.now();
    console.log("Elapsed time (cached): " + (end - start) + "ms");
    return score_position.cache[key];
  }
  
  var score = 0;
  var opp_piece = piece === 1 ? 2 : 1;

  // 计算中心列分数
  var center_count = 0;
  for (var r = 0; r < ROW_COUNT; r++) {
    if (board[r][3] === piece) center_count++;
  }
  score += center_count * 3;

  // 处理行窗口
  for (var r = 0; r < ROW_COUNT; r++) {
    var row = board[r];
    for (var c = 0; c <= COLUMN_COUNT - WINDOW_LENGTH; c++) {
      var own = 0, opp = 0, empty = 0;
      // 遍历窗口内的4个位置统计数量
      for (var i = 0; i < WINDOW_LENGTH; i++) {
        var val = row[c + i];
        if (val === piece) own++;
        else if (val === opp_piece) opp++;
        else empty++;
      }
      // 直接计算当前窗口的分数
      if (own === 4) score += 100;
      else if (own === 3 && empty === 1) score += 5;
      else if (own === 2 && empty === 2) score += 2;
      if (opp === 3 && empty === 1) score -= 4;
    }
  }

  // 处理列窗口
  for (var c = 0; c < COLUMN_COUNT; c++) {
    for (var r = 0; r <= ROW_COUNT - WINDOW_LENGTH; r++) {
      var own = 0, opp = 0, empty = 0;
      for (var i = 0; i < WINDOW_LENGTH; i++) {
        var val = board[r + i][c];
        if (val === piece) own++;
        else if (val === opp_piece) opp++;
        else empty++;
      }
      if (own === 4) score += 100;
      else if (own === 3 && empty === 1) score += 5;
      else if (own === 2 && empty === 2) score += 2;
      if (opp === 3 && empty === 1) score -= 4;
    }
  }

  // 处理正对角线(左上到右下)
  for (var r = 0; r <= ROW_COUNT - WINDOW_LENGTH; r++) {
    for (var c = 0; c <= COLUMN_COUNT - WINDOW_LENGTH; c++) {
      var own = 0, opp = 0, empty = 0;
      for (var i = 0; i < WINDOW_LENGTH; i++) {
        var val = board[r + i][c + i];
        if (val === piece) own++;
        else if (val === opp_piece) opp++;
        else empty++;
      }
      if (own === 4) score += 100;
      else if (own === 3 && empty === 1) score += 5;
      else if (own === 2 && empty === 2) score += 2;
      if (opp === 3 && empty === 1) score -= 4;
    }
  }

  // 处理反对角线(右上到左下)
  for (var r = WINDOW_LENGTH - 1; r < ROW_COUNT; r++) {
    for (var c = 0; c <= COLUMN_COUNT - WINDOW_LENGTH; c++) {
      var own = 0, opp = 0, empty = 0;
      for (var i = 0; i < WINDOW_LENGTH; i++) {
        var val = board[r - i][c + i];
        if (val === piece) own++;
        else if (val === opp_piece) opp++;
        else empty++;
      }
      if (own === 4) score += 100;
      else if (own === 3 && empty === 1) score += 5;
      else if (own === 2 && empty === 2) score += 2;
      if (opp === 3 && empty === 1) score -= 4;
    }
  }

  score_position.cache[key] = score;
  
  var end = Date.now();
  console.log("Elapsed time:" + (end - start) + "ms");
  
  return score;
}

内容的提问来源于stack exchange,提问作者Salban Nithilaselvan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 17:27:37