We’re rewarding the question askers & reputations are being recalculated! Read more.
9 added 4047 characters in body
source | link

Finally, PublicAPI: I hate the whitespace there, the way you indented that.

Generally, if I have to multi-line things like that I line break and then start indentation.

let movePiece startCoord endCoord gameController :Option<GameController> =
    let board = movePiece startCoord endCoord gameController.Board
    match (isValidMove startCoord endCoord gameController) with
    | false -> None
    | true ->
        Some <|
        {
            Board = board.Value;
            CurrentPlayer =
                match playerTurnEnds [startCoord; endCoord] gameController.Board board.Value with
                | true -> otherPlayer gameController.CurrentPlayer
                | false -> gameController.CurrentPlayer        
            CurrentCoord =
                match playerTurnEnds [startCoord; endCoord] gameController.Board board.Value with
                | true -> None
                | false -> Some endCoord
        }

let move (move :Coord seq) (gameController) :Option<GameController> =
    let board = moveSequence move (Some gameController.Board)
    match board with
    | None -> None
    | Some b ->
        Some <|
        {
            Board = board.Value;
            CurrentPlayer =
                match playerTurnEnds (List.ofSeq move) gameController.Board board.Value with
                | true -> otherPlayer gameController.CurrentPlayer
                | false -> gameController.CurrentPlayer        
            CurrentCoord =
                match playerTurnEnds (List.ofSeq move) gameController.Board board.Value with
                | true -> None
                | false -> Some (Seq.last move)
        }

It's a lot easier to follow. I also tend to put the single-line match patterns at the top, where it allows, so that there's not one line underneath a massive block like that just hanging out.


Overall it was quite good, excepting the few idiomatic issues.


Finally, PublicAPI: I hate the whitespace there, the way you indented that.

Generally, if I have to multi-line things like that I line break and then start indentation.

let movePiece startCoord endCoord gameController :Option<GameController> =
    let board = movePiece startCoord endCoord gameController.Board
    match (isValidMove startCoord endCoord gameController) with
    | false -> None
    | true ->
        Some <|
        {
            Board = board.Value;
            CurrentPlayer =
                match playerTurnEnds [startCoord; endCoord] gameController.Board board.Value with
                | true -> otherPlayer gameController.CurrentPlayer
                | false -> gameController.CurrentPlayer        
            CurrentCoord =
                match playerTurnEnds [startCoord; endCoord] gameController.Board board.Value with
                | true -> None
                | false -> Some endCoord
        }

let move (move :Coord seq) (gameController) :Option<GameController> =
    let board = moveSequence move (Some gameController.Board)
    match board with
    | None -> None
    | Some b ->
        Some <|
        {
            Board = board.Value;
            CurrentPlayer =
                match playerTurnEnds (List.ofSeq move) gameController.Board board.Value with
                | true -> otherPlayer gameController.CurrentPlayer
                | false -> gameController.CurrentPlayer        
            CurrentCoord =
                match playerTurnEnds (List.ofSeq move) gameController.Board board.Value with
                | true -> None
                | false -> Some (Seq.last move)
        }

It's a lot easier to follow. I also tend to put the single-line match patterns at the top, where it allows, so that there's not one line underneath a massive block like that just hanging out.


Overall it was quite good, excepting the few idiomatic issues.

8 added 4047 characters in body
source | link

Do note I've not tested this at all yet, this was a rewrite in Notepad. Something like the following should work, though you've mentioned to me in chat, so I've updated it substantially.

