1 {-# LANGUAGE FlexibleContexts, FlexibleInstances, MultiParamTypeClasses,
2 ScopedTypeVariables, TypeSynonymInstances #-}
4 module RMCA.GUI.Board ( GUICell (..)
18 import Data.Array.MArray
19 import qualified Data.Bifunctor as BF
20 import Data.Board.GameBoardIO
23 import Data.ReactiveValue
24 import Game.Board.BasicTurnGame
25 import Graphics.UI.Gtk hiding (Action)
26 import Graphics.UI.Gtk.Board.BoardLink hiding (attachGameRules)
27 import Graphics.UI.Gtk.Board.TiledBoard hiding
29 , boardOnPieceDragDrop
30 , boardOnPieceDragOver
31 , boardOnPieceDragStart
33 import qualified Graphics.UI.Gtk.Board.TiledBoard as BIO
35 import RMCA.Global.Clock
36 import RMCA.GUI.HelpersRewrite
39 newtype GUIBoard = GUIBoard { toGS :: GameState Int Tile Player GUICell }
41 type IOBoard = BIO.Board Int Tile (Player,GUICell)
43 -- There are two types of tiles that can be distinguished by setting
44 -- two different colors for debugging purposes. A future release might
45 -- want to remove that.
46 data Tile = TileW | TileB
49 rotateGUICell :: GUICell -> GUICell
50 rotateGUICell g = g { cellAction = rotateAction $ cellAction g }
51 where rotateAction (ChDir b na d) = ChDir b na (nextDir d)
60 d = sqrt 3 * fromIntegral tileW / 3
65 d = 4 * fromIntegral tileW / 3
70 d = sqrt 3 * fromIntegral hexW / 2
72 boardToTile :: [(Int,Int,Tile)]
73 boardToTile = [(x,y,selTile) | (x,y) <- range ( (xMin-1,yMin)
75 , let selTile = if even x && even y
83 outGUIBoard :: (Int,Int) -> Bool
84 outGUIBoard (xf,yf) = xf < xMin || xf > xMax || yf < yMin || yf > yMax
87 inertCell = GUICell { cellAction = Inert
92 initGUIBoard :: GUIBoard
93 initGUIBoard = GUIBoard GameState
95 , boardPos = boardToTile
96 , boardPieces' = boardToPiece [] $ makeBoard []
99 instance PlayableGame GUIBoard Int Tile Player GUICell where
101 allPos (GUIBoard game) = boardPos game
102 allPieces (GUIBoard game) = boardPieces' game
105 canMove (GUIBoard game) _ (x,y)
106 | Just (_,p) <- getPieceAt game (x,y)
107 , GUICell { cellAction = Inert } <- p = False
108 | Nothing <- getPieceAt game (x,y) = False
110 canMoveTo _ _ _ fPos = fPos `elem` validArea || outGUIBoard fPos
112 move (GUIBoard game) _ iPos@(_,yi) fPos@(xf,yf)
113 | outGUIBoard iPos && outGUIBoard fPos = []
114 | outGUIBoard fPos = [ RemovePiece iPos
115 , AddPiece iPos Player nCell ]
116 | iPos `elem` ctrlCoords = [ RemovePiece fPos'
117 , AddPiece fPos' Player
118 (nCell { cellAction = ctrlAction }) ]
119 | otherwise = [ MovePiece iPos fPos'
120 , AddPiece iPos Player nCell ]
122 | (xf `mod` 2 == 0 && yf `mod` 2 == 0)
123 || (xf `mod` 2 /= 0 && yf `mod` 2 /= 0) = (xf,yf)
124 | otherwise = (xf,yf+signum' (yf-yi))
127 | otherwise = signum x
128 ctrlAction = cellAction $ snd $ fromJust $ getPieceAt game iPos
130 | Just (_,GUICell { asPh = ph, repeatCount = n }) <-
131 getPieceAt game iPos = inertCell { repeatCount = n
134 | otherwise = inertCell
136 applyChange (GUIBoard game) (AddPiece (x,y) Player piece) =
137 GUIBoard $ game { boardPieces' = bp' }
138 where bp' = (x,y,Player,piece):boardPieces' game
140 applyChange (GUIBoard game) (RemovePiece (x,y)) = GUIBoard $
141 game { boardPieces' = bp' }
142 where bp' = [p | p@(x',y',_,_) <- boardPieces' game
143 , x /= x' || y /= y']
145 applyChange guiBoard@(GUIBoard game) (MovePiece iPos fPos)
146 | Just (_,p) <- getPieceAt game iPos
147 = applyChanges guiBoard [ RemovePiece iPos
149 , AddPiece fPos Player p]
150 | otherwise = guiBoard
152 initGame :: IO (Game GUIBoard Int Tile Player GUICell)
154 pixbufs <- fileToPixbuf
155 tilePixbufB <- pixbufNew ColorspaceRgb False 8 tileW tileH
156 tilePixbufW <- pixbufNew ColorspaceRgb False 8 tileW tileH
157 pixbufFill tilePixbufB 50 50 50 0
158 pixbufFill tilePixbufW 50 50 50 0
159 let pixPiece :: (Player,GUICell) -> Pixbuf
160 pixPiece (_,a) = fromJust $ lookup (actionToFile a) pixbufs
161 pixTile :: Tile -> Pixbuf
162 pixTile TileW = tilePixbufW
163 pixTile TileB = tilePixbufB
164 visualA = VisualGameAspects { tileF = pixTile
166 , bgColor = (1000,1000,1000)
170 return $ Game visualA initGUIBoard
172 -- Initializes a readable RV for the board and an readable-writable RV
173 -- for the playheads. Also installs some handlers for pieces modification.
174 initBoardRV :: BIO.Board Int Tile (Player,GUICell)
175 -> IO ( ReactiveFieldRead IO Board
176 , Array Pos (ReactiveFieldWrite IO GUICell)
177 , ReactiveFieldWrite IO [PlayHead])
178 initBoardRV board@BIO.Board { boardPieces = (GameBoard gArray) } = do
180 phMVar <- newCBMVar []
181 notBMVar <- mkClockRV 50
182 let getterB :: IO Board
184 (boardArray :: [((Int,Int),Maybe (Player,GUICell))]) <- getAssocs gArray
185 let board = makeBoard $
186 map (BF.first fromGUICoords .
187 BF.second ((\(_,c) -> (cellAction c,repeatCount c)) .
189 filter (isJust . snd) boardArray
192 notifierB :: IO () -> IO ()
193 notifierB = reactiveValueOnCanRead notBMVar
195 getterP :: IO [PlayHead]
196 getterP = readCBMVar phMVar
198 setterP :: [PlayHead] -> IO ()
200 oph <- readCBMVar phMVar
201 let offPh :: PlayHead -> IO ()
203 let pos = toGUICoords $ phPos ph
204 piece <- boardGetPiece pos board
205 when (isJust piece) $ do
206 let (_,c) = fromJust piece
207 boardSetPiece pos (Player, c { asPh = False }) board
208 onPh :: PlayHead -> IO ()
210 let pos = toGUICoords $ phPos ph
211 piece <- boardGetPiece pos board
212 when (isJust piece) $ do
213 let (_,c) = fromJust piece
214 boardSetPiece pos (Player, c { asPh = True }) board
215 postGUIAsync $ mapM_ offPh oph
216 postGUIAsync $ mapM_ onPh lph
217 writeCBMVar phMVar lph
219 notifierP :: IO () -> IO ()
220 notifierP = installCallbackCBMVar phMVar
222 b = ReactiveFieldRead getterB notifierB
223 ph = ReactiveFieldReadWrite setterP getterP notifierP
225 setterW :: (Int,Int) -> GUICell -> IO ()
226 setterW i g = postGUIAsync $ boardSetPiece i (Player,g) board
229 arrW :: Array Pos (ReactiveFieldWrite IO GUICell)
230 arrW = array (minimum validArea, maximum validArea)
231 [(i, ReactiveFieldWrite (setterW i))
232 | i <- validArea :: [(Int,Int)]]
234 return (b,arrW,writeOnly ph)
236 fileToPixbuf :: IO [(FilePath,Pixbuf)]
237 fileToPixbuf = mapM (\f -> let f' = ("img/" ++ f) in
240 , getDataFileName f' >>=
241 (pixbufNewFromFile >=>
242 \p -> pixbufScaleSimple p hexW hexW InterpBilinear)))
243 (["hexOn.png","hexOff.png","stop.svg","split.svg","absorb.svg"] ++
244 concat [["start" ++ show d ++ ".svg","ric" ++ show d ++ ".svg"]
247 actionToFile :: GUICell -> FilePath
248 actionToFile GUICell { cellAction = a
252 Inert -> "img/hexO" ++ (if ph then "n" else "ff") ++ ".png"
253 Absorb -> "img/absorb.svg"
254 Stop _ -> "img/stop.svg"
255 ChDir True _ d -> "img/start" ++ show d ++ ".svg"
256 ChDir False _ d -> "img/ric" ++ show d ++ ".svg"
257 Split _ -> "img/split.svg"