-- |Generates the game tree up to a given depth ('maxDepth'). The 'Tree' data
-- structure from the 'Data.Tree' package is lazy, which means that its nodes
-- will be generated on demand as opposed to being generated at once.
-- The alpha-beta pruning algorithm takes advantage of this by not visiting
-- the pruned subtrees.
genGameTree :: Int -> GameNode -> Tree GameNode
genGameTree maxDepth = unfoldTree generatingFun
    where -- Function used to generate the nodes of the tree. The seed value is
          -- of the same type as the node type ('GameNode'). At each step, the
          -- function expands the current node generating its children
          -- except when the node is a child node or it's already at the
          -- maximum depth.
          generatingFun node@(GN board _ player depth) =
            let genNode col = do
                    newBoard <- putPiece player col board
                    return (newBoard, col)
                -- Create a column ordering for the children nodes to be 
                -- generated. Here, we want to prioritise the columns closer
                -- to the center of the board, as these are the ones that are
                -- more likely to be the best choices.
                columnArrangement =
                    sortBy (comparing (\col -> abs $ (boardCols `div` 2) - col))
                           [0..boardCols-1]
                -- Get lists of possible boards and used columns.
                (possiblePlays, columns) = unzip $ mapMaybe genNode
                                                   columnArrangement
                newDepth = depth + 1
                -- Generate childs of current node.
                childNodes = if newDepth > maxDepth
                                then []
                                else zipWith4 GN possiblePlays
                                                 columns
                                                 (repeat $ nextPlayer player)
                                                 (repeat newDepth)
            in  case getMatchState board of
                    Win _  -> (node, [])
                    Tie    -> (node, [])
                    NotEnd -> (node, childNodes)

-- |Eval the game tree using the negamax algorithm, returning the best
-- column selection for the current player.
genGameTree :: Int -> GameNode -> Tree GameNode
genGameTree maxDepth = unfoldTree generatingFun
    where -- Function used to generate the nodes of the tree. The seed value is
          -- of the same type as the node type ('GameNode'). At each step, the
          -- function expands the current node generating its children
          -- except when the node is a child node or it's already at the
          -- maximum depth.
          generatingFun node@(GN board _ player depth) =
            let genNode col = do
                    newBoard <- putPiece player col board
                    return (newBoard, col)
                -- Create a column ordering for the children nodes to be 
                -- generated. Here, we want to prioritise the columns closer
                -- to the center of the board, as these are the ones that are
                -- more likely to be the best choices.
                columnArrangement =
                    sortBy (comparing (\col -> abs $ (boardCols `div` 2) - col))
                           [0..boardCols-1]
                -- Get lists of possible boards and used columns.
                (possiblePlays, columns) = unzip $ mapMaybe genNode
                                                   columnArrangement
                newDepth = depth + 1
                -- Generate childs of current node.
                childNodes = if newDepth > maxDepth
                                then []
                                else zipWith4 GN possiblePlays
                                                 columns
                                                 (repeat $ nextPlayer player)
                                                 (repeat newDepth)
            in  case getMatchState board of
                    Win _  -> (node, [])
                    Tie    -> (node, [])
                    NotEnd -> (node, childNodes)

-- |Eval the game tree using the negamax algorithm, returning the best
-- column selection for the current player.
genGameTree maxDepth = unfoldTree generatingFun
    where -- Function used to generate the nodes of the tree. The seed value is
          -- of the same type as the node type ('GameNode'). At each step, the
          -- function expands the current node generating its children
          -- except when the node is a child node or it's already at the
          -- maximum depth.
          generatingFun node@(GN board _ player depth) =
            let genNode col = do
                    newBoard <- putPiece player col board
                    return (newBoard, col)
                -- Create a column ordering for the children nodes to be 
                -- generated. Here, we want to prioritise the columns closer
                -- to the center of the board, as these are the ones that are
                -- more likely to be the best choices.
                columnArrangement =
                    sortBy (comparing (\col -> abs $ (boardCols `div` 2) - col))
                           [0..boardCols-1]
                -- Get lists of possible boards and used columns.
                (possiblePlays, columns) = unzip $ mapMaybe genNode
                                                   columnArrangement
                newDepth = depth + 1
                -- Generate childs of current node.
                childNodes = if newDepth > maxDepth
                                then []
                                else zipWith4 GN possiblePlays
                                                 columns
                                                 (repeat $ nextPlayer player)
                                                 (repeat newDepth)
            in  case getMatchState board of
                    Win _  -> (node, [])
                    Tie    -> (node, [])
                    NotEnd -> (node, childNodes)

-- |Eval the game tree using the negamax algorithm, returning the best
-- column selection for the current player.