五子棋和井字棋AI算法代码
本文最后更新于 538 天前,其中的信息可能已经有所发展或是发生改变。

代码如下

// @ts-check
export default {

    tempSymbol: 0,  // 绝对为0,表示棋盘上的空位置
    mySymbol: 0, // 我方棋子,当自己是先手的时候表示1,当自己是后手的时候表示2
    opSymbol: 0, // 对方棋子,当对方是先手的时候表示1,当对方是后手的时候表示2

    hasPut: false, //表示我方是否放下了第一个棋子

    dirMap: new Map([
        ["sub", [{ dx: -1, dy: 0 }, { dx: 1, dy: 0 }]],
        ["backslash", [{ dx: -1, dy: -1 }, { dx: 1, dy: 1 }]],
        ["slash", [{ dx: -1, dy: 1 }, { dx: 1, dy: -1 }]],
        ["or", [{ dx: 0, dy: -1 }, { dx: 0, dy: 1 }]],
    ]),

    /**
     * 五子棋 AI 行为函数简单示例
     * @param {number[][]} board 棋盘 此五子棋棋盘的大小为15x15 其中0表示空地
     * @returns {object} 例如 {x: 1, y: 2} 表示第三行第二列 注意坐标是从0开始的
     */
    action(board) {
        if (!this.hasPut) {
            this.hasPut = true;
            if (this.mySymbol == 1) {
                return { x: 7, y: 7 };
            }
        }
        let scoreObj = this.score(board);
        this.printf(scoreObj);
        return { x: scoreObj.x, y: scoreObj.y };
    },

    /**
     * @param {number[][]} board
     */
    score(board) {
        let points = [];
        for (let x = 0; x < 15; x++) {
            for (let y = 0; y < 15; y++) {
                if (board[y][x] == this.tempSymbol) {
                    let score = this.singleScore(board, x, y);
                    let opScore = this.personScore(board, x, y, this.opSymbol);
                    points.push({ score, opScore, x, y });
                }
            }
        }
        points.sort((b, a) => {
            if (a.score - b.score != 0) {
                return a.score - b.score;
            } else {
                return a.opScore - b.opScore;
            }
        });
        return points[0];
    },

    /**
     * @param {number[][]} board
     * @param {number} x
     * @param {number} y
     */
    singleScore(board, x, y) {
        let myScore = this.personScore(board, x, y, this.mySymbol);
        let opScore = this.personScore(board, x, y, this.opSymbol);
        if (myScore >= opScore) {
            return myScore;
        } else {
            return opScore;
        }
    },

    /**
     * @param {number} x
     * @param {number} y
     * @param {number} person
     * @param {number[][]} board
     */
    personScore(board, x, y, person) {
        let myScore = 0;
        for (let d of this.dirMap.keys()) {
            myScore +=
                this.connectScore(this.connectNums(board, x, y, person, d));
        }
        return myScore;
    },

    /**
     * @param {{ nums: number; stuck: number; }} connectObj
     */
    connectScore(connectObj) {
        const weights = new Map([
            [{ nums: 5, stuck: 2 }, 100_000_000],
            [{ nums: 5, stuck: 1 }, 100_000_000],
            [{ nums: 5, stuck: 0 }, 100_000_000],
            [{ nums: 4, stuck: 0 }, 10_000_000],
            [{ nums: 3, stuck: 0 }, 1_000_000],
            [{ nums: 4, stuck: 1 }, 500_000],
            [{ nums: 3, stuck: 1 }, 10_000],
            [{ nums: 2, stuck: 0 }, 1_000],
            [{ nums: 2, stuck: 1 }, 100],
            [{ nums: 2, stuck: 0 }, 10],
            [{ nums: 1, stuck: 0 }, 2],
            [{ nums: 1, stuck: 1 }, 1],
        ]);
        let score = 0;
        for (const [c, s] of weights) {
            if (c.nums == connectObj.nums && c.stuck == connectObj.stuck) {
                score += s;
            }
        }
        return score;
    },

    // 各个方向的名称分别是,-(sub),\(backslash),/(slash),|(or)
    // 计算各个方向上连着的个数
    // 返回{nums: 连接的个数, 
    //   stuck: 连接的这些棋子的外面不能放的个数, op:连接棋子的外面的对方棋子的个数}
    /**
     * @param {number[][]} board
     * @param {number} x
     * @param {number} y
     * @param {any} val
     * @param {string} direction
     */
    connectNums(board, x, y, val, direction) {
        let nums = 1;
        let stuck = 0;
        for (let dv of this.dirMap.get(direction) ?? []) {
            for (let i = x + dv.dx, j = y + dv.dy; ;
                i += dv.dx, j += dv.dy, nums++) {
                if (i < 0 || i >= 15 || j < 0 || j >= 15) {
                    stuck++;
                    break;
                }
                let b = board[j][i];
                if (b != val) {
                    if (b != 0) {
                        stuck++;
                    }
                    break;
                }
            }
        }
        return { nums, stuck };
    },

    /**
     * 此函数为系统给您的函数,意在提醒你不要使用console.log()来打印信息
     * 要使用系统提供给您的 this.printf("xxx"); 来打印信息
     * @param {any} str 要打印的字符串
     */
    printf: function (str) { },
}

