import Data.List (intersperse) import System.Random (randomRIO) import Data.Array (Array, (!), (//), array, bounds) import Control.Monad (zipWithM_, replicateM, foldM, when) type Board = Array (Char, Char) Int flp :: Int -> Int flp 0 = 1 flp 1 = 0 numRows, numCols :: Board -> String numRows t = let ((a, _), (b, _)) = bounds t in [a .. b] numCols t = let ((_, a), (_, b)) = bounds t in [a .. b] flipRow, flipCol :: Board -> Char -> Board flipRow t r = let e = [ (ix, flp (t ! ix)) | ix <- zip (repeat r) (numCols t) ] in t // e flipCol t c = let e = [ (ix, flp (t ! ix)) | ix <- zip (numRows t) (repeat c) ] in t // e printBoard :: Board -> IO () printBoard t = do let rows = numRows t cols = numCols t f 0 = '0' f 1 = '1' p r xs = putStrLn $ [r, ' '] ++ intersperse ' ' (map f xs) putStrLn $ " " ++ intersperse ' ' cols zipWithM_ p rows [ [ t ! (y, x) | x <- cols ] | y <- rows ] -- create a random goal board, and flip rows and columns randomly -- to get a starting board setupGame :: Char -> Char -> IO (Board, Board) setupGame sizey sizex -- random cell value at (row, col) = do let mk rc = (\v -> (rc, v)) <$> randomRIO (0, 1) rows = ['a' .. sizey] cols = ['1' .. sizex] goal <- array (('a', '1'), (sizey, sizex)) <$> mapM mk [ (r, c) | r <- rows , c <- cols ] start <- do let change :: Board -> Int -> IO Board -- flip random row change t 0 = flipRow t <$> randomRIO ('a', sizey) -- flip random col change t 1 = flipCol t <$> randomRIO ('1', sizex) numMoves <- randomRIO (3, 15) -- how many flips (3 - 15) -- determine if rows or cols are flipped moves <- replicateM numMoves $ randomRIO (0, 1) -- make changes and get a starting board foldM change goal moves if goal /= start -- check if boards are different then return (goal, start) -- all ok, return both boards else setupGame sizey sizex -- try again main :: IO () main = do putStrLn "Select a board size (1 - 9).\nPress any other key to exit." sizec <- getChar when (sizec `elem` ['1' .. '9']) $ do let size = read [sizec] - 1 (g, s) <- setupGame (['a' ..] !! size) (['1' ..] !! size) turns g s 0 where turns goal current moves = do putStrLn "\nGoal:" printBoard goal putStrLn "\nBoard:" printBoard current when (moves > 0) $ putStrLn $ "\nYou've made " ++ show moves ++ " moves so far." putStrLn $ "\nFlip a row (" ++ numRows current ++ ") or a column (" ++ numCols current ++ ")" v <- getChar if v `elem` numRows current then check $ flipRow current v else if v `elem` numCols current then check $ flipCol current v else tryAgain where check t = if t == goal then putStrLn $ "\nYou've won in " ++ show (moves + 1) ++ " moves!" else turns goal t (moves + 1) tryAgain = do putStrLn ": Invalid row or column." turns goal current moves