intricacy-0.4.1: GameState.hs
-- This file is part of Intricacy
-- Copyright (C) 2013 Martin Bays <mbays@sdf.org>
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of version 3 of the GNU General Public License as
-- published by the Free Software Foundation, or any later version.
--
-- You should have received a copy of the GNU General Public License
-- along with this program. If not, see http://www.gnu.org/licenses/.
module GameState where
import Control.Applicative hiding ((<*>))
import Data.Function (on)
import qualified Data.Map as Map
import Data.Map (Map)
import qualified Data.Set as Set
import Data.Set (Set)
import qualified Data.Vector as Vector
import Data.Maybe
import Control.Monad
import Control.Monad.State
import Data.List
import Data.Vector (Vector, (!), (//))
import Hex
import Util
import GameStateTypes
--import Debug
ppidxs :: GameState -> [PieceIdx]
ppidxs = Vector.toList . (Vector.findIndices $ const True) . placedPieces
getpp :: GameState -> PieceIdx -> PlacedPiece
getpp st idx = (placedPieces st) ! idx
setpp :: PieceIdx -> PlacedPiece -> GameState -> GameState
setpp idx pp st@(GameState pps _) =
let displacement = (placedPos $ getpp st idx) <-> placedPos pp
updateConn conn@(Connection root@(ridx,rpos) end@(eidx,epos) link)
| ridx == idx = Connection (ridx,rpos<+>displacement) end link
| eidx == idx = Connection root (eidx,epos<+>displacement) link
| otherwise = conn
in st {placedPieces = pps // [(idx, pp)]
, connections = map updateConn $ connections st }
addpp :: PlacedPiece -> GameState -> GameState
addpp pp st@(GameState pps _) = st {placedPieces = Vector.snoc pps pp}
addConn :: Connection -> GameState -> GameState
addConn conn st@(GameState _ conns) = st {connections = conn:conns}
type Component = (HexVec, Set HexVec)
components :: Set HexVec -> [Component]
components patt
| Set.null patt = []
| otherwise =
let c = if zero `Set.member` patt then zero else head $ Set.toList patt
(patt',comp) = floodfill c patt
in ( (c, Set.map (<+> neg c) comp) : components patt' )
floodfill :: HexVec -> Set HexVec -> (Set HexVec, Set HexVec)
floodfill start patt = floodfill' start `execState` (patt, Set.empty)
where
floodfill' :: HexVec -> State (Set HexVec, Set HexVec) ()
floodfill' start = do
(patt, dels) <- get
let patt' = Set.delete start patt
unless (Set.size patt' == Set.size patt) $ do
put (patt', Set.insert start dels)
sequence_ [ floodfill' (dir<+>start) | dir <- hexDirs ]
delPiece :: PieceIdx -> GameState -> GameState
delPiece idx (GameState pps conns) =
GameState (Vector.concat [Vector.take idx pps, Vector.drop (idx+1) pps])
[ Connection (ridx',rv) (eidx',ev) link |
Connection (ridx,rv) (eidx,ev) link <- conns
, ridx /= idx
, eidx /= idx
, let ridx' = if ridx > idx then ridx-1 else ridx
, let eidx' = if eidx > idx then eidx-1 else eidx ]
delPieceIn :: HexPos -> GameState -> GameState
delPieceIn pos st =
case liftM fst $ Map.lookup pos $ stateBoard st of
Just idx -> delPiece idx st
_ -> st
setPiece :: PieceIdx -> Piece -> GameState -> GameState
setPiece idx p st =
setpp idx (PlacedPiece (placedPos $ getpp st idx) p) st
adjustPieces :: (Piece -> Piece) -> GameState -> GameState
adjustPieces f st =
st { placedPieces = fmap
(\pp -> pp { placedPiece = f $ placedPiece pp })
$ placedPieces st }
addBlockPos :: PieceIdx -> HexPos -> GameState -> GameState
addBlockPos b pos st =
let PlacedPiece ppos (Block patt) = getpp st b
in setPiece b (Block (pos <-> ppos:patt)) st
addPivotArm :: PieceIdx -> HexPos -> GameState -> GameState
addPivotArm p pos st =
let PlacedPiece ppos (Pivot arms) = getpp st p
in setPiece p (Pivot (pos <-> ppos:arms)) st
locusPos :: GameState -> Locus -> HexPos
locusPos s (idx,v) = v <+> (placedPos $ getpp s idx)
posLocus :: GameState -> HexPos -> Maybe Locus
posLocus st pos = listToMaybe [ (idx,pos<->ppos) |
(idx,pp@(PlacedPiece ppos _)) <- enumVec $ placedPieces st
, pos `elem` plPieceFootprint pp ]
connectionLength :: GameState -> Connection -> Int
connectionLength st (Connection root end _) =
let rootPos = locusPos st root
endPos = locusPos st end
in hexLen (endPos <-> rootPos) - 1
springsAtIdx,springsEndAtIdx,springsRootAtIdx :: GameState -> PieceIdx -> [Connection]
springsAtIdx st idx =
[ c | c@(Connection (ridx,_) (eidx, _) (Spring _ _)) <- connections st
, idx `elem` [ridx,eidx] ]
springsAtIdxIgnoring st idx idx' =
[ c | c@(Connection (ridx,_) (eidx, _) (Spring _ _)) <- connections st
, idx `elem` [ridx,eidx], idx' `notElem` [ridx,eidx] ]
springsEndAtIdx st idx =
[ c | c@(Connection _ (eidx, _) (Spring _ _)) <- connections st
, eidx==idx ]
springsRootAtIdx st idx =
[ c | c@(Connection (ridx, _) _ (Spring _ _)) <- connections st
, ridx==idx ]
connectionsBetween :: GameState -> PieceIdx -> PieceIdx -> [Connection]
connectionsBetween st idx idx' =
filter connIsBetween $ connections st
where
connIsBetween conn =
isPerm (idx,idx') (fst $ connectionRoot conn, fst $ connectionEnd conn)
isPerm = (==) `on` (\(x,y) -> Set.fromList [x,y])
connGraphPathExists :: GameState -> PieceIdx -> PieceIdx -> Bool
connGraphPathExists st ridx eidx = (ridx == eidx) ||
any ((connGraphPathExists st `flip` eidx) . fst . connectionEnd)
(springsRootAtIdx st ridx)
connGraphHeight :: GameState -> PieceIdx -> Int
connGraphHeight st idx =
maximum (0 : map ((+1) . connGraphHeight st . fst . connectionRoot) (springsEndAtIdx st idx))
type Digraph a = Map a (Set a)
checkConnGraphAcyclic :: GameState -> Bool
checkConnGraphAcyclic st =
let idxs = ppidxs st
leaves dg = map fst $ filter (Set.null . snd) $ Map.toList dg
checkDigraphAcyclic :: Ord a => Digraph a -> Bool
checkDigraphAcyclic dg = case listToMaybe $ leaves dg of
Nothing -> Map.null dg
Just leaf -> checkDigraphAcyclic $ Map.delete leaf $ fmap (Set.delete leaf) dg
in checkDigraphAcyclic $ Map.fromList
[ (idx, Set.fromList $ map (fst.connectionRoot) $ springsEndAtIdx st idx) | idx <- idxs ]
repossessConns :: GameState -> GameState -> GameState
repossessConns st st' =
st' {connections = [ Connection root' end' link |
Connection root end link <- connections st
, root' <- maybeToList $ posLocus st' $ locusPos st root
, end' <- maybeToList $ posLocus st' $ locusPos st end ] }
delConnectionsIn :: HexPos -> GameState -> GameState
delConnectionsIn pos st =
st {connections = filter
((pos `notElem`) . connectionFootPrint st)
$ connections st}
delPiecePos :: PieceIdx -> HexPos -> GameState -> (GameState, Maybe PieceIdx)
-- ^ returns new state and the new index of what remains of the piece, if
-- anything
delPiecePos idx pos st =
let PlacedPiece ppos p = getpp st idx
v = pos <-> ppos
in case p of
Block patt ->
let (st',midx) = componentify idx $ setpp idx (PlacedPiece ppos $ Block $ patt \\ [v]) st
in (repossessConns st st', midx)
Pivot arms -> if v == zero
then (delPiece idx st, Nothing)
else ((setPiece idx $ Pivot $ arms \\ [v]) st, Just idx)
_ -> (delPiece idx st, Nothing)
componentify :: PieceIdx -> GameState -> (GameState, Maybe PieceIdx)
componentify idx st = let PlacedPiece ppos p = getpp st idx
in case p of
Block patt ->
let comps = components $ Set.fromList patt
ppOfComp (v,patt) = PlacedPiece (v<+>ppos) $ Block $ Set.toList patt
in case comps of
[] -> (delPiece idx st, Nothing)
zeroComp:newComps ->
(setpp idx (ppOfComp zeroComp)
$ foldr (addpp . ppOfComp) st newComps, Just idx)
_ -> (st,Nothing)
springExtended,springCompressed,springFullyExtended
,springFullyCompressed :: GameState -> Connection -> Bool
springExtended st c@(Connection _ _ (Spring _ natLen)) =
connectionLength st c > natLen
springExtended _ _ = False
springCompressed st c@(Connection _ _ (Spring _ natLen)) =
connectionLength st c < natLen
springCompressed _ _ = False
springFullyExtended st c@(Connection _ _ (Spring _ natLen)) =
connectionLength st c >= 2*natLen
springFullyExtended _ _ = False
springFullyCompressed st c@(Connection _ _ (Spring _ natLen)) =
connectionLength st c <= (natLen+1)`div`2
springFullyCompressed _ _ = False
springExtensionValid st c@(Connection _ _ (Spring _ natLen)) =
let l = connectionLength st c
in l >= (natLen+1)`div`2 && l <= 2*natLen
springExtensionValid _ _ = True
stateBoard :: GameState -> GameBoard
stateBoard st@(GameState plPieces conns) =
addConnAdjs st conns $
(Map.unions $ map plPieceBoard $ enumVec plPieces) `Map.union`
(Map.unions $ map (connectionBoard st) conns)
addConnAdjs :: GameState -> [Connection] -> GameBoard -> GameBoard
addConnAdjs st = flip $ foldr addConnAdj
where
addConnAdj (Connection root end (Spring dir _)) board =
addAdj (locusPos st root) dir $
addAdj (locusPos st end) (neg dir) board
addConnAdj _ board = board
addAdj pos d =
Map.adjust (\(o,tile) -> (o,case tile of
BlockTile adjs -> BlockTile (d:adjs)
_ -> tile))
pos
plPieceBoard :: (PieceIdx,PlacedPiece) -> GameBoard
plPieceBoard (idx,pp) = fmap (\x -> (idx,x)) $ plPieceMap pp
plPieceMap :: PlacedPiece -> Map HexPos Tile
plPieceMap (PlacedPiece pos (Block pattern)) =
Map.fromList [ (rel <+> pos, BlockTile adjs)
| rel <- pattern
, let adjs = filter (\dir -> rel <+> dir `elem` pattern) hexDirs ]
plPieceMap (PlacedPiece pos (Pivot arms)) =
let overarmed = length arms > 2 in
Map.fromList $ (pos, PivotTile $ if overarmed then (head arms) else zero ) :
[ (rel <+> pos, ArmTile rel main)
| (rel,main) <- zip arms $ repeat False ]
plPieceMap (PlacedPiece pos (Hook arm _)) =
Map.fromList $ (pos, HookTile) : [ (arm <+> pos, ArmTile arm True) ]
plPieceMap (PlacedPiece pos (Wrench mom)) = Map.singleton pos $ WrenchTile mom
plPieceMap (PlacedPiece pos Ball) = Map.singleton pos BallTile
plPieceFootprint :: PlacedPiece -> [HexPos]
plPieceFootprint = Map.keys . plPieceMap
fullFootprint :: GameState -> PieceIdx -> [HexPos]
-- ^footprint of piece and connections ending at it
fullFootprint st idx = plPieceFootprint (getpp st idx) ++
(concat $ map (connectionFootPrint st) $ springsEndAtIdx st idx)
footprintAt :: GameState -> PieceIdx -> [HexPos]
-- ^footprint of piece and any connections at it
footprintAt st idx = plPieceFootprint (getpp st idx) ++
(concat $ map (connectionFootPrint st) $ springsAtIdx st idx)
footprintAtIgnoring :: GameState -> PieceIdx -> PieceIdx -> [HexPos]
-- ^footprint of piece and any connections at it, except those with idx'
footprintAtIgnoring st idx idx' = plPieceFootprint (getpp st idx) ++
(concat $ map (connectionFootPrint st) $ springsAtIdxIgnoring st idx idx')
collisions :: GameState -> PieceIdx -> PieceIdx -> [HexPos]
-- ^intersections of two pieces and their connections, disregarding
-- the connections which connect the two pieces
collisions st idx idx' =
intersect (footprintAt st idx) (footprintAt st idx') \\
(concat $ map (connectionFootPrint st) $ connectionsBetween st idx idx')
connectionBoard :: GameState -> Connection -> GameBoard
connectionBoard st (Connection root end@(eidx,_) (Spring dir natLen)) =
let rootPos = locusPos st root
endPos = locusPos st end
curLen = hexLen (endPos <-> rootPos) - 1
in Map.fromList $
[ ((d <*> dir) <+> rootPos, (eidx, SpringTile extension dir))
| d <- [1..curLen],
let extension | d <= natLen - curLen = Compressed
| curLen-d < 2*(curLen - natLen) = Stretched
| otherwise = Relaxed ]
connectionBoard _ _ = Map.empty
connectionFootPrint :: GameState -> Connection -> [HexPos]
connectionFootPrint s c = Map.keys $ connectionBoard s c
castRay :: HexPos -> HexDir -> GameBoard -> Maybe (PieceIdx, HexPos)
castRay start dir board =
castRay' 30 start
where castRay' 0 _ = Nothing
castRay' n pos =
case Map.lookup pos board of
Nothing -> castRay' (n-1) (dir<+>pos)
Just (idx,_) -> Just (idx,pos)
validGameState :: GameState -> Bool
validGameState st@(GameState pps conns) = and
[ checkValidHex st
, checkConnGraphAcyclic st
, and [ null $ collisions st idx idx'
| idx <- ppidxs st
, idx' <- [0..idx-1] ]
, and [ isHexDir dir
&& castRay (dir<+>rpos) dir
(stateBoard $ GameState pps (conns \\ [c]))
== Just (eidx, epos)
&& springExtensionValid st c
&& (validRoot st root)
&& (validEnd st end)
| c@(Connection root@(ridx,_) end@(eidx,_) (Spring dir _)) <- conns
, let [rpos,epos] = map (locusPos st) [root,end] ]
, and [ 1 == length (components $ Set.fromList patt)
| Block patt <- map placedPiece $ Vector.toList pps ]
]
validRoot st (idx,v) = case placedPiece $ getpp st idx of
(Block _) -> True
(Pivot _) -> v==zero
_ -> False
validEnd st (idx,_) = case placedPiece $ getpp st idx of
(Block _) -> True
_ -> False
checkValidHex (GameState pps conns) = and
[ all validPP $ Vector.toList pps
, all validConn conns ]
where
validVec (HexVec x y z) = x+y+z==0
validPos (PHS v) = validVec v
validDir v = validVec v && isHexDir v
validPP (PlacedPiece pos piece) = validPos pos && validPiece piece
validPiece (Block patt) = all validVec patt
validPiece (Pivot arms) = all validDir arms
validPiece (Hook dir _) = validDir dir
validPiece _ = True
validConn (Connection (_,rv) (_,ev) link) = all validVec [rv,ev] && validLink link
validLink (Free v) = validVec v
validLink (Spring dir _) = validDir dir
protectedPiece :: PieceIdx -> Bool
protectedPiece = isFrame
isFrame :: PieceIdx -> Bool
isFrame = (==0)