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
51 rotateGUICell :: GUICell -> GUICell
52 rotateGUICell g = g { cellAction = rotateAction $ cellAction g }
53 where rotateAction (ChDir b na d) = ChDir b na (nextDir d)
54 rotateAction (Split na ds) = Split na (turnQueue ds 1)
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 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
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 -- If the repeatCount of some tile is superior to mrc,
242 -- then this tile will be undistinguishable from any other tile with a
243 -- repeat count superior to mrc.
247 pixbufForPiece :: IO (GUICell -> Pixbuf)
249 let colorPlayHead _ r g b ma = if (r == 0 && g == 0 && b == 0)
252 colorRC 0 _ _ _ _ ma = (0, 0, 0, ma)
253 colorRC rc _ r g b ma =
254 if (r == 0 && g == 0 && b == 0)
256 else let (gradr, gradg, gradb) = ( (maxBound - r) `quot` mrc
257 , (g - minBound) `quot` mrc
258 , (b - minBound) `quot` mrc
260 in ( r + gradr * (rc - 1)
261 , g - gradg * (rc - 1)
262 , b - gradb * (rc - 1)
265 pixbufs <- mapM (\(a,rc) -> do df <- getDataFileName $ actionToFile a
266 p <- do p' <- pixbufNewFromFile df
267 pixbufScaleSimple p' hexW hexW InterpBilinear
268 modifyPixbuf (colorRC rc) p
270 modifyPixbuf colorPlayHead p'
271 return ((a,rc), (p, p'))
272 ) [(a,r) | r <- [0..mrc], a <- actionList]
273 let f GUICell { cellAction = a
275 , repeatCount = r } =
276 (if t then snd else fst) $ fromJust $
277 lookup (anonymizeConstructor a, min (fromIntegral r) mrc) pixbufs
280 modifyPixbuf :: ((Int, Int) -> Word8 -> Word8 -> Word8 -> Maybe Word8 ->
281 (Word8, Word8, Word8, Maybe Word8))
283 modifyPixbuf f p = do
284 pixs <- pixbufGetPixels p
285 w <- pixbufGetWidth p
286 h <- pixbufGetHeight p
287 rs <- pixbufGetRowstride p
288 chans <- pixbufGetNChannels p
289 forM_ [(x,y) | x <- [0..w - 1], y <- [0..h - 1]] $ \(x,y) -> do
290 let p = x * rs + y * chans
291 red <- readArray pixs p
292 green <- readArray pixs (p + 1)
293 blue <- readArray pixs (p + 2)
294 alpha <- if (chans == 4)
295 then fmap Just $ readArray pixs (p + 3)
297 let (nr, ng, nb, na) = f (x,y) red green blue alpha
299 writeArray pixs (p + 1) ng
300 writeArray pixs (p + 2) nb
301 when (isJust na) $ writeArray pixs (p + 3) $ fromJust na
304 actionToFile :: Action -> FilePath
306 Inert -> "img/hexOff.png"
307 Absorb -> "img/absorb.svg"
308 Stop _ -> "img/stop.svg"
309 ChDir True _ d -> "img/start" ++ show d ++ ".svg"
310 ChDir False _ d -> "img/ric" ++ show d ++ ".svg"
311 Split _ _ -> "img/split.svg"