1 module shogi.shogiban;
2 
3 import shogi.constants, shogi.bitboard, shogi.move;
4 
5 class Shogiban {
6   //--------------------------------------------------------
7   //  盤面表現
8   //--------------------------------------------------------
9   //各駒の有る位置を表す(指し手生成に利用)
10   mixin("Bitboard _bbYYXX;".generateReplace("XX", KOMA_BB).generateReplace("YY", [ "B", "W" ]));
11   //成金については金のビットボードに集約する
12   mixin("alias _bbYYXX=_bbYYKI;".generateReplace("XX", [ "pFU", "pKY", "pKE", "pGI" ]).generateReplace("YY", [ "B", "W" ]));
13 
14   //駒の有る位置を表す(取る手生成、打つ手生成、飛び利きの計算に利用)
15   Bitboard _bbOccupyB, _bbOccupyW, _bbOccupy;
16 
17   byte[81] _masu;   //マスごとの駒(取る手の更新に使用)
18   Teban _teban;     //手番
19   Hash _boardHash;  //盤面ハッシュ(持ち駒のハッシュ値は含まない)
20 
21   //持ち駒
22   Mochigoma _mochigomaB = Mochigoma(0), _mochigomaW = Mochigoma(1);
23 
24   //--------------------------------------------------------
25   //  構造体定義
26   //--------------------------------------------------------
27   //盤面ハッシュ
28   struct Hash {
29     /// current hash key
30     ulong _key;
31     alias _key this;
32 
33     /// update hash
34     void update(in uint to, in uint koma) @nogc { _key ^= _zobrist[koma - 4][to]; }
35 
36     /// hash
37     static private immutable ulong[81][28] _zobrist = {
38       import std.random: Random, uniform;
39       ulong[81][28] z;
40       auto gen = Random(77);
41       foreach (koma_i; 4..32)
42         foreach (square_i; 0..81) { z[koma_i - 4][square_i] = uniform(ulong.min, ulong.max, gen) << 1; }
43       return z;
44     }();
45   }
46 
47   //持ち駒
48   struct Mochigoma {
49     uint _a;
50     alias _a this;
51     void init(int i) { _a = i; }
52     //そのままでもハッシュキーとして使えるように桁数の多い歩の数を上位のビットに
53     //そのままでも優劣比較ができるように1bitずつ空ける
54     enum idx { FU, KY, KE, GI, KI, KA, HI, OU };
55     static immutable int[9] shift = [ 23, 19, 15, 11, 7, 4, 1, 29, 0 ];
56     static immutable int[9] mask = [ 31, 7, 7, 7, 7, 3, 3, 3, 0 ];
57     mixin(q{
58       void addYYXX() @nogc { _a += 1 << shift[idx.XX]; }
59       void remYYXX() @nogc { _a -= 1 << shift[idx.XX]; }
60       uint numYYXX() @nogc const { return (_a >> shift[idx.XX]) & mask[idx.XX]; }
61     }.generateReplace("YY", [ "", "p" ])
62               .generateReplace("XX", [ "FU", "KY", "KE", "GI", "KI", "KA", "HI", "OU" ]));
63     uint num(in uint i) const { return (_a >> shift[i]) & mask[i]; }
64     wstring toString(in uint i, in uint w) const {
65       import std.conv, std.format;
66       immutable wstring strKoma = "歩香桂銀金角飛玉  ";
67       return num(i) ? format(" %s%s%2d %s", w ? "\x1b[31m" : "", strKoma[i], num(i), w ? "\x1b[39m" : "").to !wstring : "      ";
68     }
69   };
70 
71   //-------------------------------------------------------
72   //  初期化とか局面のセットとか
73   //-------------------------------------------------------
74 
75   // SFENの読み込み
76   this(in string sfen = "lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL b - 1") {
77     import std.range : split, front, retro;
78     import std.algorithm : countUntil, map, reverse, joiner;
79     import std.ascii : isDigit;
80     immutable PieceToChar = "PpLlNnSsBbRrGgKk";
81 
82     //盤面、手番、持ち駒、手数の文字列に分割
83     auto list = sfen.split;
84 
85     //盤面
86     int idx = -1;
87     int sq = 0;
88     foreach (token; list[0].split('/').map !retro.joiner) {
89       if (token.isDigit)
90         sq += token - '0';  // sqを進める
91       else if (token == '+') {
92         setKomaToBoard(komaType.BFU + idx, sq - 1);       // XOR
93         setKomaToBoard(komaType.BFU + idx + 16, sq - 1);  //直前のsqの駒を成にする
94       } else if ((idx = cast(int)(PieceToChar.countUntil(token))) != -1)
95         setKomaToBoard(komaType.BFU + idx, sq++);
96     }
97 
98     //手番
99     _teban = (list[1].front == 'b' ? Teban.SENTE : Teban.GOTE);
100 
101     //持ち駒
102     uint num = 1;  //数の指定が無ければ持ち駒は1枚
103     uint beforeNum = 0;
104     foreach (token; list[2]) {
105       if (token.isDigit) {
106         num = token - '0' + beforeNum;
107         beforeNum = num * 10;
108       } else if ((idx = cast(int)(PieceToChar.countUntil(token))) != -1) {
109         foreach (_; 0..num) { setKomaToHand(komaType.BFU + idx); }
110         num = 1;
111         beforeNum = 0;
112       }
113     }
114     // TODO 手数を考慮する必要があるケースがあるかも(256手で引き分けとか)
115   }
116 
117   //駒の設置。初期化や盤面読み込み用
118   void setKomaToBoard(in uint kt, in uint sq) {
119     assert(sq < 81);
120     assert(kt < 32);
121     final switch (cast(komaType) kt) {
122       mixin(q{
123         case komaType.YYXX:
124           mixin(q{ BB ^= MASK_SQ[sq]; }.generateReplace("BB", [ "_bbYYXX", "_bbOccupyYY", "_bbOccupy" ]));
125           _masu[sq] = komaType.YYXX;
126           _boardHash.update(sq, komaType.YYXX);
127           break;
128       }.generateReplace("YY", [ "B", "W" ])
129                 .generateReplace("XX", KOMA));
130       case komaType.none:
131         break;
132     }
133   }
134   void setKomaToHand(in uint kt) {
135     import std.algorithm : startsWith;
136     assert(kt < 20);
137     final switch (cast(komaType) kt) {
138       mixin(q{
139         case komaType.YYXX:
140           static if ("XX".startsWith("FU", "KY", "KE", "GI", "KA", "HI", "KI")) {
141             _mochigomaYY.addXX;
142             break;
143           }
144           assert(false);
145       }.generateReplace("YY", [ "B", "W" ])
146                 .generateReplace("XX", KOMA));
147       case komaType.none:
148         break;
149     }
150   }
151 
152   override string toString() const {
153     import std.conv, std.format, std.range;
154     immutable wstring strKoma = "・X歩香桂銀角飛金玉と杏圭全馬竜";
155     auto app = appender !wstring;
156     app.put(format("%s手番\n", "先後"w[_teban & 1]));
157     app.put("      987654321\n");
158     foreach (i; 0..9) {
159       app.put(_mochigomaW.toString(i, 1));
160       foreach (j; 0..9) {
161         uint k = _masu[i * 9 + 8 - j];
162         app.put(format("%s%s%s", (k & 1) ? "\x1b[31m" : "", strKoma[k >> 1], (k & 1) ? "\x1b[39m" : ""));
163       }
164       app.put(format("%s%s\n", "一二三四五六七八九"w[i], _mochigomaB.toString(8 - i, 0)));
165     }
166     app.put(format("\nHashkey(Board): %016x", _boardHash));
167     app.put(format("\nHashkey(HandB): %016x", _mochigomaB._a));
168     app.put(format("\nHashkey(HandW): %016x", _mochigomaW._a));
169     return app.data.to !string;
170   }
171 
172   //盤面更新の展開
173   mixin(import("_domove.d").generateReplace("ACT", [ "do", "undo" ]));
174 }
175 
176 //手生成の展開
177 mixin(import("_movegen.d").generateReplace("YY", "ZZ", [ "B", "W" ]));