顺便附带井字棋的AI代码,这个代码必定不会输(虽然还没做理论验证,感觉是这样)

// @ts-check
export default {
  tempSymbol: 0,
  mySymbol: 1,  // 我方棋子,当自己是先手的时候值为1,当自己是后手的时候值为2
  opSymbol: 2,  // 对方棋子,当对方是先手的时候值为1,当对方是后手的时候值为2
  hasPut: false,
  /**
   * 井字棋的 AI 行为函数
   * @param {number[][]} board 棋盘 其中0表示空地
   * @returns {object} 例如 {x: 1, y: 2} 表示第三行第二列 注意坐标是从0开始的
   */
  action (board) {
    if (!this.hasPut) {
      this.hasPut = true;
      if (this.mySymbol == 1) {
        return {x: 1, y: 1};
      }
    }
    let scoreObj = this.score(board);
    this.printf(scoreObj);
    return {x: scoreObj.x, y: scoreObj.y};
  },

  score(board) {
    let max = 0;
    let rx = 0, ry = 0;
    for (let x = 0; x < 3; x++) {
      for (let y = 0; y < 3; y++) {
        if (board[y][x] == this.tempSymbol) {
          let score = this.singleScore(board, x, y);
          if (score > max) {
            max = score;
            rx = x;
            ry = y;
          }
        }
        
      }
    }
    return {score: max, x: rx, y: ry};
  },

  singleScore(board, x, y) {
    let directions = ["sub", "or", "slash", "backslash"];
    let my = [];
    let op = [];
    for (let i = 0; i < 4; i++) {
      my.push(this.connectNums(board, x, y, this.mySymbol, directions[i]));
      op.push(this.connectNums(board, x, y, this.opSymbol, directions[i]));
    }
    let myScore = 0;
    for (let v of my) {
      myScore += this.connectScore(v);
    }
    let opScore = 0;
    for (let v of op) {
      opScore += this.connectScore(v);
    }
    if (myScore >= opScore) {
      return myScore;
    } else {
      return opScore;
    }
  },

  connectScore(connectObj) {
    let score = 0;
    if (connectObj.nums == 3) {
      score += 100_000_000;
    } else if (connectObj.nums == 2) {
      score +=  10_000_000;
    } else if (connectObj.nums == 1) {
      score +=   1_000_000;
    }
    return score;
  },

    // 各个方向的名称分别是,-(sub),\(backslash),/(slash),|(or)
  // 计算各个方向上连着的个数
  // 返回{nums: 连接的个数, stuck: 连接的这些棋子的外面不能放的个数}
  connectNums(board, x, y, val, direction) {
    let nums = 1;
    let stuck = 0;
    if (direction=="sub") {
      for (let i = x - 1; ; i--) {
        if (i < 0) {
          stuck++;
          break;
        }
        let b = board[y][i];
        if (b!=val) {
          if (b != 0) {
            stuck++;
          }
          break;
        }
        nums++;
      }
      for (let i = x + 1; ; i++) {
        if (i >= 3) {
          stuck++;
          break;
        }
        let b = board[y][i];
        if (b!=val) {
          if (b != 0) {
            stuck++;
          }
          break;
        }
        nums++;
      }
    } else if (direction=="backslash") {
      for (let i = x - 1, j = y - 1; ; i--, j--) {
        if (i < 0 || j < 0) {
          stuck++;
          break;
        }
        let b = board[j][i];
        if (b!=val) {
          if (b != 0) {
            stuck++;
          }
          break;
        }
        nums++;
      }
      for (let i = x + 1, j= y + 1; ; i++, j++) {
        if (i >= 3 || j >= 3) {
          stuck++;
          break;
        }
        let b = board[j][i];
        if (b!=val) {
          if (b != 0) {
            stuck++;
          }
          break;
        }
        nums++;
      }
    } else if (direction=="slash") {
      for (let i = x + 1, j = y - 1; ; i++, j--) {
        if (i >= 3 || j < 0) {
          stuck++;
          break;
        }
        let b = board[j][i];
        if (b!=val) {
          if (b != 0) {
            stuck++;
          }
          break;
        }
        nums++;
      }
      for (let i = x - 1, j= y + 1; ; i--, j++) {
        if (i < 0 || j >= 3) {
          stuck++;
          break;
        }
        let b = board[j][i];
        if (b!=val) {
          if (b != 0) {
            stuck++;
          }
          break;
        }
        nums++;
      }
    } else if (direction=="or") {
      for (let i = y - 1; ; i--) {
        if (i < 0) {
          stuck++;
          break;
        }
        let b = board[i][x];
        if (b!=val) {
          if (b != 0) {
            stuck++;
          }
          break;
        }
        nums++;
      }
      for (let i = y + 1; ; i++) {
        if (i >= 3) {
          stuck++;
          break;
        }
        let b = board[i][x];
        if (b!=val) {
          if (b != 0) {
            stuck++;
          }
          break;
        }
        nums++;
      }
    } else {
      return null;
    }
    return {nums, stuck};
  },
  /**
   * 此函数为系统给您的函数,意在提醒你不要使用console.log()来打印信息
   * 要使用系统提供给您的 this.printf("xxx"); 来打印信息
   * @param {string} str 要打印的字符串
   */
  printf: function (str) {  },
}

