Here are the exercises from the chapter 9 of the book Programming in Haskell. As there were some compatibilities issues between the content of the book and my realities, We will only concentrate the efforts on the nim one.
Anyway, enough chit chat, here is the exercise:
Nim is a game that is played on a board comprising five numbered rows of stars, which is initially set up as follows:
1:∗∗∗∗∗
2:∗∗∗∗
3:∗∗∗
4:∗∗
5:∗
Two players take it in turn to remove one or more stars from the end of a single row. The winner is the player who removes the last star or stars from the board. Implement the game of nim in Haskell.
Hint: represent the board as a list comprising the number of stars remaining on each row, with the initial board being
[5, 4, 3, 2, 1]
.
We will begin simply by representing a board following the recommandations.
type Board = [Int]
I'm creating a utility function to create a board:
makeBoard :: Int -> Board makeBoard n = [n, n-1..1]
For example:
*Ch9> makeBoard 5 [5,4,3,2,1]
Now we can compute the stars to display on board:
computeStars :: Board -> [(Int, String)] computeStars b = zip [0..(length b - 1)] (map (flip replicate '*') b)
Example:
*Ch9> computeStars board [(0,"*****"),(1,"****"),(2,"***"),(3,"**"),(4,"*")]
Now, we can show the board:
showBoard :: [(Int, String)] -> IO () showBoard b = mapM_ putStrLn [ show x ++ ": " ++ s | (x, s) <- b ]
Possible output:
*Ch9> showBoard $ computeStars board 0: ***** 1: **** 2: *** 3: ** 4: *
We will also need some utility function to deal silently with error. That is if we want to remove more stars than exists, then we no longer have stars.
wrap :: Int -> Int wrap n = if n >= 0 then n else 0
*Ch9> wrap 0 0 *Ch9> wrap (-1) 0
Same idea for dealing with stars.
wrapStars :: Int -> Int -> Int wrapStars r l | r < 0 = 0 | l <= r = l | otherwise = r
Output:
*Ch9> wrapStars 10 12 10 *Ch9> wrapStars 13 12 12 *Ch9> wrapStars (-1) 12 0
Now we need some function to help in removing stars. Simply, I split at the index r and retrieve the head of the second list (which then correponds to the value we want to decrement). Then we update the new value by decrementing the value by n. We wrap the result to return 0 at the worst case scenario. Then we recreate the list with the new value.
remove :: Int -> Int -> Board -> Board remove r n b = let (h, (v:t)) = splitAt r b ns = wrap (v - n) in h ++ (ns:t)
Output:
*Ch9> remove 0 2 board [3,4,3,2,1] *Ch9> remove 0 10 board [0,4,3,2,1] *Ch9> remove 0 5 board [0,4,3,2,1]
The game is finished if the board no longer has stars. As the board is represented by a list of int, we simply compute the sum of the list. It the rest is zero, there is no more stars.
win :: Board -> Bool win = (== 0) . sum
Output:
*Ch9> win [4,4,3,2,1] False *Ch9> win [0,0,0,0,0] True
turn :: Int -> Board -> IO Board turn p b = do showBoard $ computeStars b putStrLn $ "Player " ++ (show p) ++ ", on which row do you want to remove stars?" x <- getLine let r = read x in do putStrLn "How many stars?" s <- getLine let n = wrapStars (read s) (length b) in return $ remove r n b
Output:
*Ch9> turn 1 board ***** **** *** ** * Player 1, what stars do you want to remove? 1 [5,3,3,2,1]
Here is a small function to compute the next player:
nextplayer :: Int -> Int nextplayer p = ((p+1) `mod` 2)
Output:
*Ch9> nextplayer 1 0 *Ch9> nextplayer 0 1
The main function which, given a player and a board, launches the game:
game :: Int -> Board -> IO () game p b = do nb <- turn p b if win nb then putStrLn $ "p" ++ (show p) ++ " won!" else game (nextplayer p) nb
A small utility function to setup the game regarding the size of the board and which player starts:
setupGame :: IO (Int, Int) setupGame = do putStrLn "What size for the board?" n <- getLine let size = read n in do putStrLn "What player first? (0 or 1)" p <- getLine let player = read p in return (size, player)
At last, the main function which launches the game:
main :: IO () main = do (size, player) <- setupGame game player (makeBoard size)
A sample run:
*Ch9> main What size for the board? 3 What player first? (0 or 1) 1 0: *** 1: ** 2: * Player 1, on which row do you want to remove stars? 0 How many stars? 3 0: 1: ** 2: * Player 0, on which row do you want to remove stars? 1 How many stars? 1 0: 1: * 2: * Player 1, on which row do you want to remove stars? 0 How many stars? 1 0: 1: * 2: * Player 0, on which row do you want to remove stars? 2 How many stars? 1 0: 1: * 2: Player 1, on which row do you want to remove stars? 1 How many stars? 1 p1 won!
As usual, here is the complete source.