Skip to content

Commit a7d6f69

Browse files
committed
Null moves and perft
1 parent 477db90 commit a7d6f69

File tree

6 files changed

+323
-62
lines changed

6 files changed

+323
-62
lines changed

engine.go

Lines changed: 163 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -156,13 +156,19 @@ func root(toEngine chan bool, frEngine chan string) {
156156
alpha, beta = minEval, maxEval
157157
for ix, mv := range ml {
158158
childPV.clear()
159-
160159
b.move(mv)
161160
tell("info depth ", strconv.Itoa(depth), " currmove ", mv.String(), " currmovenumber ", strconv.Itoa(ix+1))
162161
score := -search(-beta, -alpha, depth-1, 1, &childPV, b)
163-
162+
164163
b.unmove(mv)
165164

165+
////////////////////////////////////
166+
if !checkKey(b) {
167+
fmt.Println("fullkey=", b.fullKey(), "Key", b.key, mv.StringFull())
168+
fmt.Println("INVALID KEY AFTER UNMOVE ROOT")
169+
}
170+
///////////////////////////////////////
171+
166172
if limits.stop {
167173
break
168174
}
@@ -201,11 +207,8 @@ func root(toEngine chan bool, frEngine chan string) {
201207
}
202208
}
203209

204-
//TODO search: hash table/transposition table
205-
206-
//TODO search: history table and maybe counter move table
207-
//TODO search: move generation. More fast and accurate
208210
//TODO search: Null Move
211+
209212
//TODO search: Late Move Reduction
210213
//TODO search: Internal Iterative Depening
211214
//TODO search: Delta Pruning
@@ -219,15 +222,19 @@ func search(alpha, beta, depth, ply int, pv *pvList, b *boardStruct) int {
219222
}
220223
pv.clear()
221224

225+
pvNode := depth > 0 && beta != alpha+1
226+
222227
transMove := noMove
228+
useTT := depth >= 0
223229
transDepth := depth
224-
pvNode := depth > 0 && beta != alpha+1
230+
inCheck := b.isAttacked(b.King[b.stm], b.stm.opp())
225231

226-
if depth < 0 { // inCheck?
232+
if depth < 0 && inCheck {
233+
useTT = true
227234
transDepth = 0
228235
}
229236

230-
{ // keep spme variables local just to be sure - TRANS.RETRIEVE
237+
if useTT {
231238
var transSc, scType int
232239
ok := false
233240

@@ -248,14 +255,34 @@ func search(alpha, beta, depth, ply int, pv *pvList, b *boardStruct) int {
248255

249256
var childPV pvList
250257
childPV.new() // TODO? make it smaller for each depth maxDepth-ply
258+
/////////////////////////////////////// NULL MOVE /////////////////////////////////////////
259+
ev := signEval(b.stm, evaluate(b))
260+
// null-move pruning
261+
if !pvNode && depth > 0 && !isMateScore(beta) && !inCheck && !b.isAntiNullMove() && ev >= beta {
262+
nullMv := b.moveNull()
263+
sc := minEval
264+
if depth <= 3 { // static
265+
// if you don't beat me with 100 points,
266+
// then I think your position sucks
267+
sc = -qs(-beta+1, b)
268+
} else { // dynamic
269+
sc = -search(-beta, -beta+1, depth-3-1, ply, &childPV, b)
270+
}
271+
272+
b.undoNull(nullMv)
273+
274+
if sc >= beta {
275+
if useTT {
276+
trans.store(b.fullKey(), noMove, transDepth, ply, sc, scoreTypeLower)
277+
}
278+
return sc
279+
}
280+
}
281+
/////////////////////// NULL MOVE END //////////////////////
282+
251283
bs, score := noScore, noScore
252284
bm := noMove
253285

254-
/* var ml moveList
255-
ml.new(60)
256-
genInOrder(b, &ml, ply, transMove)
257-
for _, mv := range ml {
258-
*/
259286
var genInfo = genInfoStruct{sv: 0, ply: ply, transMove: transMove}
260287
next = nextNormal
261288
for mv, msg := next(&genInfo, b); mv != noMove; mv, msg = next(&genInfo, b) {
@@ -267,36 +294,37 @@ func search(alpha, beta, depth, ply int, pv *pvList, b *boardStruct) int {
267294

268295
childPV.clear()
269296

270-
/* if pvNode && bm != noMove {
271-
score = -search(-alpha-1, -alpha, depth-1, ply+1, &childPV, b)
272-
if score > alpha { // PVS/LMR re-search
297+
if pvNode && bm != noMove {
298+
score = -search(-alpha-1, -alpha, depth-1, ply+1, &childPV, b)
299+
if score > alpha {
273300
score = -search(-beta, -alpha, depth-1, ply+1, &childPV, b)
274301
}
275302
} else {
276-
*/
277-
score = -search(-beta, -alpha, depth-1, ply+1, &childPV, b)
278-
// }
303+
score = -search(-beta, -alpha, depth-1, ply+1, &childPV, b)
304+
}
279305

280306
b.unmove(mv)
281-
307+
282308
if score > bs {
283309
bs = score
284310
bm = mv
285311
pv.catenate(mv, &childPV)
286312
if score > alpha {
287313
alpha = score
288-
trans.store(b.fullKey(), mv, depth, ply, score, scoreType(score, alpha, beta))
314+
if useTT {
315+
trans.store(b.fullKey(), mv, transDepth, ply, score, scoreType(score, alpha, beta))
316+
}
289317
}
290318

291319
if score >= beta { // beta cutoff
292320
// add killer and update history
293321
if mv.cp() == empty && mv.pr() == empty {
294322
killers.add(mv, ply)
323+
history.inc(mv.fr(), mv.to(), b.stm, depth)
295324
}
296325
if mv.cmp(transMove) {
297326
trans.cPrune++
298327
}
299-
history.inc(mv.fr(), mv.to(), b.stm, depth)
300328
return score
301329
}
302330
}
@@ -328,6 +356,14 @@ func search(alpha, beta, depth, ply int, pv *pvList, b *boardStruct) int {
328356
return bs
329357
}
330358

359+
// is this a position to avoid null move?
360+
func (b *boardStruct) isAntiNullMove() bool {
361+
if b.wbBB[b.stm] == b.pieceBB[King]&b.wbBB[b.stm] {
362+
return true
363+
}
364+
return false
365+
}
366+
331367
func initQS(ml *moveList, b *boardStruct) {
332368
ml.clear()
333369
b.genAllCaptures(ml)
@@ -448,8 +484,8 @@ func see(fr, to int, b *boardStruct) int {
448484
n++
449485

450486
// save the value of tha capturing piece to be used later
451-
lastAtkVal = pVal[pt2pc(pt, WHITE)] // using WHITE always gives positive integer
452-
stm = stm.opp() // next side to move
487+
lastAtkVal = pVal[pt2pc(pt, WHITE)] // using WHITE always gives positive integer
488+
stm = stm.opp() // next side to move
453489

454490
if pt == King && (attackingBB&b.wbBB[stm]) != 0 { //NOTE: just changed stm-color above
455491
// if king capture and 'they' are atting we have to stop
@@ -582,7 +618,7 @@ func (k *killerStruct) clear() {
582618

583619
// add killer 1 and 2 (Not inCheck, caaptures and promotions)
584620
func (k *killerStruct) add(mv move, ply int) {
585-
if !k[ply].k1.cmp(mv) {
621+
if !k[ply].k1.cmpFrTo(mv) {
586622
k[ply].k2 = k[ply].k1
587623
k[ply].k1 = mv
588624
}
@@ -691,6 +727,7 @@ func nextNormal(genInfo *genInfoStruct, b *boardStruct) (move, string) {
691727
if b.isLegal(genInfo.transMove) {
692728
return genInfo.transMove, "transMove"
693729
}
730+
genInfo.transMove = noMove
694731
}
695732
fallthrough
696733
case nextFirstGoodCp:
@@ -747,7 +784,7 @@ func nextNormal(genInfo *genInfoStruct, b *boardStruct) (move, string) {
747784
fallthrough
748785
case nextK1: // not transMove
749786
genInfo.sv = nextK2
750-
if killers[genInfo.ply].k1 != noMove && !genInfo.transMove.cmpFrTo(killers[genInfo.ply].k1) {
787+
if killers[genInfo.ply].k1 != noMove && !genInfo.transMove.cmpFrToP(killers[genInfo.ply].k1) {
751788
if b.isLegal(killers[genInfo.ply].k1) {
752789
var mv move
753790
mv.packMove(killers[genInfo.ply].k1.fr(), killers[genInfo.ply].k1.to(), b.sq[killers[genInfo.ply].k1.fr()], b.sq[killers[genInfo.ply].k1.to()], killers[genInfo.ply].k1.pr(), b.ep, b.castlings)
@@ -758,7 +795,7 @@ func nextNormal(genInfo *genInfoStruct, b *boardStruct) (move, string) {
758795
fallthrough
759796
case nextK2: // not transMove
760797
genInfo.sv = nextCounterMv
761-
if killers[genInfo.ply].k2 != noMove && !genInfo.transMove.cmpFrTo(killers[genInfo.ply].k2) {
798+
if killers[genInfo.ply].k2 != noMove && !genInfo.transMove.cmpFrToP(killers[genInfo.ply].k2) {
762799
if b.isLegal(killers[genInfo.ply].k2) {
763800
var mv move
764801
mv.packMove(killers[genInfo.ply].k2.fr(), killers[genInfo.ply].k2.to(), b.sq[killers[genInfo.ply].k2.fr()], b.sq[killers[genInfo.ply].k2.to()], killers[genInfo.ply].k2.pr(), b.ep, b.castlings)
@@ -768,11 +805,12 @@ func nextNormal(genInfo *genInfoStruct, b *boardStruct) (move, string) {
768805

769806
fallthrough
770807
case nextCounterMv: // not transMove, not killer1, not killer2
808+
genInfo.counterMv = noMove
771809
genInfo.sv = nextFirstNonCp
772-
// if counterMovex[genInfo.ply][mv.to()] != noMove { // and not transMove and not k1 and not k2
810+
// if genInfo.counterMv != noMove && !genInfo.counterMv.cmpFrTo(genInfo.transMove) &&
811+
// genInfo.counterMv.cmpFrTo(killers[genInfo.ply].k1) && genInfo.counterMv.cmpFrTo(killers[genInfo.ply].k2) {
773812
// var mv move
774813
// mv.packMove(counterMv.fr(), counterMv.to(),b.sq[counterMv.fr()],b.sq[counterMv.to()],counterMv.pr(), b.ep,b.castlings)
775-
genInfo.counterMv = noMove
776814
//check if it is a valid move
777815
// return sv, counterMovex[genInfo.ply][mv.to()]
778816
// }
@@ -786,7 +824,8 @@ func nextNormal(genInfo *genInfoStruct, b *boardStruct) (move, string) {
786824
bs := minEval
787825
bIx := -1
788826
for ix := 0; ix < len(*ml); ix++ {
789-
if (*ml)[ix].cmp(genInfo.transMove) || (*ml)[ix].cmp(genInfo.counterMv) || (*ml)[ix].cmp(killers[genInfo.ply].k1) || (*ml)[ix].cmp(killers[genInfo.ply].k2) {
827+
if (*ml)[ix].cmpFrToP(genInfo.transMove) || (*ml)[ix].cmpFrToP(genInfo.counterMv) ||
828+
(*ml)[ix].cmpFrToP(killers[genInfo.ply].k1) || (*ml)[ix].cmpFrToP(killers[genInfo.ply].k2) {
790829
continue
791830
}
792831
sc := int(history.get((*ml)[ix].fr(), (*ml)[ix].to(), b.stm))
@@ -814,8 +853,9 @@ func nextNormal(genInfo *genInfoStruct, b *boardStruct) (move, string) {
814853
bIx := -1
815854
ml := &genInfo.nonCapt
816855
for ix := 0; ix < len(*ml); ix++ {
817-
if (*ml)[ix].cmp(genInfo.transMove) || (*ml)[ix].cmp(genInfo.counterMv) || (*ml)[ix].cmp(killers[genInfo.ply].k1) || (*ml)[ix].cmp(killers[genInfo.ply].k2) {
818-
continue
856+
if (*ml)[ix].cmpFrToP(genInfo.transMove) || (*ml)[ix].cmpFrToP(genInfo.counterMv) ||
857+
(*ml)[ix].cmpFrToP(killers[genInfo.ply].k1) || (*ml)[ix].cmpFrToP(killers[genInfo.ply].k2) {
858+
continue
819859
}
820860
sc := int(history.get((*ml)[ix].fr(), (*ml)[ix].to(), b.stm))
821861
if sc > bs {
@@ -837,13 +877,14 @@ func nextNormal(genInfo *genInfoStruct, b *boardStruct) (move, string) {
837877
// pick a bad capt - use see?
838878
mv := noMove
839879
ml := &genInfo.captures
840-
for ix := len(*ml)-1; ix >=0; ix-- {
880+
for ix := len(*ml) - 1; ix >= 0; ix-- {
841881
if (*ml)[ix].cmp(genInfo.transMove) {
882+
*ml = (*ml)[:len(*ml)-1]
842883
continue
843884
}
844885

845886
mv = (*ml)[ix]
846-
// (*ml)[ix], (*ml)[len(*ml)-1] = (*ml)[len(*ml)-1], (*ml)[ix]
887+
// (*ml)[ix], (*ml)[len(*ml)-1] = (*ml)[len(*ml)-1], (*ml)[ix]
847888
*ml = (*ml)[:len(*ml)-1]
848889
break
849890
}
@@ -853,3 +894,90 @@ func nextNormal(genInfo *genInfoStruct, b *boardStruct) (move, string) {
853894
panic("neve come here! nextNormal sv=" + strconv.Itoa(genInfo.sv))
854895
}
855896
}
897+
898+
// StartPerft starts the Perft command that generates all moves until the given depth.
899+
// It counts the leafs only taht is printed out for each possible move from current pos
900+
func startPerft(depth int, bd *boardStruct) uint64 {
901+
if depth <= 0 {
902+
fmt.Printf("Total:\t%v\n", 1)
903+
return 0
904+
}
905+
906+
transMove := noMove
907+
transMove, _, _, _ = trans.retrieve(bd.fullKey(), depth, 0)
908+
909+
totCount := uint64(0)
910+
var genInfo = genInfoStruct{sv: 0, ply: 0, transMove: transMove}
911+
next = nextNormal
912+
ix := 0
913+
for mv, msg := next(&genInfo, bd); mv != noMove; mv, msg = next(&genInfo, bd) {
914+
if !bd.move(mv) {
915+
continue
916+
}
917+
dbg := false
918+
/*
919+
/////////////////////////////////////////////////////////////
920+
if mv.fr() == D4 && mv.to() == F4 {
921+
dbg = true
922+
}
923+
/////////////////////////////////////////////////////////////
924+
*/
925+
count := perft(dbg, depth-1, 1, bd)
926+
totCount += count
927+
fmt.Printf("%2d: %v \t%v \t%v\n", ix+1, mv.String(), count, msg)
928+
929+
bd.unmove(mv)
930+
ix++
931+
}
932+
fmt.Println("------------------")
933+
fmt.Println()
934+
fmt.Printf("Total:\t%v\n", totCount)
935+
return totCount
936+
}
937+
938+
func perft(dbg bool, depth, ply int, bd *boardStruct) uint64 {
939+
if depth == 0 {
940+
return 1
941+
}
942+
943+
transMove := noMove
944+
transMove, _, _, _ = trans.retrieve(bd.fullKey(), depth, ply)
945+
ix := 0
946+
count := uint64(0)
947+
var genInfo = genInfoStruct{sv: 0, ply: ply, transMove: transMove}
948+
next = nextNormal
949+
for mv, msg := next(&genInfo, bd); mv != noMove; mv, msg = next(&genInfo, bd) {
950+
if !bd.move(mv) {
951+
continue
952+
}
953+
_ = msg
954+
deb := false
955+
/*
956+
////////////////////////////////////////////////////////////////
957+
if dbg && mv.fr() == F5 && mv.to() == F4 {
958+
deb = true
959+
}
960+
if dbg && mv.fr() == E2 && mv.to() == E4 {
961+
deb = true
962+
}
963+
////////////////////////////////////////////////////////////////
964+
*/
965+
cnt := perft(deb, depth-1, ply+1, bd)
966+
count += cnt
967+
/*
968+
/////////////////////////////////////////////
969+
if dbg && !deb {
970+
fmt.Println(ix+1, ":(e4) ", mv.String(), msg, "\t", cnt)
971+
if ix==1{
972+
fmt.Println("K1",killers[ply].k1.StringFull())
973+
fmt.Println("K2",killers[ply].k2.StringFull())
974+
}
975+
}
976+
////////////////////////////////////////////
977+
*/
978+
bd.unmove(mv)
979+
ix++
980+
}
981+
982+
return count
983+
}

moves.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,12 @@ func (m move) cmpFrTo(m2 move) bool {
7070
return m.fr() == m2.fr() && m.to() == m2.to()
7171
}
7272

73+
// compare two moves - only frSq, toSq and pc
74+
func (m move) cmpFrToP(m2 move) bool {
75+
// return (m & move(^evalMask)) == (m2 & move(^evalMask))
76+
return m.fr() == m2.fr() && m.to() == m2.to() && m.pc() == m2.pc()
77+
}
78+
7379
// compare two moves
7480
func (m move) cmp(m2 move) bool {
7581
return (m & move(^evalMask)) == (m2 & move(^evalMask))

0 commit comments

Comments
 (0)