Search code examples
haskellasciin-queens

Haskell: n queens ASCII graphic


I've been working on the n queens problem on Haskell and I have been able to solve it for the most part.

queens :: Int -> [[Int]]
queens 0 = [[]]
queens n = [ x : y | y <- queens (n-1), x <- [1..8], safe x y 1]
     where
         safe x [] n = True
         safe x (y:ys) n = and [ x /= y , x /= y + n , x /= y - n , safe x ys (n+1)]
drawQueens :: [Int] -> String
drawQueens [] = ""
drawQueens x = "1 2 3 4 5 6 7 8" ++ ['\n'] ++ concatMap showRow x  ++       ['\n']
    where
        size = length x
        spaces = replicate size '_' ++  ""
        showRow n = take (n - 1) spaces ++ "D" ++ take (size - n) spaces   ++ ['\n']

My second function, drawQueens, should ideally transform one solution to the queens problem to an ASCII graphic (see image below). My problems/questions are:

  1. how do I get the queens function which outputs [[Int]] to "give" one [Int] to the drawQueens function?

2.Why isn't the compiler interpreting the ['\n']? How can I modify my code in order to achieve the desired output?

This was initially a homework question during the semester but I am now doing it just for myself.

My desired output for n = 8

This is what my output of drawQueens looks like currently

Thank you for all your help.


Solution

  • The compiler “interprets” '\n' just fine. Only, newlines aren't really “safe characters”, as in: you can't use a string literal with newlines in it directly in Haskell code. And the output of print, which GHCi uses by default to, well, print stuff, always tries to generate valid Haskell code, hence it escapes these newlines in again. This can be surpressed if you just instruct it to cat the string as it is to the terminal:

    *Main> drawQueens [4,2,7,3,6,8,5,1]
    "1 2 3 4 5 6 7 8\n___D____\n_D______\n______D_\n__D_____\n_____D__\n_______D\n____D___\nD_______\n\n"
    *Main> putStrLn $ drawQueens [4,2,7,3,6,8,5,1]
    1 2 3 4 5 6 7 8
    ___D____
    _D______
    ______D_
    __D_____
    _____D__
    _______D
    ____D___
    D_______
    

    There's still another problem: you don't have the same spacing in the number ow as in the actuall chessboard. Well, that's easily fixed too.

    drawQueens :: [Int] -> String
    drawQueens [] = ""
    drawQueens x = "1 2 3 4 5 6 7 8" ++ "\n" ++ concatMap showRow x
        where
            size = length x
            spaces n = concat $ replicate n "□ "
            showRow n = spaces (n - 1) ++ "♛ " ++ spaces (size - n) ++ "\n"
    

    This then gives:

    *Main> putStrLn $ drawQueens [4,2,7,3,6,8,5,1]
    1 2 3 4 5 6 7 8
    □ □ □ ♛ □ □ □ □ 
    □ ♛ □ □ □ □ □ □ 
    □ □ □ □ □ □ ♛ □ 
    □ □ ♛ □ □ □ □ □ 
    □ □ □ □ □ ♛ □ □ 
    □ □ □ □ □ □ □ ♛ 
    □ □ □ □ ♛ □ □ □ 
    ♛ □ □ □ □ □ □ □ 
    

    Fancy version:

    chessboardRow, chessboardRow' :: [Maybe Char] -> String
    chessboardRow' [] = "▌"
    chessboardRow' (Just c:cs) = '▌':c:chessboardRow cs
    chessboardRow' (Nothing:cs) = "▌ "++chessboardRow cs
    chessboardRow [] = " "
    chessboardRow (Just c:cs) = '▐':c:chessboardRow' cs
    chessboardRow (Nothing:cs) = "▐█"++chessboardRow' cs
    
    drawQueens :: [Int] -> String
    drawQueens [] = ""
    drawQueens x = "  a b c d e f g h" ++ "\n"
                     ++ concat (reverse $ 
                                 zipWith3 showRow
                                          ['1'..]
                                          (cycle [chessboardRow, chessboardRow'])
                                          x)
                     ++ "\n"
        where
            size = length x
            showRow i rsh n = i : rsh (replicate (n - 1) Nothing
                                          ++ [Just '♛']
                                          ++ replicate (size - n) Nothing)
                                  ++ "\n"
    

    gives

      a b c d e f g h
    8▌♛▐█▌ ▐█▌ ▐█▌ ▐█▌
    7▐█▌ ▐█▌ ▐♛▌ ▐█▌  
    6▌ ▐█▌ ▐█▌ ▐█▌ ▐♛▌
    5▐█▌ ▐█▌ ▐█▌♛▐█▌  
    4▌ ▐█▌♛▐█▌ ▐█▌ ▐█▌
    3▐█▌ ▐█▌ ▐█▌ ▐♛▌  
    2▌ ▐♛▌ ▐█▌ ▐█▌ ▐█▌
    1▐█▌ ▐█▌♛▐█▌ ▐█▌