This turned out to be a much better learning experience for Haskell than I anticipated. In case Google screws up the formatting, here's a link to hpaste:
Everything went pretty smoothly until I came to the "play" function which handles all the move making and backtracking. I'm still not pleased with that function; after many years of imperative thinking, it may take a while longer to get into the functional mindset. However, even with my very rough newbie skills, I think Haskell provides a reasonably elegant solution.
Even though I only wanted the first solution, I think it may have been more idiomatic to have a function lazily compute a list of all solutions and just take the head of that list.
Brian
-- Solve the Cracker Barrel Peg Board Puzzle
module Main where
type Pos = (Int, Int)
type Move = (Pos, Pos)
type Board = [ Pos ]
isOccupied b p = elem p b
isEmpty b p = not (isOccupied b p)
isPos (r,c) = elem r [0..4] && elem c [0..r]
-- Possible moves for one position
positionMoves b p = [ (p, dst) | (neighbor, dst) <- pairs,
isOccupied b neighbor &&
isEmpty b dst ]
where (r, c) = p
pairs = filter (\(p1,p2) -> isPos p1 && isPos p2)
[ ((r + or `div` 2, c + oc `div` 2),(r + or, c + oc)) |
(or, oc) <- [ (-2,0), (0,2), (2,2), (2,0), (0,-2), (-2,-2) ] ]
-- Possible moves for all positions on the board
possibleMoves b = concat [ positionMoves b pos | pos <- b ]
-- Make a move and return the new board
move b (src,dst) = dst:filter pred b
where ((sr,sc),(dr,dc)) = (src,dst)
neighbor = (div (sr+dr) 2, div (sc+dc) 2)
pred = \pos -> (pos /= src) && (pos /= neighbor)
-- Make moves until the goal position is met
play b p moves =
if null nextMoves then
if length b == 1 && head b == p then reverse moves else []
else
tryMoves nextMoves
where
nextMoves = possibleMoves b
tryMoves [] = []
tryMoves (m:ms) =
let result = play (move b m) p (m:moves)
in if null result then tryMoves ms else result
-- Compute the initial empty position to know the goal, then solve the puzzle
solve b = let emptyPos = head [ (r,c) | r <- [0..4], c <- [0..r], isEmpty b (r,c) ]
in play b emptyPos []
-- A sample board with the topmost hole empty
board :: Board
board = [ (1,0), (1,1),
(2,0), (2,1), (2,2),
(3,0), (3,1), (3,2), (3,3),
(4,0), (4,1), (4,2), (4,3), (4,4) ]
main = print (solve board)
--
Brian Adkins
Lojic Technologies, LLC