let rec minimax player searchDepth alpha beta (board:Board) =
    match searchDepth = 0 || (isWon board).IsSome with
    | true ->
        let weightDiffweightDifference = Some <| calculateWeightDifference board
        let newAlpha = 
            match player with
            | Black -> weightDifference
            | _ -> alpha
        let newBeta =
            match player with
            | White -> weightDifference
            | _ -> beta
        { Alpha = newBeta; Beta = newAlpha; Move = [] }
    | false ->
        let moves = calculateMoves player board
        let rec loop alphaForNode betaForNode loopAlphanewAlpha loopBetanewBeta move moves =
            ifmatch List.isEmpty moves thenwith
             | true -> { Alpha = betaForNode; Beta = alphaForNode; Move = move }
            | false ->
            if loopAlpha   let currentMove = moves |> List.head

                match newAlpha.IsNone || loopBetanewBeta.IsNone || loopAlphanewAlpha.Value < loopBetanewBeta.Value thenwith
                | false -> loop alphaForNode betaForNode newAlpha newBeta move (moves |> List.tail)
                | true ->
                    let newBoard = uncheckedMoveSequence xcurrentMove board
                    let alphaBetaMove = minimax (otherPlayer player) (searchDepth - 1) alphaForNode betaForNode newBoard

                    match player with
                    | Black ->
                        let newAlphaForNode = chooseNewAlpha alphaForNode alphaBetaMove.Alpha
                        let newLoopAlphanewNewAlpha = choseNewAlpha loopAlphanewAlpha newAlphaForNode
                        let newMove = 
                            match newAlphanewNewAlpha with
                            | a when a = alphaBetaMove.Alpha -> acurrentMove
                            | _ -> move
                        loop newAlphaForNode betaForNode newLoopAlphanewNewAlpha loopBetanewBeta newMove (moves |> List.tail)
                    | White ->
                        let newBetaForNode = chooseNewBeta betaForNode alphaBetaMove.Beta
                        let newLoopBetanewNewBeta = chooseNewBeta loopBetanewBeta newBetaForNode
                        let newMove =
                            match newBetanewNewBeta with
                            | b when b = alphaBetaMove.Beta -> bcurrentMove
                            | _ -> move
                        loop alphaForNode newBetaForNode loopAlphanewAlpha newLoopBetanewNewBeta newMove (moves |> List.tail)
            else
                loop alphaForNode betaForNode loopAlphalet loopBetamoves move= (movescalculateMoves |>player List.tail)board
        loop None None alpha beta [] moves

 

Of course that's huge and does a lot of stuff. We obviously want to break it down.

I'll haveSo we'll extract our match player with to use some methods, function composition, etc.

It's still long, but it's slightly more coming shortlymaintainable.

let rec minimax player searchDepth alpha beta (board:Board) =
    match searchDepth = 0 || (isWon board).IsSome with
    | true ->
        let weightDifference = Some <| calculateWeightDifference board
        let newAlpha = 
            match player with
            | Black -> weightDifference
            | _ -> alpha
        let newBeta =
            match player with
            | White -> weightDifference
            | _ -> beta
        { Alpha = newBeta; Beta = newAlpha; Move = [] }
    | false ->
        let getNewValueAndMove chooseMethod nodeValue moveValue currentValue currentMove newMove =
            let newNodeValue = chooseMethod nodeValue moveValue
            let newValue = chooseMethod currentValue newNodeValue
            let finalMove = 
                match newValue with
                | a when a = moveValue -> currentMove
                | _ -> newMove
            (newNodeValue, newValue, finalMove)

        let rec loop alphaForNode betaForNode newAlpha newBeta move moves =
            match List.isEmpty moves with
            | true -> { Alpha = betaForNode; Beta = alphaForNode; Move = move }
            | false ->
                let currentMove = moves |> List.head

                match newAlpha.IsNone || newBeta.IsNone || newAlpha.Value < newBeta.Value with
                | false -> loop alphaForNode betaForNode newAlpha newBeta move (moves |> List.tail)
                | true ->
                    let newBoard = uncheckedMoveSequence currentMove board
                    let alphaBetaMove = minimax (otherPlayer player) (searchDepth - 1) alphaForNode betaForNode newBoard

                    match player with
                    | Black ->
                        let (newAlphaForNode, newNewAlpha, newMove) = getNewValueAndMove chooseNewAlpha alphaForNode alphaBetaMove.Alpha newAlpha currentMove move
                        loop newAlphaForNode betaForNode newNewAlpha newBeta newMove (moves |> List.tail)
                    | White ->
                        let (newBetaForNode, newNewBeta, newMove) = getNewValueAndMove chooseNewBeta betaForNode alphaBetaMove.Beta newBeta currentMove move
                        loop alphaForNode newBetaForNode newAlpha newNewBeta newMove (moves |> List.tail)
        loop None None alpha beta [] (calculateMoves player board)

