-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcodevsreborn.cpp
More file actions
2888 lines (2755 loc) · 137 KB
/
codevsreborn.cpp
File metadata and controls
2888 lines (2755 loc) · 137 KB
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 "codevsreborn.hpp"
#include "tt.hpp"
// 乱数関連。使わないのでコメントアウト
//mt19937 mt(20190405);
//uniform_real_distribution<> rnd_01(0, 1.0);
// ゲームに関連する情報を格納する変数
GameInfo ginfo;
// AIの思考に関するパラメータ
Parameter parameter;
// スレッド使用時の排他制御に使うミューテックス
std::mutex mtx;
// 置換表
TT_TABLE tt;
// AIの行動の結果を出力する関数
void output(int bestx, int bestr) {
// スキル使用の場合
if (bestx == -1) {
cout << "S" << endl;
}
// スキルを使用しない場合
else {
cout << bestx << " " << bestr << endl;
}
cout.flush();
}
// PlayerInfo のフィールドを横に並べて表示する
// (besthistory を並べて表示するための関数)
// PlayerInfo の dump_field の複数版
// 引数:
// pinfos: PlayerInfoの配列のポインタ
// depth: pinfos の配列の数 - 1
// isprev: trueの場合は、並べたPlayerInfoがビーム探索によってつながっている場合を表す
void dump_fields(PlayerInfo *pinfos, int depth, bool isprev=false) {
// 各PlayerInfoのフィールドの情報を格納する変数
int field[MAX_BEAM_DEPTH + 1][BOARD_WIDTH][BOARD_HEIGHT];
// field の情報の設定
for (int i = 0; i <= depth; i++) {
for (int y = BOARD_HEIGHT - 1; y >= 0; y--) {
for (int x = 0; x < BOARD_WIDTH; x++) {
field[i][x][y] = 0;
}
}
for (int b = 1; b <= 10; b++) {
for (int y = BOARD_HEIGHT - 1; y >= 0; y--) {
for (int x = 0; x < BOARD_WIDTH; x++) {
if (pinfos[i].issetbb(b, x, y)) {
field[i][x][y] = b;
}
}
}
}
}
cerr << endl;
// 間のスペース
string spaces = " ";
// 深さの表示
for (int i = 0; i <= depth; i++) {
cerr << "depth " << setw(4) << i << spaces;
}
cerr << endl;
// 自分の残り思考時間
for (int i = 0; i <= depth; i++) {
cerr << "ti " << setw(7) << pinfos[i].timeleft << spaces;
}
cerr << endl;
for (int i = 0; i <= depth; i++) {
cerr << "turn " << setw(5) << pinfos[i].turn << spaces;
}
cerr << endl;
// 致死量のお邪魔の数
for (int i = 0; i <= depth; i++) {
cerr << "do " << setw(3) << pinfos[i].deadojamanum[0] / 10 << "/" << setw(3) << pinfos[i].deadojamanum[1] / 10 << spaces;
}
cerr << endl;
// 自分のお邪魔以外のブロック数、全ブロック数
for (int i = 0; i <= depth; i++) {
cerr << "bl " << setw(3) << pinfos[i].bb[0].pcount() - pinfos[i].bb[10].pcount() << "/" << setw(3) << pinfos[i].bb[0].pcount() << spaces;
}
cerr << endl;
// 自分のお邪魔のブロック数、スキルで消えるブロック数
for (int i = 0; i <= depth; i++) {
cerr << "os " << setw(3) << pinfos[i].bb[10].pcount() << "/" << setw(3) << pinfos[i].calcskilldeleteblocknum() << spaces;
}
cerr << endl;
// 相手に降らせるお邪魔の合計の表示
for (int i = 0; i <= depth; i++) {
cerr << "oj " << setw(3) << pinfos[i].getojama << "/" << setw(3) << static_cast<int>(pinfos[i].minojamanum) << spaces;
}
cerr << endl;
// 一つ前の深さの連鎖数とスキルを使用したかどうか
for (int i = 0; i <= depth; i++) {
cerr << "ch " << setw(2) << static_cast<int>(pinfos[i].chain) << "/" << setw(2) << static_cast<int>(pinfos[i].maxchain) << " ";
if (pinfos[i].skillusedinthisdepth) {
cerr << "S";
}
else if (pinfos[i].skillused) {
cerr << "s";
}
else {
cerr << "-";
}
cerr << spaces;
}
cerr << endl;
// 自分と相手のスキルポイント
for (int i = 0; i <= depth; i++) {
cerr << "sp " << setw(3) << pinfos[i].skill[0] << "/" << setw(3) << pinfos[i].skill[1] << spaces;
}
cerr << endl;
// 自分と相手のお邪魔の数
for (int i = 0; i <= depth; i++) {
cerr << "oj " << setw(3) << pinfos[i].ojama[0] << "/" << setw(3) << pinfos[i].ojama[1] << spaces;
}
cerr << endl;
// 自分と相手のお邪魔が降ってきたターンをビットで表した数値
for (int i = 0; i <= depth; i++) {
cerr << "od " << setw(3) << static_cast<int>(pinfos[i].ojamadroppeddepthbit) << "/" << setw(3) << static_cast<int>(pinfos[i].eneojamadroppeddepthbit) << spaces;
}
cerr << endl;
for (int i = 0; i <= depth; i++) {
cerr << "en " << setw(3) << static_cast<int>(pinfos[i].firstenedroppedojamanum) << "/" << setw(1) << static_cast<int>(pinfos[i].enemayojamadroppedturnnum) << "/" << static_cast<int>(pinfos[i].enenotojamadroppedturnnum) << spaces;
}
cerr << endl;
for (int i = 0; i <= depth; i++) {
cerr << "mos " << setw(2) << static_cast<int>(pinfos[i].enemayojamadropnum) << "/" << setw(3) << static_cast<int>(pinfos[i].enemayskillminusnum) << spaces;
}
cerr << endl;
// ビームサーチの場合の評価
for (int i = 0; i <= depth; i++) {
cerr << "bc " << setw(2) << static_cast<int>(pinfos[i].hyouka.chain) << " xr" << setw(2) << static_cast<int>(pinfos[i].hyouka.chainxr) << spaces;
}
cerr << endl;
// その深さで行った行動
for (int i = 0; i <= depth; i++) {
if (!isprev || i == depth) {
cerr << "x " << setw(3) << static_cast<int>(pinfos[i].x) << " r " << setw(2) << static_cast<int>(pinfos[i].r) << spaces;
}
else {
cerr << "x " << setw(3) << static_cast<int>(pinfos[i + 1].px) << " r " << setw(2) << static_cast<int>(pinfos[i + 1].pr) << spaces;
}
}
cerr << endl;
// その深さで降らすブロックの位置
for (int by = 1; by >= 0; by--) {
for (int i = 0; i <= depth; i++) {
int x, r;
if (!isprev || i == depth) {
x = pinfos[i].x;
r = pinfos[i].r;
}
else {
x = pinfos[i + 1].px;
r = pinfos[i + 1].pr;
}
if (x == 9) {
x = pinfos[i].hyouka.chainxr / 10;
r = pinfos[i].hyouka.chainxr % 10;
}
if (x >= 0) {
for (int bx = 0; bx < x; bx++) {
cerr << " ";
}
for (int bx = 0; bx < 2; bx++) {
cerr << ginfo.blockinfo[ginfo.turn + i][r][bx][by];
}
for (int bx = 0; bx < 10 - x - 2; bx++) {
cerr << " ";
}
}
else {
cerr << "SKILL ";
}
cerr << spaces;
}
cerr << endl;
}
// 盤面の表示
for (int y = BOARD_HEIGHT - 1; y >= 0; y--) {
for (int i = 0; i <= depth; i++) {
for (int x = 0; x < BOARD_WIDTH; x++) {
if (field[i][x][y] == 0) {
// 危険ゾーンでブロックがない場所は ! を表示。
if (y >= 16) {
cerr << "!";
}
else {
cerr << ".";
}
}
else if (field[i][x][y] < 10) {
cerr << field[i][x][y];
}
else {
cerr << "X";
}
}
cerr << spaces;
}
cerr << endl;
}
cerr << endl;
// 各列のブロック数(10以上は16進数のようにA、B...で表示)
for (int i = 0; i <= depth; i++) {
for (int x = 0; x < BOARD_WIDTH; x++) {
if (pinfos[i].blocknum[x] < 10) {
cerr << static_cast<int>(pinfos[i].blocknum[x]);
}
else {
cerr << static_cast<char>('A' + pinfos[i].blocknum[x] - 10);
}
}
cerr << spaces;
}
cerr << endl << endl;
// 最後にdepthの評価値を表示する(この評価値は場合によっては表示内容に意味がないこともある)
pinfos[depth].hyouka.dump();
cerr << endl;
}
// 様々な初期化。返り値は所要時間
int init() {
Timer t;
// around_bb_table の初期化
for (int y = 0; y < BOARD_HEIGHT; y++) {
for (int x = 0; x < BOARD_WIDTH; x++) {
around_bb_table[x][y].clear();
for (int dy = -1; dy <= 1; dy++) {
for (int dx = -1; dx <= 1; dx++) {
if (dx == 0 && dy == 0) {
continue;
}
if (x + dx < 0 || x + dx >= BOARD_WIDTH || y + dy < 0 || y + dy >= BOARD_HEIGHT) {
continue;
}
around_bb_table[x][y].set(x + dx, y + dy);
}
}
}
}
// around_bb_x_table の初期化
for (int i = 0; i < (1 << BOARD_HEIGHT); i++) {
for (int x = 0; x < BOARD_WIDTH; x++) {
around_bb_x_table[x][i].clear();
}
for (int y = 0; y < BOARD_HEIGHT; y++) {
if (i & (1 << y)) {
for (int x = 0; x < BOARD_WIDTH; x++) {
around_bb_x_table[x][i] |= around_bb_table[x][y];
}
}
}
}
// チェインとスキルの点数とお邪魔のテーブルの作成
chain_scoretable[0] = 0;
chain_ojamatable[0] = 0;
for (int i = 1; i <= MAX_CHAIN; i++) {
chain_scoretable[i] = chain_scoretable[i - 1] + static_cast<int>(floor(pow(1.3, i)));
chain_ojamatable[i] = static_cast<int>(floor(static_cast<double>(chain_scoretable[i]) / 2.0));
}
skill_scoretable[0] = 0;
skill_ojamatable[0] = 0;
for (int i = 1; i <= MAX_BLOCK; i++) {
skill_scoretable[i] = static_cast<int>(floor(25.0 * pow(2.0, static_cast<double>(i) / 12.0)));
skill_ojamatable[i] = static_cast<int>(floor(static_cast<double>(skill_scoretable[i]) / 2.0));
}
// 所要時間を表示
#ifdef DEBUG
if (parameter.turndebugmsg) {
cerr << t.time() << " ms" << endl;
}
#endif
return t.time();
}
// 探索で見つかった最高評価値
HyoukaData besthyouka;
// ビームサーチで見つかった行動をとった場合の最高評価値
HyoukaData bestbeamhyouka;
// 関数 ai の探索時の、各深さのプレーヤー情報を格納する配列変数
PlayerInfo history[MAX_DEPTH + 1];
// 最高の評価値の各深さの情報を格納する配列変数。思考の可視化のために記録するが、コピーが重いので、
// 最善手のみ記録するバージョンも作る(todo)
PlayerInfo besthistory[MAX_DEPTH + 1];
PlayerInfo bestbeamhistory_log[MAX_DEPTH + 1];
EnemyStatusData enemystatusdata;
// pinfoorig の状態で、pinfoorig.x, pinfoorig.r が示す行動を取った時の結果計算して、pinfo に格納する
// なお、ターン開始時にお邪魔を降らす処理は、この関数を呼ぶ前に実行しておく必要がある点に注意!
// (注:pinfoorig.x == -1, pinfoorig.r == 0 の場合は、スキルの使用行動を表す)
// 不正な行動(x = -1 で r が 0 でない場合か、スキルが発動できない場合)や、ゲームオーバーになる行動の場合は、falseを返す
// 行動の結果 pinfo には下記の情報が記録される
// skillusedinthisdepth: この行動でスキルを使用したかどうか
// skillused: これまでの行動(今回の行動を含む)でスキルを使用したかどうか
// skillusedturn: スキルを使用したまたは10以上したターン(今回スキルを使用した場合のみ更新)
// prev: 直前のPlayerInfoのポインタ(=&pinfoorig)
// chain: この行動によるチェイン数
// maxchain: これまでの行動における最大チェイン数
// getojama: 相手に降らせた累積お邪魔ブロックの総数
// skillojama: 相手から減らした累積スキルポイントの総数
// ojama[1]: 相手の落下予定のお邪魔ブロックの数
bool action(PlayerInfo& pinfoorig, PlayerInfo& pinfo) {
// 消滅するブロックの位置を表すビットボード
BitBoard deletebb;
// pinfoorigの行動を x, r にコピーする
int x = pinfoorig.x;
int r = pinfoorig.r;
// インデックスが表す番号のブロックが動いたか(次に消滅判定が必要かどうか)、消滅したか(周囲のブロックの範囲の再計算が必要)どうかを表す。初期化処理を省くため、
// blockmovecount と等しい場合に動いたことを表す。その場合、
// ブロックの消滅チェックに使うので、そのブロックの対(10-そのブロックの番号)のブロックも同時に動いたことにする
// blockmovecount + 1 と等しい場合消滅したことを表す。
// blockmoved の値が用済みになった時点で blockmovecount に 2 を加算する
// なお、blockmovecountは連鎖のたびに2ずつ加算されるが、最大連鎖数は約80なので、blockmoved の型は uint8_tで足りる
uint8_t blockmoved[11];
// 初期状態として、 blockmovecount は 1、blockmoved はすべて 0 にしておく
uint8_t blockmovecount = 1;
memset(blockmoved, 0, 11);
// スキル使用行動の場合
if (x == -1) {
// r == 0で、スキルポイントが 80 以上あり、5のブロックが存在する場合のみスキルを使用できる
// そうでなければ不正な行動なので false を返す
if (r > 0 || pinfoorig.skill[0] < 80 || pinfoorig.bb[5].pcount() == 0) {
return false;
}
}
// pinfoorig に対して x, r の行動を行った結果を保存する pinfo に pinfoorig コピーする
pinfo = pinfoorig;
// ただし、スキルはまだ使用していないので false を代入する
pinfo.skillusedinthisdepth = false;
// 直前の PlayerInfo のアドレスを設定する
pinfo.prev = &pinfoorig;
// スキル以外の行動の場合(スキル使用の場合は、パックは落ちてこないのでこの処理は行わない)
if (x != -1) {
// パック情報を盤に置く
// パックの情報を表す 2x2 の配列のループ
for (int px = 0; px < 2; px++) {
// パックを落下する列のブロック数を表す変数への参照
uint8_t& blocknum = pinfo.blocknum[x + px];
// パック内の下のブロックから落とす必要があるので、py は 0, 1 の順で処理する
for (int py = 0; py < 2; py++) {
// 落下するブロックの種類
int block = ginfo.blockinfo[pinfo.turn][r][px][py];
// block が 0 の場合は、ブロックが存在しないので、0より大きい場合のみ落とす
if (block > 0) {
// 任意のブロックの Bitboard である pinfo.bb[0] の x + px 列にブロックを追加(追加する場所は(x + px, blocknum))
pinfo.setbb(0, x + px, blocknum);
// block のブロックのみの BitBoard である pinfo.bb[block] の x + px 列にブロックを追加
pinfo.setbb(block, x + px, blocknum);
// block のブロックの周囲8マスの位置を表す BitBoard にブロックが落下した (x + px, blocknum) の
// 周囲8マスのブロックを追加する(あらかじめ init で計算済の、任意の座標の周囲8マスの位置を表す around_bb_table と OR を取れば良い)
pinfo.bb[block + 10] |= around_bb_table[x + px][blocknum];
// block のブロックが移動したことを記録する(ブロックの消滅判定を、移動したブロックとその対のブロックのみ行うことによる処理の高速化のため)
blockmoved[block] = blockmovecount;
// そのブロックの対のブロックも移動したことにする
// (5のブロックの場合は、上記と同じ代入を行うが、条件分岐させてこの代入を行わない処理を行っても高速化は望めないのでそのまま実行する)
blockmoved[10 - block] = blockmovecount;
// x + px 列のブロックを1増やす
blocknum++;
}
}
}
}
// ブロック消去処理開始
// チェイン数を表す変数への参照。初期化もしておく
uint8_t& chain = pinfo.chain = 0;
// ブロックが消去されたかどうかを表すフラグの定義
bool deleted;
// ブロックが消去されなくなるまで繰り返す。ただし、必ず1回は実行する
do {
#ifdef DEBUG
// チェインを視覚化するパラメータをONにした場合のデバッグ表示
if (parameter.showchain) {
cerr << "chain " << static_cast<int>(chain) << endl;
pinfo.dump_field();
}
#endif
// このループでブロックが消去されていないことにする
deleted = false;
// スキルを使用した場合で、まだスキルの消去処理を行っていない場合の処理
if (x == -1 && !pinfo.skillusedinthisdepth) {
// スキルポイントを 0 にする
pinfo.skill[0] = 0;
// 消去されたブロックを計算する。
// A: 5のブロック(pinfo.bb[5])と、5の周囲のブロック(pinfo.bb[15])の和集合が消去される可能性のあるブロック
// B: お邪魔ブロックでないブロックがある場所が消去可能なブロック
// A AND B が消去されるブロックとなる
deletebb = (pinfo.bb[5] | pinfo.bb[15]) & BitBoard::andnot(pinfo.bb[10], pinfo.bb[0]);
// 消去されるブロックの数を計算する
int deletenum = deletebb.pcount();
// 相手に降らせるお邪魔ブロックの数を計算し、累積に加算する
pinfo.getojama += skill_ojamatable[deletenum];
// 相手のお邪魔ブロック数を増やす
pinfo.ojama[1] += skill_ojamatable[deletenum];
pinfo.getskillojama = skill_ojamatable[deletenum];
// スキルによるブロック消去はチェインに数えない。ループ時に chain を1増やすので、-1 にしておく
chain = -1;
// スキルを使用したことを変数に記録する
pinfo.skillusedinthisdepth = true;
pinfo.skillused = true;
pinfo.skillusedturn = pinfo.turn;
}
// ブロック落下によるブロック消滅判定と消滅処理
else {
// 消去されたブロックを表す BitBoard のクリア
deletebb.clear();
// 各ブロックについて、消滅した場所を deletebb に記録する
for (int i = 1; i <= 9; i++) {
// 動いたブロックとその対になるブロックだけチェックすればよい。それは blockmoved を調べればわかるようにしてある
if (blockmoved[i] == blockmovecount) {
// i 番のブロックと、10 - i 番目のブロックの周囲8マスのブロックの BitBoard の積集合が、消滅するブロック。それを deletebb に加える
deletebb |= pinfo.bb[i] & pinfo.bb[20 - i];
}
}
}
// 今回のブロック消滅判定は終了したので、blockmoved の値をすべてクリアするため、blockmovecount を 2 増やす
blockmovecount += 2;
// 消滅したブロックがある場合の処理
if (!deletebb.iszero()) {
// このループでブロックが消滅したことを表す変数のフラグを立てる
deleted = true;
// チェイン数を 1 増やす
chain++;
// 各列についてブロックを削除する
for (int x2 = 0; x2 < BOARD_WIDTH; x2++) {
// x2 列目に消滅するブロックが存在する場合
uint32_t blocks = deletebb.getx(x2);
if (blocks != 0) {
// x2 列目のブロックが存在しない場所を表すマスクビットを計算する(NOTを取る)
uint32_t mask = ~blocks;
// 任意のブロック(0)と各ブロック(1~9)とお邪魔ブロック(10)を表すビット列をすべて消えたブロックの分だけずらす
for (int i = 0; i <= 10; i++) {
// x2 列にブロックが存在する場合だけずらせば良い
uint32_t bbx = pinfo.getbbx(i, x2);
if (bbx != 0) {
// BMI命令の、pext_u32 を使えば一発でずらせる
uint32_t newbbx = _pext_u32(bbx, mask);
// ずらす前とずらした後が異なる場合のみ処理を行えばよい
if (bbx != newbbx) {
// 新しい値をセットする
pinfo.setbbx(i, x2, newbbx);
//// 任意のブロックの場合は、その列のブロック数を計算しなおす
if (i == 0) {
pinfo.blocknum[x2] = popcnt(newbbx);
}
// 消滅後の x2 列に 1~9 のブロックが残っていた場合は、連鎖の対象となるので、
// そのブロックと対になるブロックが動いたことを記録する
// (記録しても利用されないお邪魔ブロックを表す 10 と 任意のブロックの 0 のブロックの blockmoved も
// 記録されてしまうが、特に害はなく、条件分岐で記録を排除しても高速化はみこめなさそうなのでそのままにしておく)
// ここで記録した blockmoved はこの後の各ブロックの周囲9マスのブロックの再計算と、次のループでのブロック消滅判定で使用する
else if (newbbx != 0) {
blockmoved[i] = blockmovecount;
blockmoved[10 - i] = blockmovecount;
}
// ブロックが完全に削除されていて(newbbx が 0)、連鎖の対象となっていない場合でも、そのブロックの周囲8マスの再計算は必要。
// そのことを表すため、blockmoved[i] に消滅したことを表す blockmovecount + 1 を代入する
else if (blockmoved[i] != blockmovecount) {
blockmoved[i] = blockmovecount + 1;
}
}
}
}
}
}
// 各ブロック(1~9)の周囲8マスのブロックの再計算
// 消滅したブロックの周囲の8マスのビットをOFFにするという方法では、OFFにしたビットの周囲に消滅したブロックと同じ
// 番号のブロックが存在した場合はうまくいかないので、一から計算しなおすがある
for (int i = 1; i <= 9; i++) {
// 移動、削除されたブロックのみ再計算すればよい
if (blockmoved[i] >= blockmovecount) {
// 周囲8マスのブロックを表すBitBoardをクリアする
pinfo.bb[i + 10].clear();
// 各列について計算する
for (int x2 = 0; x2 < BOARD_WIDTH; x2++) {
// x2 列の i 番のブロックのビットパターンと around_bb_x_table を使ってまとめてその列のすべてのブロックに対する
// 周囲8マスのブロックのBitBoardを取得して OR をとれば良い
pinfo.bb[i + 10] |= around_bb_x_table[x2][pinfo.getbbx(i, x2)];
}
}
}
}
// 消滅するブロックが存在しなくなるまで繰り返す
} while (deleted);
// 10連鎖以上を組んだ場合、skillusedturn に入れておく
if (pinfo.chain >= 10) {
pinfo.skillusedturn = pinfo.turn;
}
if (pinfo.chain > pinfo.maxchain) {
pinfo.maxchain = pinfo.chain;
}
// ゲームオーバー判定
// お邪魔ブロックが降ってきていない場合
if (!pinfo.ojamadropped) {
// スキルを使っていない場合は、落下させたブロックの2列のみ判定すれば良い
if (!pinfo.skillusedinthisdepth) {
if (pinfo.blocknum[x] >= GAMEOVER_Y || pinfo.blocknum[x + 1] >= GAMEOVER_Y) {
// ゲームオーバーになるので、飛ばして次へいく
// 結果としてすべての行動がゲームオーバーになる場合は、評価値が一度も計算されず、besthyouka の total が -INF のままとなるので判定できる
return false;
}
}
// お邪魔ブロックが降らず、スキルを使った場合は絶対にゲームオーバーにならない
}
// お邪魔ブロックが降ってきた場合
else {
// 各列についてチェックする
for (int x2 = 0; x2 < BOARD_WIDTH; x2++) {
// 一つでもゲームオーバーとなる高さに達していればゲームオーバーとする
if (pinfo.blocknum[x2] >= GAMEOVER_Y) {
return false;
}
}
}
// 相手に降らせるお邪魔ブロックの数を計算し、累積に加算する
pinfo.getojama += chain_ojamatable[chain];
// 相手のお邪魔ブロック数を増やす
pinfo.ojama[1] += chain_ojamatable[chain];
// チェインがあった場合自分のスキルを8増やす
// 3チェイン以上の場合の、相手のお邪魔を減らす処理はここでは行わない
// (先に相手のスキルを増やす処理(増やした結果100を超えた場合は100に補正する)を行ってから減らさないとうまくいかないので)
if (chain > 0) {
// 自分のスキルを 8 増やす
pinfo.skill[0] += 8;
// 100を超えたらここで100に補正する
if (pinfo.skill[0] > 100) {
pinfo.skill[0] = 100;
}
}
// turnを1増やす
pinfo.turn++;
return true;
}
// 通常時の評価値を計算する
void PlayerInfo::calchyouka() {
// 自分のフィールド内のお邪魔の数を計算する
int onum = bb[10].pcount();
// お互いの落下予定のお邪魔ブロック数による評価値
double hyouka_ojama;
// 基本は「敵のお邪魔の数」 - 「自分のお邪魔の数」を使うが、
// お邪魔ブロックの1の位の価値は10の位の価値よりも低い(実際に落ちてこないため)ので、
// 1の位の評価値を 0.2 倍して計算する
hyouka_ojama = ((ojama[1] - ojama[0]) / 10) * 10 + ((ojama[1] - ojama[0]) % 10) * 0.2;
// 上記で計算した基本値から下記の要素を追加する
// 20ターン以内で、敵が落としてくる可能性のあるお邪魔の数が20未満(9連鎖以下)の場合は、
// そのような不利な行動をしてくることはまずないと仮定して評価値に50を加える
if (turn <= 20 && enemayojamadropnum != 0 && enemayojamadropnum < 20) {
hyouka_ojama += 50;
}
// 自分のフィールドに落下済のお邪魔が70以下で、敵が落としてくる可能性のあるお邪魔の数が 15 未満の場合は、
// そのような行動を中盤にしてくることはまずないと仮定して敵の落としてくる可能性のあるお邪魔の数によるマイナス評価を 10% にするように評価値を加算する
else if (onum <= 70 && enemayojamadropnum != 0 && enemayojamadropnum <= 15) {
hyouka_ojama += enemayojamadropnum * 0.9;
}
// 自分が大連鎖をしない場合(相手に降らすお邪魔の合計が10未満の場合。これ以上降らせてしまうと、この後しばらくの間、大連鎖できる見込みがなくなるため除外する)に
// ginfo.hyoukabonusx, ginfohyoukabonusr の行動をとった場合、
// 敵がこちらにお邪魔を降らしてきた深さのボーナスが getojama より多い場合は、この後 ginfo.hyoukabonus[enemayojamadroppeddepth] だけ相手にお邪魔を降らす見込みがあるので
// その分評価値を加算する
if (getojama < 10 && enemayojamadroppeddepth > 0 && ginfo.hyoukabonusx == fx && ginfo.hyoukabonusr == fr && ginfo.hyoukabonus[enemayojamadroppeddepth] > getojama) {
hyouka_ojama += ginfo.hyoukabonus[enemayojamadroppeddepth] - getojama;
}
// 相手に降らすお邪魔が15以下の時は、あまり効果がないとして、その評価を 10% に減らす
if (getojama <= 15) {
hyouka_ojama -= getojama * 0.9;
}
// 落下済のお邪魔の数の差による評価
// 敵のフィールドのお邪魔の最小値から自身のフィールドのおじゃまの数を引いたものを使う
int16_t hyouka_droppedojama = minojamanum - bb[10].pcount();
// 敵がお邪魔を落とすかどうか選択可能な場合や、降らすことができるのに降らさないという行動をとった時の評価値
// 敵がお邪魔を落とすか落とさないかを選択できたほうが状況が不確定なので負の評価を加算する
// 敵がお邪魔を落とすことができるのに落とさなかった回数が多いほど、そのような状況にはなりにくいと考えて評価値を加算する(あまり大きな値にしないほうがよさそう)、
double hyouka_enemaydrop = enemayojamadroppedturnnum * parameter.enemaydrophyouka + enenotojamadroppedturnnum * parameter.enenotdrophyouka;
// 致死量かどうかのチェック
// こちらの攻撃により、この深さ以降で敵は連鎖できないものと仮定する
// (カウンター戦術の普及により、この仮定はもはや通用しない??todo)
// 従って、考慮に入れるのは敵のスキルによるお邪魔のみ
// この時点で敵に致死量のお邪魔が降る場合のみ計算する
double hyouka_enedead = 0;
// 以下の場合、相手を殺しに行くための評価値を計算する
// 相手にお邪魔を降らす場合で、(todo: 最初にお邪魔を降らせているのは必須?)
// 致死量から2段目まで以上を降らせることが可能な場合(todo: 2段目までで良い?)
if (ojama[1] >= 10 && ojama[1] >= deadojamanum[1] - 30) {
// 相手がスキルを発動可能になる最短のターン数を計算する
int eneskillturn;
if (skill[1] >= 80) {
eneskillturn = 0;
}
else {
eneskillturn = (87 - skill[1]) >> 3;
}
// この深さに到達している時点で相手にお邪魔が降ることによって、大連鎖ができなくなっていると仮定する
// (この仮定が外れた場合は負ける可能性大)
// 敵がスキル発動可能なターンが、死亡ターンより後の場合は100%死亡させられる
if (eneskillturn * 10 > deadojamanum[1]) {
// なるべく早いターンで殺したいので、スキル発動ターンが速いほうを優先する
// skillusedturn は最大500なので、負の値にならないように注意!
hyouka_enedead = 1e10 - skillusedturn * 1e7;
}
// そうでない場合
else {
// 死亡ターンの計算
int deadturn;
// まず、敵がこれまでスキルを使ったかどうかによって分けて考える
// 敵がスキルを使ったと仮定した場合は、この後スキルを発動することは現実的にほぼ不可能。
// できたとしても、せいぜい20個程度しかお邪魔を降らすことはできないと考える
// ここに来ている時点で敵に致死量-2段目までのお邪魔を降らせており、スキルによって相殺されても致死量-4段目となり、
// 十分な致死量とみなせる。従って、この後敵がスキルを使った場合に、敵が状況を回避できなければ殺しに行って良い
// 敵がスキルを使っていない場合について考える。
// その場合のお邪魔の数の計算(差異の分だけ増やす)
// ojama[1]>=10の条件でここに来ているので、ojama[0] >= 0のケースはあり得ないので、単純に足せばよい
int ojamanum = ojama[1] + eneskillnotuseojamadiff;
// 敵が最大効率で、スキルを使わずに放置すると死亡するターンぎりぎりにスキルを使ってきたときのことを考える
// なお、こちらはお邪魔を大量に降らせていると仮定して、敵は大連鎖を行えなくなっていると仮定する
// まず、敵はスキルを80以上溜める必要がある。そのためには最低でも eneskillturn * 2 個のブロックを消去する必要がある
// その分をブロック数と、スキルで消去可能なブロック数から引いておく
// (最大効率を目指すため、敵は連鎖を行わない、3つ以上のブロックを消さないものと仮定する)
int blocknum = eneskillnotuseminblocknum - eneskillturn * 2;
int skillblocknum = eneskilldeleteblocknum - eneskillturn * 2;
// 次に、敵が死亡するターンの計算を行う。
// ほぼ致死量のお邪魔を降らせているはずなので、毎ターン10個のお邪魔ブロックが降ってくるものとする
// また、敵はブロックを消さないと仮定し、毎ターン3つのブロックが積まれていくとして計算する
// 死亡ターン数はそうして計算したターン数と、deadojamanum[1] / 10 のうち小さいほう
deadturn = min((160 - blocknum) / 13 + 1, deadojamanum[1] / 10);
// 相手がスキルで消せるブロックの数は毎ターン1増えるものとする todo: 1でOK?
// (こちらが大量にお邪魔を降らせているはずなので、場合、スキルで消せるブロックを一気に増やすことは困難だという仮定)
skillblocknum += deadturn;
if (ojamanum - skill_ojamatable[skillblocknum] + deadturn * 10 + blocknum - skillblocknum >= deadojamanum[1] - 20) {
// なるべく早いターンで殺したいので、スキル発動ターンが速いほうを優先する
// skillusedturn は最大500なので、負の値にならないように注意!
hyouka_enedead = 1e9 - skillusedturn * 1e6;
}
}
}
// 通常のブロック数(多いほうが連鎖しやすいので、評価値にはこの値の 0.01 倍を加算する
uint8_t hyouka_normalblocknum = bb[0].pcount() - onum;
// スキルによって消去可能なブロックの数
uint16_t hyouka_skilldeleteblocknum = calcskilldeleteblocknum();
int mincheckonum;
double mulskillojama;
// スキル発動を目指している場合
if (ginfo.skillmode) {
mincheckonum = 0;
mulskillojama = 1.0;
}
// 連鎖を目指している場合
else {
// お邪魔の数が50未満の時は、スキルは狙わない
// スキルによる評価値を半分にする
mincheckonum = 50;
mulskillojama = 0.5;
}
double hyouka_skillojama;
// 落下隅のお邪魔の数が mincheckonum 以上の時のみスキルによる評価値を計算する(早めにスキルを目指さないようにするため)
if (onum >= mincheckonum) {
// スキルを発動不可能な場合
if (skill[0] < 80) {
// skillojama に 消去可能なブロック数によって相手に降らせるお邪魔の数 * (スキルポイント + 1) / 81 を設定
// 1 を足しているのはスキルポイントが0の場合でも正の評価値を与えたいため
hyouka_skillojama = skill_ojamatable[hyouka_skilldeleteblocknum] * (skill[0] + 1.0) / 81.0 * mulskillojama;
}
// スキルを発動可能な場合は、スキルを発動した場合に降ってくるお邪魔の数を skillojama に設定する
else {
hyouka_skillojama = skill_ojamatable[hyouka_skilldeleteblocknum];
}
}
else {
hyouka_skillojama = 0;
}
// 敵のスキルに関する評価値
// ただし、あまり評価しすぎても仕方がないので、あと3回チェインした場合に使用できるようになる
// スキルポイントが 56 以上の場合のみ評価する
double hyouka_eneskillojama;
// parameter.calceneskill が false の時は評価しない
if (!parameter.calceneskill || skill[1] < 56) {
hyouka_eneskillojama = 0;
}
// スキルポイント/80 割合のさらに1/5で評価する
// 1/2だと、この深さ落ちていないにも関わらず、この深さより前にスキルでお邪魔を落とされた場合よりも評価値が悪くなるケースがあった
// そもそもここは計算しないほうがいいかも?
else if (skill[1] < 80) {
hyouka_eneskillojama = skill_ojamatable[eneskilldeleteblocknum] * skill[1] / 80.0 / 5.0;
// スキルを使った場合の評価値と二重計算にならないように、相手に送るお邪魔の差異の分だけ引く
hyouka_eneskillojama -= eneskillnotuseojamadiff;
//hyouka.normalblocknum = eneskillnotuseojamadiff;
if (hyouka_eneskillojama < 0) {
hyouka_eneskillojama = 0;
}
}
else {
// こちらは確実に次で降ってくるのでそのままの値で計算する
hyouka_eneskillojama = skill_ojamatable[eneskilldeleteblocknum];
// 既にスキルを使った場合の評価値と二重計算にならないように、相手に送るお邪魔の差異の分だけ引く
hyouka_eneskillojama -= eneskillnotuseojamadiff;
if (hyouka_eneskillojama < 0) {
hyouka_eneskillojama = 0;
}
}
// ブロックが致死量に迫っている場合の評価値
int16_t hyouka_limit = 0;
// スキルが使えない場合のみ計算する
if (skill[0] < 80) {
for (int i = 0; i < BOARD_WIDTH; i++) {
// あまりよくなさそうなので limit1 と limit2 は廃止
// ブロックの数が 15 以上の列一つに加算する(hyouka_limit_x は負の値にしておく)
//if (blocknum[i] == GAMEOVER_Y - 2) {
// hyouka.limit += parameter.hyouka_limit_2;
//}
//else if (blocknum[i] == GAMEOVER_Y - 1) {
// hyouka.limit += parameter.hyouka_limit_1;
//}
//// スキルが使えない場合で、致死量の場合は一つにつき -200 する
//else
if (blocknum[i] >= GAMEOVER_Y) {
hyouka_limit += parameter.hyouka_limit;
}
}
}
// 連鎖数による評価
double chainhyouka = 0;
// 4連鎖以上した場合
if (maxchain > 3) {
// 敵のフィールドに落下済のお邪魔が存在しない場合で、 parameter.firstminchainnum 連鎖以下の場合は、効果がないとして、parameter.firstminchainminus の分だけ評価値を足す(負の値)
if (firstenedroppedojamanum <= 0 && maxchain <= parameter.firstminchainnum) {
chainhyouka = parameter.firstminchainminus;
}
// 敵のフィールドに落下済のお邪魔が20以下の場合で、 parameter.secondminchainnum 連鎖以下の場合は、効果がないとして、parameter.secondminchainminus の分だけ評価値を足す(負の値)
else if (firstenedroppedojamanum <= 20 && maxchain <= parameter.secondminchainnum) {
chainhyouka = parameter.secondminchainminus;
}
}
double penalty = 0;
// こちらが最初に連鎖した場合に相手にカウンターで大連鎖される場合の評価値のペナルティを計算する
if (fchain > ginfo.maxfirstchain && enemayojamadropnum < ginfo.firstchainpenalty) {
// 既にこの時点で落とされているお邪魔から、将来落とされるお邪魔の数を引いたものをペナルティとする
penalty = enemayojamadropnum - ginfo.firstchainpenalty;
}
double bonus = 0;
// こちらが最初に連鎖した場合に、相手がカウンターできない場合の評価値のボーナスを計算する
if (fx == ginfo.firstx && fr == ginfo.firstr) {
bonus = ginfo.firstbonus;
}
// 敵は死亡していないのでこの評価値を 0 とする
#ifdef DEBUG
hyouka.enedeaddepth = 0;
#endif
// 最終評価値の計算
#ifdef DEBUG
hyouka.ojama = hyouka_ojama;
hyouka.skillojama = hyouka_skillojama;
hyouka.eneskillojama = hyouka_eneskillojama;
hyouka.enedead = hyouka_enedead;
hyouka.enemaydrop = hyouka_enemaydrop;
hyouka.limit = hyouka_limit;
hyouka.droppedojama = hyouka_droppedojama;
hyouka.normalblocknum = hyouka_normalblocknum;
#endif
hyouka.total = hyouka_ojama + hyouka_droppedojama + hyouka_skillojama + hyouka_limit + hyouka_normalblocknum * 0.01 + hyouka_enedead - hyouka_eneskillojama + hyouka_enemaydrop + chainhyouka + penalty + bonus;
}
// ビームサーチの場合の評価値の計算
void PlayerInfo::calcbeamhyouka() {
PlayerInfo pinfo;
// 見つかった評価値の最大値
double maxhyouka = -100000;
// 最大チェーン数
uint8_t mchain = 0;
// 最大チェーン時の行動を x, r とした際の x * 10 + r
int maxxr;
for (x = 0; x <= 8; x++) {
for (r = 0; r < 4; r++) {
// pinfoorig の行動を行い、行動後の状態を pinfo に記録する関数を呼ぶ
if (!action(*this, pinfo)) {
continue;
}
// チェーン数
uint8_t cnum = pinfo.chain;
// 行動後の通常のブロック数(多いほうが連鎖後の状況が良いという仮定)
int blnum = pinfo.bb[0].pcount() - pinfo.bb[10].pcount();
// 評価値。チェーンで落とせるブロックの数 * 100 + チェーン後のブロック数
double h;
h = chain_ojamatable[cnum] * 100 + blnum;
// 最初(1ターン目の大連鎖を組むためのビーム探索時)のみ上3つ分に積まないように制限する(積みすぎるとカウンターしずらくなるため)
if (ginfo.firstbeamcheck) {
for (int i = 0; i < BOARD_WIDTH; i++) {
if (blocknum[i] >= GAMEOVER_Y - 3) {
h = 0;
break;
}
}
}
// 最大の評価値の記録
if (maxhyouka < h) {
maxhyouka = h;
mchain = cnum;
maxxr = x * 10 + r;
}
}
}
// 評価値の maxchain (ビーム探索時に発見した最大連鎖数) の更新
if (this->maxchain < mchain) {
this->maxchain = mchain;
}
// 今回みつかった最大チェーン数とそのチェーンを実現する行動の記録
hyouka.chain = mchain;
hyouka.chainxr = maxxr;
// maxchain の記録
if (hyouka.maxchain < mchain) {
hyouka.maxchain = mchain;
}
// maxhyoukaが負の場合は死んでいる
if (maxhyouka < 0) {
hyouka.total = 0;
}
// そうでなければ、評価値は過去に見つかった最大チェーン数によるお邪魔落下数 * 1000 + 今回の最大評価値とする
else {
hyouka.total = chain_ojamatable[hyouka.maxchain] * 1000 + maxhyouka;
}
}
// ai(味方の探索)。ginfo.searchDepth までの深さの局面の全探索。深さ優先探索
// 返り値として、parameter.ai2 が true の場合はそのノードの評価値を返す。falseの場合は0を返す
// 各深さのデータは引数では渡さずに、history の配列に格納する
// 味方と敵の区別は ginfo.isenemy で区別する(引数にしたほうが良いか?)
// bhistory はこれまでに見つかった最高の評価値に至る行動を表す PlayerInfo の配列
// bbeamhistory はビーム評価の場合
// ginfo.searchDepth で評価関数を計算する
// 敵の場合は、味方の探索深さ-1まで探索を行う。また、各ノードでこちらに影響する条件の最大値である
// 「お邪魔を降らせた数」、「相手のスキルを減らした数」を記録する(評価関数ではない)
// estatus は、味方の探索時に、敵の行動の結果、味方のお邪魔を増やしたり、スキルを減らしたりする処理で使用する
//// 引数 depth は探索する深さ
double mysearch(int depth, PlayerInfo *history, PlayerInfo *bhistory, PlayerInfo *bbeamhistory, HyoukaData& bhyouka, HyoukaData& bbeamhyouka, EnemyStatusData& estatusdata) {
// この深さのノードのプレーヤーの情報への参照
PlayerInfo& pinfoorig = history[depth];
// pinfoorig に対してプレーヤーが行動を行った結果の局面を表すプレーヤーの情報への参照
PlayerInfo& pinfo = history[depth + 1];
#ifdef DEBUG
// この関数を呼んだ回数のカウント(デバッグ用)
ginfo.aicount++;
#endif
// お邪魔落下処理
// 深さが 0 または、深さが ginfo.ai2deth 以外の場合に落下処理を行う
// ginfo.ai2depth が 1 以上の場合は、その深さにおける最善手を計算している(デバッグ用)
// その場合は、お邪魔は落下済の状態でここにくるので、落下処理を行う必要はない
if (depth == 0 || depth != ginfo.ai2depth) {
pinfoorig.ojamadrop(depth);
}
// リーフノードまたは、最終ターンを過ぎた場合の処理
if (depth == ginfo.searchDepth || pinfoorig.turn == MAX_TURN) {
// 評価値を計算する関数を呼ぶ
pinfoorig.calchyouka();
// parameter.ai2 の場合は評価値を返す
if (parameter.ai2) {
return pinfoorig.hyouka.total;
}
// そうでない場合は、これまでに見つかっている最高の評価と比較し、より評価値が高い場合は bhyouka と bhistory を更新する
HyoukaData& hyouka = pinfoorig.hyouka;
if (hyouka.total > bhyouka.total) {
bhyouka = hyouka;
memcpy(bhistory, history, sizeof(PlayerInfo) * (ginfo.searchDepth + 1));
}
// 初手がビーム探索で見つかった行動をとっていた場合は、同様に最高の評価を更新する
if (history[0].x == ginfo.beamx && history[0].r == ginfo.beamr && hyouka.total > bbeamhyouka.total) {
bbeamhyouka = hyouka;
#ifdef DEBUG
// こちらの記録はデバッグ時のみ必要
if (parameter.debugmsg) {
memcpy(bbeamhistory, history, sizeof(PlayerInfo) * (ginfo.searchDepth + 1));
}
#endif
}
// 終了する
return 0;
}
// 置換表を使って同じ局面が出てきた場合の処理を行う
TT_DATA *tdata = nullptr;
Key key;
// 深さが 0 の場合は置換表は使わない(スレッド版で置換表を使ってしまうと、ビーム探索の行動が排除されてしまう可能性があるため)
if (parameter.usett && depth != 0) {
// ハッシュ地を計算
key = pinfoorig.calc_hash();
bool found;
// 置換表を引く
tdata = tt.findcache(key, found);
// 存在した場合は登録されている評価値を返す
if (found) {
return tdata->get();
}
}
// この深さでの行動を表す変数への参照(x:落下位置(-1の場合はスキルを使う),r:回転数)
int8_t& x = pinfoorig.x;
int8_t& r = pinfoorig.r;
// 最大評価値の初期化
double maxhyouka = -INF;
// 可能な行動をすべて試す
for (x = -1; x <= 8; x++) {
// 深さ 0 の場合でスレッドを使う場合は、xはpinfoorig.px に固定して実行する(他のxは他のスレッドで実行するのでここでは計算しない)
if (depth == 0 && parameter.usethread) {
x = pinfoorig.px;
}
for (r = 0; r < 4; r++) {
// 深さ0における行動を fx, fr に記録する
if (depth == 0) {
pinfoorig.fx = x;
pinfoorig.fr = r;
}
// pinfoorig の行動を行い、行動後の状態を pinfo に記録する関数を呼ぶ
if (!action(pinfoorig, pinfo)) {
// 不正な行動、ゲームオーバー時は飛ばす
continue;
}
// 深さ0におけるチェーン数を fchain に記録する
if (depth == 0) {
pinfo.fchain = pinfo.chain;
}
// etatus の情報を使って、敵のこの深さでの行動の結果を反映させる。
// この時点では、まだこのターンの開始時の敵のスキルポイントの情報を使用したいので、
// 味方のチェーンによる敵のスキルポイントを減らす処理を行う前にこの処理を行う必要がある
// 敵のスキルの使用状況にる estatus のインデックス番号を表す変数。初期値としてスキルを全く使用しないことを表す 0 を設定しておく
int skillindex = 0;
// もう一つの playerinfo をチェックするかどうか
bool checkanotherpinfo = false;
// もう一つの playerinfo の情報を格納する変数
PlayerInfo anotherpinfo;
// 深さが、敵の探索深さ未満の場合のみ計算する
if (depth < ginfo.enesearchDepth) {
// この深さで敵がスキルを使えるかどうかのチェック
if (pinfo.skill[1] >= 80 && pinfo.eneskilldeleteblocknum > 0) {
// 過去に敵がスキルを使用可能だったとしても、その時点で敵がスキルを使用していたかどうかが不明なので
// estatus のすべての可能性の中のいいとこどりをしたインデックス(4)を使用する
// なお、ここで敵がスキルを使用するかどうかは不確定なので、敵のスキルポイントを0にする処理は行わない
skillindex = 4;
// 使用した可能性があることを表すフラグを立てる
pinfo.mayeneuseskill = true;
}
// 使えない場合は、過去に使った可能性があるかどうかで分ける
// 使った可能性があれば過去に使用した可能性がある場合のインデックス番号1を採用する
// なお、pinfo.mayeneuseskill は実際にはスキルを使用不能な場合も含まれる可能性がある
// (敵のスキルポイントは estatus の chain が 1 以上の時に換算するが、実際にはスキルが増えないケースがあるため)
// estatusdata.data[1][pinfo.eneojamadroppeddepthbit][depth + 1].isdead が true の場合は次で死亡するので除外する
else if (pinfo.mayeneuseskill && !estatusdata.data[1][pinfo.eneojamadroppeddepthbit][depth + 1].isdead) {
skillindex = 1;
}
// 敵が死亡している場合はこれ以上探索を続ける必要はない
if (estatusdata.data[skillindex][pinfo.eneojamadroppeddepthbit][depth + 1].isdead) {
// 敵が死亡した場合の評価を計算(次の深さで敵は死亡するので、引数には depth + 1 を入れる)
pinfoorig.calcenedeadhyouka(depth + 1);
HyoukaData& hyouka = pinfoorig.hyouka;
// ai2がfalseの場合は、ここがリーフノードなので、ここで最高評価値の情報の更新を行う
if (!parameter.ai2) {
// 評価値の最高値の更新
if (hyouka.total > bhyouka.total) {
bhyouka = hyouka;
memcpy(bhistory, history, sizeof(PlayerInfo) * (depth + 1));
}
// ビーム探索で得られた行動に対する最高評価値の情報の更新
if (history[0].x == ginfo.beamx && history[0].r == ginfo.beamr && hyouka.total > bbeamhyouka.total) {
bbeamhyouka = hyouka;
#ifdef DEBUG
if (parameter.debugmsg) {
memcpy(bbeamhistory, history, sizeof(PlayerInfo) * (ginfo.searchDepth + 1));
}