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
156 | otherwise = guiBoard
158 initGame :: IO (Game GUIBoard Int Tile Player GUICell)
160 --pixbufs <- fileToPixbuf
161 tilePixbufB <- pixbufNew ColorspaceRgb False 8 tileW tileH
162 tilePixbufW <- pixbufNew ColorspaceRgb False 8 tileW tileH
163 pixbufFill tilePixbufB 50 50 50 0
164 pixbufFill tilePixbufW 50 50 50 0
165 pixPiece <- pixbufForPiece
166 let pixTile :: Tile -> Pixbuf
167 pixTile TileW = tilePixbufW
168 pixTile TileB = tilePixbufB
169 visualA = VisualGameAspects { tileF = pixTile
170 , pieceF = \(_,g) -> pixPiece g
171 , bgColor = (1000,1000,1000)
175 return $ Game visualA initGUIBoard
177 -- Initializes a readable RV for the board and an readable-writable RV
178 -- for the playheads. Also installs some handlers for pieces modification.
179 initBoardRV :: IOTick
180 -> BIO.Board Int Tile (Player,GUICell)
181 -> IO ( ReactiveFieldRead IO Board
182 , Array Pos (ReactiveFieldWrite IO GUICell)
183 , ReactiveFieldWrite IO [PlayHead])
184 initBoardRV tc board@BIO.Board { boardPieces = (GameBoard gArray) } = do
186 phMVar <- newCBMVar []
187 let getterB :: IO Board
189 (boardArray :: [((Int,Int),Maybe (Player,GUICell))]) <- getAssocs gArray
190 let board = makeBoard $
191 map (first fromGUICoords .
192 second ((\(_,c) -> (cellAction c,repeatCount c)) .
194 filter (isJust . snd) boardArray
197 notifierB :: IO () -> IO ()
198 notifierB = reactiveValueOnCanRead tc
200 getterP :: IO [PlayHead]
201 getterP = readCBMVar phMVar
203 setterP :: [PlayHead] -> IO ()
205 oph <- readCBMVar phMVar
206 unless (oph == lph) $ do
207 let offPh :: PlayHead -> IO ()
209 let pos = toGUICoords $ phPos ph
210 piece <- boardGetPiece pos board
211 when (isJust piece) $ do
212 let (_,c) = fromJust piece
213 boardSetPiece pos (Player, c { asPh = False }) board
214 onPh :: PlayHead -> IO ()
216 let pos = toGUICoords $ phPos ph
217 piece <- boardGetPiece pos board
218 when (isJust piece) $ do
219 let (_,c) = fromJust piece
220 boardSetPiece pos (Player, c { asPh = True }) board
221 postGUIAsync $ mapM_ offPh oph
222 postGUIAsync $ mapM_ onPh lph
223 writeCBMVar phMVar lph
225 notifierP :: IO () -> IO ()
226 notifierP = installCallbackCBMVar phMVar
228 b = ReactiveFieldRead getterB notifierB
229 ph = ReactiveFieldReadWrite setterP getterP notifierP
231 setterW :: (Int,Int) -> GUICell -> IO ()
232 setterW i g = postGUIAsync $ boardSetPiece i (Player,g) board
234 arrW :: Array Pos (ReactiveFieldWrite IO GUICell)
235 arrW = array (minimum validArea, maximum validArea)
236 [(i, ReactiveFieldWrite (setterW i))
237 | i <- validArea :: [(Int,Int)]]
239 return (b,arrW,writeOnly ph)
241 fileToPixbuf :: IO [(FilePath,Pixbuf)]
242 fileToPixbuf = mapM (\f -> let f' = ("img/" ++ f) in
245 , getDataFileName f' >>=
246 (pixbufNewFromFile >=>
247 \p -> pixbufScaleSimple p hexW hexW InterpBilinear)))
248 (["hexOn.png","hexOff.png","stop.svg","split.svg","absorb.svg"] ++
249 concat [["start" ++ show d ++ ".svg","ric" ++ show d ++ ".svg"]
253 -- If the repeatCount of some tile is superior to mrc,
254 -- then this tile will be undistinguishable from any other tile with a
255 -- repeat count superior to mrc.
259 pixbufForPiece :: IO (GUICell -> Pixbuf)
261 let colorPlayHead _ r g b ma = if (r == 0 && g == 0 && b == 0)
264 colorRC 0 _ _ _ _ ma = (0, 0, 0, ma)
265 colorRC rc _ r g b ma =
266 if (r == 0 && g == 0 && b == 0)
268 else let (gradr, gradg, gradb) = ( (maxBound - r) `quot` mrc
269 , (g - minBound) `quot` mrc
270 , (b - minBound) `quot` mrc
272 in ( r + gradr * (rc - 1)
273 , g - gradg * (rc - 1)
274 , b - gradb * (rc - 1)
277 pixbufs <- mapM (\(a,rc) -> do df <- getDataFileName $ actionToFile a
278 p <- do p' <- pixbufNewFromFile df
279 pixbufScaleSimple p' hexW hexW InterpBilinear
280 modifyPixbuf (colorRC rc) p
282 modifyPixbuf colorPlayHead p'
283 return ((a,rc), (p, p'))
284 ) [(a,r) | r <- [0..mrc], a <- actionList]
285 let f GUICell { cellAction = a
287 , repeatCount = r } =
288 (if t then snd else fst) $ fromJust $
289 lookup (anonymizeConstructor a, min (fromIntegral r) mrc) pixbufs
292 modifyPixbuf :: ((Int, Int) -> Word8 -> Word8 -> Word8 -> Maybe Word8 ->
293 (Word8, Word8, Word8, Maybe Word8))
295 modifyPixbuf f p = do
296 pixs <- pixbufGetPixels p
297 w <- pixbufGetWidth p
298 h <- pixbufGetHeight p
299 rs <- pixbufGetRowstride p
300 chans <- pixbufGetNChannels p
301 forM_ [(x,y) | x <- [0..w - 1], y <- [0..h - 1]] $ \(x,y) -> do
302 let p = x * rs + y * chans
303 red <- readArray pixs p
304 green <- readArray pixs (p + 1)
305 blue <- readArray pixs (p + 2)
306 alpha <- if (chans == 4)
307 then fmap Just $ readArray pixs (p + 3)
309 let (nr, ng, nb, na) = f (x,y) red green blue alpha
311 writeArray pixs (p + 1) ng
312 writeArray pixs (p + 2) nb
313 when (isJust na) $ writeArray pixs (p + 3) $ fromJust na
316 actionToFile :: Action -> FilePath
318 Inert -> "img/hexOff.png"
319 Absorb -> "img/absorb.svg"
320 Stop _ -> "img/stop.svg"
321 ChDir True _ d -> "img/start" ++ show d ++ ".svg"
322 ChDir False _ d -> "img/ric" ++ show d ++ ".svg"
323 Split _ -> "img/split.svg"