Do note I've not tested this at all yet, this was a rewrite in Notepad.

let rec minimax player searchDepth alpha beta (board:Board) =
    match searchDepth = 0 || (isWon board).IsSome with
    | true ->
        let weightDiff = Some <| calculateWeightDifference board
        let newAlpha = 
            match player with
            | Black -> weightDifference
            | _ -> alpha
        let newBeta =
            match player with
            | White -> weightDifference
            | _ -> beta
        { Alpha = newBeta; Beta = newAlpha; Move = [] }
    | false ->
        let moves = calculateMoves player board
        let rec loop alphaForNode betaForNode loopAlpha loopBeta move moves =
            if List.isEmpty moves then
                { Alpha = betaForNode; Beta = alphaForNode; Move = move }

            if loopAlpha.IsNone || loopBeta.IsNone || loopAlpha.Value < loopBeta.Value then
                let newBoard = uncheckedMoveSequence x board
                let alphaBetaMove = minimax (otherPlayer player) (searchDepth - 1) alphaForNode betaForNode newBoard

                match player with
                | Black ->
                    let newAlphaForNode = chooseNewAlpha alphaForNode alphaBetaMove.Alpha
                    let newLoopAlpha = choseNewAlpha loopAlpha newAlphaForNode
                    let newMove = 
                        match newAlpha with
                        | a when a = alphaBetaMove.Alpha -> a
                        | _ -> move
                    loop newAlphaForNode betaForNode newLoopAlpha loopBeta newMove (moves |> List.tail)
                | White ->
                    let newBetaForNode = chooseNewBeta betaForNode alphaBetaMove.Beta
                    let newLoopBeta = chooseNewBeta loopBeta newBetaForNode
                    let newMove =
                        match newBeta with
                        | b when b = alphaBetaMove.Beta -> b
                        | _ -> move
                    loop alphaForNode newBetaForNode loopAlpha newLoopBeta newMove (moves |> List.tail)
            else
                loop alphaForNode betaForNode loopAlpha loopBeta move (moves |> List.tail)
        loop None None alpha beta [] moves

 

I'll have more coming shortly.

Do note I've not tested this at all yet, this was a rewrite in Notepad. Something like the following should work, though you've mentioned to me in chat, so I've updated it substantially.

let rec minimax player searchDepth alpha beta (board:Board) =
    match searchDepth = 0 || (isWon board).IsSome with
    | true ->
        let weightDifference = Some <| calculateWeightDifference board
        let newAlpha = 
            match player with
            | Black -> weightDifference
            | _ -> alpha
        let newBeta =
            match player with
            | White -> weightDifference
            | _ -> beta
        { Alpha = newBeta; Beta = newAlpha; Move = [] }
    | false ->
        let rec loop alphaForNode betaForNode newAlpha newBeta move moves =
            match List.isEmpty moves with
            | true -> { Alpha = betaForNode; Beta = alphaForNode; Move = move }
            | false ->
                let currentMove = moves |> List.head

                match newAlpha.IsNone || newBeta.IsNone || newAlpha.Value < newBeta.Value with
                | false -> loop alphaForNode betaForNode newAlpha newBeta move (moves |> List.tail)
                | true ->
                    let newBoard = uncheckedMoveSequence currentMove board
                    let alphaBetaMove = minimax (otherPlayer player) (searchDepth - 1) alphaForNode betaForNode newBoard

                    match player with
                    | Black ->
                        let newAlphaForNode = chooseNewAlpha alphaForNode alphaBetaMove.Alpha
                        let newNewAlpha = choseNewAlpha newAlpha newAlphaForNode
                        let newMove = 
                            match newNewAlpha with
                            | a when a = alphaBetaMove.Alpha -> currentMove
                            | _ -> move
                        loop newAlphaForNode betaForNode newNewAlpha newBeta newMove (moves |> List.tail)
                    | White ->
                        let newBetaForNode = chooseNewBeta betaForNode alphaBetaMove.Beta
                        let newNewBeta = chooseNewBeta newBeta newBetaForNode
                        let newMove =
                            match newNewBeta with
                            | b when b = alphaBetaMove.Beta -> currentMove
                            | _ -> move
                        loop alphaForNode newBetaForNode newAlpha newNewBeta newMove (moves |> List.tail)
        let moves = calculateMoves player board
        loop None None alpha beta [] moves

