使用位字段和掩碼寫一個(gè)國際象棋游戲
使用位字段和掩碼是不用數(shù)據(jù)結(jié)構(gòu)組合數(shù)據(jù)的常用方法。
假設(shè)你在用 C 語言寫一個(gè)國際象棋游戲。追蹤棋盤上棋子的一種方法是定義一個(gè)結(jié)構(gòu),該結(jié)構(gòu)定義了棋盤上每個(gè)可能的棋子及其顏色,因此每個(gè)格子都包含該結(jié)構(gòu)中的一個(gè)元素。例如,你可以將結(jié)構(gòu)定義成下面這樣:
struct chess_pc {
int piece;
int is_black;
}
有了這個(gè)數(shù)據(jù)結(jié)構(gòu),你的程序就會(huì)知道每個(gè)格子里是什么棋子及棋子的顏色。你可以快速識別出棋子是兵、車、馬、象、后還是王,以及棋子是黑還是白。但是,有一種更直接的方法來跟蹤這些信息,同時(shí)只用更少的數(shù)據(jù)和內(nèi)存。與為棋盤上的每個(gè)方格存儲兩個(gè) int? 值的結(jié)構(gòu)不同,我們可以存儲單個(gè) int 值,并使用二進(jìn)制位字段和掩碼來標(biāo)識每個(gè)方格中的棋子和顏色。
比特和二進(jìn)制
當(dāng)使用位字段表示數(shù)據(jù)時(shí),我們最好像計(jì)算機(jī)一樣思考。讓我們從列出可能的棋子開始,并為每個(gè)棋子分配一個(gè)數(shù)字。讓我們進(jìn)入下一個(gè)步驟,用二進(jìn)制表示這個(gè)數(shù)字,也就是按照計(jì)算機(jī)追蹤它的方式。記住,二進(jìn)制數(shù)是由比特組成的,比特要么是 0,要么是 1。
- 00000000: 空(0)
- 00000001: 兵(1)
- 00000010: 車(2)
- 00000011: 馬(3)
- 00000100: 象(4)
- 00000101: 后(5)
- 00000110: 王(6)
要列出一個(gè)棋盤上的所有棋子,我們只需要三個(gè)比特從右到左依次代表值 1、2 和 4。例如,數(shù)字 6 是二進(jìn)制的 110。6 的二進(jìn)制表示中的其他所有位都是 0。
一個(gè)聰明一點(diǎn)的方法:我們可以使用那些額外的總是為零的比特來跟蹤一個(gè)棋子是黑還是白。我們可以使用數(shù)字 8(二進(jìn)制 00001000)來表示棋子是否為黑色。如果這一位是 1,則代表該棋子是黑色;如果是 0,則代表該棋子是白色。這被稱為位字段,稍后我們可以使用二進(jìn)制掩碼將其取出。
用位字段存儲數(shù)據(jù)
要編寫一個(gè)使用位字段和掩碼的國際象棋程序,我們可以從以下定義開始:
/* 棋子 */
#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
當(dāng)你為一個(gè)棋格賦值時(shí),比如初始化棋盤,你可以賦一個(gè) int? 類型的值來跟蹤棋子及其顏色。例如,要在棋盤的 0,0 位置存儲棋子黑車,你可以使用下面的代碼:
int board[8][8];
..
board[0][0] = BLACK | ROOK;
|? 是二進(jìn)制“或”(OR?)操作符,這意味著計(jì)算機(jī)將合并兩個(gè)數(shù)字的比特。對于每個(gè)比特的位置,如果任意一個(gè)數(shù)字的比特為 1,該位置比特的結(jié)果也是 1。BLACK? 的值(8,即二進(jìn)制下的 00001000?)和 ROOK? 的值(2,即二進(jìn)制下的 00000010?)的二進(jìn)制或結(jié)果是二進(jìn)制下的 00001010,即 10:
00001000 = 8
OR 00000010 = 2
________
00001010 = 10
類似地,要在棋盤的 6,0 位置存儲一個(gè)白色兵,你可以這樣做:
board[6][0] = WHITE | PAWN;
這樣存儲的值就是 WHITE?(0)和 PAWN(1)的二進(jìn)制或的結(jié)果,也即是 1。
00000000 = 0
OR 00000001 = 1
________
00000001 = 1
用掩碼獲取數(shù)據(jù)
在下棋過程中,程序需要知道棋格中的棋子和它的顏色。我們可以使用二進(jìn)制掩碼來分離這部分。
舉個(gè)例子,程序可能需要知道棋局中棋盤上特定棋格的內(nèi)容,例如位于 board[5][3]? 的數(shù)組元素。這個(gè)是什么棋子,是黑的還是白的?為了識別棋子,使用二進(jìn)制“與”(AND?)操作符將元素的值與掩碼 PIECE 結(jié)合起來:
int board[8][8];
int piece;
..
piece = board[5][3] & PIECE;
二進(jìn)制“與”(AND?)操作符(&?)將兩個(gè)二進(jìn)制值結(jié)合,這樣對于任意位,如果兩個(gè)數(shù)字中的那個(gè)位都是 1,那么結(jié)果也是 1。例如,如果 board[5][3]? 的值是 11(二進(jìn)制下的 00001011?),那么 11 和 掩碼 PIECE?(7,二進(jìn)制下的 00000111?)二進(jìn)制與的結(jié)果為二進(jìn)制下的 00000011,也即 3。這代表馬,馬的值是 3。
00001011 = 11
AND 00000111 = 7
________
00000011 = 3
解析棋子的顏色是一個(gè)簡單的事情,只需要將棋子的值與 BLACK? 位字段進(jìn)行二進(jìn)制與操作。比如,你可以寫一個(gè)名為 is_black 的函數(shù)來確定棋子是黑還是白:
int
is_black(int piece)
{
return (piece & BLACK);
}
之所以可以這樣,是因?yàn)?nbsp;BLACK? 的值為 8(二進(jìn)制下的 00001000?)。在 C 語言中,任何非零值都被視為 True?,零總是 False?。所以如果 5,3? 處的棋子是黑色的,則 is_black(board[5][3]) 返回 True 值(8);如果是白色的,則返回 False 值(0)。
位字段
使用位字段和掩碼是不使用結(jié)構(gòu)組合數(shù)據(jù)的常用方法。它們值得被程序員收藏到“工具包”中。雖然數(shù)據(jù)結(jié)構(gòu)對于需要跟蹤相關(guān)數(shù)據(jù)的有序編程是一種有價(jià)值的工具,但是使用單獨(dú)的元素來跟蹤單個(gè)的開或閉值(例如棋子的顏色)的效率較低。在這些情況下,可以考慮使用位字段和掩碼來更高效地組合數(shù)據(jù)。