1 {-# LANGUAGE FlexibleContexts, FlexibleInstances, LambdaCase,
2 MultiParamTypeClasses, ScopedTypeVariables, TypeSynonymInstances
5 module RMCA.GUI.Board ( GUICell (..)
21 import Data.Array.MArray
22 import Data.Board.GameBoardIO
25 import Data.ReactiveValue
27 import Game.Board.BasicTurnGame
28 import Graphics.UI.Gtk hiding (Action)
29 import Graphics.UI.Gtk.Board.BoardLink hiding (attachGameRules)
30 import Graphics.UI.Gtk.Board.TiledBoard hiding
32 , boardOnPieceDragDrop
33 , boardOnPieceDragOver
34 , boardOnPieceDragStart
36 import qualified Graphics.UI.Gtk.Board.TiledBoard as BIO
37 import Paths_arpeggigon
38 import RMCA.GUI.HelpersRewrite
39 import RMCA.IOClockworks
44 newtype GUIBoard = GUIBoard (GameState Int Tile Player GUICell)
46 -- There are two types of tiles that can be distinguished by setting
47 -- two different colors for debugging purposes. A future release might
48 -- want to remove that.
49 data Tile = TileW | TileB
52 rotateGUICell :: GUICell -> GUICell
53 rotateGUICell g = g { cellAction = rotateAction $ cellAction g }
54 where rotateAction (ChDir b na d) = ChDir b na (nextDir d)
63 d = sqrt 3 * fromIntegral tileW / 3
68 d = 4 * fromIntegral tileW / 3
74 d = sqrt 3 * fromIntegral hexW / 2
77 boardToTile :: [(Int,Int,Tile)]
78 boardToTile = [(x,y,selTile) | (x,y) <- range ( (xMin-1,yMin)
80 , let selTile = if even x && even y
88 outGUIBoard :: (Int,Int) -> Bool
89 outGUIBoard (xf,yf) = xf < xMin || xf > xMax || yf < yMin || yf > yMax
92 inertCell = GUICell { cellAction = Inert
97 initGUIBoard :: GUIBoard
98 initGUIBoard = GUIBoard GameState
100 , boardPos = boardToTile
101 , boardPieces' = boardToPiece [] $ makeBoard []
104 instance PlayableGame GUIBoard Int Tile Player GUICell where
106 allPos (GUIBoard game) = boardPos game
107 allPieces (GUIBoard game) = boardPieces' game
110 canMove (GUIBoard game) _ (x,y)
111 | Just (_,p) <- getPieceAt game (x,y)
112 , GUICell { cellAction = Inert } <- p = False
113 | Nothing <- getPieceAt game (x,y) = False
115 canMoveTo _ _ _ fPos = fPos `elem` validArea || outGUIBoard fPos
117 move (GUIBoard game) _ iPos@(_,yi) fPos@(xf,yf)
118 | outGUIBoard iPos && outGUIBoard fPos = []
119 | outGUIBoard fPos = [ RemovePiece iPos
120 , AddPiece iPos Player nCell ]
121 | iPos `elem` ctrlCoords = [ RemovePiece fPos'
122 , AddPiece fPos' Player
123 (nCell { cellAction = ctrlAction }) ]
124 | otherwise = [ MovePiece iPos fPos'
125 , AddPiece iPos Player nCell ]
127 | (xf `mod` 2 == 0 && yf `mod` 2 == 0)
128 || (xf `mod` 2 /= 0 && yf `mod` 2 /= 0) = (xf,yf)
129 | otherwise = (xf,yf+signum' (yf-yi))
132 | otherwise = signum x
133 ctrlAction = cellAction $ snd $ fromJust $ getPieceAt game iPos
135 | Just (_,GUICell { asPh = ph, repeatCount = n }) <-
136 getPieceAt game iPos = inertCell { repeatCount = n
139 | otherwise = inertCell
141 applyChange (GUIBoard game) (AddPiece (x,y) Player piece) =
142 GUIBoard $ game { boardPieces' = bp' }
143 where bp' = (x,y,Player,piece):boardPieces' game
145 applyChange (GUIBoard game) (RemovePiece (x,y)) = GUIBoard $
146 game { boardPieces' = bp' }
147 where bp' = [p | p@(x',y',_,_) <- boardPieces' game
148 , x /= x' || y /= y']
150 applyChange guiBoard@(GUIBoard game) (MovePiece iPos fPos)
151 | Just (_,p) <- getPieceAt game iPos
152 = applyChanges guiBoard [ RemovePiece iPos
154 , AddPiece fPos Player p]
155 | otherwise = guiBoard
157 initGame :: IO (Game GUIBoard Int Tile Player GUICell)
159 --pixbufs <- fileToPixbuf
160 tilePixbufB <- pixbufNew ColorspaceRgb False 8 tileW tileH
161 tilePixbufW <- pixbufNew ColorspaceRgb False 8 tileW tileH
162 pixbufFill tilePixbufB 50 50 50 0
163 pixbufFill tilePixbufW 50 50 50 0
164 pixPiece <- pixbufForPiece
165 let pixTile :: Tile -> Pixbuf
166 pixTile TileW = tilePixbufW
167 pixTile TileB = tilePixbufB
168 visualA = VisualGameAspects { tileF = pixTile
169 , pieceF = \(_,g) -> pixPiece g
170 , bgColor = (1000,1000,1000)
174 return $ Game visualA initGUIBoard
176 -- Initializes a readable RV for the board and an readable-writable RV
177 -- for the playheads. Also installs some handlers for pieces modification.
178 initBoardRV :: IOTick
179 -> BIO.Board Int Tile (Player,GUICell)
180 -> IO ( ReactiveFieldRead IO Board
181 , Array Pos (ReactiveFieldWrite IO GUICell)
182 , ReactiveFieldWrite IO [PlayHead])
183 initBoardRV tc board@BIO.Board { boardPieces = (GameBoard gArray) } = do
185 phMVar <- newCBMVar []
186 let getterB :: IO Board
188 (boardArray :: [((Int,Int),Maybe (Player,GUICell))]) <- getAssocs gArray
189 let board = makeBoard $
190 map (first fromGUICoords .
191 second ((\(_,c) -> (cellAction c,repeatCount c)) .
193 filter (isJust . snd) boardArray
196 notifierB :: IO () -> IO ()
197 notifierB = reactiveValueOnCanRead tc
199 getterP :: IO [PlayHead]
200 getterP = readCBMVar phMVar
202 setterP :: [PlayHead] -> IO ()
204 oph <- readCBMVar phMVar
205 let offPh :: PlayHead -> IO ()
207 let pos = toGUICoords $ phPos ph
208 piece <- boardGetPiece pos board
209 when (isJust piece) $ do
210 let (_,c) = fromJust piece
211 boardSetPiece pos (Player, c { asPh = False }) board
212 onPh :: PlayHead -> IO ()
214 let pos = toGUICoords $ phPos ph
215 piece <- boardGetPiece pos board
216 when (isJust piece) $ do
217 let (_,c) = fromJust piece
218 boardSetPiece pos (Player, c { asPh = True }) board
219 postGUIAsync $ mapM_ offPh oph
220 postGUIAsync $ mapM_ onPh lph
221 writeCBMVar phMVar lph
223 notifierP :: IO () -> IO ()
224 notifierP = installCallbackCBMVar phMVar
226 b = ReactiveFieldRead getterB notifierB
227 ph = ReactiveFieldReadWrite setterP getterP notifierP
229 setterW :: (Int,Int) -> GUICell -> IO ()
230 setterW i g = postGUIAsync $ boardSetPiece i (Player,g) board
232 arrW :: Array Pos (ReactiveFieldWrite IO GUICell)
233 arrW = array (minimum validArea, maximum validArea)
234 [(i, ReactiveFieldWrite (setterW i))
235 | i <- validArea :: [(Int,Int)]]
237 return (b,arrW,writeOnly ph)
239 fileToPixbuf :: IO [(FilePath,Pixbuf)]
240 fileToPixbuf = mapM (\f -> let f' = ("img/" ++ f) in
243 , getDataFileName f' >>=
244 (pixbufNewFromFile >=>
245 \p -> pixbufScaleSimple p hexW hexW InterpBilinear)))
246 (["hexOn.png","hexOff.png","stop.svg","split.svg","absorb.svg"] ++
247 concat [["start" ++ show d ++ ".svg","ric" ++ show d ++ ".svg"]
251 -- If the repeatCount of some tile is superior to mrc,
252 -- then this tile will be undistinguishable from any other tile with a
253 -- repeat count superior to mrc.
257 pixbufForPiece :: IO (GUICell -> Pixbuf)
259 let colorPlayHead _ r g b ma = if (r == 0 && g == 0 && b == 0)
262 colorRC 0 _ _ _ _ ma = (0, 0, 0, ma)
263 colorRC rc _ r g b ma =
264 if (r == 0 && g == 0 && b == 0)
266 else let (gradr, gradg, gradb) = ( (maxBound - r) `quot` mrc
267 , (g - minBound) `quot` mrc
268 , (b - minBound) `quot` mrc
270 in ( r + gradr * (rc - 1)
271 , g - gradg * (rc - 1)
272 , b - gradb * (rc - 1)
275 pixbufs <- mapM (\(a,rc) -> do df <- getDataFileName $ actionToFile a
276 p <- do p' <- pixbufNewFromFile df
277 pixbufScaleSimple p' hexW hexW InterpBilinear
278 modifyPixbuf (colorRC rc) p
280 modifyPixbuf colorPlayHead p'
281 return ((a,rc), (p, p'))
282 ) [(a,r) | r <- [0..mrc], a <- actionList]
283 let f GUICell { cellAction = a
285 , repeatCount = r } =
286 (if t then snd else fst) $ fromJust $
287 lookup (anonymizeConstructor a, min (fromIntegral r) mrc) pixbufs
290 modifyPixbuf :: ((Int, Int) -> Word8 -> Word8 -> Word8 -> Maybe Word8 ->
291 (Word8, Word8, Word8, Maybe Word8))
293 modifyPixbuf f p = do
294 pixs <- pixbufGetPixels p
295 w <- pixbufGetWidth p
296 h <- pixbufGetHeight p
297 rs <- pixbufGetRowstride p
298 chans <- pixbufGetNChannels p
299 forM_ [(x,y) | x <- [0..w - 1], y <- [0..h - 1]] $ \(x,y) -> do
300 let p = x * rs + y * chans
301 red <- readArray pixs p
302 green <- readArray pixs (p + 1)
303 blue <- readArray pixs (p + 2)
304 alpha <- if (chans == 4)
305 then fmap Just $ readArray pixs (p + 3)
307 let (nr, ng, nb, na) = f (x,y) red green blue alpha
309 writeArray pixs (p + 1) ng
310 writeArray pixs (p + 2) nb
311 when (isJust na) $ writeArray pixs (p + 3) $ fromJust na
314 actionToFile :: Action -> FilePath
316 Inert -> "img/hexOff.png"
317 Absorb -> "img/absorb.svg"
318 Stop _ -> "img/stop.svg"
319 ChDir True _ d -> "img/start" ++ show d ++ ".svg"
320 ChDir False _ d -> "img/ric" ++ show d ++ ".svg"
321 Split _ -> "img/split.svg"