packages feed

boardgame-0.0.0.1: executable/Cross.hs

{-# LANGUAGE CPP #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}

module Cross where

import Data.List (intersect)
import Prelude hiding (lookup)  

import Data.Map (
    Map
  , elems
  , keys
  , lookup
  , member
  , adjust
  )

import Boardgame (
    Player(..)
  , Position(..)
  , PositionalGame(..)
  , mapPosition
  , isOccupied
  , takeEmptyMakeMove
  , nextPlayer
  , drawIf
  , criteria
  , symmetric
  , unless
  , player1LosesWhen
  , player1WinsWhen
  )

import Boardgame.ColoredGraph (
    ColoredGraph
  , values
  , mapValues
  , anyConnections
  , filterValues
  , filterG
  , hexHexGraph
  )

#ifdef WASM
import Data.Aeson (ToJSON(..))
#endif

-------------------------------------------------------------------------------
-- * Cross
-------------------------------------------------------------------------------

newtype Cross = Cross (ColoredGraph (Int, Int) Position (Int, Int))

instance Show Cross where
  show (Cross b) = show b

#ifdef WASM
instance ToJSON Cross where
  toJSON (Cross b) = toJSON b
#endif

instance PositionalGame Cross (Int, Int) where
  positions   (Cross b)     = values b
  getPosition (Cross b) c   = fst <$> lookup c b
  setPosition (Cross b) c p = if member c b
    then Just $ Cross $ adjust (\(_, xs) -> (p, xs)) c b
    else Nothing
  makeMove = takeEmptyMakeMove

  gameOver (Cross b) = criterion b
    where
      criterion =
        drawIf (all isOccupied . values) `unless` -- It's a draw if all tiles are owned.
        -- Here we say that in any position where one player wins,
        -- the other player would win instead if the pieces were swapped.
        symmetric (mapValues $ mapPosition nextPlayer)
        (criteria (player1LosesWhen <$> -- you lose if you have connected 2 opposite sides.
          [ anyConnections (==2) [side1, side4] . filterValues (== Occupied Player1)
          , anyConnections (==2) [side2, side5] . filterValues (== Occupied Player1)
          , anyConnections (==2) [side3, side6] . filterValues (== Occupied Player1)
          ]) `unless`
        criteria (player1WinsWhen <$> -- you win if you have connected 3 non-adjacent sides.
          [ anyConnections (==3) [side1, side3, side5] . filterValues (== Occupied Player1)
          , anyConnections (==3) [side2, side4, side6] . filterValues (== Occupied Player1)
          ]))

      dirs =
        [ (1, 0)
        , (1, -1)
        , (0, -1)
        , (-1, 0)
        , (-1, 1)
        , (0, 1)
        ]
      emptyNeighbours xs = keys $ filterG (null . intersect xs . elems . snd) b

      side1 = emptyNeighbours [dirs !! 0, dirs !! 1]
      side2 = emptyNeighbours [dirs !! 1, dirs !! 2]
      side3 = emptyNeighbours [dirs !! 2, dirs !! 3]
      side4 = emptyNeighbours [dirs !! 3, dirs !! 4]
      side5 = emptyNeighbours [dirs !! 4, dirs !! 5]
      side6 = emptyNeighbours [dirs !! 5, dirs !! 0]

emptyCross :: Int -> Cross
emptyCross = Cross . hexHexGraph