标签 国际象棋 下的文章

使用位字段和掩码是不用数据结构组合数据的常用方法。

 title=

假设你在用 C 语言写一个国际象棋游戏。追踪棋盘上棋子的一种方法是定义一个结构,该结构定义了棋盘上每个可能的棋子及其颜色,因此每个格子都包含该结构中的一个元素。例如,你可以将结构定义成下面这样:

struct chess_pc {
   int piece;
   int is_black;
}

有了这个数据结构,你的程序就会知道每个格子里是什么棋子及棋子的颜色。你可以快速识别出棋子是兵、车、马、象、后还是王,以及棋子是黑还是白。但是,有一种更直接的方法来跟踪这些信息,同时只用更少的数据和内存。与为棋盘上的每个方格存储两个 int 值的结构不同,我们可以存储单个 int 值,并使用二进制位字段和掩码来标识每个方格中的棋子和颜色。

比特和二进制

当使用位字段表示数据时,我们最好像计算机一样思考。让我们从列出可能的棋子开始,并为每个棋子分配一个数字。让我们进入下一个步骤,用二进制表示这个数字,也就是按照计算机追踪它的方式。记住,二进制数是由比特组成的,比特要么是 0,要么是 1。

  • 00000000: 空(0)
  • 00000001: 兵(1)
  • 00000010: 车(2)
  • 00000011: 马(3)
  • 00000100: 象(4)
  • 00000101: 后(5)
  • 00000110: 王(6)

要列出一个棋盘上的所有棋子,我们只需要三个比特从右到左依次代表值 1、2 和 4。例如,数字 6 是二进制的 110。6 的二进制表示中的其他所有位都是 0。

一个聪明一点的方法:我们可以使用那些额外的总是为零的比特来跟踪一个棋子是黑还是白。我们可以使用数字 8(二进制 00001000)来表示棋子是否为黑色。如果这一位是 1,则代表该棋子是黑色;如果是 0,则代表该棋子是白色。这被称为位字段,稍后我们可以使用二进制掩码将其取出。

用位字段存储数据

要编写一个使用位字段和掩码的国际象棋程序,我们可以从以下定义开始:

/* 棋子 */

#define EMPTY 0   // 空
#define PAWN 1    // 兵
#define ROOK 2    // 车
#define KNIGHT 3  // 马
#define BISHOP 4  // 象
#define QUEEN 5   // 后
#define KING 6    // 王

/* 棋色 */

#define BLACK 8   // 黑
#define WHITE 0   // 白

/* 掩码 */

#define PIECE 7

当你为一个棋格赋值时,比如初始化棋盘,你可以赋一个 int 类型的值来跟踪棋子及其颜色。例如,要在棋盘的 0,0 位置存储棋子黑车,你可以使用下面的代码:

  int board[8][8];
..
  board[0][0] = BLACK | ROOK;

| 是二进制“或”(OR)操作符,这意味着计算机将合并两个数字的比特。对于每个比特的位置,如果任意一个数字的比特为 1,该位置比特的结果也是 1。BLACK 的值(8,即二进制下的 00001000)和 ROOK 的值(2,即二进制下的 00000010)的二进制或结果是二进制下的 00001010,即 10:

    00001000 = 8
 OR 00000010 = 2
    ________
    00001010 = 10

类似地,要在棋盘的 6,0 位置存储一个白色兵,你可以这样做:

  board[6][0] = WHITE | PAWN;

这样存储的值就是 WHITE(0)和 PAWN(1)的二进制或的结果,也即是 1。

    00000000 = 0
 OR 00000001 = 1
    ________
    00000001 = 1

用掩码获取数据

在下棋过程中,程序需要知道棋格中的棋子和它的颜色。我们可以使用二进制掩码来分离这部分。

举个例子,程序可能需要知道棋局中棋盘上特定棋格的内容,例如位于 board[5][3] 的数组元素。这个是什么棋子,是黑的还是白的?为了识别棋子,使用二进制“与”(AND)操作符将元素的值与掩码 PIECE 结合起来:

  int board[8][8];
  int piece;
..
  piece = board[5][3] & PIECE;

二进制“与”(AND)操作符(&)将两个二进制值结合,这样对于任意位,如果两个数字中的那个位都是 1,那么结果也是 1。例如,如果 board[5][3] 的值是 11(二进制下的 00001011),那么 11 和 掩码 PIECE(7,二进制下的 00000111)二进制与的结果为二进制下的 00000011,也即 3。这代表马,马的值是 3。

    00001011 = 11
AND 00000111 = 7
    ________
    00000011 = 3

解析棋子的颜色是一个简单的事情,只需要将棋子的值与 BLACK 位字段进行二进制与操作。比如,你可以写一个名为 is_black 的函数来确定棋子是黑还是白:

int
is_black(int piece)
{
  return (piece & BLACK);
}

之所以可以这样,是因为 BLACK 的值为 8(二进制下的 00001000)。在 C 语言中,任何非零值都被视为 True,零总是 False。所以如果 5,3 处的棋子是黑色的,则 is_black(board[5][3]) 返回 True 值(8);如果是白色的,则返回 False 值(0)。

位字段

使用位字段和掩码是不使用结构组合数据的常用方法。它们值得被程序员收藏到“工具包”中。虽然数据结构对于需要跟踪相关数据的有序编程是一种有价值的工具,但是使用单独的元素来跟踪单个的开或闭值(例如棋子的颜色)的效率较低。在这些情况下,可以考虑使用位字段和掩码来更高效地组合数据。


via: https://opensource.com/article/21/8/binary-bit-fields-masks

作者:Jim Hall 选题:lujun9972 译者:FYJNEVERFOLLOWS 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

