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/.
13 import Control
.Monad
.State
14 import Control
.Monad
.Writer
15 import Data
.Foldable
(foldrM
)
18 import qualified Data
.Set
as Set
19 import Data
.Vector
(Vector
, (!), (//))
20 import qualified Data
.Vector
as Vector
27 -- | a list of forces to try in order:
28 newtype ForceChoice
= ForceChoice
{getForceChoice
:: [Force
]}
30 type ForceChoices
= Vector
.Vector ForceChoice
32 forceIdx
:: Force
-> PieceIdx
33 forceIdx force
= case force
of (Push idx _
) -> idx
36 isPush
,isTorque
,forceIsNull
:: Force
-> Bool
37 isPush
(Push _ _
) = True
39 isTorque
= not . isPush
40 forceIsNull
(Push _ dir
) = dir
== zero
41 forceIsNull
(Torque _ dir
) = dir
== 0
43 getForcedpp
:: GameState
-> Force
-> PlacedPiece
44 getForcedpp s f
= getpp s
(forceIdx f
)
46 -- |PlayerMove: if not NullPM, the direction should be non-zero
47 data PlayerMove
= NullPM | HookPush HexDir | HookTorque TorqueDir | WrenchPush HexDir
48 deriving (Eq
, Ord
, Show, Read)
50 toolForces
:: GameState
-> PlayerMove
-> ForceChoices
51 toolForces st pm
= Vector
.fromList
$
52 [ ForceChoice
(wmom
++wmove
)
53 |
(widx
, PlacedPiece _
(Wrench mom
)) <- epps
54 , let wmom
= [Push widx mom | mom
/= zero
]
55 , let wmove
= case pm
of {WrenchPush v
-> [Push widx v
]; _
-> []}
56 , not $ null (wmom
++wmove
)
58 HookTorque ht
-> [ ForceChoice
[Torque hidx ht
] | hidx
<- hidxs
]
59 HookPush hp
-> [ ForceChoice
[Push hidx hp
] | hidx
<- hidxs
]
62 epps
= enumVec
$ placedPieces st
63 hidxs
= [ hidx |
(hidx
, PlacedPiece _
(Hook _ _
)) <- epps
]
65 -- |Dominance: a propagand of a source force F can not block any progagand of
66 -- a source force which dominates F.
67 -- F dominates F' iff F and F' are spring forces, and the end of F is an
68 -- ancestor of the root of F'.
69 -- Note that by acyclicity of the connection digraph, domination is
71 type Dominance
= Int -> Int -> Bool
73 envForces
:: GameState
-> (ForceChoices
, Dominance
)
74 envForces st
@(GameState _ conns
) =
75 let rootedForces
:: Vector
(Maybe PieceIdx
, Force
)
76 rootedForces
= Vector
.fromList
[ (Just rootIdx
, Push endIdx dir
)
77 | c
@(Connection
(rootIdx
,_
) (endIdx
,_
) (Spring outDir natLen
)) <- conns
78 , let curLen
= connectionLength st c
80 , let dir
= if natLen
> curLen
then outDir
else neg outDir
]
82 in ( Vector
.map (ForceChoice
. replicate 1 . snd) rootedForces
,
83 \f1 f2
-> Just
True == do
84 rootIdx
<- fst $ rootedForces
!f2
85 return $ connGraphPathExists st
(forceIdx
.snd $ rootedForces
!f1
) rootIdx
)
88 setTools
:: PlayerMove
-> GameState
-> GameState
91 HookTorque dir
-> TorqueHF dir
92 HookPush v
-> PushHF v
94 in adjustPieces
(\p
-> case p
of
95 Hook arm _
-> Hook arm hf
98 physicsTick
:: PlayerMove
-> GameState
-> Writer
[Alert
] GameState
100 let tfs
= toolForces st pm
101 (efs
, dominates
) = envForces st
103 st
' <- resolveForces tfs Vector
.empty (\_ _
->False) $ setTools pm st
104 tell
[AlertIntermediateState st
']
105 resolveForces Vector
.empty efs dominates
$ setTools NullPM st
'
107 stepPhysics
:: GameState
-> GameState
108 stepPhysics
= fst.runWriter
.physicsTick NullPM
111 data SourcedForce
= SForce Source Force
Bool Bool
112 deriving (Eq
, Ord
, Show)
113 resolveForces
:: ForceChoices
-> ForceChoices
-> Dominance
-> GameState
-> Writer
[Alert
] GameState
114 resolveForces plForces eForces eDominates st
=
115 let pln
= Vector
.length plForces
116 dominates i j
= case map (< pln
) [i
,j
] of
118 [False,False] -> eDominates
(i
-pln
) (j
-pln
)
120 initGrps
= (propagate st
True <$> plForces
) Vector
.++
121 (propagate st
False <$> eForces
)
122 blockInconsistent
:: Int -> Int -> StateT
(Vector
(Writer Any
[Force
])) (Writer
[Alert
]) ()
123 blockInconsistent i j
= do
124 grps
<- mapM gets
[(! i
),(! j
)]
125 blocks
<- lift
$ checkInconsistent i j
$ map (fst.runWriter
) grps
126 modify
$ Vector
.imap
(\k
-> if k `
elem` blocks
then (tell
(Any
True) >>) else id)
127 checkInconsistent
:: Int -> Int -> [[Force
]] -> Writer
[Alert
] [Int]
128 checkInconsistent i j fss
=
129 let st
' = foldr applyForce st
$ nub $ concat fss
130 (inconsistencies
,cols
) = runWriter
$ sequence
131 [ tell cols
>> return [f
,f
']
132 |
[f
,f
'] <- sequence fss
134 if forceIdx f
== forceIdx f
'
136 else let cols
= collisions st
' (forceIdx f
) (forceIdx f
')
137 in (not $ null cols
, cols
) ]]
138 badConns
= [ c | c
<- connections st
'
139 , not $ springExtensionValid st
' c
]
141 tell
$ map AlertBlockingForce
$ concat inconsistencies
142 tell
$ map AlertCollision cols
143 tell
$ map AlertCollision
$ concat
144 [ locusPos st
' <$> [r
, e
]
145 | Connection r e _
<- badConns
]
146 return $ if null inconsistencies
&& null badConns
then []
147 else if i
==j
then [i
]
148 else if dominates i j
then [j
]
149 else if dominates j i
then [i
]
152 stopWrench idx
= setPiece idx
(Wrench zero
)
153 stopBlockedWrenches blocked unblocked st
' = foldr stopWrench st
' $
154 forcedWrenches blocked
\\ forcedWrenches unblocked
155 where forcedWrenches fs
= [ forceIdx f
156 | f
<- fs
, isWrench
.placedPiece
$ getForcedpp st
' f
]
157 divertedWrenches fs
= [ idx
159 , Wrench mom
<- [placedPiece
$ getpp st idx
]
160 , mom `
notElem`
[zero
,dir
] ]
162 let unresisted
= [ s |
(s
, (_
, Any
False)) <- enumVec
$ runWriter
<$> initGrps
]
164 -- check for inconsistencies within, and between pairs of, forcegroups
165 grps
<- sequence [ blockInconsistent i j
166 |
[i
,j
] <- sequence [unresisted
,unresisted
]
168 `execStateT` initGrps
170 let [blocked
, unblocked
] = map (nub.concatMap (fst.runWriter
) . Vector
.toList
) $
171 (\(x
,y
) -> [x
,y
]) $ Vector
.partition (getAny
.snd.runWriter
) grps
172 tell
$ map AlertBlockedForce blocked
173 tell
$ map AlertAppliedForce unblocked
174 tell
$ map AlertDivertedWrench
$ divertedWrenches unblocked
175 return $ stopBlockedWrenches blocked unblocked
$ foldr applyForce st unblocked
177 resolveSinglePlForce
:: Force
-> GameState
-> Writer
[Alert
] GameState
178 resolveSinglePlForce force
= resolveForces
179 (Vector
.singleton
(ForceChoice
[force
])) Vector
.empty
182 applyForce
:: Force
-> GameState
-> GameState
185 pp
' = applyForceTo
(getpp s idx
) f
186 pp
'' = case (placedPiece pp
',f
) of
187 ( Wrench _
, Push _ dir
) -> pp
' {placedPiece
= Wrench dir
}
190 s
{ placedPieces
= placedPieces s
// [(idx
, pp
'')] }
192 collisionsWithForce
:: GameState
-> Force
-> PieceIdx
-> [HexPos
]
193 collisionsWithForce st
(Push idx dir
) idx
' =
194 map (dir
+^
) (footprintAtIgnoring st idx idx
') `
intersect` footprintAtIgnoring st idx
' idx
195 collisionsWithForce st force idx
' =
196 collisions
(applyForce force st
) (forceIdx force
) idx
'
198 applyForceTo
:: PlacedPiece
-> Force
-> PlacedPiece
199 applyForceTo
(PlacedPiece pos piece
) (Push _ dir
) =
200 PlacedPiece
(dir
+^ pos
) piece
201 applyForceTo
(PlacedPiece pos
(Pivot arms
)) (Torque _ dir
) =
202 PlacedPiece pos
(Pivot
$ map (rotate dir
) arms
)
203 applyForceTo
(PlacedPiece pos
(Hook arm hf
)) (Torque _ dir
) =
204 PlacedPiece pos
(Hook
(rotate dir arm
) hf
)
205 applyForceTo pp _
= pp
207 -- A force on a piece which resists it is immediately blocked
208 pieceResists
:: GameState
-> Force
-> Bool
209 pieceResists st force
=
210 let idx
= forceIdx force
211 PlacedPiece _ piece
= getpp st idx
212 springs
= springsEndAtIdx st idx
213 fixed
= case piece
of
214 (Pivot _
) -> isPush force
215 (Block _
) -> null springs
216 (Wrench mom
) -> case force
of
219 (Hook _ hf
) -> case force
of
220 Push _ v
-> hf
/= PushHF v
221 Torque _ dir
-> hf
/= TorqueHF dir
225 -- |transmittedForce: convert pushes into torques as appropriate
226 transmittedForce
:: GameState
-> Source
-> HexPos
-> HexDir
-> Force
227 transmittedForce st idx cpos dir
=
228 let pp
@(PlacedPiece _ piece
) = getpp st idx
229 rpos
= cpos
-^ placedPos pp
231 (dir `hexDot`
(rotate
1 rpos
-^ rpos
)) `
compare`
232 (dir `hexDot`
(rotate
(-1) rpos
-^ rpos
)) of
234 LT
-> Torque idx
$ -1
238 Hook _
(TorqueHF _
) -> armPush
241 -- |propagateForce: return forces a force causes via bumps and fully
242 -- compressed/extended springs
243 propagateForce
:: GameState
-> Bool -> Force
-> [ForceChoice
]
244 propagateForce st
@(GameState _ conns
) isPlSource force
=
245 bumps
++ springTransmissions
248 bumps
= [ ForceChoice
$ map (transmittedForce st idx
' cpos
) dirs
251 , cpos
<- collisionsWithForce st force idx
'
252 , let dirs
= case force
of
254 Torque _ dir
-> [push
,claw
]
255 where push
= arm
-^ rotate
(-dir
) arm
256 claw
= rotate dir arm
-^ arm
257 arm
= cpos
-^ placedPos
(getpp st idx
) ]
258 springTransmissions
=
260 Push _ dir
-> [ ForceChoice
[Push idx
' dir
]
261 | c
@(Connection
(ridx
,_
) (eidx
,_
) (Spring sdir _
)) <- conns
262 , let root
= idx
== ridx
263 , let end
= idx
== eidx
265 , let idx
' = if root
then eidx
else ridx
266 , let pull
= (root
&& dir
== neg sdir
) ||
(end
&& dir
== sdir
)
267 , let push
= (root
&& dir
== sdir
) ||
(end
&& dir
== neg sdir
)
268 , (push
&& if isPlSource
then springFullyCompressed st c
else not $ springExtended st c
) ||
269 (pull
&& if isPlSource
then springFullyExtended st c
else not $ springCompressed st c
) ||
270 (not push
&& not pull
) ]
273 -- |propagate: find forcegroup generated by a forcechoice, and note if the
274 -- group is blocked due to resistance. If there are multiple forces in a
275 -- forcechoice and the first results in a block due to resistance, try the
277 propagate
:: GameState
-> Bool -> ForceChoice
-> Writer Any
[Force
]
278 propagate st isPlSource fch
= Set
.toList
<$> propagate
' isPlSource Set
.empty fch
where
279 propagate
' isPlForce ps
(ForceChoice
(f
:backups
)) =
280 if f `Set
.member` ps
then return ps
282 let (ps
', failed
) = if pieceResists st f
&& not isPlForce
284 else runWriter
$ foldrM
285 (flip $ propagate
' False)
287 $ propagateForce st isPlSource f
290 then tell
(Any
True) >> return ps
'
291 else propagate
' isPlForce ps
$ ForceChoice backups
293 propagate
' _ _
(ForceChoice
[]) = error "null ForceChoice"