ビットボードの任意のラインを8ビットに変換・逆変換


本記事で用いるビットボード

符号なし64ビット整数を8×8の行列と考えたものを用います。
63ビット目がA1、0ビット目がH8に対応します。

また、ビットの行数、列数を以下のように考えます。

よって、本記事ではあるビットの行数、列数を取得する、以下の2つの関数を使用します。

  • あるビットの行数を取得する
int getRow(int bit) {
    return bit / 8;
}
  • あるビットの列数を取得する
int getColumn(int bit) {
    return bit % 8;
}

本記事で用いるA1-H8方向、A8-H1方向とは

本記事では、左斜め上から右斜め下に向かう方向をA1-H8方向と呼び、左斜め下から右斜め上に向かう方向をA8-H1方向と呼びます。

A1-H8方向(左斜め上から右斜め下に向かう方向)

A8-H1方向(左斜め下から右斜め上に向かう方向)

あるビットの水平方向のラインを8ビットに変換する

あるビットの水平方向のラインをマスクし、あるビットの行数の8倍だけ右シフトすれば良いです。

  • コード例
using ull = unsigned long long;
unsigned char convertHorizontalTo8(ull x, int bit) {
	int const SHIFT = getRow(bit) * 8;
	ull const MASK = 0x00000000000000FF << SHIFT;
	return (x & MASK) >> SHIFT;
}

あるビットの垂直方向のラインを8ビットに変換する

あるビットの垂直方向のラインをマスクし、列数だけ右シフトすることで右端に集め、0x0102040810204080を掛けることで垂直方向のラインが上位8ビットに集まるので、56ビットだけ右シフトすれば良いです。

  1. 垂直方向のラインを列数だけ右シフトして、右端に集める
  2. 0x0102040810204080を掛けると上位8ビットに垂直方向のラインが集まる
  3. 56ビットだけ右シフトする
  • コード例
unsigned char convertVerticalTo8(ull x, int bit) {
	int const CLMN = getColumn(bit);
	ull const MASK = 0x0101010101010101 << CLMN;
	return (((x & MASK) >> CLMN) * 0x0102040810204080) >> 56;
}

あるビットのA1-H8方向のラインを8ビットに変換する

あるビットのA1-H8方向のマスクをあらかじめ配列に持っておき、マスクを取って0x0101010101010101を掛けることで、あるビットのA1-H8方向のラインが上位8ビットに集まるので、これを56ビットだけ右シフトすれば良いです。

  • 例1
  1. あるビットのA1-H8方向のラインが以下のとき
  2. 0x0101010101010101を掛けると上位8ビットにA1-H8方向のラインが集まる
  3. 56ビットだけ右シフトする
  • 例2
  1. あるビットのA1-H8方向のラインが以下のとき
  2. 0x0101010101010101を掛けると上位8ビットにA1-H8方向のラインが集まる
  3. 56ビットだけ右シフトする

ここで、あるビットのA1-H8方向のマスクは、0から63までのビットに対して個別に用意しなくとも、(あるビットの行数 - あるビットの列数)の値が一緒ならマスクも同じものが使えるので、(行数 - 列数 + 7)を添え字としてマスクの配列を参照すれば良いです。

  • (あるビットの行数 - あるビットの列数 + 7)の関係

    よって、あるビットのA1-H8方向のラインを8ビットに変換するコード例は以下のようになります。

  • コード例

ull const DIA_A1H8_MASK[] = {
	0x0000000000000080, 0x0000000000008040, 0x0000000000804020,
	0x0000000080402010, 0x0000008040201008, 0x0000804020100804,
	0x0080402010080402, 0x8040201008040201, 0x4020100804020100,
	0x2010080402010000, 0x1008040201000000, 0x0804020100000000,
	0x0402010000000000, 0x0201000000000000, 0x0100000000000000
};

ull getDiagonalA1H8Mask(int bit) {
	return DIA_A1H8_MASK[(getRow(bit) - getColumn(bit)) + 7];
}

unsigned char convertDiagonalA1H8To8(ull x, int bit) {
	ull const MASK = getDiagonalA1H8Mask(bit);
	return ((x & MASK) * 0x0101010101010101) >> 56;
}

あるビットのA8-H1方向のラインを8ビットに変換する

