{-# 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
        ]