Skip to content

Commit 2444d6c

Browse files
committed
Add Zobrist Hashing; repetition check
1 parent fb92100 commit 2444d6c

File tree

6 files changed

+156
-6
lines changed

6 files changed

+156
-6
lines changed

engine/board.go

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ type Board struct {
3737
whiteKingPosition Square
3838
blackKingPosition Square
3939
status int
40+
zobristTable *ZobristTable
41+
currentHash int64
4042
}
4143

4244
// NewBoard creates a new chessboard from given fen
@@ -47,6 +49,9 @@ func NewBoard(fen string) *Board {
4749
fmt.Printf("invalid FEN: \"%s\"\n", fen)
4850
}
4951

52+
b.zobristTable = NewZobristTable()
53+
b.currentHash = b.generateHash()
54+
5055
return b
5156
}
5257

@@ -163,10 +168,12 @@ func (b *Board) MakeMove(m Move) {
163168
}
164169

165170
b.sideToMove = opponent(b.sideToMove)
171+
b.ply++
166172

173+
historyItem.hash = b.currentHash
167174
b.history = append(b.history, historyItem)
168-
b.ply++
169175

176+
b.updateHash(m)
170177
}
171178

172179
// UndoMove undoes the last move on the board
@@ -187,6 +194,9 @@ func (b *Board) UndoMove() {
187194

188195
m := historyItem.move
189196

197+
// XXX: might to be done at the end?
198+
b.updateHash(m)
199+
190200
switch {
191201
case m.Special == moveOrdinary || m.Special == movePromotion:
192202
b.data[m.To] = m.Content
@@ -245,10 +255,75 @@ func (b *Board) isEmpty(squares ...Square) bool {
245255

246256
func (b *Board) repetitions() int {
247257
r := 0
248-
258+
first := len(b.history) - b.halfMoveClock
259+
if first >= 0 {
260+
for i := first; i < len(b.history)-1; i++ {
261+
if b.history[i].hash == b.currentHash {
262+
r++
263+
}
264+
}
265+
}
249266
return r
250267
}
251268

269+
func (b *Board) updateHash(m Move) {
270+
key := b.currentHash
271+
color := 0
272+
if b.sideToMove == Black {
273+
color = 1
274+
}
275+
piece := abs(m.MovedPiece) - 1
276+
277+
key ^= b.zobristTable.hashPieces[piece][color][int8(m.From)]
278+
key ^= b.zobristTable.hashPieces[piece][color][int8(m.To)]
279+
280+
if m.Content != Empty {
281+
key ^= b.zobristTable.hashPieces[abs(m.Content)-1][color][int8(m.To)]
282+
}
283+
284+
switch m.Special {
285+
case moveCastelingLong:
286+
if color == 0 {
287+
key ^= b.zobristTable.hashCastelingWhite[castleLong]
288+
} else {
289+
key ^= b.zobristTable.hashCastelingBlack[castleLong]
290+
}
291+
case moveCastelingShort:
292+
if color == 0 {
293+
key ^= b.zobristTable.hashCastelingWhite[castleShort]
294+
} else {
295+
key ^= b.zobristTable.hashCastelingBlack[castleLong]
296+
}
297+
case moveEnPassant:
298+
key ^= b.zobristTable.hashEnPassant[int8(m.To)-int8(m.MovedPiece)*nextRank]
299+
case movePromotion:
300+
key ^= b.zobristTable.hashPromotion[abs(m.Promoted)-1]
301+
}
302+
303+
b.currentHash = key
304+
}
305+
306+
func (b *Board) generateHash() int64 {
307+
key := int64(0)
308+
309+
for square := int8(0); square < boardSize; square++ {
310+
piece := b.data[square]
311+
if piece > Empty {
312+
key ^= b.zobristTable.hashPieces[piece-1][0][square]
313+
} else if piece < Empty {
314+
key ^= b.zobristTable.hashPieces[-piece-1][1][square]
315+
}
316+
}
317+
318+
key ^= b.zobristTable.hashCastelingWhite[b.whiteCastle]
319+
key ^= b.zobristTable.hashCastelingBlack[b.blackCastle]
320+
if b.sideToMove == Black {
321+
key ^= b.zobristTable.hashSide
322+
}
323+
324+
return key
325+
}
326+
252327
func (b *Board) String() string {
253328
files := " a b c d e f g h"
254329
str := fmt.Sprintf("%s\n", files)

engine/fen.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,15 @@ import (
99

1010
const (
1111
defaultFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
12+
1213
// defaultFEN = "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq -"
14+
1315
// defaultFEN = "k7/8/K7/8/7p/7P/8/8 w - - 0 1" // only kings
16+
// defaultFEN = "8/7K/5k2/7P/8/8/8/8 w - - 7 15" // repetitions
17+
// defaultFEN = "6k1/8/6KP/8/8/8/8/8 w - - 7 15" // repetitions
18+
19+
// defaultFEN = "r7/8/8/2k5/5K2/8/8/7R - - 49 1" // repetitions
20+
1421
// defaultFEN = "r1bqkb1r/ppp1pppp/1nnp4/4P3/2P5/2N2N2/PP1PQPPP/R1B1KB1R b KQkq - 1 6" // leads to a thrid rook for black
1522
// defaultFEN = "rnbqkbnr/3p1ppp/p3p3/1pp3B1/3PP3/2N2N2/PPP2PPP/R2QKB1R b KQkq - 1 5" // thinks it's check but isn't
1623
)

engine/hash.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package engine
2+
3+
import "math/rand"
4+
5+
const (
6+
numPieces = 6
7+
numColors = 2
8+
numCastelings = 4 // none, short, long, short&long
9+
)
10+
11+
type ZobristTable struct {
12+
hashPieces [numPieces][numColors][boardSize]int64
13+
hashEnPassant [boardSize]int64
14+
hashCastelingBlack [numCastelings]int64
15+
hashCastelingWhite [numCastelings]int64
16+
hashPromotion [numPieces]int64
17+
hashSide int64
18+
hashCurrent int64
19+
}
20+
21+
func NewZobristTable() *ZobristTable {
22+
23+
rand.Seed(4711)
24+
25+
z := ZobristTable{}
26+
27+
// pieces
28+
for piece := 0; piece < numPieces; piece++ {
29+
for color := 0; color < numColors; color++ {
30+
for square := int8(0); square < boardSize; square++ {
31+
z.hashPieces[piece][color][square] = hashRand()
32+
}
33+
}
34+
}
35+
// en passant
36+
for square := int8(0); square < boardSize; square++ {
37+
z.hashEnPassant[square] = hashRand()
38+
}
39+
// castling options
40+
for i := int8(0); i <= castleLong; i++ {
41+
z.hashCastelingBlack[i] = hashRand()
42+
z.hashCastelingWhite[i] = hashRand()
43+
}
44+
45+
// promotion pieces
46+
for piece := int8(0); piece < numPieces; piece++ {
47+
z.hashPromotion[piece] = hashRand()
48+
}
49+
50+
// side
51+
z.hashSide = hashRand()
52+
53+
return &z
54+
}
55+
56+
func hashRand() int64 {
57+
return rand.Int63()
58+
}

engine/move.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ type Move struct {
2929
}
3030

3131
func (m Move) String() string {
32-
capture := ""
3332

3433
if m.Special == moveCastelingLong {
3534
return "O-O-O"
@@ -38,11 +37,15 @@ func (m Move) String() string {
3837
return "O-O"
3938
}
4039

40+
str := SquareMap[m.From]
41+
4142
if m.Content != Empty {
42-
capture = "x"
43+
str += "x"
4344
}
4445

45-
return fmt.Sprintf("%s%s%s", SquareMap[m.From], capture, SquareMap[m.To])
46+
str += SquareMap[m.To]
47+
48+
return str
4649
}
4750

4851
func createMove(str string) (Move, error) {

engine/search.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ func (pv *pvSearch) alphaBeta(depth, alpha, beta int) int {
106106
depth++
107107
}
108108

109-
// TODO repetition
109+
// repetition
110110
if pv.board.ply > 0 && pv.board.repetitions() > 0 {
111111
return scoreDraw
112112
}

engine/util.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,10 @@ func formatNodesCount(nodes int64) string {
104104
}
105105
return fmt.Sprintf("%.2fM", float64(nodes)/1000000)
106106
}
107+
108+
func abs(v int8) int8 {
109+
if v >= 0 {
110+
return v
111+
}
112+
return -v
113+
}

0 commit comments

Comments
 (0)