diff --git a/engine/move.go b/engine/move.go index dad737e..5d844d8 100644 --- a/engine/move.go +++ b/engine/move.go @@ -5,8 +5,9 @@ package engine 6-11: to square 12-13: promotion piece (knight, bishop, rook, queen) 14-15: castling (01), en passant (10), promotion (11) +16+: move score */ -type Move uint16 +type Move uint32 const ( NoneFlag = iota << 14 @@ -79,6 +80,15 @@ func (m Move) Type() int { return int(m & PromotionFlag) } +func (m Move) Score() int { + return int(m&0xffff0000) >> 16 +} + +func (m *Move) GiveScore(score int) { + *m &= 0xffff + *m |= Move(score << 16) +} + func (m Move) ToString() string { if m.IsPromotion() { return m.From().ToString() + m.To().ToString() + string(PieceToChar[m.Promotion()]) diff --git a/engine/move_test.go b/engine/move_test.go new file mode 100644 index 0000000..45b4b09 --- /dev/null +++ b/engine/move_test.go @@ -0,0 +1,20 @@ +package engine_test + +import ( + "silverfish/engine" + "testing" +) + +// rn2kb1r/pp3ppp/2p1pn2/3p3b/8/1P1P1NPP/PBPqPPB1/RN2K2R w KQkq - 0 9 + +func TestGiveScore(t *testing.T) { + move1 := engine.NewMoveFromStr("g2d2") + move2 := move1 + move2.GiveScore(100) + if move2.Score() != 100 { + t.Errorf(`TestGiveScore: "Score": expected %d, got %d`, 100, move2.Score()) + } + if move1.To() != move2.To() { + t.Errorf(`TestGiveScore: "To": expected %d, got %d`, move1.To(), move2.To()) + } +} diff --git a/engine/search.go b/engine/search.go index b92782d..59eb7f1 100644 --- a/engine/search.go +++ b/engine/search.go @@ -28,6 +28,47 @@ func TimeLimit(pos *Position, command *UciGoMessage) time.Duration { return min(MaxMovetime, time.Duration(ourTime/int32(estimatedMovesLeft)+ourInc/4)) } +var MvvLva = [7][7]int{ + {0, 0, 0, 0, 0, 0, 0}, // victim K, attacker K, Q, R, B, N, P, None + {50, 51, 52, 53, 54, 55, 0}, // victim Q, attacker K, Q, R, B, N, P, None + {40, 41, 42, 43, 44, 45, 0}, // victim R, attacker K, Q, R, B, N, P, None + {30, 31, 32, 33, 34, 35, 0}, // victim B, attacker K, Q, R, B, N, P, None + {20, 21, 22, 23, 24, 25, 0}, // victim N, attacker K, Q, R, B, N, P, None + {10, 11, 12, 13, 14, 15, 0}, // victim P, attacker K, Q, R, B, N, P, None + {0, 0, 0, 0, 0, 0, 0}, // victim None, attacker K, Q, R, B, N, P, None +} + +func ScoreMoves(pos *Position, moveList *MoveList) { + for i := 0; i < int(moveList.Count); i++ { + move := &moveList.Moves[i] + _, victim := pos.GetSquare(move.From()) + _, attacker := pos.GetSquare(move.To()) + value := MvvLva[victim][attacker] + move.GiveScore(value) + } +} + +// swap the highest score move to the front, leaving everything else untouched +func OrderMoves(pos *Position, moveList *MoveList) { + if moveList.Count <= 1 { + return + } + + bestIdx := 0 + bestScore := moveList.Moves[0].Score() + + for j := 1; j < int(moveList.Count); j++ { + if moveList.Moves[j].Score() > bestScore { + bestIdx = j + bestScore = moveList.Moves[j].Score() + } + } + + if bestIdx != 0 { + moveList.Moves[0], moveList.Moves[bestIdx] = moveList.Moves[bestIdx], moveList.Moves[0] + } +} + // based on negamax (flip sign), each player maximizes their own score // alpha: best score guaranteed for max-player. can prune branches that give less than this // beta: upper limit that min-player will tolerate. min-player will prune lines exceeding this @@ -40,11 +81,9 @@ func Search(pos *Position, maxDepth int, timeLimit time.Duration) (int32, Move) bestScore := -Infinity moveList := GenMoves(pos, BB_Full) - nodes := 0 for depth := 1; depth <= maxDepth; depth++ { - alpha := -Infinity beta := Infinity @@ -115,6 +154,9 @@ func Quiescence(pos *Position, alpha, beta int32, nodes *int, startTime *time.Ti moveList = GenMoves(pos, pos.Sides[pos.Turn^1]) // only captures } + ScoreMoves(pos, &moveList) + OrderMoves(pos, &moveList) + for i := uint8(0); i < moveList.Count; i++ { move := moveList.Moves[i] if !pos.MoveIsLegal(move) { @@ -140,6 +182,8 @@ func Quiescence(pos *Position, alpha, beta int32, nodes *int, startTime *time.Ti func alphaBetaInner(pos *Position, alpha, beta int32, depth int, nodes *int, startTime *time.Time, timeLimit *time.Duration) int32 { moveList := GenMoves(pos, BB_Full) + ScoreMoves(pos, &moveList) + OrderMoves(pos, &moveList) if moveList.Count == 0 { if pos.Checkers(pos.Turn) != 0 {