packages feed

toysolver-0.2.0: samples/programs/sudoku/sudoku.hs

{-# LANGUAGE ScopedTypeVariables #-}
{-# OPTIONS_GHC -Wall #-}
module Main where

import Control.Monad
import Data.Array.IArray
import Data.Char
import System.Environment
import System.Exit
import System.IO
import qualified ToySolver.SAT as SAT

main :: IO ()
main = do
  args <- getArgs
  case args of
    [fname] -> do
      s <- readFile fname
      let b = readBoard $ filter (not . isSpace) $ removeComments s
      ans <- solve b
      case ans of
        Nothing -> hPutStrLn stderr "NO SOLUTION"
        Just b2 -> do
          hPutStrLn stderr "SOLUTION FOUND"
          printAnswer b2
    _ -> do
      hPutStrLn stderr "Usage: sudoku input.sdk"
      exitFailure

removeComments :: String -> String
removeComments = unlines . filter p . lines
  where
    p ('#':_) = False
    p _ = True

readBoard :: String -> Array (Int,Int) (Maybe Int)
readBoard s = array ((1,1),(9,9)) $ 
  [ (idx, if isDigit c then Just (read [c]) else Nothing)
  | (idx, c) <- zip [(i,j) | i <- [1..9], j <- [1..9]] s
  ]

solve :: Array (Int,Int) (Maybe Int) -> IO (Maybe (Array (Int,Int) Int))
solve board = do
  solver <- SAT.newSolver 
  SAT.setLogger solver (hPutStrLn stderr)
 
  (vs :: Array (Int,Int,Int) SAT.Var) <-
    liftM (array ((1,1,1), (9,9,9))) $
      forM [(i,j,k) | i<-[1..9], j<-[1..9], k<-[1..9]] $ \idx -> do
        v <- SAT.newVar solver
        return (idx,v)

  forM_ (assocs board) $ \((i,j), m) -> do
    case m of 
      Nothing -> return ()
      Just k -> SAT.addClause solver [vs ! (i,j,k)]

  -- Every cell contains exactly one digit.
  forM_ [1..9] $ \i -> do
    forM_ [1..9] $ \j -> do
      SAT.addExactly solver [vs ! (i,j,k) | k <- [1..9]] 1

  forM_ [1..9] $ \k -> do
    -- Every row contains exactly one occurence of the digit.
    forM_ [1..9] $ \i -> do
      SAT.addExactly solver [vs ! (i,j,k) | j <- [1..9]] 1
    -- Every column contains exactly one occurence of the digit.
    forM_ [1..9] $ \j -> do
      SAT.addExactly solver [vs ! (i,j,k) | i <- [1..9]] 1
    -- Every block contains exactly one occurence of the digit.
    forM_ [0..2] $ \bi ->
      forM_ [0..2] $ \bj ->
        SAT.addExactly solver [vs ! (bi*3 + i', bj*3 + j', k) | i' <- [1..3], j' <- [1..3]] 1

  ret <- SAT.solve solver
  if ret then do
    m <- SAT.getModel solver
    return $ Just $ array ((1,1),(9,9)) [((i,j),k) | ((i,j,k),v) <- assocs vs, m ! v]
  else
    return Nothing

printAnswer :: Array (Int,Int) Int -> IO ()
printAnswer b =
  forM_ [1..9] $ \i ->
    putStrLn $ concat [show (b ! (i,j)) | j <- [1..9]]