{-# LANGUAGE TupleSections #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE FlexibleInstances #-}
module Boardgame.ColoredGraph (
ColoredGraph
, ColoredGraphTransformer(..)
, hexHexGraph
, paraHexGraph
, rectOctGraph
, triHexGraph
, completeGraph
, mapValues
, mapEdges
, filterValues
, filterEdges
, filterG
, components
, anyConnections
, edgePath
, inARow
, values
, winningSetPaths
, winningSetPaths'
, coloredGraphVertexPositions
, coloredGraphSetVertexPosition
, coloredGraphGetVertexPosition
, coloredGraphEdgePositions
, coloredGraphGetEdgePosition
, coloredGraphSetEdgePosition
, coloredGraphSetBidirectedEdgePosition
) where
import Data.Map (Map, mapMaybeWithKey, filterWithKey)
import qualified Data.Map as Map
import Data.Set (Set)
import qualified Data.Set as Set
import Data.List ( find, intersect, (\\) )
import Data.Maybe ( fromJust, isJust, listToMaybe, mapMaybe )
import Data.Tree (Tree(..), foldTree)
import Control.Monad ((<=<))
import Data.Bifunctor ( bimap, Bifunctor (first, second) )
import Boardgame (Position(..))
type ColoredGraph i a b = Map i (a, Map i b)
type Coordinate = (Int, Int)
hexDirections :: [Coordinate]
hexDirections =
[ (1, 0)
, (1, -1)
, (0, -1)
, (-1, 0)
, (-1, 1)
, (0, 1)
]
hexNeighbors :: Coordinate -> [Coordinate]
hexNeighbors (i, j) = bimap (+ i) (+ j) <$> hexDirections
octoDirections :: [Coordinate]
octoDirections =
[ (1, 0)
, (1, -1)
, (0, -1)
, (-1, -1)
, (-1, 0)
, (-1, 1)
, (0, 1)
, (1, 1)
]
octoNeighbors :: Coordinate -> [Coordinate]
octoNeighbors (i, j) = bimap (+ i) (+ j) <$> octoDirections
mapBoth :: (a -> b) -> (a, a) -> (b, b)
mapBoth f (x, y) = (f x, f y)
binaryOp :: (a -> b -> c) -> (a, a) -> (b, b) -> (c, c)
binaryOp op (x, y) (z, w) = (op x z, op y w)
hexHexGraphRing :: Int -> [Coordinate]
hexHexGraphRing base = concat [oneSide k | k <- [0..5]]
where
oneSide :: Int -> [Coordinate]
oneSide i = [binaryOp (\z w -> base*z + k*w) (hexDirections !! i) (hexDirections !! ((i+2) `mod` 6)) | k <- [1..base]]
distance :: Coordinate -> Coordinate -> Int
distance (x, y) (i, j) = (abs(x - i) + abs(x + y - i - j) + abs(y - j)) `div` 2
hexHexGraph :: Int -> ColoredGraph (Int, Int) Position (Int, Int)
hexHexGraph radius = Map.fromList ((\z -> (z , (Empty, Map.fromList $ filter ((< radius) . distance (0, 0) . fst) $ (\i -> (hexNeighbors z !! i, hexDirections !! i)) <$> [0..5]))) <$> nodes)
where
nodes :: [Coordinate]
nodes = (0, 0) : concatMap hexHexGraphRing [1..radius-1]
paraHexGraph :: Int -> ColoredGraph (Int, Int) Position (Int, Int)
paraHexGraph n = Map.fromList ((\z -> (z , (Empty, Map.fromList $ filter ((\(i, j) -> i < n && i >= 0 && j < n && j >= 0) . fst) $ (\i -> (hexNeighbors z !! i, hexDirections !! i)) <$> [0..5]))) <$> nodes)
where
nodes :: [Coordinate]
nodes = [(i, j) | i <- [0..n-1], j <- [0..n-1]]
rectOctGraph :: Int -> Int -> ColoredGraph (Int, Int) Position (Int, Int)
rectOctGraph m n = Map.fromList ((\z -> (z , (Empty, Map.fromList $ filter ((\(i, j) -> i < m && i >= 0 && j < n && j >= 0) . fst) $ (\i -> (octoNeighbors z !! i, octoDirections !! i)) <$> [0..7]))) <$> nodes)
where
nodes :: [Coordinate]
nodes = [(i, j) | i <- [0..m-1], j <- [0..n-1]]
triHexGraph :: Int -> ColoredGraph (Int, Int) Position (Int, Int)
triHexGraph n = Map.fromList ((\z -> (z, (Empty, Map.fromList $ filter ((\(i, j) -> i < n && i >= 0 && j < n && j >= 0 && i + j >= n) . fst) $ (\i -> (hexNeighbors z !! i, hexDirections !! i)) <$> [0 .. 5]))) <$> nodes)
where
nodes :: [Coordinate]
nodes = [(i, j) | i <- [0 .. n -1], j <- [0 .. n -1], i + j >= n]
completeGraph :: Int -> ColoredGraph Int () ()
completeGraph n = Map.fromList [ (i, ((), Map.fromList [(j, ()) | j <- [0..n-1], i /= j])) | i <- [0..n-1]]
firstJust :: (a -> Maybe b) -> [a] -> Maybe b
firstJust f = listToMaybe . mapMaybe f
mapMaybeG :: Ord i => ((a, Map i b) -> Maybe c) -> ColoredGraph i a b -> ColoredGraph i c b
mapMaybeG f g = fmap (second (Map.filterWithKey (\k _ -> Map.member k g'))) g'
where
g' = Map.mapMaybe (\(a, xs) -> (, xs) <$> f (a, xs)) g
filterG :: Ord i => ((a, Map i b) -> Bool) -> ColoredGraph i a b -> ColoredGraph i a b
filterG pred = mapMaybeG (\(z, w) -> if pred (z, w) then Just z else Nothing)
filterValues :: Ord i => (a -> Bool) -> ColoredGraph i a b -> ColoredGraph i a b
filterValues pred = filterG $ pred . fst
mapValues :: Ord i => (a -> c) -> ColoredGraph i a b -> ColoredGraph i c b
mapValues = fmap . first
mapEdges :: Ord i => (b -> c) -> ColoredGraph i a b -> ColoredGraph i a c
mapEdges = fmap . second . fmap
nodesPred :: (a -> Map i b -> Bool) -> ColoredGraph i a b -> [i]
nodesPred pred g = fst <$> filter (uncurry pred . snd) (Map.toList g)
filterEdges :: (b -> Bool) -> ColoredGraph i a b -> ColoredGraph i a b
filterEdges pred = fmap $ second $ Map.filter pred
path :: Ord i => ColoredGraph i a b -> i -> i -> Maybe [(b, i)]
path = path' Set.empty
path' :: Ord i => Set i -> ColoredGraph i a b -> i -> i -> Maybe [(b, i)]
path' s g i j
| i == j = Just []
| otherwise = firstJust (\(k, d) -> ((d, k):) <$> path' s' g k j) $ filter (\(k, _) -> not $ k `Set.member` s') neighbours
where
neighbours = Map.assocs $ snd $ g Map.! i
s' = Set.insert i s
components :: (Eq i, Ord i) => ColoredGraph i a b -> [[i]]
components = components' []
where
components' :: (Eq i, Ord i) => [[i]] -> ColoredGraph i a b -> [[i]]
components' state g = case find (\k -> all (notElem k) state) (Map.keys g) of
Just i -> components' (component g i : state) g
Nothing -> state
component :: Ord i => ColoredGraph i a b -> i -> [i]
component g = fst . component' Set.empty g
where
component' :: Ord i => Set i -> ColoredGraph i a b -> i -> ([i], Set i)
component' inputState g i = (i : xs, newState)
where
neighbours = Map.assocs $ snd $ g Map.! i
(xs, newState) = foldl tmp ([], Set.insert i inputState) (fst <$> neighbours)
tmp (ks, state) k
| k `Set.member` state = (ks, state)
| otherwise = let (x, y) = component' state g k in (ks ++ x, y)
values :: ColoredGraph i a b -> [a]
values = fmap fst . Map.elems
inducedSubgraph :: Eq i => ColoredGraph i a b -> [i] -> ColoredGraph i a b
inducedSubgraph g nodes = mapMaybeWithKey tmp g
where
tmp i (a, xs) = if i `elem` nodes
then Just (a, filterWithKey (const . flip elem nodes) xs)
else Nothing
anyConnections :: Ord i => (Int -> Bool) -> [[i]] -> ColoredGraph i a b -> Maybe [i]
anyConnections pred groups = findComponent cond
where
cond z = pred $ length $ filter (not . Prelude.null . intersect z) groups
inARow :: (Ord i, Eq b) => (Int -> Bool) -> b -> ColoredGraph i a b -> Maybe [i]
inARow pred dir = findComponent (pred . length) . filterEdges (==dir)
findComponent :: Ord i => ([i] -> Bool) -> ColoredGraph i a b -> Maybe [i]
findComponent pred g = minimizeComponent <$> find pred (components g)
where
minimizeComponent xs = maybe xs minimizeComponent $ find cond $ oneRemoved xs
where
cond z = pred z && 1 == length (components $ inducedSubgraph g z)
oneRemoved :: [i] -> [[i]]
oneRemoved [] = []
oneRemoved [x] = [[]]
oneRemoved (x:xs) = xs : ((x:) <$> oneRemoved xs)
winningSetPaths :: Ord i => ColoredGraph i a b -> [i] -> [i] -> [[i]]
winningSetPaths g is js = concat [foldTree (\(isLeaf, z) xs -> if isLeaf then [[z]] else concatMap (fmap (z:)) xs) $ winningSetPaths' g start i goal | i <- is]
where
allTrue = True <$ g
start = foldr (`Map.insert` False) allTrue is
allFalse = False <$ g
goal = foldr (`Map.insert` True) allFalse js
winningSetPaths' :: Ord i => ColoredGraph i a b -> Map i Bool -> i -> Map i Bool -> Tree (Bool, i)
winningSetPaths' g allowed i goal = Node (False, i) $ (\k -> if fromJust $ Map.lookup k goal then Node (True, k) [] else winningSetPaths' g allowed' k goal) <$> neighbourIndices
where
neighbourIndices = filter (fromJust . flip Map.lookup allowed) $ Map.keys $ snd $ fromJust $ Map.lookup i g
allowed' = foldr (`Map.insert` False) allowed neighbourIndices
edgePath :: [a] -> [(a, a)]
edgePath a = zip a (tail a)
coloredGraphVertexPositions :: (ColoredGraphTransformer i a b g, Ord i) => g -> [a]
coloredGraphVertexPositions = values . toColoredGraph
coloredGraphGetVertexPosition :: (ColoredGraphTransformer i a b g, Ord i) => g -> i -> Maybe a
coloredGraphGetVertexPosition g i = fst <$> Map.lookup i (toColoredGraph g)
coloredGraphSetVertexPosition :: (ColoredGraphTransformer i a b g, Ord i) => g -> i -> a -> Maybe g
coloredGraphSetVertexPosition g i p = if Map.member i c
then Just $ fromColoredGraph g $ Map.adjust (\(_, xs) -> (p, xs)) i c
else Nothing
where
c = toColoredGraph g
coloredGraphEdgePositions :: (ColoredGraphTransformer i a b g, Ord i) => g -> [b]
coloredGraphEdgePositions = Map.elems . snd <=< Map.elems . toColoredGraph
coloredGraphGetEdgePosition :: (ColoredGraphTransformer i a b g, Ord i) => g -> (i, i) -> Maybe b
coloredGraphGetEdgePosition g (from, to) = Map.lookup from (toColoredGraph g) >>= (Map.lookup to . snd)
coloredGraphSetEdgePosition :: (ColoredGraphTransformer i a b g, Ord i) => g -> (i, i) -> b -> Maybe g
coloredGraphSetEdgePosition g (from, to) p = Map.lookup from c >>=
\(a, edges) -> if Map.member to edges
then Just $ fromColoredGraph g $ Map.insert from (a, Map.insert to p edges) c
else Nothing
where
c = toColoredGraph g
coloredGraphSetBidirectedEdgePosition :: (ColoredGraphTransformer i a b g, Ord i) => g -> (i, i) -> b -> Maybe g
coloredGraphSetBidirectedEdgePosition c (from, to) p = coloredGraphSetEdgePosition c (from, to) p >>=
\c' -> coloredGraphSetEdgePosition c' (to, from) p
class ColoredGraphTransformer i a b g | g -> i, g -> a, g -> b where
toColoredGraph :: g -> ColoredGraph i a b
fromColoredGraph :: g -> ColoredGraph i a b -> g
instance ColoredGraphTransformer i a b (ColoredGraph i a b) where
toColoredGraph c = c
fromColoredGraph _ = id