[Haskell-cafe] Where is a good place to place code like this,
so if I may be so bold, people can learn from it?
Casey Hawthorne
caseyh at istar.ca
Sat Nov 14 13:15:51 EST 2009
Where is a good place to place code like this, so if I may be so bold,
people can learn from it?
{- Author Modifications: Casey Hawthorne
Author Original: Jeff Newbern
Maintainer: Casey Hawthorne <caseyh at istar.ca>
Maintainer?: Jeff Newbern <jnewbern at nomaware.com>
Time-stamp: Jeff <Tue Aug 19 09:31:32 2003>
Time-stamp: Casey <Sat Nov 14 10:10 2009>
License: GPL
The N-queens puzzle is to place N queens on an N by N chess board
so that no queen can attack another one.
Compiler Flags: ghc -O2 -fvia-c --make N-Queens.hs
Description
http://www.haskell.org/all_about_monads/html/stacking.html#example
Original Code
http://www.haskell.org/all_about_monads/examples/example25.hs
-}
{- Description
Example 25 - Using the StateT monad transformer
with the List monad to achieve non-deterministic
stateful computations, and the Writer monad to
do logging
Usage: Compile the code and run it with an argument between 1 and 8.
It will print a solution to the N-queens puzzle along with
a log of the number of choices it had at each step.
The N-queens puzzle is to place N queens on a chess board
so that no queen can attack another one. The original version
always used an 8x8 board.
Try: ./ex25 8
./ex25 1
./ex25 7
Added by Casey:
- different board sizes
-- up to a maximum 26x26 square board
- updated imports list
-}
import IO
import System
import Monad
import Data.Maybe
import Data.List
import Data.Char (toLower)
import Control.Monad.State
import Control.Monad.Writer
-- describe Chess Units and positions
type Rank = Int
data File = A | B | C | D | E | F | G | H | I | J | K | L | M | N | O
| P | Q | R | S | T | U | V | X | Y | Z
deriving (Eq, Show, Ord, Enum)
data Position = Pos {file::File, rank::Rank}
deriving Eq
instance Show Position where
show (Pos f r) = (map toLower (show f)) ++ (show r)
instance Ord Position where
compare p1 p2 =
case (rank p1) `compare` (rank p2) of
LT -> GT
GT -> LT
_ -> (file p1) `compare` (file p2)
data Kind = Pawn | Knight | Bishop | Rook | Queen | King
deriving (Eq, Ord, Enum)
instance Show Kind where
show Pawn = "P"
show Knight = "N"
show Bishop = "B"
show Rook = "R"
show Queen = "Q"
show King = "K"
data Color = Black | White
deriving (Eq, Ord, Enum)
instance Show Color where
show Black = "b"
show White = "w"
data Unit = Unit {color::Color, kind::Kind}
deriving (Eq, Ord)
instance Show Unit where
show (Unit c k) = ((show c) ++ (show k))
data Board = Board {size::Int, psns::[(Unit,Position)]}
-- newtype Board = Board [(Unit,Position)]
-- newtype BoardMax = BoardMax Int
instance Show Board where
show (Board n ps) =
let ordered = (sort . swap) ps
ranks = map (showRank ordered) [n,(n-1)..1]
board = intersperse
(concat (take n (repeat "--+"))) ranks
rlabels = intersperse " " (map (\n->(twoSpaces n)++"
") [n,(n-1)..1])
flabels = take (n*3) " a b c d e f g h i j
k l m n o p q r s t u v w x y z"
twoSpaces n
| length (show n) == 2 = show n
| otherwise = " " ++ (show n)
in unlines $ zipWith (++) rlabels board ++ [flabels]
where
swap = map (\(a,b)->(b,a))
showRank ps r = let rnk = filter (\(p,_)->(rank p)==r)
ps
cs = map (showUnit rnk) (take n
[A .. Z])
in concat (intersperse "|" cs)
showUnit ps f = maybe " " (show . snd) (find
(\(p,_)->(file p)==f) ps)
data Diagonal = Ascending Position | Descending Position
deriving (Eq, Show)
-- define the diagonal according to its interesction with rank 1 or
size of board)
-- or with file a
normalize :: Int -> Diagonal -> Diagonal
normalize n d@(Ascending psn)
| (rank psn) == 1 = d
| (file psn) == A = d
| otherwise = normalize n (Ascending (Pos (pred
(file psn)) ((rank psn)-1)))
normalize n d@(Descending psn)
| (rank psn) == n = d
| (file psn) == A = d
| otherwise = normalize n (Descending (Pos (pred
(file psn)) ((rank psn)+1)))
-- get the diagonals corresponding to a location on the board
getDiags :: Int -> Position -> (Diagonal,Diagonal)
getDiags n p = (normalize n (Ascending p), normalize n (Descending p))
-- this is the type of our problem description
data NQueensProblem = NQP {board::Board,
ranks::[Rank],
files::[File],
asc::[Diagonal],
desc::[Diagonal]}
-- initial state = empty board, all ranks, files, and diagonals free
initialState n = let fileA = map (\r->Pos A r) [1..n]
rankMax = map (\f->Pos f n) (take n [A .. Z])
rank1 = map (\f->Pos f 1) (take n [A .. Z])
asc = map Ascending (nub (fileA ++ rank1))
desc = map Descending (nub (fileA ++
rankMax))
in NQP (Board n []) [1..n] (take n [A .. Z]) asc
desc
-- this is our combined monad type for this problem
type NDS a = WriterT [String] (StateT NQueensProblem []) a
-- Get the first solution to the problem, by evaluating the solver
computation with
-- an initial problem state and then returning the first solution in
the result list,
-- or Nothing if there was no solution.
getSolution :: NDS a -> NQueensProblem -> Maybe (a,[String])
getSolution c i = listToMaybe (evalStateT (runWriterT c) i)
-- add a Queen to the board in a specific position
addQueen :: Position -> NDS ()
addQueen p = do (Board n b) <- gets board
rs <- gets ranks
fs <- gets files
as <- gets asc
ds <- gets desc
let b' = (Unit Black Queen, p):b
rs' = delete (rank p) rs
fs' = delete (file p) fs
(a,d) = getDiags n p
as' = delete a as
ds' = delete d ds
tell ["Added Queen at " ++ (show p)]
put (NQP (Board n b') rs' fs' as' ds')
-- test if a position is in the set of allowed diagonals
inDiags :: Int -> Position -> NDS Bool
inDiags n p = do
let (a,d) = getDiags n p
as <- gets asc
ds <- gets desc
return $ (elem a as) && (elem d ds)
-- add a Queen to the board in all allowed positions
addQueens :: NDS ()
addQueens = do rs <- gets ranks
fs <- gets files
(Board n b) <- gets board
allowed <- filterM (inDiags n) [Pos f r | f <- fs, r <-
rs]
tell [show (length allowed) ++ " possible choices"]
msum (map addQueen allowed)
-- Start with an empty chess board and add the requested number of
queens,
-- then get the board and print the solution along with the log
main :: IO ()
main = do
args <- getArgs
let n = read (args!!0)
cmds = replicate n addQueens
sol = (`getSolution` (initialState n)) $
do sequence_ cmds
gets board
case sol of
Just (b,l) -> do putStr $ show b -- show the solution
putStr $ unlines l -- show the log
Nothing -> putStrLn "No solution"
-- END OF FILE
--
Regards,
Casey
More information about the Haskell-Cafe
mailing list