{-# OPTIONS_GHC -Wno-missing-methods #-} {-# LANGUAGE BlockArguments #-} {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DerivingStrategies #-} -- Futoshiki is one of my favourite number games. If you're unfamiliar with the -- rules, we'll use the following configuration for this example: -- -- ┌───┐ ┌───┐ ┌───┐ ┌───┐ -- │ │ │ │ < │ │ < │ │ -- └───┘ └───┘ └───┘ └───┘ -- ^ -- ┌───┐ ┌───┐ ┌───┐ ┌───┐ -- │ │ │ │ │ │ │ 3 │ -- └───┘ └───┘ └───┘ └───┘ -- v -- ┌───┐ ┌───┐ ┌───┐ ┌───┐ -- │ │ │ │ │ │ │ │ -- └───┘ └───┘ └───┘ └───┘ -- ^ -- ┌───┐ ┌───┐ ┌───┐ ┌───┐ -- │ │ │ │ │ │ │ │ -- └───┘ └───┘ └───┘ └───┘ -- -- The goal is to fill a four-by-four board with numbers `[1 .. 4]` such that -- every number is __unique__ in its __row__ and __column__. As well as that, -- if a @<@ symbol appears between two cells, the right cell must be **greater -- than** the left. This "greater than" symbol can appear between any two -- adjacent cells, though, so we represent it using the @<@, @>@, @^@, and @v@ -- symbols, depending on its direction. module Futoshiki where import Data.Hashable (Hashable) import Control.Monad.Watson (satisfying) import Data.Holmes hiding (satisfying) import Data.List (transpose) import Data.List.Split (chunksOf) import GHC.Generics (Generic) import Test.Hspec -- We'll be using @Intersect@ for this one, so we need to establish our enum -- type for the parameter space. data Choice = V1 | V2 | V3 | V4 deriving stock (Eq, Ord, Show, Bounded, Enum, Generic) deriving anyclass (Hashable) instance Num Choice where fromInteger = toEnum . pred . fromInteger -- Here's the translation of the board shown above, with the constraints -- expressed as a `Prop` predicate: solution :: Maybe [ Intersect Choice ] solution = do -- For this example, the board is a @4 × 4@ grid with each cell being a -- number between @1@ and @4@. (16 `from` [1 .. 4]) `satisfying` \board -> do let rows = chunksOf 4 board columns = transpose rows and' [ -- First up, the rules of the game: all' distinct rows , all' distinct columns -- Then, the constraints on this particular board: , (rows !! 0 !! 1) .< (rows !! 0 !! 2) , (rows !! 0 !! 2) .< (rows !! 0 !! 3) , (rows !! 0 !! 0) .< (rows !! 1 !! 0) , (rows !! 1 !! 3) .== 3 , (rows !! 2 !! 1) .< (rows !! 1 !! 1) , (rows !! 2 !! 3) .< (rows !! 3 !! 3) ] -- All being well, this should be the result! Use `cabal new-test examples` to -- run these tests and check for correct solutions. spec_futoshiki :: Spec spec_futoshiki = it "computes the solution" do solution `shouldBe` Just [ 1, 2, 3, 4 , 2, 4, 1, 3 , 4, 3, 2, 1 , 3, 1, 4, 2 ]