Search.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558
  1. import { _decorator } from "cc";
  2. import GameUtil from "../gcommon/GameUtil";
  3. import ChessPos from "./ChessPos";
  4. const { ccclass } = _decorator;
  5. @ccclass("Search")
  6. export default class Search {
  7. hashMask:any = null
  8. pos:ChessPos = null
  9. setSearch(pos, hashLevel) {
  10. this.hashMask = (1 << hashLevel) - 1;
  11. this.pos = pos;
  12. }
  13. getHashItem() {
  14. return this.hashTable[this.pos.zobristKey & this.hashMask];
  15. }
  16. probeHash(vlAlpha, vlBeta, depth, mv) {
  17. var hash = this.getHashItem();
  18. if (hash.zobristLock != this.pos.zobristLock) {
  19. mv[0] = 0;
  20. return -MATE_VALUE;
  21. }
  22. mv[0] = hash.mv;
  23. var mate = false;
  24. if (hash.vl > WIN_VALUE) {
  25. if (hash.vl <= BAN_VALUE) {
  26. return -MATE_VALUE;
  27. }
  28. hash.vl -= this.pos.distance;
  29. mate = true;
  30. } else if (hash.vl < -WIN_VALUE) {
  31. if (hash.vl >= -BAN_VALUE) {
  32. return -MATE_VALUE;
  33. }
  34. hash.vl += this.pos.distance;
  35. mate = true;
  36. } else if (hash.vl == this.pos.drawValue()) {
  37. return -MATE_VALUE;
  38. }
  39. if (hash.depth < depth && !mate) {
  40. return -MATE_VALUE;
  41. }
  42. if (hash.flag == HASH_BETA) {
  43. return (hash.vl >= vlBeta ? hash.vl : -MATE_VALUE);
  44. }
  45. if (hash.flag == HASH_ALPHA) {
  46. return (hash.vl <= vlAlpha ? hash.vl : -MATE_VALUE);
  47. }
  48. return hash.vl;
  49. }
  50. recordHash(flag, vl, depth, mv) {
  51. var hash = this.getHashItem();
  52. if (hash.depth > depth) {
  53. return;
  54. }
  55. hash.flag = flag;
  56. hash.depth = depth;
  57. if (vl > WIN_VALUE) {
  58. if (mv == 0 && vl <= BAN_VALUE) {
  59. return;
  60. }
  61. hash.vl = vl + this.pos.distance;
  62. } else if (vl < -WIN_VALUE) {
  63. if (mv == 0 && vl >= -BAN_VALUE) {
  64. return;
  65. }
  66. hash.vl = vl - this.pos.distance;
  67. } else if (vl == this.pos.drawValue() && mv == 0) {
  68. return;
  69. } else {
  70. hash.vl = vl;
  71. }
  72. hash.mv = mv;
  73. hash.zobristLock = this.pos.zobristLock;
  74. }
  75. setBestMove(mv, depth) {
  76. this.historyTable[this.pos.historyIndex(mv)] += depth * depth;
  77. var mvsKiller = this.killerTable[this.pos.distance];
  78. if (mvsKiller[0] != mv) {
  79. mvsKiller[1] = mvsKiller[0];
  80. mvsKiller[0] = mv;
  81. }
  82. }
  83. searchQuiesc(vlAlpha_, vlBeta) {
  84. var vlAlpha = vlAlpha_;
  85. this.allNodes ++;
  86. var vl = this.pos.mateValue();
  87. if (vl >= vlBeta) {
  88. return vl;
  89. }
  90. var vlRep = this.pos.repStatus(1);
  91. if (vlRep > 0) {
  92. return this.pos.repValue(vlRep);
  93. }
  94. if (this.pos.distance == LIMIT_DEPTH) {
  95. return this.pos.evaluate();
  96. }
  97. var vlBest = -MATE_VALUE;
  98. var mvs = [], vls = [];
  99. if (this.pos.inCheck()) {
  100. mvs = this.pos.generateMoves(null);
  101. for (var i = 0; i < mvs.length; i ++) {
  102. vls.push(this.historyTable[this.pos.historyIndex(mvs[i])]);
  103. }
  104. shellSort(mvs, vls);
  105. } else {
  106. vl = this.pos.evaluate();
  107. if (vl > vlBest) {
  108. if (vl >= vlBeta) {
  109. return vl;
  110. }
  111. vlBest = vl;
  112. vlAlpha = Math.max(vl, vlAlpha);
  113. }
  114. mvs = this.pos.generateMoves(vls);
  115. shellSort(mvs, vls);
  116. for (var i = 0; i < mvs.length; i ++) {
  117. if (vls[i] < 10 || (vls[i] < 20 && HOME_HALF(DST(mvs[i]), this.pos.sdPlayer))) {
  118. mvs.length = i;
  119. break;
  120. }
  121. }
  122. }
  123. for (var i = 0; i < mvs.length; i ++) {
  124. if (!this.pos.makeMove(mvs[i])) {
  125. continue;
  126. }
  127. vl = -this.searchQuiesc(-vlBeta, -vlAlpha);
  128. this.pos.undoMakeMove();
  129. if (vl > vlBest) {
  130. if (vl >= vlBeta) {
  131. return vl;
  132. }
  133. vlBest = vl;
  134. vlAlpha = Math.max(vl, vlAlpha);
  135. }
  136. }
  137. return vlBest == -MATE_VALUE ? this.pos.mateValue() : vlBest;
  138. }
  139. searchFull(vlAlpha_, vlBeta, depth, noNull) {
  140. var vlAlpha = vlAlpha_;
  141. if (depth <= 0) {
  142. return this.searchQuiesc(vlAlpha, vlBeta);
  143. }
  144. this.allNodes ++;
  145. var vl = this.pos.mateValue();
  146. if (vl >= vlBeta) {
  147. return vl;
  148. }
  149. var vlRep = this.pos.repStatus(1);
  150. if (vlRep > 0) {
  151. return this.pos.repValue(vlRep);
  152. }
  153. var mvHash = [0];
  154. vl = this.probeHash(vlAlpha, vlBeta, depth, mvHash);
  155. if (vl > -MATE_VALUE) {
  156. return vl;
  157. }
  158. if (this.pos.distance == LIMIT_DEPTH) {
  159. return this.pos.evaluate();
  160. }
  161. if (!noNull && !this.pos.inCheck() && this.pos.nullOkay()) {
  162. this.pos.nullMove();
  163. vl = -this.searchFull(-vlBeta, 1 - vlBeta, depth - NULL_DEPTH - 1, true);
  164. this.pos.undoNullMove();
  165. if (vl >= vlBeta && (this.pos.nullSafe() ||
  166. this.searchFull(vlAlpha, vlBeta, depth - NULL_DEPTH, true) >= vlBeta)) {
  167. return vl;
  168. }
  169. }
  170. var hashFlag = HASH_ALPHA;
  171. var vlBest = -MATE_VALUE;
  172. var mvBest = 0;
  173. var sort = new MoveSort(mvHash[0], this.pos, this.killerTable, this.historyTable);
  174. var mv;
  175. while ((mv = sort.next()) > 0) {
  176. if (!this.pos.makeMove(mv)) {
  177. continue;
  178. }
  179. var newDepth = this.pos.inCheck() || sort.singleReply ? depth : depth - 1;
  180. if (vlBest == -MATE_VALUE) {
  181. vl = -this.searchFull(-vlBeta, -vlAlpha, newDepth, false);
  182. } else {
  183. vl = -this.searchFull(-vlAlpha - 1, -vlAlpha, newDepth, false);
  184. if (vl > vlAlpha && vl < vlBeta) {
  185. vl = -this.searchFull(-vlBeta, -vlAlpha, newDepth, false);
  186. }
  187. }
  188. this.pos.undoMakeMove();
  189. if (vl > vlBest) {
  190. vlBest = vl;
  191. if (vl >= vlBeta) {
  192. hashFlag = HASH_BETA;
  193. mvBest = mv;
  194. break;
  195. }
  196. if (vl > vlAlpha) {
  197. vlAlpha = vl;
  198. hashFlag = HASH_PV;
  199. mvBest = mv;
  200. }
  201. }
  202. }
  203. if (vlBest == -MATE_VALUE) {
  204. return this.pos.mateValue();
  205. }
  206. this.recordHash(hashFlag, vlBest, depth, mvBest);
  207. if (mvBest > 0) {
  208. this.setBestMove(mvBest, depth);
  209. }
  210. return vlBest;
  211. }
  212. searchRoot(depth) {
  213. var vlBest = -MATE_VALUE;
  214. var sort = new MoveSort(this.mvResult, this.pos, this.killerTable, this.historyTable);
  215. var mv;
  216. while ((mv = sort.next()) > 0) {
  217. if (!this.pos.makeMove(mv)) {
  218. continue;
  219. }
  220. var newDepth = this.pos.inCheck() ? depth : depth - 1;
  221. var vl;
  222. if (vlBest == -MATE_VALUE) {
  223. vl = -this.searchFull(-MATE_VALUE, MATE_VALUE, newDepth, true);
  224. } else {
  225. vl = -this.searchFull(-vlBest - 1, -vlBest, newDepth, false);
  226. if (vl > vlBest) {
  227. vl = -this.searchFull(-MATE_VALUE, -vlBest, newDepth, true);
  228. }
  229. }
  230. this.pos.undoMakeMove();
  231. if (vl > vlBest) {
  232. vlBest = vl;
  233. this.mvResult = mv;
  234. if (vlBest > -WIN_VALUE && vlBest < WIN_VALUE) {
  235. vlBest += Math.floor(Math.random() * RANDOMNESS) -
  236. Math.floor(Math.random() * RANDOMNESS);
  237. vlBest = (vlBest == this.pos.drawValue() ? vlBest - 1 : vlBest);
  238. }
  239. }
  240. }
  241. this.setBestMove(this.mvResult, depth);
  242. return vlBest;
  243. }
  244. searchUnique(vlBeta, depth) {
  245. var sort = new MoveSort(this.mvResult, this.pos, this.killerTable, this.historyTable);
  246. sort.next();
  247. var mv;
  248. while ((mv = sort.next()) > 0) {
  249. if (!this.pos.makeMove(mv)) {
  250. continue;
  251. }
  252. var vl = -this.searchFull(-vlBeta, 1 - vlBeta,
  253. this.pos.inCheck() ? depth : depth - 1, false);
  254. this.pos.undoMakeMove();
  255. if (vl >= vlBeta) {
  256. return false;
  257. }
  258. }
  259. return true;
  260. }
  261. index:number=0;
  262. getT:number=0;
  263. getdp:number=0;
  264. getmillis:number=0;
  265. mvResult:number = 0;
  266. allNodes:number = 0;
  267. historyTable:any[] = [];
  268. hashTable: any[] =[];
  269. killerTable:any[] = [];
  270. callfun=function(a){
  271. }
  272. SIDE_TAG(sd) {
  273. return 8 + (sd << 3);
  274. }
  275. OPP_SIDE_TAG(sd) {
  276. return 16 - (sd << 3);
  277. }
  278. MOVE(sqSrc, sqDst) {
  279. return sqSrc + (sqDst << 8);
  280. }
  281. //小白ai
  282. xiaoBaiSearchMain(call){
  283. this.callfun=call;
  284. //先遍历所有棋子,查询是否可吃的情况
  285. var tempArray = [];
  286. console.log("this.pos.sdPlayer == ",this.pos.sdPlayer)
  287. var pcSelfSide = this.SIDE_TAG(this.pos.sdPlayer);
  288. var pcOppSide = this.OPP_SIDE_TAG(this.pos.sdPlayer);
  289. for (let sq = 0; sq < this.pos.squares.length; sq++) {
  290. const pcSrc = this.pos.squares[sq];
  291. if ((pcSrc & pcSelfSide) != 0 ) {
  292. tempArray.push(sq)
  293. }
  294. }
  295. console.log("小白",tempArray)
  296. var pathList = []
  297. for (let index = 0; index < tempArray.length; index++) {
  298. var sqSrc = tempArray[index];
  299. var xb_list = this.pos.findAllCanMove(sqSrc);
  300. for (let xb_sq_index = 0; xb_sq_index < xb_list.length; xb_sq_index++) {
  301. var xb_sq = xb_list[xb_sq_index];
  302. var mv = this.MOVE(sqSrc,xb_sq)
  303. if (!this.pos.makeMove(mv)) {
  304. continue;
  305. }
  306. pathList.push(mv)
  307. this.pos.undoMakeMove()
  308. var pcSrc = this.pos.squares[xb_sq];
  309. if ((pcSrc & pcOppSide) != 0 ) {
  310. //console.log("可以吃的",xb_sq,sqSrc,pcSrc,pcSelfSide,this.pos.squares[sqSrc])
  311. this.callfun(mv)
  312. return;
  313. }
  314. }
  315. }
  316. var i = GameUtil.RandomRangeInt(0,pathList.length);
  317. this.callfun(pathList[i])
  318. //console.log(" 默认走的",pathList,i)
  319. //无任何棋子,小白按顺序随便走, 车,马,炮,兵,士,象,将 (上一级存在,不走下步棋子)
  320. }
  321. searchMain(depth, millis,call) {
  322. this.mvResult = this.pos.bookMove();
  323. if (this.mvResult > 0) {
  324. this.pos.makeMove(this.mvResult);
  325. if (this.pos.repStatus(3) == 0) {
  326. this.pos.undoMakeMove();
  327. return this.mvResult;
  328. }
  329. this.pos.undoMakeMove();
  330. }
  331. this.hashTable = [];
  332. for (var i = 0; i <= this.hashMask; i ++) {
  333. this.hashTable.push({depth: 0, flag: 0, vl: 0, mv: 0, zobristLock: 0});
  334. }
  335. this.killerTable = [];
  336. for (var i = 0; i < LIMIT_DEPTH; i ++) {
  337. this.killerTable.push([0, 0]);
  338. }
  339. this.historyTable = [];
  340. for (var i = 0; i < 4096; i ++) {
  341. this.historyTable.push(0);
  342. }
  343. this.mvResult = 0;
  344. this.allNodes = 0;
  345. this.pos.distance = 0;
  346. // ///
  347. // var t = new Date().getTime();
  348. // for (var i = 1; i <= depth; i ++) {
  349. // var vl = this.searchRoot(i);
  350. // console.log(i)
  351. // this.allMillis = new Date().getTime() - t;
  352. // if (this.allMillis > millis) {
  353. // break;
  354. // }
  355. // if (vl > WIN_VALUE || vl < -WIN_VALUE) {
  356. // break;
  357. // }
  358. // if (this.searchUnique(1 - WIN_VALUE, i)) {
  359. // break;
  360. // }
  361. // }
  362. // call(this.mvResult);
  363. /////
  364. this.getT = new Date().getTime();
  365. this.index=1;
  366. this.getdp=depth;
  367. this.getmillis=millis;
  368. this.callfun=call;
  369. this.abc();
  370. }
  371. allMillis:number = 0
  372. abc(){
  373. setTimeout(() => {
  374. var vl = this.searchRoot(this.index);
  375. this.allMillis = new Date().getTime() - this.getT;
  376. if (this.allMillis > this.getmillis) {
  377. this.callfun(this.mvResult);
  378. this.callfun=null;
  379. return;
  380. }
  381. if (vl > WIN_VALUE || vl < -WIN_VALUE) {
  382. this.callfun(this.mvResult);
  383. this.callfun=null;
  384. return;
  385. }
  386. if (this.searchUnique(1 - WIN_VALUE, this.index)) {
  387. this.callfun(this.mvResult);
  388. this.callfun=null;
  389. return;
  390. }
  391. if(this.index<=this.getdp)
  392. {
  393. this.index++;
  394. //console.log(index);
  395. this.abc();
  396. }
  397. else{
  398. this.callfun(this.mvResult);
  399. this.callfun=null;
  400. }
  401. }, 0);
  402. }
  403. getKNPS() {
  404. return this.allNodes / this.allMillis;
  405. }
  406. }
  407. var SHELL_STEP = [0, 1, 4, 13, 40, 121, 364, 1093];
  408. function shellSort(mvs, vls) {
  409. var stepLevel = 1;
  410. while (SHELL_STEP[stepLevel] < mvs.length) {
  411. stepLevel ++;
  412. }
  413. stepLevel --;
  414. while (stepLevel > 0) {
  415. var step = SHELL_STEP[stepLevel];
  416. for (var i = step; i < mvs.length; i ++) {
  417. var mvBest = mvs[i];
  418. var vlBest = vls[i];
  419. var j = i - step;
  420. while (j >= 0 && vlBest > vls[j]) {
  421. mvs[j + step] = mvs[j];
  422. vls[j + step] = vls[j];
  423. j -= step;
  424. }
  425. mvs[j + step] = mvBest;
  426. vls[j + step] = vlBest;
  427. }
  428. stepLevel --;
  429. }
  430. }
  431. //
  432. var MATE_VALUE = 10000;
  433. var BAN_VALUE = MATE_VALUE - 100;
  434. var WIN_VALUE = MATE_VALUE - 200;
  435. function HOME_HALF(sq, sd) {
  436. return (sq & 0x80) != (sd << 7);
  437. }
  438. function DST(mv) {
  439. return mv >> 8;
  440. }
  441. //
  442. var PHASE_HASH = 0;
  443. var PHASE_KILLER_1 = 1;
  444. var PHASE_KILLER_2 = 2;
  445. var PHASE_GEN_MOVES = 3;
  446. var PHASE_REST = 4;
  447. function MoveSort(mvHash, pos, killerTable, historyTable) {
  448. this.mvs = [];
  449. this.vls = [];
  450. this.mvHash = this.mvKiller1 = this.mvKiller2 = 0;
  451. this.pos = pos;
  452. this.historyTable = historyTable;
  453. this.phase = PHASE_HASH;
  454. this.index = 0;
  455. this.singleReply = false;
  456. if (pos.inCheck()) {
  457. this.phase = PHASE_REST;
  458. var mvsAll = pos.generateMoves(null);
  459. for (var i = 0; i < mvsAll.length; i ++) {
  460. var mv = mvsAll[i]
  461. if (!pos.makeMove(mv)) {
  462. continue;
  463. }
  464. pos.undoMakeMove();
  465. this.mvs.push(mv);
  466. this.vls.push(mv == mvHash ? 0x7fffffff :
  467. historyTable[pos.historyIndex(mv)]);
  468. }
  469. shellSort(this.mvs, this.vls);
  470. this.singleReply = this.mvs.length == 1;
  471. } else {
  472. this.mvHash = mvHash;
  473. this.mvKiller1 = killerTable[pos.distance][0];
  474. this.mvKiller2 = killerTable[pos.distance][1];
  475. }
  476. }
  477. MoveSort.prototype.next = function() {
  478. switch (this.phase) {
  479. case PHASE_HASH:
  480. this.phase = PHASE_KILLER_1;
  481. if (this.mvHash > 0) {
  482. return this.mvHash;
  483. }
  484. // No Break
  485. case PHASE_KILLER_1:
  486. this.phase = PHASE_KILLER_2;
  487. if (this.mvKiller1 != this.mvHash && this.mvKiller1 > 0 &&
  488. this.pos.legalMove(this.mvKiller1)) {
  489. return this.mvKiller1;
  490. }
  491. // No Break
  492. case PHASE_KILLER_2:
  493. this.phase = PHASE_GEN_MOVES;
  494. if (this.mvKiller2 != this.mvHash && this.mvKiller2 > 0 &&
  495. this.pos.legalMove(this.mvKiller2)) {
  496. return this.mvKiller2;
  497. }
  498. // No Break
  499. case PHASE_GEN_MOVES:
  500. this.phase = PHASE_REST;
  501. this.mvs = this.pos.generateMoves(null);
  502. this.vls = [];
  503. for (var i = 0; i < this.mvs.length; i ++) {
  504. this.vls.push(this.historyTable[this.pos.historyIndex(this.mvs[i])]);
  505. }
  506. shellSort(this.mvs, this.vls);
  507. this.index = 0;
  508. // No Break
  509. default:
  510. while (this.index < this.mvs.length) {
  511. var mv = this.mvs[this.index];
  512. this.index ++;
  513. if (mv != this.mvHash && mv != this.mvKiller1 && mv != this.mvKiller2) {
  514. return mv;
  515. }
  516. }
  517. }
  518. return 0;
  519. }
  520. var LIMIT_DEPTH = 64;
  521. var NULL_DEPTH = 2;
  522. var RANDOMNESS = 8;
  523. var HASH_ALPHA = 1;
  524. var HASH_BETA = 2;
  525. var HASH_PV = 3;