返回

用 JS 精湛实现:仅需 10 位/步的象棋历史记录保存之法(编码篇)

前端

在上篇文章中,我们探讨了一种巧妙的方案:将象棋每一步操作的信息压缩至 7 到 14 个二进制位。如今,我欣然宣布,我们已将这一方案成功付诸实现,打造出空间占用极低的象棋历史记录保存方案,为悔棋操作铺平了道路。

算法精髓

我们的算法建立在这样一种洞见之上:象棋中的绝大多数合法操作都能通过改变棋盘上某个棋子的位置来实现。而棋盘上棋子的位置又可以用一对数字来唯一表示:纵向坐标和横向坐标。

基于这一洞见,我们巧妙地将每一步操作拆解为三个部分:

  1. 棋子类型: 4 位,表示移动的棋子类型(如车、马、象等)。
  2. 起始位置: 1 位,表示棋子移动前的纵向坐标。
  3. 终点位置: 2-5 位,表示棋子移动后的横向坐标(取决于棋子类型)。

通过这种方式,我们能够将绝大多数操作压缩至 7-14 个二进制位。其中,吃子操作需要额外的 5 位来表示被吃掉的棋子类型。

JS 实现

以下 JS 代码展示了这一算法的实现:

function encodeMove(move) {
  // 棋子类型:4 位
  const pieceType = move.piece.charCodeAt(0) - "P".charCodeAt(0);
  // 起始位置:1 位
  const fromRank = move.from[1] - 1;
  // 终点位置:2-5 位(取决于棋子类型)
  const toRank = move.to[1] - 1;
  const toFile = move.to[0].charCodeAt(0) - "a".charCodeAt(0);
  let toFileBits;
  switch (pieceType) {
    case 0: // 兵
    case 1: // 卒
    case 6: // 士
    case 7: // 仕
      toFileBits = toFile;
      break;
    case 2: // 车
    case 3: // 炮
    case 4: // 马
    case 5: // 象
      toFileBits = toFile << 1;
      break;
    case 8: // 将
    case 9: // 帅
      toFileBits = toFile << 2;
      break;
  }
  // 吃子情况:额外 5 位
  const capturedPiece = move.captured ? move.captured.charCodeAt(0) - "P".charCodeAt(0) : 0;
  return (pieceType << 4) | (fromRank << 3) | toFileBits | capturedPiece;
}

性能优势

该算法在性能方面表现优异。对于典型的象棋对局,历史记录所占空间仅为几百字节。这极大地降低了存储和传输历史记录的负担,从而为流畅的悔棋操作奠定了基础。

应用场景

除了悔棋操作,这一算法还有广泛的应用场景:

  • AI 分析: 对大量历史对局进行分析,以优化 AI 算法。
  • 教学工具: 记录和回放对局,以帮助棋手分析自己的比赛或学习新的开局。
  • 在线对战: 在网络对战中传输历史记录,实现悔棋和观战功能。

总结

我们开发的象棋历史记录保存方案巧妙地利用了象棋操作的本质,以极低的存储空间实现了每一步操作的完整记录。该算法的 JS 实现高效且轻量,为悔棋和各种应用场景提供了强有力的支持。