1 -- This file is part of Intricacy
2 -- Copyright (C) 2013 Martin Bays <mbays@sdf.org>
4 -- This program is free software: you can redistribute it and/or modify
5 -- it under the terms of version 3 of the GNU General Public License as
6 -- published by the Free Software Foundation, or any later version.
8 -- You should have received a copy of the GNU General Public License
9 -- along with this program. If not, see http://www.gnu.org/licenses/.
11 {-# LANGUAGE FlexibleContexts #-}
12 {-# LANGUAGE TupleSections #-}
13 module GameState
where
15 import Control
.Applicative
17 import Control
.Monad
.State
18 import Data
.Function
(on
)
21 import qualified Data
.Map
as Map
24 import qualified Data
.Set
as Set
25 import Data
.Vector
(Vector
, (!), (//))
26 import qualified Data
.Vector
as Vector
33 ppidxs
:: GameState
-> [PieceIdx
]
34 ppidxs
= Vector
.toList
. Vector
.findIndices (const True) . placedPieces
36 getpp
:: GameState
-> PieceIdx
-> PlacedPiece
37 getpp st idx
= placedPieces st
! idx
39 setpp
:: PieceIdx
-> PlacedPiece
-> GameState
-> GameState
40 setpp idx pp st
@(GameState pps _
) =
41 let displacement
= placedPos
(getpp st idx
) -^ placedPos pp
42 updateConn conn
@(Connection root
@(ridx
,rpos
) end
@(eidx
,epos
) link
)
43 | ridx
== idx
= Connection
(ridx
,rpos
+^displacement
) end link
44 | eidx
== idx
= Connection root
(eidx
,epos
+^displacement
) link
46 in st
{placedPieces
= pps
// [(idx
, pp
)]
47 , connections
= updateConn
<$> connections st
}
49 addpp
:: PlacedPiece
-> GameState
-> GameState
50 addpp pp st
@(GameState pps _
) = st
{placedPieces
= Vector
.snoc pps pp
}
52 addConn
:: Connection
-> GameState
-> GameState
53 addConn conn st
@(GameState _ conns
) = st
{connections
= conn
:conns
}
55 type Component
= (HexVec
, Set HexVec
)
56 components
:: Set HexVec
-> [Component
]
60 let c
= if zero `Set
.member` patt
then zero
else head $ Set
.toList patt
61 (patt
',comp
) = floodfill c patt
62 in ( (c
, Set
.map (+^ neg c
) comp
) : components patt
' )
64 floodfill
:: HexVec
-> Set HexVec
-> (Set HexVec
, Set HexVec
)
65 floodfill start patt
= floodfill
' start `execState`
(patt
, Set
.empty)
67 floodfill
' :: HexVec
-> State
(Set HexVec
, Set HexVec
) ()
70 let patt
' = Set
.delete start patt
71 unless (Set
.size patt
' == Set
.size patt
) $ do
72 put
(patt
', Set
.insert start dels
)
73 sequence_ [ floodfill
' (dir
+^start
) | dir
<- hexDirs
]
75 delPiece
:: PieceIdx
-> GameState
-> GameState
76 delPiece idx
(GameState pps conns
) =
77 GameState
(Vector
.concat [Vector
.take idx pps
, Vector
.drop (idx
+1) pps
])
78 [ Connection
(ridx
',rv
) (eidx
',ev
) link |
79 Connection
(ridx
,rv
) (eidx
,ev
) link
<- conns
82 , let ridx
' = if ridx
> idx
then ridx
-1 else ridx
83 , let eidx
' = if eidx
> idx
then eidx
-1 else eidx
]
85 delPieceIn
:: HexPos
-> GameState
-> GameState
87 case fst <$> Map
.lookup pos
(stateBoard st
) of
88 Just idx
-> delPiece idx st
91 setPiece
:: PieceIdx
-> Piece
-> GameState
-> GameState
93 setpp idx
(PlacedPiece
(placedPos
$ getpp st idx
) p
) st
95 adjustPieces
:: (Piece
-> Piece
) -> GameState
-> GameState
98 (\pp
-> pp
{ placedPiece
= f
$ placedPiece pp
}) <$> placedPieces st
}
100 addBlockPos
:: PieceIdx
-> HexPos
-> GameState
-> GameState
101 addBlockPos b pos st
=
102 let PlacedPiece ppos
(Block patt
) = getpp st b
103 in setPiece b
(Block
(pos
-^ ppos
:patt
)) st
105 addPivotArm
:: PieceIdx
-> HexPos
-> GameState
-> GameState
106 addPivotArm p pos st
=
107 let PlacedPiece ppos
(Pivot arms
) = getpp st p
108 in setPiece p
(Pivot
(pos
-^ ppos
:arms
)) st
110 locusPos
:: GameState
-> Locus
-> HexPos
111 locusPos s
(idx
,v
) = v
+^ placedPos
(getpp s idx
)
113 posLocus
:: GameState
-> HexPos
-> Maybe Locus
114 posLocus st pos
= listToMaybe [ (idx
,pos
-^ppos
) |
115 (idx
,pp
@(PlacedPiece ppos _
)) <- enumVec
$ placedPieces st
116 , pos `
elem` plPieceFootprint pp
]
118 connectionLength
:: GameState
-> Connection
-> Int
119 connectionLength st
(Connection root end _
) =
120 let rootPos
= locusPos st root
121 endPos
= locusPos st end
122 in hexLen
(endPos
-^ rootPos
) - 1
124 springsAtIdx
,springsEndAtIdx
,springsRootAtIdx
:: GameState
-> PieceIdx
-> [Connection
]
125 springsAtIdx st idx
=
126 [ c | c
@(Connection
(ridx
,_
) (eidx
, _
) (Spring _ _
)) <- connections st
127 , idx `
elem`
[ridx
,eidx
] ]
128 springsAtIdxIgnoring st idx idx
' =
129 [ c | c
@(Connection
(ridx
,_
) (eidx
, _
) (Spring _ _
)) <- connections st
130 , idx `
elem`
[ridx
,eidx
], idx
' `
notElem`
[ridx
,eidx
] ]
131 springsEndAtIdx st idx
=
132 [ c | c
@(Connection _
(eidx
, _
) (Spring _ _
)) <- connections st
134 springsRootAtIdx st idx
=
135 [ c | c
@(Connection
(ridx
, _
) _
(Spring _ _
)) <- connections st
137 connectionsBetween
:: GameState
-> PieceIdx
-> PieceIdx
-> [Connection
]
138 connectionsBetween st idx idx
' =
139 filter connIsBetween
$ connections st
142 isPerm
(idx
,idx
') (fst $ connectionRoot conn
, fst $ connectionEnd conn
)
143 isPerm
= (==) `on`
(\(x
,y
) -> Set
.fromList
[x
,y
])
145 connGraphPathExists
:: GameState
-> PieceIdx
-> PieceIdx
-> Bool
146 connGraphPathExists st ridx eidx
= (ridx
== eidx
) ||
147 any ((connGraphPathExists st `
flip` eidx
) . fst . connectionEnd
)
148 (springsRootAtIdx st ridx
)
150 connGraphHeight
:: GameState
-> PieceIdx
-> Int
151 connGraphHeight st idx
=
152 maximum . (0:) $ (+1) . connGraphHeight st
. fst . connectionRoot
<$> springsEndAtIdx st idx
154 type Digraph a
= Map a
(Set a
)
155 checkConnGraphAcyclic
:: GameState
-> Bool
156 checkConnGraphAcyclic st
=
158 leaves dg
= (fst <$>) . filter (Set
.null . snd) $ Map
.toList dg
159 checkDigraphAcyclic
:: Ord a
=> Digraph a
-> Bool
160 checkDigraphAcyclic dg
= case listToMaybe $ leaves dg
of
161 Nothing
-> Map
.null dg
162 Just leaf
-> checkDigraphAcyclic
$ Map
.delete leaf
$ Set
.delete leaf
<$> dg
163 in checkDigraphAcyclic
$ Map
.fromList
164 [ (idx
, Set
.fromList
$ fst . connectionRoot
<$> springsEndAtIdx st idx
) | idx
<- idxs
]
166 repossessConns
:: GameState
-> GameState
-> GameState
167 repossessConns st st
' =
168 st
' {connections
= [ Connection root
' end
' link |
169 Connection root end link
<- connections st
170 , root
' <- maybeToList $ posLocus st
' $ locusPos st root
171 , end
' <- maybeToList $ posLocus st
' $ locusPos st end
] }
173 delConnectionsIn
:: HexPos
-> GameState
-> GameState
174 delConnectionsIn pos st
=
175 st
{connections
= filter
176 ((pos `
notElem`
) . connectionFootPrint st
)
179 delPiecePos
:: PieceIdx
-> HexPos
-> GameState
-> (GameState
, Maybe PieceIdx
)
180 -- ^ returns new state and the new index of what remains of the piece, if
182 delPiecePos idx pos st
=
183 let PlacedPiece ppos p
= getpp st idx
187 let (st
',midx
) = componentify idx
$ setpp idx
(PlacedPiece ppos
$ Block
$ patt
\\ [v
]) st
188 in (repossessConns st st
', midx
)
189 Pivot arms
-> if v
== zero
190 then (delPiece idx st
, Nothing
)
191 else ((setPiece idx
$ Pivot
$ arms
\\ [v
]) st
, Just idx
)
192 _
-> (delPiece idx st
, Nothing
)
193 componentify
:: PieceIdx
-> GameState
-> (GameState
, Maybe PieceIdx
)
194 componentify idx st
= let PlacedPiece ppos p
= getpp st idx
197 let comps
= components
$ Set
.fromList patt
198 ppOfComp
(v
,patt
) = PlacedPiece
(v
+^ppos
) $ Block
$ Set
.toList patt
200 [] -> (delPiece idx st
, Nothing
)
202 (setpp idx
(ppOfComp zeroComp
)
203 $ foldr (addpp
. ppOfComp
) st newComps
, Just idx
)
206 springExtended
,springCompressed
,springFullyExtended
207 ,springFullyCompressed
:: GameState
-> Connection
-> Bool
208 springExtended st c
@(Connection _ _
(Spring _ natLen
)) =
209 connectionLength st c
> natLen
210 springExtended _ _
= False
211 springCompressed st c
@(Connection _ _
(Spring _ natLen
)) =
212 connectionLength st c
< natLen
213 springCompressed _ _
= False
214 springFullyExtended st c
@(Connection _ _
(Spring _ natLen
)) =
215 connectionLength st c
>= 2*natLen
216 springFullyExtended _ _
= False
217 springFullyCompressed st c
@(Connection _ _
(Spring _ natLen
)) =
218 connectionLength st c
<= (natLen
+1)`
div`
2
219 springFullyCompressed _ _
= False
220 springExtensionValid st c
@(Connection _ _
(Spring _ natLen
)) =
221 let l
= connectionLength st c
222 in l
>= (natLen
+1)`
div`
2 && l
<= 2*natLen
223 springExtensionValid _ _
= True
225 stateBoard
:: GameState
-> GameBoard
226 stateBoard st
@(GameState plPieces conns
) =
227 addConnAdjs st conns
$
228 Map
.unions
(plPieceBoard
<$> enumVec plPieces
) `Map
.union`
229 Map
.unions
(connectionBoard st
<$> conns
)
231 addConnAdjs
:: GameState
-> [Connection
] -> GameBoard
-> GameBoard
232 addConnAdjs st
= flip $ foldr addConnAdj
234 addConnAdj
(Connection root end
(Spring dir _
)) board
=
235 addAdj
(locusPos st root
) dir
$
236 addAdj
(locusPos st end
) (neg dir
) board
237 addConnAdj _ board
= board
239 Map
.adjust
(\(o
,tile
) -> (o
,case tile
of
240 BlockTile adjs
-> BlockTile
(d
:adjs
)
244 plPieceBoard
:: (PieceIdx
,PlacedPiece
) -> GameBoard
245 plPieceBoard
(idx
,pp
) = (idx
,) <$> plPieceMap pp
247 plPieceMap
:: PlacedPiece
-> Map HexPos Tile
248 plPieceMap
(PlacedPiece pos
(Block patt
)) =
249 let pattSet
= Set
.fromList patt
250 in Map
.fromList
[ (rel
+^ pos
, BlockTile adjs
)
252 , let adjs
= filter (\dir
-> (rel
+^ dir
) `Set
.member` pattSet
) hexDirs
]
253 plPieceMap
(PlacedPiece pos
(Pivot arms
)) =
254 let overarmed
= length arms
> 2 in
255 Map
.fromList
$ (pos
, PivotTile
$ if overarmed
then head arms
else zero
) :
256 [ (rel
+^ pos
, ArmTile rel main
)
257 |
(rel
,main
) <- map (,False) arms
]
258 plPieceMap
(PlacedPiece pos
(Hook arm _
)) =
259 Map
.fromList
$ (pos
, HookTile
) : [ (arm
+^ pos
, ArmTile arm
True) ]
260 plPieceMap
(PlacedPiece pos
(Wrench mom
)) = Map
.singleton pos
$ WrenchTile mom
261 plPieceMap
(PlacedPiece pos Ball
) = Map
.singleton pos BallTile
263 plPieceFootprint
:: PlacedPiece
-> [HexPos
]
264 plPieceFootprint
= Map
.keys
. plPieceMap
266 fullFootprint
:: GameState
-> PieceIdx
-> [HexPos
]
267 -- ^footprint of piece and connections ending at it
268 fullFootprint st idx
= plPieceFootprint
(getpp st idx
) ++
269 concatMap (connectionFootPrint st
) (springsEndAtIdx st idx
)
271 footprintAt
:: GameState
-> PieceIdx
-> [HexPos
]
272 -- ^footprint of piece and any connections at it
273 footprintAt st idx
= plPieceFootprint
(getpp st idx
) ++
274 concatMap (connectionFootPrint st
) (springsAtIdx st idx
)
276 footprintAtIgnoring
:: GameState
-> PieceIdx
-> PieceIdx
-> [HexPos
]
277 -- ^footprint of piece and any connections at it, except those with idx'
278 footprintAtIgnoring st idx idx
' = plPieceFootprint
(getpp st idx
) ++
279 concatMap (connectionFootPrint st
) (springsAtIdxIgnoring st idx idx
')
281 collisions
:: GameState
-> PieceIdx
-> PieceIdx
-> [HexPos
]
282 -- ^intersections of two pieces and their connections, disregarding
283 -- the connections which connect the two pieces
284 collisions st idx idx
' =
285 intersect (footprintAt st idx
) (footprintAt st idx
') \\
286 concatMap (connectionFootPrint st
) (connectionsBetween st idx idx
')
288 connectionBoard
:: GameState
-> Connection
-> GameBoard
289 connectionBoard st
(Connection root end
@(eidx
,_
) (Spring dir natLen
)) =
290 let rootPos
= locusPos st root
291 endPos
= locusPos st end
292 curLen
= hexLen
(endPos
-^ rootPos
) - 1
294 [ ((d
*^ dir
) +^ rootPos
, (eidx
, SpringTile extension dir
))
296 let extension | d
<= natLen
- curLen
= Compressed
297 | curLen
-d
< 2*(curLen
- natLen
) = Stretched
298 |
otherwise = Relaxed
]
299 connectionBoard _ _
= Map
.empty
301 connectionFootPrint
:: GameState
-> Connection
-> [HexPos
]
302 connectionFootPrint s c
= Map
.keys
$ connectionBoard s c
304 castRay
:: HexPos
-> HexDir
-> GameBoard
-> Maybe (PieceIdx
, HexPos
)
305 castRay start dir board
=
307 where castRay
' 0 _
= Nothing
309 case Map
.lookup pos board
of
310 Nothing
-> castRay
' (n
-1) (dir
+^pos
)
311 Just
(idx
,_
) -> Just
(idx
,pos
)
313 validGameState
:: GameState
-> Bool
314 validGameState st
@(GameState pps conns
) = and
316 , checkConnGraphAcyclic st
317 , and [ null $ collisions st idx idx
'
319 , idx
' <- [0..idx
-1] ]
321 && castRay
(dir
+^rpos
) dir
322 (stateBoard
$ GameState pps
(conns
\\ [c
]))
324 && springExtensionValid st c
327 | c
@(Connection root
@(ridx
,_
) end
@(eidx
,_
) (Spring dir _
)) <- conns
328 , let [rpos
,epos
] = locusPos st
<$> [root
,end
] ]
329 , and [ 1 == length (components
$ Set
.fromList patt
)
330 | Block patt
<- placedPiece
<$> Vector
.toList pps
]
333 validRoot st
(idx
,v
) = case placedPiece
$ getpp st idx
of
337 validEnd st
(idx
,_
) = case placedPiece
$ getpp st idx
of
341 checkValidHex
(GameState pps conns
) = all validPP
(Vector
.toList pps
) && all validConn conns
343 validVec
(HexVec x y z
) = x
+y
+z
==0
344 validPos
(PHS v
) = validVec v
345 validDir v
= validVec v
&& isHexDir v
346 validPP
(PlacedPiece pos piece
) = validPos pos
&& validPiece piece
347 validPiece
(Block patt
) = all validVec patt
348 validPiece
(Pivot arms
) = all validDir arms
349 validPiece
(Hook dir _
) = validDir dir
351 validConn
(Connection
(_
,rv
) (_
,ev
) link
) = all validVec
[rv
,ev
] && validLink link
352 validLink
(Free v
) = validVec v
353 validLink
(Spring dir _
) = validDir dir
355 protectedPiece
:: PieceIdx
-> Bool
356 protectedPiece
= isFrame
357 isFrame
:: PieceIdx
-> Bool