Of course that's huge and does a lot of stuff. We obviously want to break it down.

So we'll extract our match player with to use some methods, function composition, etc.

It's still long, but it's slightly more maintainable.

let rec minimax player searchDepth alpha beta (board:Board) =
    match searchDepth = 0 || (isWon board).IsSome with
    | true ->
        let weightDifference = Some <| calculateWeightDifference board
        let newAlpha = 
            match player with
            | Black -> weightDifference
            | _ -> alpha
        let newBeta =
            match player with
            | White -> weightDifference
            | _ -> beta
        { Alpha = newBeta; Beta = newAlpha; Move = [] }
    | false ->
        let getNewValueAndMove chooseMethod nodeValue moveValue currentValue currentMove newMove =
            let newNodeValue = chooseMethod nodeValue moveValue
            let newValue = chooseMethod currentValue newNodeValue
            let finalMove = 
                match newValue with
                | a when a = moveValue -> currentMove
                | _ -> newMove
            (newNodeValue, newValue, finalMove)

        let rec loop alphaForNode betaForNode newAlpha newBeta move moves =
            match List.isEmpty moves with
            | true -> { Alpha = betaForNode; Beta = alphaForNode; Move = move }
            | false ->
                let currentMove = moves |> List.head

                match newAlpha.IsNone || newBeta.IsNone || newAlpha.Value < newBeta.Value with
                | false -> loop alphaForNode betaForNode newAlpha newBeta move (moves |> List.tail)
                | true ->
                    let newBoard = uncheckedMoveSequence currentMove board
                    let alphaBetaMove = minimax (otherPlayer player) (searchDepth - 1) alphaForNode betaForNode newBoard

                    match player with
                    | Black ->
                        let (newAlphaForNode, newNewAlpha, newMove) = getNewValueAndMove chooseNewAlpha alphaForNode alphaBetaMove.Alpha newAlpha currentMove move
                        loop newAlphaForNode betaForNode newNewAlpha newBeta newMove (moves |> List.tail)
                    | White ->
                        let (newBetaForNode, newNewBeta, newMove) = getNewValueAndMove chooseNewBeta betaForNode alphaBetaMove.Beta newBeta currentMove move
                        loop alphaForNode newBetaForNode newAlpha newNewBeta newMove (moves |> List.tail)
        loop None None alpha beta [] (calculateMoves player board)
7 added 2154 characters in body
source | link

I've finally had time to review minimax, and I think this should be a suitable version that uses tail-call recursion and should do what you want.

Do note I've not tested this at all yet, this was a rewrite in Notepad.

