// Accepts a string such as "pe2-e4" and converts it to the Move struct. func stringToMove(s string) *engine.Move { var move engine.Move move.Begin = stringToSquare(s[1:3]) move.End = stringToSquare(s[4:]) move.Piece = s[0] return &move }
// 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 }
// Intended to run as a goroutine. // Keeps track of the state of a single game, recieving and sending moves through the appropriate channel. func game() { board := &engine.Board{Turn: 1} board.SetUpPieces() url := fmt.Sprintf("http://localhost%s", PORT) cmd := exec.Command("open", url) if _, err := cmd.Output(); err != nil { panic(err) } rand.Seed(time.Now().UTC().UnixNano()) for { select { case oppmove := <-incmoves: for _, p := range board.Board { if p.Position.X == oppmove.Begin.X && p.Position.Y == oppmove.Begin.Y { oppmove.Piece = p.Name break } } board.ForceMove(oppmove) if LOG { fmt.Println(oppmove.ToString()) board.PrintBoard() } var mymove *engine.Move if moves, ok := search.Book[board.ToFen()]; ok { mymove = stringToMove(moves[rand.Intn(len(moves))]) } else { if m := search.AlphaBeta(board, 4, search.BLACKWIN, search.WHITEWIN); m != nil { mymove = m } else { quit <- 1 break } } board.ForceMove(mymove) outmoves <- mymove if LOG { fmt.Println(mymove.ToString()) board.PrintBoard() } case <-quit: board.SetUpPieces() board.Turn = 1 } } }
// 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 }