Search code examples
gohashmapbitboard

How can I optimize this transposition table for connect 4 AI?


The transposition table uses keys from the position of player 0 and player 1 from bitboards and includes the player turn at the end, I did that to guarantee unique keys for all states. But this seems to be slow because of fmt.Sprintf.

I have two functions where I store and retrieve, and in each of them I build the key and do a check or store.

type TTFlag int64

const (
    Exact      TTFlag = 0
    UpperBound TTFlag = 1
    LowerBound TTFlag = 2
)

type TTEntry struct {
    BestScore float64
    BestMove  int
    Flag      TTFlag
    Depth     int
    IsValid   bool
}

type Solver struct {
    NodeVisitCounter int
    TTMapHitCounter  int
    TTMap            map[string]TTEntry
}

func NewSolver() *Solver {
    return &Solver{
        NodeVisitCounter: 0,
        TTMapHitCounter:  0,
        TTMap:            make(map[string]TTEntry),
    }
}

func (s *Solver) StoreEntry(p *Position, entry TTEntry) {
    key := fmt.Sprintf("%d:%d:%d", p.Bitboard[0], p.Bitboard[1], p.PlayerTurn)
    s.TTMap[key] = entry
}

func (s *Solver) RetrieveEntry(p *Position) TTEntry {
    key := fmt.Sprintf("%d:%d:%d", p.Bitboard[0], p.Bitboard[1], p.PlayerTurn)
    if entry, exists := s.TTMap[key]; exists {
        s.TTMapHitCounter++
        return entry
    }
    return TTEntry{}
}

What can I do to optimize this further? How can I make better keys without the use of fmt.Sprintf and string concatenation?

Edit:

The Position struct is keeps track of game state:

type Position struct {
    Bitboard      [2]uint64
    PlayerTurn    int
    HeightBB      [7]int
    NumberOfMoves int
}

HeightBB is an array that keeps track of the height of the columns, so we know where to put the piece in the bitmap.


Solution

  • If you pay attention to the design of your data structures then I don't see any need for fmt.Sprintf.

    For example,

    type TTKey struct {
        Bitboard   [2]uint64
        PlayerTurn int
    }
    
    type TTFlag int64
    
    type TTEntry struct {
        BestScore float64
        BestMove  int
        Flag      TTFlag
        Depth     int
        IsValid   bool
    }
    
    type Solver struct {
        NodeVisitCounter int
        TTMapHitCounter  int
        TTMap            map[TTKey]TTEntry
    }
    
    type Position struct {
        Key           TTKey
        HeightBB      [7]int
        NumberOfMoves int
    }
    
    func (s *Solver) StoreEntry(p *Position, entry TTEntry) {
        s.TTMap[p.Key] = entry
    }
    
    func (s *Solver) RetrieveEntry(p *Position) TTEntry {
        if entry, exists := s.TTMap[p.Key]; exists {
            s.TTMapHitCounter++
            return entry
        }
        return TTEntry{}
    }