Skip to content

Commit

Permalink
Implementing Parallel search algorithm ABDADA #95
Browse files Browse the repository at this point in the history
New TranspositionTableEntry type, concurrent read for ABDADA
  • Loading branch information
Vadman97 committed Apr 17, 2019
1 parent b03452e commit f994634
Show file tree
Hide file tree
Showing 6 changed files with 246 additions and 76 deletions.
98 changes: 98 additions & 0 deletions pkg/chessai/player/ai/abdada.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package ai

import (
"fmt"
"github.com/Vadman97/ChessAI3/pkg/chessai/board"
"github.com/Vadman97/ChessAI3/pkg/chessai/color"
"github.com/Vadman97/ChessAI3/pkg/chessai/location"
"github.com/Vadman97/ChessAI3/pkg/chessai/transposition_table"
"time"
)

func (ab *ABDADA) ABDADA(root *board.Board, depth, alpha, beta int, exclusiveProbe bool, currentPlayer color.Color, previousMove *board.LastMove) *ScoredMove {
if depth == 0 {
return &ScoredMove{
Score: ab.player.EvaluateBoard(root, ab.player.PlayerColor).TotalScore,
}
} else {
var best ScoredMove

answerChan := ab.asyncTTRead(root, uint16(depth), alpha, beta, currentPlayer, exclusiveProbe)
// generate moves while waiting for the answer ...
moves := root.GetAllMoves(currentPlayer, previousMove)

// block and grab the answer
ttAnswer := <-answerChan

}
}

type ABDADA struct {
player *AIPlayer
currentSearchDepth int
lastSearchDepth int
lastSearchTime time.Duration
}

func (ab *ABDADA) GetName() string {
return fmt.Sprintf("%s,[D:%d;T:%s]", AlgorithmABDADA, ab.lastSearchDepth, ab.lastSearchTime)
}

func (ab *ABDADA) GetBestMove(p *AIPlayer, b *board.Board, previousMove *board.LastMove) *ScoredMove {
ab.player = p
}

type TTAnswer struct {
alpha, beta, score int
bestMove location.Move
onEvaluation bool
}

