/
position.cpp
2366 lines (1844 loc) · 84.2 KB
/
position.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#include "position.h"
#include "misc.h"
#include "tt.h"
#include "thread.h"
#include <iostream>
#include <sstream>
#include <cstring> // std::memset()
#if defined(EVAL_KPPT) || defined(EVAL_KPP_KKPT) || defined(EVAL_NNUE)
#include "eval/evaluate_common.h"
#endif
using namespace std;
using namespace Effect8;
std::string SFEN_HIRATE = "lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL b - 1";
// 局面のhash keyを求めるときに用いるZobrist key
namespace Zobrist {
HASH_KEY zero; // ゼロ(==0)
HASH_KEY side; // 手番(==1)
HASH_KEY psq[SQ_NB_PLUS1][PIECE_NB]; // 駒pcが盤上sqに配置されているときのZobrist Key
HASH_KEY hand[COLOR_NB][PIECE_HAND_NB]; // c側の手駒prが一枚増えるごとにこれを加算するZobristKey
HASH_KEY depth[MAX_PLY]; // 深さも考慮に入れたHASH KEYを作りたいときに用いる(実験用)
}
// ----------------------------------
// CheckInfo
// ----------------------------------
// 王手情報の初期化
template <bool doNullMove>
void Position::set_check_info(StateInfo* si) const {
//: si->blockersForKing[WHITE] = slider_blockers(pieces(BLACK), square<KING>(WHITE),si->pinners[WHITE]);
//: si->blockersForKing[BLACK] = slider_blockers(pieces(WHITE), square<KING>(BLACK),si->pinners[BLACK]);
// ↓Stockfishのこの部分の実装、将棋においては良くないので、以下のように変える。
// ※ 将棋においては駒の動きが上下対称ではないので手番を引数で渡す必要がある。
if (!doNullMove)
{
// null moveのときは前の局面でこの情報は設定されているので更新する必要がない。
si->blockersForKing[WHITE] = slider_blockers(BLACK, square<KING>(WHITE), si->pinners[WHITE]);
si->blockersForKing[BLACK] = slider_blockers(WHITE, square<KING>(BLACK), si->pinners[BLACK]);
}
Square ksq = square<KING>(~sideToMove);
// 駒種Xによって敵玉に王手となる升のbitboard
// 歩であれば、自玉に敵の歩を置いたときの利きにある場所に自分の歩があればそれは敵玉に対して王手になるので、
// そういう意味で(ksq,them)となっている。
Bitboard occ = pieces();
Color them = ~sideToMove;
// この指し手が二歩でないかは、この時点でテストしない。指し手生成で除外する。なるべくこの手のチェックは遅延させる。
si->checkSquares[PAWN] = pawnEffect(them, ksq);
si->checkSquares[KNIGHT] = knightEffect(them, ksq);
si->checkSquares[SILVER] = silverEffect(them, ksq);
si->checkSquares[BISHOP] = bishopEffect(ksq, occ);
si->checkSquares[ROOK] = rookEffect(ksq, occ);
si->checkSquares[GOLD] = goldEffect(them, ksq);
// 香で王手になる升は利きを求め直さずに飛車で王手になる升を香のstep effectでマスクしたものを使う。
si->checkSquares[LANCE] = si->checkSquares[ROOK] & lanceStepEffect(them,ksq);
// 王を移動させて直接王手になることはない。それは自殺手である。
si->checkSquares[KING] = ZERO_BB;
// 成り駒。この初期化は馬鹿らしいようだが、gives_check()は指し手ごとに呼び出されるので、その処理を軽くしたいので
// ここでの初期化は許容できる。(このコードはdo_move()に対して1回呼び出されるだけなので)
si->checkSquares[PRO_PAWN] = si->checkSquares[GOLD];
si->checkSquares[PRO_LANCE] = si->checkSquares[GOLD];
si->checkSquares[PRO_KNIGHT] = si->checkSquares[GOLD];
si->checkSquares[PRO_SILVER] = si->checkSquares[GOLD];
si->checkSquares[HORSE] = si->checkSquares[BISHOP] | kingEffect(ksq);
si->checkSquares[DRAGON] = si->checkSquares[ROOK] | kingEffect(ksq);
}
// ----------------------------------
// Zorbrist keyの初期化
// ----------------------------------
#if defined(CUCKOO)
// Marcel van Kervinck's cuckoo algorithm for fast detection of "upcoming repetition"
// situations. Description of the algorithm in the following paper:
// https://marcelk.net/2013-04-06/paper/upcoming-rep-v2.pdf
// Stockfishの2倍の配列を確保
// First and second hash functions for indexing the cuckoo tables
inline int H1(Key h) { return h & 0x3fff; }
inline int H2(Key h) { return (h >> 16) & 0x3fff; }
// Cuckoo tables with Zobrist hashes of valid reversible moves, and the moves themselves
Key cuckoo[8192*2];
Move cuckooMove[8192*2];
// → cuckooアルゴリズムとやらで、千日手局面に到達する指し手の検出が高速化できるらしい。
// (数手前の局面と現在の局面の差が、ある駒の移動(+捕獲)だけであることが高速に判定できれば、
// 早期枝刈りとしてdraw_valueを返すことができる。)
#endif
void Position::init() {
PRNG rng(20151225); // 開発開始日 == 電王トーナメント2015,最終日
// 手番としてbit0を用いる。それ以外はbit0を使わない。これをxorではなく加算して行ってもbit0は汚されない。
SET_HASH(Zobrist::side, 1, 0, 0, 0);
SET_HASH(Zobrist::zero, 0, 0, 0, 0);
// 64bit hash keyは256bit hash keyの下位64bitという解釈をすることで、256bitと64bitのときとでhash keyの下位64bitは合致するようにしておく。
// これは定跡DBなどで使うときにこの性質が欲しいからである。
// またpc==NO_PIECEのときは0であることを保証したいのでSET_HASHしない。
// psqは、C++の規約上、事前にゼロであることは保証される。
for (auto pc : Piece())
for (auto sq : SQ)
if (pc)
SET_HASH(Zobrist::psq[sq][pc], rng.rand<Key>() & ~1ULL, rng.rand<Key>(), rng.rand<Key>(), rng.rand<Key>());
// またpr==NO_PIECEのときは0であることを保証したいのでSET_HASHしない。
for (auto c : COLOR)
for (PieceType pr = NO_PIECE_TYPE; pr < PIECE_HAND_NB; ++pr)
if (pr)
SET_HASH(Zobrist::hand[c][pr], rng.rand<Key>() & ~1ULL, rng.rand<Key>(), rng.rand<Key>(), rng.rand<Key>());
for (int i = 0; i < MAX_PLY; ++i)
SET_HASH(Zobrist::depth[i], rng.rand<Key>() & ~1ULL, rng.rand<Key>(), rng.rand<Key>(), rng.rand<Key>());
#if defined(CUCKOO)
// Prepare the cuckoo tables
std::memset(cuckoo, 0, sizeof(cuckoo));
std::memset(cuckooMove, 0, sizeof(cuckooMove));
int count = 0;
// 重複カウント用
int count2 = 0;
for (auto pc : Piece())
{
auto pt = type_of(pc);
if (!(pt == PAWN || pt == LANCE || pt == KNIGHT || pt == SILVER || pt == GOLD || pt == BISHOP || pt == ROOK || pt == KING
|| pt == PRO_PAWN || pt == PRO_LANCE || pt == PRO_KNIGHT || pt == PRO_SILVER || pt == HORSE || pt == DRAGON))
continue;
// 将棋だとチェスと異なり、from → toに動かせるからと言ってto→fromに動かせるとは限らないので
// ここのコード、ずいぶん違ってくる。
for (auto s1 : SQ)
for (Square s2 : SQ)
if (effects_from(pc, s1, ZERO_BB) & s2)
{
Move move = (Move)(make_move(s1, s2) + (pc << 16));
// 手番のところ使わない。無視するために潰す。
Key key = (Zobrist::psq[s2][pc] - Zobrist::psq[s1][pc]) >> 1/* Zobrist::side*/;
int i = H1(key);
while (true)
{
std::swap(cuckoo[i], key);
std::swap(cuckooMove[i], move);
if (move == MOVE_NONE) // Arrived at empty slot?
break;
//i = (i == H1(key)) ? H2(key) : H1(key); // Push victim to alternative slot
// → これ、テーブル小さいので衝突しつづける…(´ω`) H1になかったらH2でええで..
i = H2(key);
std::swap(cuckoo[i], key);
std::swap(cuckooMove[i], move);
if (move != MOVE_NONE)
count2++;
break;
}
count++;
}
}
//assert(count == 3668); // chessの場合
// cout << "count = " << count << " , count2 = " << count2 << endl;
// chessの2倍の配列時 : count = 16456 , count2 = 4499
// chessの4倍の配列時 : count = 16456 , count2 = 1623
ASSERT_LV3(count == 16456);
#endif
}
// depthに応じたZobrist Hashを得る。depthを含めてhash keyを求めたいときに用いる。
HASH_KEY DepthHash(int depth) { return Zobrist::depth[depth]; }
// ----------------------------------
// Position::set()とその逆変換sfen()
// ----------------------------------
// Pieceを綺麗に出力する(USI形式ではない) 先手の駒は大文字、後手の駒は小文字、成り駒は先頭に+がつく。盤面表示に使う。
#if !defined (PRETTY_JP)
std::string pretty(Piece pc) { return std::string(USI_PIECE).substr(pc * 2, 2); }
#else
// "□"(四角)は文字フォントによっては半分の幅しかない。"口"(くち)にする。
std::string USI_PIECE_KANJI[] = {
" 口"," 歩"," 香"," 桂"," 銀"," 角"," 飛"," 金"," 玉"," と"," 杏"," 圭"," 全"," 馬"," 龍"," 菌"," 王",
"^歩","^香","^桂","^銀","^角","^飛","^金","^玉","^と","^杏","^圭","^全","^馬","^龍","^菌","^王"
};
std::string pretty(Piece pc) {
#if 1
return USI_PIECE_KANJI[pc];
#else
// 色を変えたほうがわかりやすい。Linuxは簡単だが、MS-DOSは設定が面倒。
// Linux : https://qiita.com/dojineko/items/49aa30018bb721b0b4a9
// MS-DOS : https://one-person.hatenablog.jp/entry/2017/02/23/125809
std::string result;
if (pc != NO_PIECE)
result = (color_of(pc) == BLACK) ? "\\e[32;40;1m" : "\\e[33;40;1m";
result += USI_PIECE_KANJI[pc];
if (pc != NO_PIECE)
result += "\\e[m";
return result;
#endif
}
#endif
// sfen文字列で盤面を設定する
void Position::set(std::string sfen , StateInfo* si , Thread* th)
{
std::memset(this, 0, sizeof(Position));
// 局面をrootより遡るためには、ここまでの局面情報が必要で、それは引数のsiとして渡されているという解釈。
// ThreadPool::start_thinking()では、
// ここをいったんゼロクリアしたのちに、呼び出し側で、そのsiを復元することにより、局面を遡る。
std::memset(si, 0, sizeof(StateInfo));
st = si;
// 変な入力をされることはあまり想定していない。
// sfen文字列は、普通GUI側から渡ってくるのでおかしい入力であることはありえないからである。
// --- 盤面
// 盤面左上から。Square型のレイアウトに依らずに処理を進めたいため、Square型は使わない。
File f = FILE_9;
Rank r = RANK_1;
std::istringstream ss(sfen);
// 盤面を読むときにスペースが盤面と手番とのセパレーターなのでそこを読み飛ばさないようにnoskipwsを指定しておく。
ss >> std::noskipws;
uint8_t token;
bool promote = false;
size_t idx;
#if defined (USE_EVAL_LIST)
// evalListのclear。上でmemsetでゼロクリアしたときにクリアされているが…。
evalList.clear();
// PieceListを更新する上で、どの駒がどこにあるかを設定しなければならないが、
// それぞれの駒をどこまで使ったかのカウンター
PieceNumber piece_no_count[KING] = { PIECE_NUMBER_ZERO,PIECE_NUMBER_PAWN,PIECE_NUMBER_LANCE,PIECE_NUMBER_KNIGHT,
PIECE_NUMBER_SILVER, PIECE_NUMBER_BISHOP, PIECE_NUMBER_ROOK,PIECE_NUMBER_GOLD };
// 先手玉のいない詰将棋とか、駒落ちに対応させるために、存在しない駒はすべてBONA_PIECE_ZEROにいることにする。
// 上のevalList.clear()で、ゼロクリアしているので、それは達成しているはず。
#endif
kingSquare[BLACK] = kingSquare[WHITE] = SQ_NB;
while ((ss >> token) && !isspace(token))
{
// 数字は、空の升の数なのでその分だけ筋(File)を進める
if (isdigit(token))
f -= File(token - '0');
// '/'は次の段を意味する
else if (token == '/')
{
f = FILE_9;
++r;
}
// '+'は次の駒が成駒であることを意味する
else if (token == '+')
promote = true;
// 駒文字列か?
else if ((idx = PieceToCharBW.find(token)) != string::npos)
{
// 盤面の(f,r)の駒を設定する
auto sq = f | r;
auto pc = Piece(idx + (promote ? u32(PIECE_PROMOTE) : 0));
put_piece(sq, pc);
#if defined (USE_EVAL_LIST)
PieceNumber piece_no =
(idx == B_KING) ? PIECE_NUMBER_BKING : // 先手玉
(idx == W_KING) ? PIECE_NUMBER_WKING : // 後手玉
piece_no_count[raw_type_of(Piece(idx))]++; // それ以外
evalList.put_piece(piece_no, sq, pc); // sqの升にpcの駒を配置する
#endif
// 1升進める
--f;
// 成りフラグ、戻しておく。
promote = false;
}
}
// put_piece()を使ったので更新しておく。
// set_state()で駒種別のbitboardを参照するのでそれまでにこの関数を呼び出す必要がある。
update_bitboards();
// kingSquare[]の更新
update_kingSquare();
// --- 手番
ss >> token;
sideToMove = (token == 'w' ? WHITE : BLACK);
ss >> token; // 手番と手駒とを分かつスペース
// --- 手駒
hand[BLACK] = hand[WHITE] = (Hand)0;
int ct = 0;
while ((ss >> token) && !isspace(token))
{
// 手駒なし
if (token == '-')
break;
if (isdigit(token))
// 駒の枚数。歩だと18枚とかあるので前の値を10倍して足していく。
ct = (token - '0') + ct * 10;
else if ((idx = PieceToCharBW.find(token)) != string::npos)
{
// 個数が省略されていれば1という扱いをする。
ct = max(ct, 1);
add_hand(hand[color_of(Piece(idx))], type_of(Piece(idx)), ct);
// FV38などではこの個数分だけpieceListに突っ込まないといけない。
for (int i = 0; i < ct; ++i)
{
PieceType rpc = raw_type_of(Piece(idx));
#if defined (USE_EVAL_LIST)
PieceNumber piece_no = piece_no_count[rpc]++;
ASSERT_LV1(is_ok(piece_no));
evalList.put_piece(piece_no, color_of(Piece(idx)), rpc, i);
#endif
}
ct = 0;
}
}
// --- 手数(平手の初期局面からの手数)
// gamePlyとして将棋所では(検討モードなどにおいて)ここで常に1が渡されている。
// 検討モードにおいても棋譜上の手数を渡して欲しい気がするし、棋譜上の手数がないなら0を渡して欲しい気はする。
// ここで渡されてきた局面をもとに探索してその指し手を定跡DBに登録しようとするときに、ここの手数が不正確であるのは困る。
gamePly = 0;
ss >> std::skipws >> gamePly;
// --- StateInfoの更新
set_state(st);
// 現局面で王手がかかっているならst->continuous_check[them] = 1にしないと
// 連続王手の千日手の判定が不正確な気がするが、どのみち2回目の出現を負け扱いしているのでまあいいか..
// --- effect
#if defined (LONG_EFFECT_LIBRARY)
// 利きの全計算による更新
LongEffect::calc_effect(*this);
#endif
// --- evaluate
#if defined (USE_PIECE_VALUE)
st->materialValue = Eval::material(*this);
#endif
Eval::compute_eval(*this);
// --- validation
#if ASSERT_LV >= 3
// これassertにしてしまうと、先手玉のいない局面や駒落ちの局面で落ちて困る。
if (!is_ok(*this))
std::cout << "info string Illigal Position?" << endl;
#endif
thisThread = th;
}
// 局面のsfen文字列を取得する。
// Position::set()の逆変換。
const std::string Position::sfen() const
{
std::ostringstream ss;
// --- 盤面
int emptyCnt;
for (Rank r = RANK_1; r <= RANK_9; ++r)
{
for (File f = FILE_9; f >= FILE_1; --f)
{
// それぞれの升に対して駒がないなら
// その段の、そのあとの駒のない升をカウントする
for (emptyCnt = 0; f >= FILE_1 && piece_on(f | r) == NO_PIECE; --f)
++emptyCnt;
// 駒のなかった升の数を出力
if (emptyCnt)
ss << emptyCnt;
// 駒があったなら、それに対応する駒文字列を出力
if (f >= FILE_1)
ss << (piece_on(f | r));
}
// 最下段以外では次の行があるのでセパレーターである'/'を出力する。
if (r < RANK_9)
ss << '/';
}
// --- 手番
ss << (sideToMove == WHITE ? " w " : " b ");
// --- 手駒(UCIプロトコルにはないがUSIプロトコルにはある)
int n;
bool found = false;
for (Color c = BLACK; c <= WHITE; ++c)
for (int pn = 0 ; pn < 7; ++ pn)
{
// 手駒の出力順はUSIプロトコルでは規定されていないが、
// USI原案によると、飛、角、金、銀、桂、香、歩の順である。
// sfen文字列を一意にしておかないと定跡データーをsfen文字列で書き出したときに
// 他のソフトで文字列が一致しなくて困るので、この順に倣うことにする。
const PieceType USI_Hand[7] = { ROOK,BISHOP,GOLD,SILVER,KNIGHT,LANCE,PAWN };
auto p = USI_Hand[pn];
// その種類の手駒の枚数
n = hand_count(hand[c], p);
// その種類の手駒を持っているか
if (n != 0)
{
// 手駒が1枚でも見つかった
found = true;
// その種類の駒の枚数。1ならば出力を省略
if (n != 1)
ss << n;
ss << PieceToCharBW[make_piece(c, p)];
}
}
// 手駒がない場合はハイフンを出力
ss << (found ? " " : "- ");
// --- 初期局面からの手数
ss << gamePly;
return ss.str();
}
void Position::set_state(StateInfo* si) const {
// --- bitboard
// この局面で自玉に王手している敵駒
st->checkersBB = attackers_to(~sideToMove, king_square(sideToMove));
// 王手情報の初期化
set_check_info<false>(si);
// --- hash keyの計算
si->board_key_ = sideToMove == BLACK ? Zobrist::zero : Zobrist::side;
si->hand_key_ = Zobrist::zero;
for (auto sq : pieces())
{
auto pc = piece_on(sq);
si->board_key_ += Zobrist::psq[sq][pc];
}
for (auto c : COLOR)
for (PieceType pr = PAWN; pr < PIECE_HAND_NB; ++pr)
si->hand_key_ += Zobrist::hand[c][pr] * (int64_t)hand_count(hand[c], pr); // 手駒はaddにする(差分計算が楽になるため)
// --- hand
si->hand = hand[sideToMove];
}
// put_piece(),remove_piece(),xor_piece()を用いたあとに呼び出す必要がある。
void Position::update_bitboards()
{
// 王・馬・龍を合成したbitboard
byTypeBB[HDK] = pieces(KING , HORSE , DRAGON);
// 金と同じ移動特性を持つ駒
byTypeBB[GOLDS] = pieces(GOLD , PRO_PAWN , PRO_LANCE , PRO_KNIGHT , PRO_SILVER);
// 以下、attackers_to()で頻繁に用いるのでここで1回計算しておいても、トータルでは高速化する。
// 角と馬
byTypeBB[BISHOP_HORSE] = pieces(BISHOP , HORSE);
// 飛車と龍
byTypeBB[ROOK_DRAGON] = pieces(ROOK , DRAGON);
// 銀とHDK
byTypeBB[SILVER_HDK] = pieces(SILVER , HDK);
// 金相当の駒とHDK
byTypeBB[GOLDS_HDK] = pieces(GOLDS , HDK);
}
// このクラスが保持しているkingSquare[]の更新。
void Position::update_kingSquare()
{
for (auto c : COLOR)
{
// 玉がいなければSQ_NBを設定しておいてやる。(片玉対応)
auto b = pieces(c, KING);
kingSquare[c] = b ? b.pop() : SQ_NB;
}
}
// ----------------------------------
// Positionの表示
// ----------------------------------
// 盤面を出力する。(USI形式ではない) デバッグ用。
std::ostream& operator<<(std::ostream& os, const Position& pos)
{
// 盤面
for (Rank r = RANK_1; r <= RANK_9; ++r)
{
for (File f = FILE_9; f >= FILE_1; --f)
os << pretty(pos.board[f | r]);
os << endl;
}
#if !defined (PRETTY_JP)
// 手駒
os << "BLACK HAND : " << pos.hand[BLACK] << " , WHITE HAND : " << pos.hand[WHITE] << endl;
// 手番
os << "Turn = " << pos.sideToMove << endl;
#else
os << "先手 手駒 : " << pos.hand[BLACK] << " , 後手 手駒 : " << pos.hand[WHITE] << endl;
os << "手番 = " << pos.sideToMove << endl;
#endif
// sfen文字列もおまけで表示させておく。(デバッグのときに便利)
os << "sfen " << pos.sfen() << endl;
return os;
}
#if defined (KEEP_LAST_MOVE)
#include <stack>
// 開始局面からこの局面にいたるまでの指し手を表示する。
std::string Position::moves_from_start(bool is_pretty) const
{
StateInfo* p = st;
std::stack<StateInfo*> states;
while (p->previous != nullptr)
{
states.push(p);
p = p->previous;
}
stringstream ss;
while (states.size())
{
auto& top = states.top();
if (is_pretty)
ss << pretty(top->lastMove, top->lastMovedPieceType) << ' ';
else
ss << top->lastMove << ' ';
states.pop();
}
return ss.str();
}
#endif
// ----------------------------------
// ある升へ利いている駒など
// ----------------------------------
// Position::slider_blockers() は、c側の長い利きを持つ駒(sliders)から、升sへの利きを
// 遮っている先後の駒の位置をBitboardで返す。ただし、2重に遮っている場合はそれらの駒は返さない。
// もし、この関数のこの返す駒を取り除いた場合、升sに対してsliderによって利きがある状態になる。
// 升sにある玉に対してこの関数を呼び出した場合、それはpinされている駒と両王手の候補となる駒である。
// また、升sにある玉は~c側のKINGであるとする。
Bitboard Position::slider_blockers(Color c, Square s , Bitboard& pinners) const {
Bitboard blockers = ZERO_BB;
// pinnersは返し値。
pinners = ZERO_BB;
// cが与えられていないと香の利きの方向を確定させることが出来ない。
// ゆえに将棋では、この関数は手番を引数に取るべき。(チェスとはこの点において異なる。)
// snipersとは、pinされている駒が取り除かれたときに升sに利きが発生する大駒である。
Bitboard snipers =
( (pieces(ROOK_DRAGON) & rookStepEffect(s))
| (pieces(BISHOP_HORSE) & bishopStepEffect(s))
// 香に関しては攻撃駒が先手なら、玉より下側をサーチして、そこにある先手の香を探す。
| (pieces(LANCE) & lanceStepEffect(~c, s))
) & pieces(c);
//Bitboard occupancy = pieces() ^ snipers;
// ↑このStockfishの元のコード、snipersを除いた盤上の駒で考えているが、
// ^王 歩 角 飛
// このような状況で飛車に対して角を取り除いてから敵玉への射線を考えるので、
// 歩がslider_blocker扱いになってしまう。つまり、このコードは間違っているのでは?
while (snipers)
{
Square sniperSq = snipers.pop();
Bitboard b = between_bb(s, sniperSq) & pieces() /* occupancy */;
// snipperと玉との間にある駒が1個であるなら。
if (b && !more_than_one(b))
{
blockers |= b;
if (b & pieces(~c))
// sniperと玉に挟まれた駒が玉と同じ色の駒であるなら、pinnerに追加。
pinners |= sniperSq;
}
}
return blockers;
}
// sに利きのあるc側の駒を列挙する。
// (occが指定されていなければ現在の盤面において。occが指定されていればそれをoccupied bitboardとして)
Bitboard Position::attackers_to(Color c, Square sq, const Bitboard& occ) const
{
ASSERT_LV3(is_ok(c) && sq <= SQ_NB);
Color them = ~c;
// sの地点に敵駒ptをおいて、その利きに自駒のptがあればsに利いているということだ。
// 香の利きを求めるコストが惜しいのでrookEffect()を利用する。
return
( (pawnEffect(them, sq) & pieces(PAWN) )
| (knightEffect(them, sq) & pieces(KNIGHT) )
| (silverEffect(them, sq) & pieces(SILVER_HDK) )
| (goldEffect(them, sq) & pieces(GOLDS_HDK) )
| (bishopEffect(sq, occ) & pieces(BISHOP_HORSE))
| (rookEffect(sq, occ) & (
pieces(ROOK_DRAGON)
| (lanceStepEffect(them,sq) & pieces(LANCE))
))
// | (kingEffect(sq) & pieces(c, HDK));
// → HDKは、銀と金のところに含めることによって、参照するテーブルを一個減らして高速化しようというAperyのアイデア。
) & pieces(c); // 先後混在しているのでc側の駒だけ最後にマスクする。
;
}
// sに利きのあるc側の駒を列挙する。先後両方。
// (occが指定されていなければ現在の盤面において。occが指定されていればそれをoccupied bitboardとして)
// sq == SQ_NBでの呼び出しは合法。ZERO_BBが返る。
Bitboard Position::attackers_to(Square sq, const Bitboard& occ) const
{
ASSERT_LV3(sq <= SQ_NB);
// sqの地点に敵駒ptをおいて、その利きに自駒のptがあればsqに利いているということだ。
return
// 先手の歩・桂・銀・金・HDK
(( (pawnEffect(WHITE, sq) & pieces(PAWN) )
| (knightEffect(WHITE, sq) & pieces(KNIGHT) )
| (silverEffect(WHITE, sq) & pieces(SILVER_HDK) )
| (goldEffect(WHITE, sq) & pieces(GOLDS_HDK) )
) & pieces(BLACK))
|
// 後手の歩・桂・銀・金・HDK
(( (pawnEffect(BLACK, sq) & pieces(PAWN) )
| (knightEffect(BLACK, sq) & pieces(KNIGHT) )
| (silverEffect(BLACK, sq) & pieces(SILVER_HDK) )
| (goldEffect(BLACK, sq) & pieces(GOLDS_HDK) )
) & pieces(WHITE))
// 先後の角・飛・香
| (bishopEffect(sq, occ) & pieces(BISHOP_HORSE) )
| (rookEffect(sq, occ) & (
pieces(ROOK_DRAGON)
| (pieces(BLACK , LANCE) & lanceStepEffect(WHITE , sq))
| (pieces(WHITE , LANCE) & lanceStepEffect(BLACK , sq))
// 香も、StepEffectでマスクしたあと飛車の利きを使ったほうが香の利きを求めなくて済んで速い。
));
}
// 打ち歩詰め判定に使う。王に打ち歩された歩の升をpawn_sqとして、c側(王側)のpawn_sqへ利いている駒を列挙する。香が利いていないことは自明。
inline Bitboard Position::attackers_to_pawn(Color c, Square pawn_sq) const
{
ASSERT_LV3(is_ok(c) && pawn_sq <= SQ_NB);
Color them = ~c;
const Bitboard& occ = pieces();
// 馬と龍
const Bitboard bb_hd = kingEffect(pawn_sq) & pieces(HORSE,DRAGON);
// 馬、龍の利きは考慮しないといけない。しかしここに玉が含まれるので玉は取り除く必要がある。
// bb_hdは銀と金のところに加えてしまうことでテーブル参照を一回減らす。
// sの地点に敵駒ptをおいて、その利きに自駒のptがあればsに利いているということだ。
// 打ち歩詰め判定なので、その打たれた歩を歩、香、王で取れることはない。(王で取れないことは事前にチェック済)
return
( (knightEffect(them, pawn_sq) & pieces(KNIGHT) )
| (silverEffect(them, pawn_sq) & (pieces(SILVER) | bb_hd) )
| (goldEffect(them, pawn_sq) & (pieces(GOLDS) | bb_hd) )
| (bishopEffect(pawn_sq, occ) & pieces(BISHOP_HORSE) )
| (rookEffect(pawn_sq, occ) & pieces(ROOK_DRAGON) )
) & pieces(c);
}
// 指し手が、(敵玉に)王手になるかをテストする。
bool Position::gives_check(Move m) const
{
// 指し手がおかしくないか
ASSERT_LV3(is_ok(m));
// 移動先
const Square to = to_sq(m);
// 駒打ち・移動する指し手どちらであってもmove_piece_after(m)で移動後の駒が取得できるので
// 直接王手の処理は共通化できる。
if (st->checkSquares[type_of(moved_piece_after(m))] & to)
return true;
// -- 移動する指し手ならば、これで開き王手になるかどうかの判定が必要。
// 移動元
const Square from = from_sq(m);
// 開き王手になる駒の候補があるとして、fromにあるのがその駒で、fromからtoは玉と直線上にないなら
// 前提条件より、fromにあるのが自駒であることは確定しているので、pieces(sideToMove)は不要。
return !is_drop(m)
&& (((blockers_for_king(~sideToMove) /*& pieces(sideToMove)*/) & from)
&& !aligned(from, to, square<KING>(~sideToMove)));
}
Bitboard Position::pinned_pieces(Color c, Square avoid) const {
Bitboard b, pinners, result = ZERO_BB;
Square ksq = king_square(c);
// avoidを除外して考える。
Bitboard avoid_bb = ~Bitboard(avoid);
pinners = (
(pieces(ROOK_DRAGON) & rookStepEffect(ksq))
| (pieces(BISHOP_HORSE) & bishopStepEffect(ksq))
| (pieces(LANCE) & lanceStepEffect(c, ksq))
) & avoid_bb & pieces(~c);
while (pinners)
{
b = between_bb(ksq, pinners.pop()) & pieces() & avoid_bb;
if (!more_than_one(b))
result |= b & pieces(c);
}
return result;
}
Bitboard Position::pinned_pieces(Color c, Square from, Square to) const {
Bitboard b, pinners, result = ZERO_BB;
Square ksq = king_square(c);
// avoidを除外して考える。
Bitboard avoid_bb = ~Bitboard(from);
pinners = (
(pieces(ROOK_DRAGON) & rookStepEffect(ksq))
| (pieces(BISHOP_HORSE) & bishopStepEffect(ksq))
| (pieces(LANCE) & lanceStepEffect(c, ksq))
) & avoid_bb & pieces(~c);
// fromからは消えて、toの地点に駒が現れているものとして
Bitboard new_pieces = (pieces() & avoid_bb) | to;
while (pinners)
{
b = between_bb(ksq, pinners.pop()) & new_pieces;
if (!more_than_one(b))
result |= b & pieces(c);
}
return result;
}
// 現局面で指し手がないかをテストする。指し手生成ルーチンを用いるので速くない。探索中には使わないこと。
bool Position::is_mated() const
{
// 不成で詰めろを回避できるパターンはないのでLEGAL_ALLである必要はない。
return MoveList<LEGAL>(*this).size() == 0;
}
// ----------------------------------
// 指し手の合法性のテスト
// ----------------------------------
bool Position::legal_drop(const Square to) const
{
const auto us = sideToMove;
// 打とうとする歩の利きに相手玉がいることは前提条件としてクリアしているはず。
ASSERT_LV3(pawnEffect(us, to) == Bitboard(king_square(~us)));
// この歩に利いている自駒(歩を打つほうの駒)がなければ詰みには程遠いのでtrue
if (!effected_to(us, to))
return true;
// ここに利いている敵の駒があり、その駒で取れるなら打ち歩詰めではない
// ここでは玉は除外されるし、香が利いていることもないし、そういう意味では、特化した関数が必要。
Bitboard b = attackers_to_pawn(~us, to);
// 敵玉に対するpinしている駒(自駒も含むが、bが敵駒なので問題ない。)
Bitboard pinned = blockers_for_king(~us);
// pinされていない駒が1つでもあるなら、相手はその駒で取って何事もない。
if (b & (~pinned | FILE_BB[file_of(to)]))
return true;
// 攻撃駒はすべてpinされていたということであり、
// 王の頭に打たれた打ち歩をpinされている駒で取れるケースは、
// いろいろあるが、例1),例2)のような場合であるから、例3)のケースを除き、
// いずれも玉の頭方向以外のところからの玉頭方向への移動であるから、
// pinされている方向への移動ということはありえない。
// 例3)のケースを除くと、この歩は取れないことが確定する。
// 例3)のケースを除外するために同じ筋のものはpinされていないものとして扱う。
// 上のコードの " | FILE_BB[file_of(to)] " の部分がそれ。
// 例1)
// ^玉 ^角 飛
// 歩
// 例2)
// ^玉
// 歩 ^飛
// 角
// 例3)
// ^玉
// 歩
// ^飛
// 香
// 玉の退路を探す
// 自駒がなくて、かつ、to(はすでに調べたので)以外の地点
// 相手玉の場所
Square sq_king = king_square(~us);
#ifndef LONG_EFFECT_LIBRARY
// LONG EFFECT LIBRARYがない場合、愚直に8方向のうち逃げられそうな場所を探すしかない。
Bitboard escape_bb = kingEffect(sq_king) & ~pieces(~us);
escape_bb ^= to;
auto occ = pieces() ^ to; // toには歩をおく前提なので、ここには駒があるものとして、これでの利きの遮断は考えないといけない。
while (escape_bb)
{
Square king_to = escape_bb.pop();
if (!attackers_to(us, king_to, occ))
return true; // 退路が見つかったので打ち歩詰めではない。
}
// すべての検査を抜けてきたのでこれは打ち歩詰めの条件を満たしている。
return false;
#else
// LONG EFFECT LIBRARYがあれば、玉の8近傍の利きなどを直列化して逃げ場所があるか調べるだけで良いはず。
auto a8_effet_us = board_effect[us].around8(sq_king);
// 玉の逃げ場所は、本来はEffect8::around8(escape_bb,sq_king)なのだが、どうせaround8が8近傍だけを直列化するので、
// これが玉の利きと一致しているからこのkingEffect(sq_king)でマスクする必要がない。
auto a8_them_movable = Effect8::around8(~pieces(~us), sq_king) & Effect8::board_mask(sq_king);
// 打った歩での遮断を考える前の段階ですでにすでに歩を打つ側の利きがない升があり、
// そこに移動できるのであれば、これは打ち歩ではない。
if (~a8_effet_us & a8_them_movable)
return true;
// 困ったことに行けそうな升がなかったので打った歩による利きの遮断を考える。
// いまから打つ歩による遮断される升の利きが2以上でなければそこに逃げられるはず。
auto a8_long_effect_to = long_effect.directions_of(us, to);
auto to_dir = (us == BLACK) ? DIRECT_D : DIRECT_U; // 王から見た歩の方角
auto a8_cutoff_dir = Effect8::cutoff_directions(to_dir, a8_long_effect_to);
auto a8_target = a8_cutoff_dir & a8_them_movable & ~board_effect[us].around8_greater_than_one(sq_king);
return a8_target != 0;
#endif
}
// ※ mがこの局面においてpseudo_legalかどうかを判定するための関数。
template <bool All>
bool Position::pseudo_legal_s(const Move m) const {
const Color us = sideToMove;
const Square to = to_sq(m); // 移動先
if (is_drop(m))
{
const PieceType pr = move_dropped_piece(m);
// 置換表から取り出してきている以上、一度は指し手生成ルーチンで生成した指し手のはずであり、
// KING打ちのような値であることはないものとする。
// 上位32bitに移動後の駒が格納されている。それと一致するかのテスト
if (moved_piece_after(m) != Piece(pr + (us == WHITE ? u32(PIECE_WHITE) : 0) ))
return false;
ASSERT_LV3(PAWN <= pr && pr < KING);
// 打つ先の升が埋まっていたり、その手駒を持っていなかったりしたら駄目。
if (piece_on(to) != NO_PIECE || hand_count(hand[us], pr) == 0)
return false;
if (in_check())
{
// 王手されている局面なので合駒でなければならない
Bitboard target = checkers();
Square checksq = target.pop();
// 王手している駒を1個取り除いて、もうひとつあるということは王手している駒が
// 2つあったということであり、両王手なので合い利かず。
if (target)
return false;
// 王と王手している駒との間の升に駒を打っていない場合、それは王手を回避していることに
// ならないので、これは非合法手。
if (!(between_bb(checksq, king_square(us)) & to))
return false;
}
// 歩のとき、二歩および打ち歩詰めであるなら非合法手
if (pr == PAWN && !legal_pawn_drop(us, to))
return false;
// --- 移動できない升への歩・香・桂打ちについて
// 打てない段に打つ歩・香・桂の指し手はそもそも生成されていない。
// 置換表のhash衝突で、後手の指し手が先手の指し手にならないことは保証されている。
// (先手の手番の局面と後手の手番の局面とのhash keyはbit0で区別しているので)
// しかし、Counter Moveの手は手番に関係ないので(駒種を保持していないなら)取り違える可能性があるため
// (しかも、その可能性はそこそこ高い)、ここで合法性をチェックする必要がある。
// → 指し手生成の段階で駒種を保存するようにしたのでこのテスト不要。
}
else {
const Square from = from_sq(m);
const Piece pc = piece_on(from);
// 動かす駒が自駒でなければならない
if (pc == NO_PIECE || color_of(pc) != us)
return false;
// toに移動できないといけない。
if (!(effects_from(pc, from, pieces()) & to))
return false;
// toの地点に自駒があるといけない
if (pieces(us) & to)
return false;
PieceType pt = type_of(pc);
if (is_promote(m))
{
// --- 成る指し手
// 成れない駒の成りではないことを確かめないといけない。
static_assert(GOLD == 7, "GOLD must be 7.");
if (pt >= GOLD)
return false;
// 上位32bitに移動後の駒が格納されている。それと一致するかのテスト
// pcが成っていない駒であることは上で確認してあるので、"+ PIECE_PROMOTE"でも十分。
if (moved_piece_after(m) != Piece(pc + PIECE_PROMOTE))
return false;
}
else {
// --- 成らない指し手
// 上位32bitに移動後の駒が格納されている。それと一致するかのテスト
if (moved_piece_after(m) != pc)
return false;
// 駒打ちのところに書いた理由により、不成で進めない升への指し手のチェックも不要。
// 間違い → 駒種をmoveに含めていないならこのチェック必要だわ。
// 52から51銀のような指し手がkillerやcountermoveに登録されていたとして、52に歩があると