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
.Applicative
15 import Control
.Monad
.IO.Class
16 import Control
.Monad
.Trans
.Class
17 import Control
.Monad
.Trans
.Maybe
18 import Control
.Monad
.Trans
.Reader
19 import Control
.Monad
.Trans
.State
20 import qualified Data
.List
as List
22 import qualified Data
.Map
as Map
23 import Graphics
.UI
.SDL
24 import System
.Random
(randomRIO)
36 data ShowBlocks
= ShowBlocksBlocking | ShowBlocksAll | ShowBlocksNone
37 deriving (Eq
, Ord
, Show, Read)
40 = TileGlyph Tile Pixel
41 | SpringGlyph HexDir HexDir SpringExtension HexDir Pixel
42 | PivotGlyph TorqueDir HexDir Pixel
43 | ArmGlyph TorqueDir HexDir Pixel
44 | BlockedArm HexDir TorqueDir Pixel
45 | TurnedArm HexDir TorqueDir Pixel
46 | BlockedBlock Tile HexDir Pixel
47 | BlockedPush HexDir Pixel
50 | HollowInnerGlyph Pixel
51 | FilledHexGlyph Pixel
52 | ScoreGlyph
(Maybe Int)
54 | PathGlyph HexDir Pixel
55 | GateGlyph HexDir Pixel
56 | UseFiveColourButton
Bool
57 | ShowBlocksButton ShowBlocks
58 | ShowButtonTextButton
Bool
59 | UseSoundsButton
Bool
60 | WhsButtonsButton
(Maybe WrHoSel
)
61 | FullscreenButton
Bool
62 | DisplacedGlyph HexDir Glyph
64 deriving (Eq
, Ord
, Show)
66 type SizedGlyph
= (Glyph
,Int)
67 data CachedGlyphs
= CachedGlyphs
(Map SizedGlyph Surface
) [SizedGlyph
]
68 deriving (Eq
, Ord
, Show)
69 emptyCachedGlyphs
= CachedGlyphs Map
.empty []
72 type RenderM
= RenderT
(StateT CachedGlyphs
IO)
73 runRenderM
:: RenderM a
-> CachedGlyphs
-> RenderContext
-> IO (a
,CachedGlyphs
)
74 runRenderM m cgs rc
= runStateT
(runReaderT m rc
) cgs
76 drawAt
:: Glyph
-> HexPos
-> RenderM
()
78 centre
<- asks renderHCentre
79 drawAtRel gl
(pos
-^ centre
)
81 drawAtRel
:: Glyph
-> HexVec
-> RenderM
()
82 drawAtRel gl v
= recentreAt v
$ renderGlyphCaching gl
84 renderGlyphCaching
:: Glyph
-> RenderM
()
86 -- We aim to cache glyphs which are "currently" being regularly drawn, so
87 -- they can be blitted from RAM rather than being drawn afresh each time.
88 -- Rather than track statistics, we adopt the following probabilistic scheme.
89 renderGlyphCaching gl
= do
90 CachedGlyphs cmap clist
<- lift get
91 size
<- asks renderSize
96 -- csurf <- liftIO $ createRGBSurface [] w h 32 0xff000000 0x00ff0000 0x0000ff00 0x000000ff
97 csurf
<- liftIO
$ createRGBSurface
[] w h
16 0 0 0 0
98 liftIO
$ setColorKey csurf
[SrcColorKey
,RLEAccel
] $ Pixel
0
100 renderOnCache csurf
=
101 let ccxt rc
= rc
{ renderSurf
= csurf
, renderSCentre
= SVec
(w`
div`
2) (h`
div`
2), renderOffset
= zero
}
102 in local ccxt
$ renderGlyph gl
103 addToCache cacheFull csurf
= do
104 CachedGlyphs cmap clist
<- lift get
105 let cmap
' = Map
.insert sgl csurf cmap
106 lift
$ put
$ if cacheFull
107 then CachedGlyphs
(Map
.delete (last clist
) cmap
') (sgl
:List
.init clist
)
108 else CachedGlyphs cmap
' (sgl
:clist
)
110 CachedGlyphs cmap clist
<- lift get
111 lift
$ put
$ CachedGlyphs cmap
(sgl
:List
.delete sgl clist
)
113 surf
<- asks renderSurf
114 (x
,y
) <- renderPos zero
115 void
$ liftIO
$ blitSurface csurf Nothing surf
$ Just
$
116 Rect
(x
-w`
div`
2) (y
-h`
div`
2) (w
+1) (h
+1)
117 let cacheFull
= Map
.size cmap
>= maxCachedGlyphs
118 let mcsurf
= Map
.lookup sgl cmap
119 -- with probability 1 in (maxCachedGlyphs`div`2), we put this glyph at the
120 -- head of the cached list, throwing away the tail to make room if needed.
121 cacheIt
<- (((cacheable
&&) . (not cacheFull ||
)) <$>) $
122 liftIO
$ (==0) <$> randomRIO (0::Int,maxCachedGlyphs`
div`
2)
124 Nothing
-> if cacheIt
126 csurf
<- newGlyphSurf
128 addToCache cacheFull csurf
136 cacheable
= case gl
of
137 -- some glyphs need to be drawn with blending - those involving
138 -- anti-aliasing which bleed over the edge of the hex or which
139 -- may be drawn on top of an existing glyph.
140 -- TODO: we should find a way to deal with at least some of these;
141 -- springs in particular are common and expensive to draw.
142 -- Maybe we could truncate the spring glyphs to a hex?
143 TileGlyph
(BlockTile adjs
) _
-> null adjs
144 TileGlyph
(SpringTile extn dir
) _
-> False
145 SpringGlyph
{} -> False
146 FilledHexGlyph _
-> False
147 HollowGlyph _
-> False
148 BlockedBlock
{} -> False
149 BlockedPush _ _
-> False
150 CollisionMarker
-> False
151 DisplacedGlyph _ _
-> False
154 renderGlyph
:: Glyph
-> RenderM
()
155 renderGlyph
(TileGlyph
(BlockTile adjs
) col
) =
156 rimmedPolygonR corners col
$ bright col
160 then [corner
$ hextant dir
]
161 else [innerCorner dir |
not (adjAt
$ -1)]
163 , let adjAt r
= rotate r dir `
elem` adjs
166 renderGlyph
(TileGlyph
(SpringTile extn dir
) col
) =
167 renderGlyph
$ SpringGlyph zero zero extn dir col
169 renderGlyph
(TileGlyph
(PivotTile dir
) col
) = do
170 renderGlyph
$ PivotGlyph
0 dir col
172 renderGlyph
(TileGlyph
(ArmTile dir _
) col
) =
173 renderGlyph
$ ArmGlyph
0 dir col
175 renderGlyph
(TileGlyph HookTile col
) =
176 rimmedCircleR zero
(7/8) col
$ bright col
178 renderGlyph
(TileGlyph
(WrenchTile mom
) col
) = do
179 rimmedCircleR zero
(1/3) col
$ bright col
182 from
= innerCorner
$ neg mom
184 shifts
= [(1 / 2) **^
(b
-^ a
) |
185 let a
= innerCorner
$ neg mom
,
187 let b
= innerCorner
$ rotate rot
$ neg mom
]
189 [ aaLineR
(from
+^shift
) (to
+^shift
) col
192 renderGlyph
(TileGlyph BallTile col
) =
193 rimmedCircleR zero
(7/8) (faint col
) (obscure col
)
195 renderGlyph
(SpringGlyph rootDisp endDisp extn dir col
) =
196 thickLinesR points
1 $ brightness col
204 dir
' = if dir
== zero
then hu
else dir
205 s
= corner
(hextant dir
' - 1) +^ innerCorner endDisp
206 off
= corner
(hextant dir
') +^ innerCorner endDisp
207 e
= corner
(hextant dir
' - 3) +^ innerCorner rootDisp
208 points
= [ b
+^
(fi i
/ fi n
) **^
(e
-^ s
)
211 , let b
= if i`
mod`
3==0 then s
else off
]
213 renderGlyph
(PivotGlyph rot dir col
) = do
214 rimmedCircleR zero
(7/8) col
$ bright col
216 $ aaLineR from to
$ bright col
218 from
= rotFVec th c
$ (7/8) **^ edge
(neg dir
)
219 to
= rotFVec th c
$ (7/8) **^ edge dir
221 th
= - fi rot
* pi / 12
223 renderGlyph
(ArmGlyph rot dir col
) =
224 thickLineR from to
1 $ bright col
226 dir
' = if dir
== zero
then hu
else dir
227 from
= rotFVec th c
$ edge
$ neg dir
'
228 to
= rotFVec th c
$ innerCorner dir
'
229 c
= 2 **^ edge
(neg dir
')
230 th
= - fi rot
* pi / 12
232 renderGlyph
(BlockedArm armdir tdir col
) =
235 from
= innerCorner
$ rotate
(2*tdir
) armdir
236 to
= edge
$ rotate tdir armdir
238 renderGlyph
(TurnedArm armdir tdir col
) =
239 sequence_ [ arcR c r a1 a2 col | r
<- [8/4,9/4] ]
241 c
= hexVec2FVec
$ neg armdir
242 a0
= fi
$ -60*hextant armdir
243 a1
' = a0
+ fi tdir
* 10
244 a2
' = a0
+ fi tdir
* 30
248 renderGlyph
(BlockedBlock tile dir col
) =
249 displaceRender shift
$ renderGlyph
(TileGlyph tile col
)
250 where shift
= innerCorner dir
-^ edge dir
252 renderGlyph
(BlockedPush dir col
) = do
253 thickLineR zero tip
1 col
254 thickLineR tip
(head arms
) 1 col
255 thickLineR tip
(arms
!!1) 1 col
257 tip
@(FVec tx ty
) = edge dir
258 arms
= [ FVec
((tx
/2) + d
*ty
/4) (ty
/2 - d
*tx
/4) | d
<- [-1,1] ]
260 renderGlyph CollisionMarker
= do
261 -- rimmedCircle surf centre (size`div`3) (bright purple) $ bright purple
262 aaLineR start end col
263 aaCircleR zero rad col
265 [start
,end
] = map (((1/2)**^
) . corner
) [0,3]
269 renderGlyph
(HollowGlyph col
) =
270 aaPolygonR outerCorners
$ opaquify col
271 renderGlyph
(HollowInnerGlyph col
) =
272 aaPolygonR innerCorners
$ opaquify col
274 renderGlyph
(FilledHexGlyph col
) =
275 rimmedPolygonR outerCorners col
$ brightish col
277 renderGlyph
(ScoreGlyph relScore
) =
278 sequence_ $ [ aaLineR from to
$ bright white |
(from
,to
) <- case relScore
of
280 Just
2 -> plus
(-1) <> plus
1
281 Just
3 -> plus
(-2) <> plus
0 <> plus
2
282 Just
(-1) -> [horiz
0]
283 Just
(-2) -> [horiz
$ -1, horiz
1]
284 Just
(-3) -> [horiz
$ -2, horiz
0, horiz
2]
288 vert n
= let x
= n
/5 in (FVec x
$ -(4/3)*ylen
, FVec x
$ -ylen
)
289 horiz n
= let y
= -(7/6)*ylen
in (FVec
(n
/5 - 1/9) y
, FVec
(n
/5 + 1/9) y
)
290 plus n
= [vert n
, horiz n
]
293 renderGlyph
(ButtonGlyph col
) =
294 renderGlyph
(TileGlyph
(BlockTile
[]) col
)
296 renderGlyph
(PathGlyph dir col
) = do
299 from
= edge
$ neg dir
302 renderGlyph
(GateGlyph dir col
) = do
305 from
= corner
$ 1 + hextant dir
306 to
= corner
$ 4 + hextant dir
308 renderGlyph
(UseFiveColourButton using
) =
309 rescaleRender
(1/2) $ sequence_ [
310 displaceRender
(corner h
) $ renderGlyph
311 (TileGlyph
(BlockTile
[])
312 (dim
$ colourWheel
(if using
then h`
div`
2 else 1)))
315 renderGlyph
(ShowBlocksButton showing
) = do
316 renderGlyph
(TileGlyph
(BlockTile
[]) (dim red
))
317 when (showing
== ShowBlocksAll
) $
318 renderGlyph
(BlockedPush hu
(bright orange
))
319 when (showing
/= ShowBlocksNone
) $
320 renderGlyph
(BlockedPush hw
(bright purple
))
322 renderGlyph
(ShowButtonTextButton showing
) = do
323 rescaleRender
(1/2) $ displaceRender
(edge
(neg hu
)) $
324 renderGlyph
(ButtonGlyph
(dim yellow
))
326 sequence_ [ pixelR
(FVec
(1/3 + i
/4) (-1/4)) (bright white
) | i
<- [-1..1] ]
328 renderGlyph
(UseSoundsButton use
) = do
329 sequence_ [ arcR
(FVec
(-2/3) 0) r
(-20) 20
330 (if use
then bright green
else dim red
)
333 aaLineR
(innerCorner hw
) (innerCorner
$ neg hw
) $ dim red
335 renderGlyph
(WhsButtonsButton Nothing
) = rescaleRender
(1/3) $ do
336 renderGlyph
(ButtonGlyph
(dim red
))
337 sequence_ [ displaceRender
((3/2) **^ edge dir
) $
338 renderGlyph
(ButtonGlyph
(dim purple
))
340 renderGlyph
(WhsButtonsButton
(Just whs
)) = rescaleRender
(1/3) $ do
341 when (whs
/= WHSHook
) $
342 displaceRender
(corner
0) $ renderGlyph
(TileGlyph
(WrenchTile zero
) col
)
343 when (whs
/= WHSWrench
) $ do
344 displaceRender
(corner
4) $ renderGlyph
(TileGlyph HookTile col
)
345 displaceRender
(corner
2) $ renderGlyph
(TileGlyph
(ArmTile hv
False) col
)
349 renderGlyph
(FullscreenButton fs
) = do
350 thickPolygonR corners
1 $ activeCol
(not fs
)
351 thickPolygonR corners
' 1 $ activeCol fs
353 activeCol
True = opaquify
$ dim green
354 activeCol
False = opaquify
$ dim red
355 corners
= [ (2/3) **^
(if dir `
elem`
[hu
,neg hu
] then edge
else innerCorner
) dir
357 corners
' = map (((2/3)**^
) . corner
) [0..5]
359 renderGlyph
(DisplacedGlyph dir glyph
) =
360 displaceRender
(innerCorner dir
) $ renderGlyph glyph
362 renderGlyph UnfreshGlyph
= do
364 renderGlyph
(HollowInnerGlyph col
)
365 sequence_ [pixelR
(FVec
(i
/4) 0) col
368 playerGlyph
= FilledHexGlyph
370 cursorGlyph
= HollowGlyph
$ bright white
372 ownedTileGlyph colouring highlight
(owner
,t
) =
373 let col
= colourOf colouring owner
374 in TileGlyph t
$ (if owner `
elem` highlight
then bright
else dim
) col
376 drawCursorAt
:: Maybe HexPos
-> RenderM
()
377 drawCursorAt
(Just pos
) = drawAt cursorGlyph pos
378 drawCursorAt _
= return ()
380 drawBasicBG
:: Int -> RenderM
()
381 drawBasicBG maxR
= sequence_ [ drawAtRel
(HollowGlyph
$ colAt v
) v | v
<- hexDisc maxR
]
383 colAt v
@(HexVec hx hy hz
) = let
384 [r
,g
,b
] = map (\h
-> fi
$ 0xff * (5 + abs h
)`
div`maxR
) [hx
,hy
,hz
]
385 a
= fi
$ (0x70 * (maxR
- abs (hexLen v
)))`
div`maxR
386 in rgbaToPixel
(r
,g
,b
,a
)
388 drawBlocked
:: GameState
-> PieceColouring
-> Bool -> Force
-> RenderM
()
389 drawBlocked st colouring blocking
(Torque idx dir
) = do
390 let (pos
,arms
) = case getpp st idx
of
391 PlacedPiece pos
(Pivot arms
) -> (pos
,arms
)
392 PlacedPiece pos
(Hook arm _
) -> (pos
,[arm
])
394 col
= if blocking
then bright purple
else dim
$ colourOf colouring idx
395 sequence_ [ drawAt
(BlockedArm arm dir col
) (arm
+^ pos
) |
397 drawBlocked st colouring blocking
(Push idx dir
) = do
398 let footprint
= plPieceFootprint
$ getpp st idx
399 fullfootprint
= fullFootprint st idx
400 col
= bright
$ if blocking
then purple
else orange
401 sequence_ [ drawAt
(BlockedPush dir col
) pos
403 , (dir
+^pos
) `
notElem` fullfootprint
]
404 -- drawAt (blockedPush dir $ bright orange) $ placedPos $ getpp st idx
406 drawApplied
:: GameState
-> PieceColouring
-> Force
-> RenderM
()
407 drawApplied st colouring
(Torque idx dir
) = do
408 let (pos
,arms
) = case getpp st idx
of
409 PlacedPiece pos
(Pivot arms
) -> (pos
,arms
)
410 PlacedPiece pos
(Hook arm _
) -> (pos
,[arm
])
412 col
= dim
$ colourOf colouring idx
413 sequence_ [ drawAt
(TurnedArm arm dir col
) (arm
+^ pos
) |
415 drawApplied _ _ _
= return ()