あるビットのA8-H1方向のマスクをあらかじめ配列に持っておき、マスクを取って0x0101010101010101を掛けることで、あるビットのA8-H1方向のラインが上位8ビットに集まるので、これを56ビットだけ右シフトすれば良いです。

  • 例1
  1. 例えば、あるビットのA8-H1方向のラインが以下のとき
  2. 0x0101010101010101を掛けると上位8ビットにA8-H1方向のラインが集まる
  3. 56ビットだけ右シフトする
  • 例2
  1. 例えば、あるビットのA8-H1方向のラインが以下のとき
  2. 0x0101010101010101を掛けると上位8ビットにA8-H1方向のラインが集まる
  3. 56ビットだけ右シフトする

先程と同様に、あるビットのA8-H1方向のマスクは、0から63までのビットに対して個別に用意しなくとも、(あるビットの行数 + あるビットの列数)の値が一緒ならマスクも同じものが使えるので、(行数 + 列数)を添え字としてマスクの配列を参照すれば良いです。

  • (あるビットの行数 + あるビットの列数)の関係

    よって、あるビットのA8-H1方向のラインを8ビットに変換するコード例は以下のようになります。

  • コード例

ull const DIA_A8H1_MASK[] = {
	0x0000000000000001, 0x0000000000000102, 0x0000000000010204,
	0x0000000001020408, 0x0000000102040810, 0x0000010204081020,
	0x0001020408102040, 0x0102040810204080, 0x0204081020408000,
	0x0408102040800000, 0x0810204080000000, 0x1020408000000000,
	0x2040800000000000, 0x4080000000000000, 0x8000000000000000
};

ull getDiagonalA8H1Mask(int bit) {
	return DIA_A8H1_MASK[getRow(bit) + getColumn(bit)];
}

unsigned char convertDiagonalA8H1To8(ull x, int bit) {
	ull const MASK = getDiagonalA8H1Mask(bit);
	return ((x & MASK) * 0x0101010101010101) >> 56;
}

あるビットの水平方向のラインを変換した8ビットを元のビットボードに逆変換する

8ビットを、あるビットの行数の8倍だけ左シフトすればよいです。

  • コード例
ull convert8ToHorizontal64(unsigned char x, int bit) {
	return (ull)x << (getRow(bit) * 8);
}

あるビットの垂直方向のラインを変換した8ビットを元のビットボードに逆変換する

8ビットに0x0101010101010101を掛けて、A1からH8の対角線のマスクを取ったものに0x00000000000000FFを掛けると、左端に垂直方向のラインが現れるので、左端のマスクを取り、(7 - あるビットの列数)だけ右シフトすれば良いです。

  1. 8ビット
  2. 0x0101010101010101を掛ける
  3. A1からH8の対角線のマスクを取る
  4. 0x00000000000000FFを掛けると、左端に垂直方向のラインが現れる
  5. 左端のマスクを取る
  6. (7 - あるビットの列数)だけ右シフトする
  • コード例
ull convert8ToVertical64(unsigned char x, int bit) {
	return (((((ull)x
			   * 0x0101010101010101) & 0x8040201008040201)
		       * 0x00000000000000FF) & 0x8080808080808080)
			   >> (7 - getColumn(bit));
}

あるビットのA1-H8方向のラインを変換した8ビットを元のビットボードに逆変換する

8ビットに0x0101010101010101を掛け、あるビットのA1-H8方向のマスクを取れば良いです。

  1. 8ビット
  2. 0x0101010101010101を掛ける
  3. あるビットのA1-H8方向のマスクを取る
  • コード例
ull convert8ToDiagonalA1H8(unsigned char x, int bit) {
    ull const MASK = getDiagonalA1H8Mask(bit);
	return ((ull)x * 0x0101010101010101) & MASK;
}

あるビットのA8-H1方向のラインを変換した8ビットを元のビットボードに逆変換する

先程のA1-H8方向と同様に、8ビットに0x0101010101010101を掛け、あるビットのA8-H1方向のマスクを取れば良いです。

  • コード例
ull convert8ToDiagonalA8H1(unsigned char x, int bit) {
    ull const MASK = getDiagonalA8H1Mask(bit);
	return ((ull)x * 0x0101010101010101) & MASK;
}

感想

オセロとかで使えます。