EDIT The bounty is regarding my follow up question re: the generic version of the code. As per my last post in this thread, Thanks, JD
Hi all,
I've just tried porting some of my C# 2D path-finding code into F#. But I'm currently in the 'limbo' of being a C# OO developer and also, probably, trying too hard to be purely functional with my F#.
So taking this piece of code, that is probably the least trivial F# I've written so far, I'd like some advice on making it a good piece of F# code. (sorry if this is a little too "do my homework for me", but I can't find a good sample of F# A* path-finding for reference.).
One thing that immediately springs to mind is: Is my excessive use of if/else/elseif "functional" enough?
Some notes:
1)"remove" is a simple utility function that removes elements from a list
2) "nodeToPathNode" simply takes a point in space and makes a path node out of it (adding a parent reference, and an h,g & f (as per typical A*))
EDIT(#1): I've updated the code with the suggestions given (sans the one about making it generic, i'll get on that later)... just incase some one arrives here via Google and was to copy my code with its bad formatting and logic errors.
A full, 2D Tile specific, implementation can be found here: http://jdoig.net/blog/?p=81
EDIT(#2) Or the generic version can be found here: http://jdoig.net/blog/?p=127
let rec pathFind (area:Map) goal start (openNodes:PathingNode list) (closedNodes:PathingNode list) =
let pointToPathNode = pointToPathNode openNodes.Head goal //localy specific version of nTPN
let rec checkNeighbours neighbours openNodeAcc= //Loop over list of neighbours accumalating a list of open nodes
match neighbours with
|[] -> openNodeAcc //When list of neighbours is exhausted return the open nodes.
|hd::tl ->
let checkNeighbours = checkNeighbours tl //localy specific version of cn
let node = {hd with parent = Some(openNodes.Head)}
if (List.exists (isShorter hd) openNodeAcc) then //if a higher costingnode is in open...
let shorterPath = remove openNodeAcc (nodePointEquals hd.point) //... remove it..
checkNeighbours (node::shorterPath)
elif not(List.exists (nodePointEquals hd.point) closedNodes) && not (List.exists (nodePointEquals hd.point) openNodeAcc) then //If path is not open or closed...
checkNeighbours (node::openNodeAcc)
else checkNeighbours openNodeAcc //Else carry on.
let neighbours =
area.GetNeighboursOf openNodes.Head.point //Get the neighbours of our current node (openNodes.Head) ...
|> List.filter isClear //...filter out collidable tiles...
|> List.map pointToPathNode //...for each neighbour we can walk to: generate a pathing node
let pathToGoal = List.tryFind (nodePointEquals goal) neighbours //Try and find the goal node in the walkable neighbours of this tile.
if pathToGoal.IsSome then pathToGoal //If we found our goal return it...
else
let nextSet =
checkNeighbours neighbours openNodes.Tail
|> List.sortBy(fun x -> x.f)
if nextSet.Length > 0 then
pathFind area goal start nextSet (nextSet.Head::closedNodes) //...Else carry on
else None
Thanks,
JD