当我在这里提到了 ZX81 电脑时,我已经暴露了我的年龄。ZX81 是一个由英国开发者(Sincilair 研究所)生产的家庭电脑,它拥有"高达" 1KB 的内存!上面的 1KB 并不是打印错误,这个家庭电脑确实只配置有 1KB 的板载内存。但这个内存大小上的限制并没有阻止爱好者制作种类繁多的软件。事实上,这个机器引发了一代编程奇才的出现,这让他们掌握了让程序在该机上运行起来的技能。这个机器可以通过一个 16 KB 的内存卡来进行升级,这就提供了更多的编程可能。但未经扩展的 1KB 机器仍然激励着编程者们发布卓越的软件。

1K ZX Chess

我最喜爱的 ZX81 游戏有: 模拟飞行(Flight Simulation), 3D 版怪物迷宫(3D Monster Maze), 小蜜蜂(Galaxians), 以及最重要的 1K ZX Chess。 只有最后一个程序是为未扩展版的 ZX81 电脑设计的。事实上,David Horne 开发的 1K ZX Chess 只使用了仅仅 672 字节的 RAM(LCTT 译注:如果读者有兴趣,可以看看这里对该程序的代码及解释)。尽管如此,该游戏尽力去实现大多数的国际象棋规则,并提供了一个计算机虚拟对手。虽然一些重要的规则被忽略了(如:王车易位,兵的升变,和吃过路兵) (LCTT 译注:参考了这里这里),但能够和人工智能相对抗,这仍然令人惊讶。这个游戏占据了我逝去的青春里的相当一部分。

1K ZX Chess 保持了在所有计算机上国际象棋的最小实现的地位长达 33 年之久,直到今年由 BootChess 打破了该记录,紧接着由 Toledo AtomChess 打破。这三个程序都没有实现所有的国际象棋规则,所以为了完整性,我介绍了我最喜爱的那些实现了所有国际象棋规则的极小的国际象棋。

Linux 有着一系列极其强大的国际象棋引擎,如 Stockfish, Critter, Togo II, Crafty, GNU Chess 和 Komodo 。 在这篇文章精选的国际象棋程序虽敌不过这些好的国际象棋程序,但它们展示了使用微不足道的代码库究竟可以实现多少东西。


Toledo Atomchess

你可能已经看到了大量有关 BootChess 新闻报道,这个只用 487 字节写就的国际象棋程序,一举打破了先前最小的国际象棋程序 1K ZX Chess 的记录。所以,Óscar Toledo Gutiérrez 挽起袖子自己编写了一个更加紧凑的国际象棋游戏。Toledo Atomchess 是仅有 481 字节的 x86 汇编代码,都能放到引导扇区里。 在难以置信的代码大小下,这个引擎实现了一个可玩的国际象棋游戏。

特点包括:

  • 基本的棋子移动
  • 用 ASCII 文本表现的棋盘
  • 以代数形式来输入移动(注:如 D2D4)
  • 3 层的搜索深度

显然,为了将这个国际象棋程序压缩到 481 字节中,作者必须做出某些牺牲,这些局限包括:

  • 没有兵的升变
  • 没有王车易位
  • 没有吃过路兵
  • 没有移动确认

该作者也使用 C,JavaScript 和 Java 来写这个国际象棋程序,每种实现都非常小。


BootChess

BootChess 是一个国际象棋的极其小巧的计算机实现。这个程序被塞进到仅仅 487 字节里,并可运行在 Windows, Mac OS X 和 Linux 等操作系统。BootChess 的棋盘和棋子单独用文本表示,其中 P 代表兵, Q 用来代表王后,以及“点”代表空白格子。

特点包括:

  • 象棋棋盘和用户输入的形象的文本表示
  • 引导扇区大小(512 字节)的可玩的象棋游戏
  • 只需 x86 bios 硬件引导程序(没有软件依赖)
  • 所有主要的正规移动包括双兵开局
  • 兵升变为王后(与 1k ZX Chess 相反)
  • 名为 taxiMax > minMax half-ply 的 CPU 人工智能
  • 硬编码的西班牙白子开局

同样,它也存在一些重要的限制。这些遗漏的功能包括:

  • 兵的低升变(升变为非王后的棋子)
  • 吃过路兵
  • 没有王车易位
  • 3 次位置重复和局规则(注:下一步之前,同样的移动出现了两次;可以参考这里
  • 50 步移动和局规则(注:在连续的50个回合内,双方既没有棋子被吃掉,也没有兵被移动过,则和局;可以参考这里
  • 没有开放式和封闭式布局
  • 一个或多个 minMAX/negaMax 全层人工智能
  • 网站: www.pouet.net/prod.php?which=64962
  • 开发者: Olivier "Baudsurfer/RSi" Poudade
  • 协议: WTFPL v2
  • 版本号: .02

Micro-Max

Micro-Max 是一个用 133 行 C 语言写就的象棋源程序。

作者实现了一个 hash 变换表,该引擎检查输入移动的合法性,以及支持 FIDE(注: World Chess Federation 缩写,参见其官网) 的全部规则,除了低升变。

特点包括:

  • 递归的 negamax 搜索
  • 反夺的静态搜索
  • 反夺规则的扩展
  • 迭代深化
  • 最佳移动优先的 排序
  • 存储分数和最佳移动的 Hash 表
  • 完整的 FIDE 规则(除了低位升变)和移动合法性检查

还有一个 1433个字符的较大版本,但允许你使用完整的 FIDE 规则的低升变。


via: http://www.linuxlinks.com/article/20150222033906262/ChessBytes.html

作者:Frazer Kline 译者:FSSlc 校对:wxy

本文由 LCTT 原创翻译,Linux中国 荣誉推出