let rec minimax player searchDepth alpha beta (board:Board) =
    match searchDepth = 0 || (isWon board).IsSome with
    | true ->
        let weightDiff = Some <| calculateWeightDifference board
        let newAlpha = 
            match player with
            | Black -> weightDifference
            | _ -> alpha
        let newBeta =
            match player with
            | White -> weightDifference
            | _ -> beta
        { Alpha = newBeta; Beta = newAlpha; Move = [] }
    | false ->
        let moves = calculateMoves player board
        let rec loop alphaForNode betaForNode loopAlpha loopBeta move moves =
            if List.isEmpty moves then
                { Alpha = betaForNode; Beta = alphaForNode; Move = move }

            if loopAlpha.IsNone || loopBeta.IsNone || loopAlpha.Value < loopBeta.Value then
                let newBoard = uncheckedMoveSequence x board
                let alphaBetaMove = minimax (otherPlayer player) (searchDepth - 1) alphaForNode betaForNode newBoard

                match player with
                | Black ->
                    let newAlphaForNode = chooseNewAlpha alphaForNode alphaBetaMove.Alpha
                    let newLoopAlpha = choseNewAlpha loopAlpha newAlphaForNode
                    let newMove = 
                        match newAlpha with
                        | a when a = alphaBetaMove.Alpha -> a
                        | _ -> move
                    loop newAlphaForNode betaForNode newLoopAlpha loopBeta newMove (moves |> List.tail)
                | White ->
                    let newBetaForNode = chooseNewBeta betaForNode alphaBetaMove.Beta
                    let newLoopBeta = chooseNewBeta loopBeta newBetaForNode
                    let newMove =
                        match newBeta with
                        | b when b = alphaBetaMove.Beta -> b
                        | _ -> move
                    loop alphaForNode newBetaForNode loopAlpha newLoopBeta newMove (moves |> List.tail)
            else
                loop alphaForNode betaForNode loopAlpha loopBeta move (moves |> List.tail)
        loop None None alpha beta [] moves

I've finally had time to review minimax, and I think this should be a suitable version that uses tail-call recursion and should do what you want.

Do note I've not tested this at all yet, this was a rewrite in Notepad.

let rec minimax player searchDepth alpha beta (board:Board) =
    match searchDepth = 0 || (isWon board).IsSome with
    | true ->
        let weightDiff = Some <| calculateWeightDifference board
        let newAlpha = 
            match player with
            | Black -> weightDifference
            | _ -> alpha
        let newBeta =
            match player with
            | White -> weightDifference
            | _ -> beta
        { Alpha = newBeta; Beta = newAlpha; Move = [] }
    | false ->
        let moves = calculateMoves player board
        let rec loop alphaForNode betaForNode loopAlpha loopBeta move moves =
            if List.isEmpty moves then
                { Alpha = betaForNode; Beta = alphaForNode; Move = move }

            if loopAlpha.IsNone || loopBeta.IsNone || loopAlpha.Value < loopBeta.Value then
                let newBoard = uncheckedMoveSequence x board
                let alphaBetaMove = minimax (otherPlayer player) (searchDepth - 1) alphaForNode betaForNode newBoard

                match player with
                | Black ->
                    let newAlphaForNode = chooseNewAlpha alphaForNode alphaBetaMove.Alpha
                    let newLoopAlpha = choseNewAlpha loopAlpha newAlphaForNode
                    let newMove = 
                        match newAlpha with
                        | a when a = alphaBetaMove.Alpha -> a
                        | _ -> move
                    loop newAlphaForNode betaForNode newLoopAlpha loopBeta newMove (moves |> List.tail)
                | White ->
                    let newBetaForNode = chooseNewBeta betaForNode alphaBetaMove.Beta
                    let newLoopBeta = chooseNewBeta loopBeta newBetaForNode
                    let newMove =
                        match newBeta with
                        | b when b = alphaBetaMove.Beta -> b
                        | _ -> move
                    loop alphaForNode newBetaForNode loopAlpha newLoopBeta newMove (moves |> List.tail)
            else
                loop alphaForNode betaForNode loopAlpha loopBeta move (moves |> List.tail)
        loop None None alpha beta [] moves

6 added 6 characters in body
source | link
5 added 6 characters in body
source | link
4 added 1378 characters in body
source | link
3 added 344 characters in body
source | link
2 added 344 characters in body
source | link
1
source | link