// Roughly orders moves in order of most likely to be good to least. // Examines all checks first, followed by captures, followed by good moves. // "Good moves" are sorted by their board evaluation after they are played. // If quiescence is set to true, then only checks and captures are returned. func orderedMoves(b *engine.Board, quiescence bool) []*engine.Move { checks := make([]*engine.Move, 0) captures := make([]*engine.Move, 0) rest := make([]*engine.Move, 0) // parentscore := EvalBoard(b) for _, move := range b.AllLegalMoves() { b.ForceMove(move) if b.IsCheck(b.Turn) { checks = append(checks, move) } else if move.Capture != 0 { captures = append(captures, move) } else if !quiescence { childscore := EvalBoard(b) * float64(b.Turn*-1) // if (b.Turn == -1 && childscore > parentscore) || (b.Turn == 1 && childscore < parentscore) { move.Score = childscore rest = append(rest, move) // } } b.UndoMove(move) } if !quiescence { sort.Sort(sort.Reverse(ByScore(rest))) } orderedmoves := make([]*engine.Move, len(checks)+len(captures)+len(rest)) index := 0 for _, l := range [][]*engine.Move{checks, captures, rest} { for _, m := range l { m.Score = 0 orderedmoves[index] = m index++ } } return orderedmoves }
// First-level NegaScout search function. // When called, alpha and beta should be set to the lowest and highest values possible. func NegaScout(b *engine.Board, depth int, alpha, beta float64) *engine.Move { if b.IsOver() != 0 || depth == 0 { b.Lastmove.Score = EvalBoard(b) return &b.Lastmove } var move engine.Move // not indended for actual use orderedmoves := b.AllLegalMoves() var score float64 for i, m := range orderedmoves { childboard := b.CopyBoard() childboard.Move(m) if i != 0 { score = -NegaScoutChild(childboard, depth-1, -alpha-1, -alpha) if alpha < score && score < beta { score = -NegaScoutChild(childboard, depth-1, -beta, -alpha) } } else { score = -NegaScoutChild(childboard, depth-1, -beta, -alpha) } alpha = math.Max(alpha, score) if alpha >= beta { move = *m.CopyMove() move.Score = alpha break } } return &move }
// Child level NegaScout search function. // Unlike its parent, only returns score and not full move func NegaScoutChild(b *engine.Board, depth int, alpha, beta float64) float64 { if b.IsOver() != 0 || depth == 0 { return EvalBoard(b) } // not indended for actual use orderedmoves := b.AllLegalMoves() var score float64 for i, m := range orderedmoves { childboard := b.CopyBoard() childboard.Move(m) if i != 0 { score = -NegaScoutChild(childboard, depth-1, -alpha-1, -alpha) if alpha < score && score < beta { score = -NegaScoutChild(childboard, depth-1, -beta, -alpha) } } else { score = -NegaScoutChild(childboard, depth-1, -beta, -alpha) } alpha = math.Max(alpha, score) if alpha >= beta { break } } return alpha }
// First-level negamax search function. func NegaMax(b *engine.Board, depth int) *engine.Move { if b.IsOver() != 0 || depth == 0 { b.Lastmove.Score = EvalBoard(b) return &b.Lastmove } var move engine.Move move.Score = LOSS for _, m := range b.AllLegalMoves() { childboard := b.CopyBoard() childboard.Move(m) childscore := -NegaMaxChild(childboard, depth-1) if childscore > move.Score { move = *m.CopyMove() move.Score = childscore if move.Score == WIN { return &move } } } return &move }
// Standard minmax search with alpha beta pruning. // Initial call: alpha set to lowest value, beta set to highest. // Top level returns a move. func AlphaBeta(b *engine.Board, depth int, alpha, beta float64) *engine.Move { if b.IsOver() != 0 || depth == 0 { return nil } var bestmove *engine.Move = nil var result float64 movelist := orderedMoves(b, false) if b.Turn == 1 { for _, move := range movelist { b.ForceMove(move) if move.Capture != 0 || b.IsCheck(b.Turn) { result = AlphaBetaChild(b, depth-1, alpha, beta, true) } else { result = AlphaBetaChild(b, depth-1, alpha, beta, false) } b.UndoMove(move) if result > alpha { alpha = result bestmove = move bestmove.Score = alpha } if alpha >= beta { bestmove = move bestmove.Score = alpha return bestmove } } if bestmove == nil { return b.AllLegalMoves()[0] } return bestmove } else { for _, move := range movelist { b.ForceMove(move) if move.Capture != 0 || b.IsCheck(b.Turn) { result = AlphaBetaChild(b, depth-1, alpha, beta, true) } else { result = AlphaBetaChild(b, depth-1, alpha, beta, false) } if LOG { fmt.Println(move.ToString(), result) } b.UndoMove(move) if result < beta { beta = result bestmove = move bestmove.Score = beta } if beta <= alpha { bestmove = move bestmove.Score = beta return bestmove } } if bestmove == nil { return b.AllLegalMoves()[0] } return bestmove } if bestmove == nil { return b.AllLegalMoves()[0] } return bestmove }