func (ab *ABDADA) asyncTTRead(root *board.Board, depth uint16, alpha, beta int, currentPlayer color.Color, exclusiveProbe bool) chan TTAnswer {
answerChan := make(chan TTAnswer)
go func(answerChan chan TTAnswer) {
answer := TTAnswer{
alpha: alpha,
beta: beta,
score: NegInf,
}
if ab.player.TranspositionTableEnabled {
// transposition table lookup
h := root.Hash()
if e, ok := ab.player.alphaBetaTable.Read(&h, currentPlayer); ok {
entry := e.(*transposition_table.TranspositionTableEntryABDADA)
entry.Lock.Lock()

if entry.Depth == depth && exclusiveProbe && entry.NumProcessors > 0 {
answer.onEvaluation = true
} else if entry.Depth >= depth {
if entry.EntryType == transposition_table.TrueScore {
answer.score = entry.Score
answer.alpha = entry.Score
answer.beta = entry.Score
} else if entry.EntryType == transposition_table.UpperBound && entry.Score < beta {
answer.score = entry.Score
answer.beta = entry.Score
} else if entry.EntryType == transposition_table.LowerBound && entry.Score > alpha {
answer.score = entry.Score
answer.alpha = entry.Score
}

if entry.Depth == depth && answer.alpha < answer.beta {
// Increment the number of processors evaluating this node
entry.NumProcessors++
}
} else {
// This is the first processor to evaluate this node
entry.Depth = depth
entry.EntryType = transposition_table.Unset
entry.NumProcessors = 1
}

entry.Lock.Unlock()
}
}
answerChan <- answer
}(answerChan)
return answerChan
}
10 changes: 6 additions & 4 deletions pkg/chessai/player/ai/ai_player.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/Vadman97/ChessAI3/pkg/chessai/color"
"github.com/Vadman97/ChessAI3/pkg/chessai/config"
"github.com/Vadman97/ChessAI3/pkg/chessai/location"
"github.com/Vadman97/ChessAI3/pkg/chessai/transposition_table"
"github.com/Vadman97/ChessAI3/pkg/chessai/util"
"log"
"math"
Expand All @@ -21,8 +22,9 @@ const (

const (
AlgorithmMiniMax = "MiniMax"
AlgorithmAlphaBetaWithMemory = "AlphaBetaMemory"
AlgorithmAlphaBetaWithMemory = "α/β Memory"
AlgorithmMTDf = "MTDf"
AlgorithmABDADA = "ABDADA (α/β Parallel)"
AlgorithmRandom = "Random"
)

Expand Down Expand Up @@ -86,7 +88,7 @@ type AIPlayer struct {
Debug bool
PrintInfo bool
evaluationMap *util.ConcurrentBoardMap
alphaBetaTable *util.TranspositionTable
alphaBetaTable *transposition_table.TranspositionTable
printer chan string
}

Expand All @@ -101,7 +103,7 @@ func NewAIPlayer(c byte, algorithm Algorithm) *AIPlayer {
Debug: config.Get().LogDebug,
PrintInfo: config.Get().PrintPlayerInfo,
evaluationMap: util.NewConcurrentBoardMap(),
alphaBetaTable: util.NewTranspositionTable(),
alphaBetaTable: transposition_table.NewTranspositionTable(),
printer: make(chan string, 1000000),
}
if config.Get().UseOpenings {
Expand Down Expand Up @@ -203,7 +205,7 @@ func (p *AIPlayer) printMoveDebug(b *board.Board, m *ScoredMove) {
func (p *AIPlayer) ClearCaches() {
// TODO(Vadim) find better way to pick when to clear, based on size #49
p.evaluationMap = util.NewConcurrentBoardMap()
p.alphaBetaTable = util.NewTranspositionTable()
p.alphaBetaTable = transposition_table.NewTranspositionTable()
}

func (p *AIPlayer) printThread(stop chan bool) {
Expand Down
30 changes: 16 additions & 14 deletions pkg/chessai/player/ai/alpha_beta.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package ai
import (
"fmt"
"github.com/Vadman97/ChessAI3/pkg/chessai/board"
"github.com/Vadman97/ChessAI3/pkg/chessai/transposition_table"
"github.com/Vadman97/ChessAI3/pkg/chessai/util"
)

Expand Down Expand Up @@ -38,27 +39,28 @@ func (ab *AlphaBetaWithMemory) AlphaBetaWithMemory(root *board.Board, depth, alp
// transposition table lookup
h = root.Hash()
if entry, ok := ab.player.alphaBetaTable.Read(&h, currentPlayer); ok {
if entry.Lower > NegInf && entry.Lower >= beta {
abEntry := entry.(*transposition_table.TranspositionTableEntryABMemory)
if abEntry.Lower > NegInf && abEntry.Lower >= beta {
ab.player.Metrics.MovesPrunedTransposition++
return &ScoredMove{
Score: entry.Lower,
Move: entry.BestMove,
Score: abEntry.Lower,
Move: abEntry.BestMove,
}
} else if entry.Upper < PosInf && entry.Upper <= alpha {
} else if abEntry.Upper < PosInf && abEntry.Upper <= alpha {
ab.player.Metrics.MovesPrunedTransposition++
return &ScoredMove{
Score: entry.Upper,
Move: entry.BestMove,
Score: abEntry.Upper,
Move: abEntry.BestMove,
}
}
if entry.Lower > NegInf && entry.Lower > alpha {
if abEntry.Lower > NegInf && abEntry.Lower > alpha {
ab.player.Metrics.MovesABImprovedTransposition++
alpha = entry.Lower
// TODO(Vadim) first in for loop of moves try entry.BestMove, same in other else
alpha = abEntry.Lower
// TODO(Vadim) first in for loop of moves try abEntry.BestMove, same in other else
}
if entry.Upper < PosInf && entry.Upper < beta {
if abEntry.Upper < PosInf && abEntry.Upper < beta {
ab.player.Metrics.MovesABImprovedTransposition++
beta = entry.Upper
beta = abEntry.Upper
}
}
}
Expand Down Expand Up @@ -117,19 +119,19 @@ func (ab *AlphaBetaWithMemory) AlphaBetaWithMemory(root *board.Board, depth, alp

if !ab.abort && ab.player.TranspositionTableEnabled {
if best.Score >= beta {
ab.player.alphaBetaTable.Store(&h, currentPlayer, &util.TranspositionTableEntry{
ab.player.alphaBetaTable.Store(&h, currentPlayer, &transposition_table.TranspositionTableEntryABMemory{
Lower: best.Score,
Upper: PosInf,
BestMove: best.Move,
})
} else if best.Score > alpha && best.Score < beta {
ab.player.alphaBetaTable.Store(&h, currentPlayer, &util.TranspositionTableEntry{
ab.player.alphaBetaTable.Store(&h, currentPlayer, &transposition_table.TranspositionTableEntryABMemory{
Lower: best.Score,
Upper: best.Score,
BestMove: best.Move,
})
} else if best.Score <= alpha {
ab.player.alphaBetaTable.Store(&h, currentPlayer, &util.TranspositionTableEntry{
ab.player.alphaBetaTable.Store(&h, currentPlayer, &transposition_table.TranspositionTableEntryABMemory{
Lower: NegInf,
Upper: best.Score,
BestMove: best.Move,
Expand Down
83 changes: 83 additions & 0 deletions pkg/chessai/transposition_table/transposition_table.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package transposition_table

import (
"fmt"
"github.com/Vadman97/ChessAI3/pkg/chessai/location"
"github.com/Vadman97/ChessAI3/pkg/chessai/util"
"sync"
)

type TranspositionTableEntryABMemory struct {
Lower, Upper int
BestMove location.Move
}

const (
Unset = byte(iota)
UpperBound = byte(iota)
LowerBound = byte(iota)
TrueScore = byte(iota)
)

type TranspositionTableEntryABDADA struct {
Score int
BestMove location.Move

// Flag that identifies the entry
EntryType byte

// Length of subtree upon which score is based.
Depth uint16

// The number of processors currently evaluating the node related to the transposition table entry
NumProcessors uint16

Lock sync.Mutex
}

type TranspositionTable struct {
entryMap map[util.BoardHash]map[byte]interface{}
numStored int
numReads, numHits int
}

func NewTranspositionTable() *TranspositionTable {
var m TranspositionTable
if m.entryMap == nil {
m.entryMap = make(map[util.BoardHash]map[byte]interface{})
}
return &m
}

/*
Note: Transposition table does not support concurrent read/write at the moment
*/
func (m *TranspositionTable) Store(hash *util.BoardHash, currentTurn byte, entry interface{}) {
_, ok := m.entryMap[*hash]
if !ok {
m.entryMap[*hash] = make(map[byte]interface{})
}
m.entryMap[*hash][currentTurn] = entry
m.numStored++
}

func (m *TranspositionTable) Read(hash *util.BoardHash, currentTurn byte) (interface{}, bool) {
m.numReads++

m1, ok := m.entryMap[*hash]
if ok {
v, ok := m1[currentTurn]
if ok {
m.numHits++
return v, true
}
}
return nil, false
}

func (m *TranspositionTable) PrintMetrics() (result string) {
result += fmt.Sprintf("\tTotal entries in transposition table %d\n", m.numStored)
result += fmt.Sprintf("\tHit ratio %f%% (%d/%d)\n", 100.0*float64(m.numHits)/float64(m.numReads),
m.numHits, m.numReads)
return
}
43 changes: 43 additions & 0 deletions pkg/chessai/transposition_table/transposition_table_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package transposition_table

import (
"github.com/Vadman97/ChessAI3/pkg/chessai/board"
"github.com/Vadman97/ChessAI3/pkg/chessai/color"
"github.com/stretchr/testify/assert"
"testing"
)

func TestTranspositionTable_StoreUpdateReadABDADA(t *testing.T) {
tt := NewTranspositionTable()

b := board.Board{}
b.ResetDefault()
b.RandomizeIllegal()
h := b.Hash()

ttEntry := TranspositionTableEntryABDADA{}
tt.Store(&h, color.Black, &ttEntry)
ttEntry.NumProcessors++

assert.Equal(t, uint16(1), ttEntry.NumProcessors)

e, _ := tt.Read(&h, color.Black)
readEntry := e.(*TranspositionTableEntryABDADA)

assert.Equal(t, uint16(1), readEntry.NumProcessors)
readEntry.NumProcessors++

assert.Equal(t, uint16(2), readEntry.NumProcessors)
assert.Equal(t, uint16(2), ttEntry.NumProcessors)

newEntry := TranspositionTableEntryABDADA{}
tt.Store(&h, color.Black, &newEntry)

assert.Equal(t, uint16(2), readEntry.NumProcessors)
assert.Equal(t, uint16(2), ttEntry.NumProcessors)

e, _ = tt.Read(&h, color.Black)
readEntry = e.(*TranspositionTableEntryABDADA)

assert.Equal(t, uint16(0), readEntry.NumProcessors)
}
Loading

0 comments on commit f994634

Please sign in to comment.