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
53 | PathGlyph HexDir Pixel
54 | GateGlyph HexDir Pixel
55 | UseFiveColourButton
Bool
56 | ShowBlocksButton ShowBlocks
57 | ShowButtonTextButton
Bool
58 | UseSoundsButton
Bool
59 | WhsButtonsButton
(Maybe WrHoSel
)
60 | FullscreenButton
Bool
61 | DisplacedGlyph HexDir Glyph
63 deriving (Eq
, Ord
, Show)
65 type SizedGlyph
= (Glyph
,Int)
66 data CachedGlyphs
= CachedGlyphs
(Map SizedGlyph Surface
) [SizedGlyph
]
67 deriving (Eq
, Ord
, Show)
68 emptyCachedGlyphs
= CachedGlyphs Map
.empty []
71 type RenderM
= RenderT
(StateT CachedGlyphs
IO)
72 runRenderM
:: RenderM a
-> CachedGlyphs
-> RenderContext
-> IO (a
,CachedGlyphs
)
73 runRenderM m cgs rc
= runStateT
(runReaderT m rc
) cgs
75 drawAt
:: Glyph
-> HexPos
-> RenderM
()
77 centre
<- asks renderHCentre
78 drawAtRel gl
(pos
-^ centre
)
80 drawAtRel
:: Glyph
-> HexVec
-> RenderM
()
81 drawAtRel gl v
= recentreAt v
$ renderGlyphCaching gl
83 renderGlyphCaching
:: Glyph
-> RenderM
()
85 -- We aim to cache glyphs which are "currently" being regularly drawn, so
86 -- they can be blitted from RAM rather than being drawn afresh each time.
87 -- Rather than track statistics, we adopt the following probabilistic scheme.
88 renderGlyphCaching gl
= do
89 CachedGlyphs cmap clist
<- lift get
90 size
<- asks renderSize
95 -- csurf <- liftIO $ createRGBSurface [] w h 32 0xff000000 0x00ff0000 0x0000ff00 0x000000ff
96 csurf
<- liftIO
$ createRGBSurface
[] w h
16 0 0 0 0
97 liftIO
$ setColorKey csurf
[SrcColorKey
,RLEAccel
] $ Pixel
0
100 let ccxt rc
= rc
{ renderSurf
= csurf
, renderSCentre
= SVec
(w`
div`
2) (h`
div`
2), renderOffset
= zero
}
101 in local ccxt
$ renderGlyph gl
102 addToCache cacheFull csurf
= do
103 CachedGlyphs cmap clist
<- lift get
104 let cmap
' = Map
.insert sgl csurf cmap
105 lift
$ put
$ if cacheFull
106 then CachedGlyphs
(Map
.delete (last clist
) cmap
') (sgl
:List
.init clist
)
107 else CachedGlyphs cmap
' (sgl
:clist
)
109 CachedGlyphs cmap clist
<- lift get
110 lift
$ put
$ CachedGlyphs cmap
(sgl
:List
.delete sgl clist
)
112 surf
<- asks renderSurf
113 (x
,y
) <- renderPos zero
114 void
$ liftIO
$ blitSurface csurf Nothing surf
$ Just
$
115 Rect
(x
-w`
div`
2) (y
-h`
div`
2) (w
+1) (h
+1)
116 let cacheFull
= Map
.size cmap
>= maxCachedGlyphs
117 let mcsurf
= Map
.lookup sgl cmap
118 -- with probability 1 in (maxCachedGlyphs`div`2), we put this glyph at the
119 -- head of the cached list, throwing away the tail to make room if needed.
120 cacheIt
<- (((cacheable
&&) . (not cacheFull ||
)) <$>) $
121 liftIO
$ (==0) <$> randomRIO (0::Int,maxCachedGlyphs`
div`
2)
123 Nothing
-> if cacheIt
125 csurf
<- newGlyphSurf
127 addToCache cacheFull csurf
135 cacheable
= case gl
of
136 -- some glyphs need to be drawn with blending - those involving
137 -- anti-aliasing which bleed over the edge of the hex or which
138 -- may be drawn on top of an existing glyph.
139 -- TODO: we should find a way to deal with at least some of these;
140 -- springs in particular are common and expensive to draw.
141 -- Maybe we could truncate the spring glyphs to a hex?
142 TileGlyph
(BlockTile adjs
) _
-> null adjs
143 TileGlyph
(SpringTile extn dir
) _
-> False
144 SpringGlyph
{} -> False
145 FilledHexGlyph _
-> False
146 HollowGlyph _
-> False
147 BlockedBlock
{} -> False
148 BlockedPush _ _
-> False
149 CollisionMarker
-> False
150 DisplacedGlyph _ _
-> False
153 renderGlyph
:: Glyph
-> RenderM
()
154 renderGlyph
(TileGlyph
(BlockTile adjs
) col
) =
155 rimmedPolygonR corners col
$ bright col
159 then [corner
$ hextant dir
]
160 else [innerCorner dir |
not (adjAt
$ -1)]
162 , let adjAt r
= rotate r dir `
elem` adjs
165 renderGlyph
(TileGlyph
(SpringTile extn dir
) col
) =
166 renderGlyph
$ SpringGlyph zero zero extn dir col
168 renderGlyph
(TileGlyph
(PivotTile dir
) col
) = do
169 renderGlyph
$ PivotGlyph
0 dir col
171 renderGlyph
(TileGlyph
(ArmTile dir _
) col
) =
172 renderGlyph
$ ArmGlyph
0 dir col
174 renderGlyph
(TileGlyph HookTile col
) =
175 rimmedCircleR zero
(7/8) col
$ bright col
177 renderGlyph
(TileGlyph
(WrenchTile mom
) col
) = do
178 rimmedCircleR zero
(1/3) col
$ bright col
181 from
= innerCorner
$ neg mom
183 shifts
= [(1 / 2) **^
(b
-^ a
) |
184 let a
= innerCorner
$ neg mom
,
186 let b
= innerCorner
$ rotate rot
$ neg mom
]
188 [ aaLineR
(from
+^shift
) (to
+^shift
) col
191 renderGlyph
(TileGlyph BallTile col
) =
192 rimmedCircleR zero
(7/8) (faint col
) (obscure col
)
194 renderGlyph
(SpringGlyph rootDisp endDisp extn dir col
) =
195 thickLinesR points
1 $ brightness col
203 dir
' = if dir
== zero
then hu
else dir
204 s
= corner
(hextant dir
' - 1) +^ innerCorner endDisp
205 off
= corner
(hextant dir
') +^ innerCorner endDisp
206 e
= corner
(hextant dir
' - 3) +^ innerCorner rootDisp
207 points
= [ b
+^
(fi i
/ fi n
) **^
(e
-^ s
)
210 , let b
= if i`
mod`
3==0 then s
else off
]
212 renderGlyph
(PivotGlyph rot dir col
) = do
213 rimmedCircleR zero
(7/8) col
$ bright col
215 $ aaLineR from to
$ bright col
217 from
= rotFVec th c
$ (7/8) **^ edge
(neg dir
)
218 to
= rotFVec th c
$ (7/8) **^ edge dir
220 th
= - fi rot
* pi / 12
222 renderGlyph
(ArmGlyph rot dir col
) =
223 thickLineR from to
1 $ bright col
225 dir
' = if dir
== zero
then hu
else dir
226 from
= rotFVec th c
$ edge
$ neg dir
'
227 to
= rotFVec th c
$ innerCorner dir
'
228 c
= 2 **^ edge
(neg dir
')
229 th
= - fi rot
* pi / 12
231 renderGlyph
(BlockedArm armdir tdir col
) =
234 from
= innerCorner
$ rotate
(2*tdir
) armdir
235 to
= edge
$ rotate tdir armdir
237 renderGlyph
(TurnedArm armdir tdir col
) =
238 sequence_ [ arcR c r a1 a2 col | r
<- [8/4,9/4] ]
240 c
= hexVec2FVec
$ neg armdir
241 a0
= fi
$ -60*hextant armdir
242 a1
' = a0
+ fi tdir
* 10
243 a2
' = a0
+ fi tdir
* 30
247 renderGlyph
(BlockedBlock tile dir col
) =
248 displaceRender shift
$ renderGlyph
(TileGlyph tile col
)
249 where shift
= innerCorner dir
-^ edge dir
251 renderGlyph
(BlockedPush dir col
) = do
252 thickLineR zero tip
1 col
253 thickLineR tip
(head arms
) 1 col
254 thickLineR tip
(arms
!!1) 1 col
256 tip
@(FVec tx ty
) = edge dir
257 arms
= [ FVec
((tx
/2) + d
*ty
/4) (ty
/2 - d
*tx
/4) | d
<- [-1,1] ]
259 renderGlyph CollisionMarker
= do
260 -- rimmedCircle surf centre (size`div`3) (bright purple) $ bright purple
261 aaLineR start end col
262 aaCircleR zero rad col
264 [start
,end
] = map (((1/2)**^
) . corner
) [0,3]
268 renderGlyph
(HollowGlyph col
) =
269 aaPolygonR corners
$ opaquify col
270 where corners
= map corner
[0..5]
271 renderGlyph
(HollowInnerGlyph col
) =
272 aaPolygonR corners
$ opaquify col
273 where corners
= map innerCorner hexDirs
275 renderGlyph
(FilledHexGlyph col
) =
276 rimmedPolygonR corners col
$ brightish col
277 where corners
= map corner
[0..5]
279 renderGlyph
(ButtonGlyph col
) =
280 renderGlyph
(TileGlyph
(BlockTile
[]) col
)
282 renderGlyph
(PathGlyph dir col
) = do
285 from
= edge
$ neg dir
288 renderGlyph
(GateGlyph dir col
) = do
291 from
= corner
$ 1 + hextant dir
292 to
= corner
$ 4 + hextant dir
294 renderGlyph
(UseFiveColourButton using
) =
295 rescaleRender
(1/2) $ sequence_ [
296 displaceRender
(corner h
) $ renderGlyph
297 (TileGlyph
(BlockTile
[])
298 (dim
$ colourWheel
(if using
then h`
div`
2 else 1)))
301 renderGlyph
(ShowBlocksButton showing
) = do
302 renderGlyph
(TileGlyph
(BlockTile
[]) (dim red
))
303 when (showing
== ShowBlocksAll
) $
304 renderGlyph
(BlockedPush hu
(bright orange
))
305 when (showing
/= ShowBlocksNone
) $
306 renderGlyph
(BlockedPush hw
(bright purple
))
308 renderGlyph
(ShowButtonTextButton showing
) = do
309 rescaleRender
(1/2) $ displaceRender
(edge
(neg hu
)) $
310 renderGlyph
(ButtonGlyph
(dim yellow
))
312 sequence_ [ pixelR
(FVec
(1/3 + i
/4) (-1/4)) (bright white
) | i
<- [-1..1] ]
314 renderGlyph
(UseSoundsButton use
) = do
315 sequence_ [ arcR
(FVec
(-2/3) 0) r
(-20) 20
316 (if use
then bright green
else dim red
)
319 aaLineR
(innerCorner hw
) (innerCorner
$ neg hw
) $ dim red
321 renderGlyph
(WhsButtonsButton Nothing
) = rescaleRender
(1/3) $ do
322 renderGlyph
(ButtonGlyph
(dim red
))
323 sequence_ [ displaceRender
((3/2) **^ edge dir
) $
324 renderGlyph
(ButtonGlyph
(dim purple
))
326 renderGlyph
(WhsButtonsButton
(Just whs
)) = rescaleRender
(1/3) $ do
327 when (whs
/= WHSHook
) $
328 displaceRender
(corner
0) $ renderGlyph
(TileGlyph
(WrenchTile zero
) col
)
329 when (whs
/= WHSWrench
) $ do
330 displaceRender
(corner
4) $ renderGlyph
(TileGlyph HookTile col
)
331 displaceRender
(corner
2) $ renderGlyph
(TileGlyph
(ArmTile hv
False) col
)
335 renderGlyph
(FullscreenButton fs
) = do
336 thickPolygonR corners
1 $ activeCol
(not fs
)
337 thickPolygonR corners
' 1 $ activeCol fs
339 activeCol
True = opaquify
$ dim green
340 activeCol
False = opaquify
$ dim red
341 corners
= [ (2/3) **^
(if dir `
elem`
[hu
,neg hu
] then edge
else innerCorner
) dir
343 corners
' = map (((2/3)**^
) . corner
) [0..5]
345 renderGlyph
(DisplacedGlyph dir glyph
) =
346 displaceRender
(innerCorner dir
) $ renderGlyph glyph
348 renderGlyph UnfreshGlyph
= do
350 renderGlyph
(HollowInnerGlyph col
)
351 sequence_ [pixelR
(FVec
(i
/4) 0) col
354 playerGlyph
= FilledHexGlyph
356 cursorGlyph
= HollowGlyph
$ bright white
358 ownedTileGlyph colouring highlight
(owner
,t
) =
359 let col
= colourOf colouring owner
360 in TileGlyph t
$ (if owner `
elem` highlight
then bright
else dim
) col
362 drawCursorAt
:: Maybe HexPos
-> RenderM
()
363 drawCursorAt
(Just pos
) = drawAt cursorGlyph pos
364 drawCursorAt _
= return ()
366 drawBasicBG
:: Int -> RenderM
()
367 drawBasicBG maxR
= sequence_ [ drawAtRel
(HollowGlyph
$ colAt v
) v | v
<- hexDisc maxR
]
369 colAt v
@(HexVec hx hy hz
) = let
370 [r
,g
,b
] = map (\h
-> fi
$ 0xff * (5 + abs h
)`
div`maxR
) [hx
,hy
,hz
]
371 a
= fi
$ (0x70 * (maxR
- abs (hexLen v
)))`
div`maxR
372 in rgbaToPixel
(r
,g
,b
,a
)
374 drawBlocked
:: GameState
-> PieceColouring
-> Bool -> Force
-> RenderM
()
375 drawBlocked st colouring blocking
(Torque idx dir
) = do
376 let (pos
,arms
) = case getpp st idx
of
377 PlacedPiece pos
(Pivot arms
) -> (pos
,arms
)
378 PlacedPiece pos
(Hook arm _
) -> (pos
,[arm
])
380 col
= if blocking
then bright purple
else dim
$ colourOf colouring idx
381 sequence_ [ drawAt
(BlockedArm arm dir col
) (arm
+^ pos
) |
383 drawBlocked st colouring blocking
(Push idx dir
) = do
384 let footprint
= plPieceFootprint
$ getpp st idx
385 fullfootprint
= fullFootprint st idx
386 col
= bright
$ if blocking
then purple
else orange
387 sequence_ [ drawAt
(BlockedPush dir col
) pos
389 , (dir
+^pos
) `
notElem` fullfootprint
]
390 -- drawAt (blockedPush dir $ bright orange) $ placedPos $ getpp st idx
392 drawApplied
:: GameState
-> PieceColouring
-> Force
-> RenderM
()
393 drawApplied st colouring
(Torque idx dir
) = do
394 let (pos
,arms
) = case getpp st idx
of
395 PlacedPiece pos
(Pivot arms
) -> (pos
,arms
)
396 PlacedPiece pos
(Hook arm _
) -> (pos
,[arm
])
398 col
= dim
$ colourOf colouring idx
399 sequence_ [ drawAt
(TurnedArm arm dir col
) (arm
+^ pos
) |
401 drawApplied _ _ _
= return ()