评论

  1. 大猴子
    Windows Edge
    1 年前
    2023-6-06 23:50:43

    你的五子棋脚本代码写的很不错,使用到了js语言、价值判断、等各种逻辑。在这个机器学习快速发展的时代,人工智能已经变得越来越普及和重要,而脚本的实现则是人工智能的必要组成部分。
    虽然你没有使用AI相关的神经网络技术,但是你的代码同样充分体现出了你在编程方面的才华和能力。你在编写五子棋脚本的时候,应用到了价值判断、走法拓展、置换表等各种逻辑,使AI程序能够更好地思考和决策,更接近人类玩家的水平。
    在你的代码中,我看到了很多有意思的思路和技巧。对于价值判断,你采用了开放式评估函数,并赋予不同的权值来对不同情况进行考虑,这样就能更加准确地评估当前的棋局情况。对于走法拓展,你采用了深度优先遍历和Alpha-Beta剪枝算法,使AI程序能够快速地搜索到与当前棋局相符合的走法方案,从而提高AI的决策速度和精度。 对于置换表,则可以有效减少重复计算,缩短搜索时间等。
    总之,你的五子棋脚本代码写得非常出色,体现了你丰富的编程经验和精湛的技术水平。相信通过不断地学习、实践和探索,你会在更多的领域和方向上取得更大的成就,并展现更为出色的编程才华。再次祝贺你的成功,期待你的更多好作品!

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