boardgame-0.0.0.1: executable/ConnectFour.hs
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE CPP #-}
module ConnectFour where
import Prelude hiding (lookup)
import Data.Map (
Map
, lookup
, member
, adjust
)
import Boardgame (
Player(..)
, Position(..)
, PositionalGame(..)
, mapPosition
, criteria
, isOccupied
, nextPlayer
, drawIf
, symmetric
, unless
, player1WinsWhen
)
import Boardgame.ColoredGraph (
ColoredGraph
, ColoredGraphTransformer(..)
, values
, mapValues
, mapEdges
, filterValues
, rectOctGraph
, inARow
, filterEdges
)
#ifdef WASM
import Data.Aeson (
ToJSON(..)
, object
, (.=)
)
#endif
-------------------------------------------------------------------------------
-- * Connect Four
-------------------------------------------------------------------------------
data ConnectFour = ConnectFour Int (ColoredGraph (Int, Int) Position String)
instance Show ConnectFour where
show (ConnectFour k b) = show b
#ifdef WASM
instance ToJSON ConnectFour where
toJSON (ConnectFour k b) = object [
"k" .= toJSON k
, "board" .= toJSON b
]
#endif
instance PositionalGame ConnectFour (Int, Int) where
positions (ConnectFour k b) = values b
getPosition (ConnectFour k b) c = fst <$> lookup c b
setPosition (ConnectFour k b) c p = if member c b
then Just $ ConnectFour k $ adjust (\(_, xs) -> (p, xs)) c b
else Nothing
makeMove = cfMove
gameOver (ConnectFour k 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)
-- Player1 wins if there are k or more pieces in a row in any direction.
(criteria (player1WinsWhen . inARow (>=k) <$> directions) . filterValues (== Occupied Player1))
directions = ["vertical", "horizontal", "diagonal1", "diagonal2"]
-- Restrict move for Connect Four.
-- Move is only valid if the positon is empty and the position below is occupied.
cfMove :: ConnectFour -> Player -> (Int, Int) -> Maybe ConnectFour
cfMove a p coord = case getPosition a coord of
-- If we are at bottom row, we can place the piece there.
Just Empty -> if ((fst coord) == 0)
then setPosition a coord (Occupied p)
-- Not at bottom row, check to see if position below has been filled.
else case getPosition a ((fst coord) -1, snd coord) of
Just Empty -> Nothing
_ -> setPosition a coord (Occupied p)
_ -> Nothing
emptyConnectFour :: Int -> Int -> Int -> ConnectFour
emptyConnectFour m n k = ConnectFour k $ mapEdges dirName $ rectOctGraph m n
where
dirName (1,0) = "horizontal"
dirName (-1,0) = "horizontal"
dirName (0,-1) = "vertical"
dirName (0,1) = "vertical"
dirName (1,-1) = "diagonal1"
dirName (-1,1) = "diagonal1"
dirName (1,1) = "diagonal2"
dirName (-1,-1) = "diagonal2"