1 {-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, ScopedTypeVariables,
2 TypeSynonymInstances #-}
4 module RMCA.GUI.Board where
6 import Control.Concurrent.MVar
8 import Control.Monad.IO.Class
10 import Data.Array.MArray
11 import qualified Data.Bifunctor as BF
12 import Data.Board.GameBoardIO
16 import Data.ReactiveValue
18 import Game.Board.BasicTurnGame
19 import Graphics.UI.Gtk hiding (Action)
20 import Graphics.UI.Gtk.Board.BoardLink
21 import Graphics.UI.Gtk.Board.TiledBoard hiding (Board)
22 import qualified Graphics.UI.Gtk.Board.TiledBoard as BIO
24 import RMCA.Global.Clock
27 data GUICell = GUICell { cellAction :: Action
32 rotateGUICell g = g { cellAction = rotateAction $ cellAction g }
33 where rotateAction (ChDir b na d) = ChDir b na (nextDir d)
36 newtype GUIBoard = GUIBoard { toGS :: GameState Int Tile Player GUICell }
38 type IOBoard = BIO.Board Int Tile (Player,GUICell)
41 data Player = Player deriving(Show)
43 -- Takes a GUI coordinate and give the corresponding coordinate on the
45 fromGUICoords :: (Int,Int) -> (Int,Int)
46 fromGUICoords (x,y) = (x,(x `mod` 2 - y) `quot` 2)
48 -- Takes coordinates from the point of view of the internal board and
49 -- translates them to GUI board coordinates.
50 toGUICoords :: (Int,Int) -> (Int,Int)
51 toGUICoords (x,y) = (x,2*(-y) + x `mod` 2)
57 tileH = round (sqrt 3 * fromIntegral tileW / 3)
60 hexW = round (4 * fromIntegral tileW / 3)
63 hexH = round (sqrt 3 * fromIntegral hexW / 2)
66 (xMax,yMax) = BF.second (*2) $ neighbor N nec
68 (xMin,yMin) = BF.second (*2) swc
70 boardToTile :: [(Int,Int,Tile)]
71 boardToTile = [(x,y,Tile) | (x,y) <- range ( (xMin-1,yMin)
75 defNa = NoteAttr { naArt = NoAccent
80 ctrlPieces :: [(Int,Int,Player,GUICell)]
81 ctrlPieces = [(xMax+2,y,Player,GUICell { cellAction = action
85 | let actions = [ Absorb, Stop defNa
86 , ChDir False defNa N, ChDir True defNa N
88 -- /!\ It would be nice to find a general formula
89 -- for placing the control pieces.
90 , (y,action) <- zip [ yMin+4,yMin+8..] actions]
92 ctrlCoords :: [(Int,Int)]
93 ctrlCoords = map (\(x,y,_,_) -> (x,y)) ctrlPieces
95 boardToPiece :: [PlayHead] -> Board -> [(Int,Int,Player,GUICell)]
96 boardToPiece ph = (++ ctrlPieces) . map placePiece .
97 filter (onBoard . fst) . assocs
98 where placePiece :: (Pos,Cell) -> (Int,Int,Player,GUICell)
99 placePiece ((x,y),(a,n)) = let c = GUICell { cellAction = a
101 , asPh = (x,y) `elem` phPosS
103 (x',y') = toGUICoords (x,y)
105 phPosS = map phPos ph
107 validArea :: [(Int,Int)]
108 validArea = filter (onBoard . fromGUICoords) $
109 map (\(x,y,_,_) -> (x,y)) $ boardToPiece [] $ makeBoard []
111 outGUIBoard :: (Int,Int) -> Bool
112 outGUIBoard (xf,yf) = xf < xMin || xf > xMax || yf < yMin || yf > yMax
117 naOrn = Ornaments Nothing [] NoSlide
121 inertCell = GUICell { cellAction = Inert
126 initGUIBoard :: GUIBoard
127 initGUIBoard = GUIBoard GameState
128 { curPlayer' = Player
129 , boardPos = boardToTile
130 , boardPieces' = boardToPiece [] $ makeBoard []
133 instance PlayableGame GUIBoard Int Tile Player GUICell where
135 allPos (GUIBoard game) = boardPos game
136 allPieces (GUIBoard game) = boardPieces' game
138 canMove (GUIBoard game) _ (x,y)
139 | Just (_,p) <- getPieceAt game (x,y)
140 , GUICell { cellAction = Inert } <- p = False
141 | Nothing <- getPieceAt game (x,y) = False
143 canMoveTo _ _ _ fPos = fPos `elem` validArea
146 move guiBoard@(GUIBoard game) p iPos@(_,yi) fPos@(xf,yf)
147 | outGUIBoard iPos && outGUIBoard fPos = []
148 | outGUIBoard fPos = [ RemovePiece iPos
149 , AddPiece iPos Player nCell ]
150 | iPos `elem` ctrlCoords = [ RemovePiece fPos'
151 , AddPiece fPos' Player
152 (nCell { cellAction = ctrlAction }) ]
153 | otherwise = [ MovePiece iPos fPos'
154 , AddPiece iPos Player nCell ]
156 | (xf `mod` 2 == 0 && yf `mod` 2 == 0)
157 || (xf `mod` 2 /= 0 && yf `mod` 2 /= 0) = (xf,yf)
158 | otherwise = (xf,yf+signum' (yf-yi))
161 | otherwise = signum x
162 ctrlAction = cellAction $ snd $ fromJust $ getPieceAt game iPos
164 | Just (_,GUICell { asPh = ph, repeatCount = n }) <-
165 getPieceAt game iPos = inertCell { repeatCount = n
168 | otherwise = inertCell
170 applyChange (GUIBoard game) (AddPiece pos@(x,y) Player piece) =
171 GUIBoard $ game { boardPieces' = bp' }
172 where bp' = (x,y,Player,piece):boardPieces' game
174 applyChange (GUIBoard game) (RemovePiece (x,y)) = GUIBoard $
175 game { boardPieces' = bp' }
176 where bp' = [p | p@(x',y',_,_) <- boardPieces' game
177 , x /= x' || y /= y']
179 applyChange guiBoard@(GUIBoard game) (MovePiece iPos fPos)
180 | Just (_,p) <- getPieceAt game iPos
181 = applyChanges guiBoard [ RemovePiece iPos
183 , AddPiece fPos Player p]
184 | otherwise = guiBoard
186 initGame :: IO (Game GUIBoard Int Tile Player GUICell)
188 pixbufs <- fileToPixbuf
189 tilePixbuf <- pixbufNew ColorspaceRgb False 8 tileW tileH
190 pixbufFill tilePixbuf 50 50 50 0
191 let pixPiece :: (Player,GUICell) -> Pixbuf
192 pixPiece (_,a) = fromJust $ lookup (actionToFile a) pixbufs
193 pixTile :: Tile -> Pixbuf
194 pixTile _ = tilePixbuf
195 visualA = VisualGameAspects { tileF = pixTile
197 , bgColor = (1000,1000,1000)
201 return $ Game visualA initGUIBoard
203 -- Initializes a readable RV for the board and an readable-writable RV
204 -- for the playheads. Also installs some handlers for pieces modification.
205 initBoardRV :: BIO.Board Int Tile (Player,GUICell)
206 -> IO ( ReactiveFieldRead IO Board
207 , Array Pos (ReactiveFieldWrite IO GUICell)
208 , ReactiveFieldReadWrite IO [PlayHead])
209 initBoardRV board@BIO.Board { boardPieces = gBoard@(GameBoard gArray) } = do
211 phMVar <- newCBMVar []
212 notBMVar <- mkClockRV 100
213 let getterB :: IO Board
215 (boardArray :: [((Int,Int),Maybe (Player,GUICell))]) <- getAssocs gArray
216 let board = makeBoard $
217 map (BF.first fromGUICoords .
218 BF.second ((\(_,c) -> (cellAction c,repeatCount c)) .
220 filter (isJust . snd) boardArray
223 notifierB :: IO () -> IO ()
224 notifierB = reactiveValueOnCanRead notBMVar
226 getterP :: IO [PlayHead]
227 getterP = readCBMVar phMVar
229 setterP :: [PlayHead] -> IO ()
231 oph <- readCBMVar phMVar
232 let phPosS = map phPos lph
233 offPh :: PlayHead -> IO ()
235 let pos = toGUICoords $ phPos ph
236 piece <- boardGetPiece pos board
237 when (isJust piece) $ do
238 let (_,c) = fromJust piece
239 boardSetPiece pos (Player, c { asPh = False }) board
240 onPh :: PlayHead -> IO ()
242 let pos = toGUICoords $ phPos ph
243 piece <- boardGetPiece pos board
244 when (isJust piece) $ do
245 let (_,c) = fromJust piece
246 boardSetPiece pos (Player, c { asPh = True }) board
247 postGUIAsync $ mapM_ offPh oph
248 postGUIAsync $ mapM_ onPh lph
249 writeCBMVar phMVar lph
251 notifierP :: IO () -> IO ()
252 notifierP = installCallbackCBMVar phMVar
254 b = ReactiveFieldRead getterB notifierB
255 ph = ReactiveFieldReadWrite setterP getterP notifierP
257 setterW :: (Int,Int) -> GUICell -> IO ()
258 setterW i g = postGUIAsync $ boardSetPiece i (Player,g) board
260 arrW :: Array Pos (ReactiveFieldWrite IO GUICell)
261 arrW = array (minimum validArea, maximum validArea)
262 [(i, ReactiveFieldWrite (setterW i))
263 | i <- (validArea :: [(Int,Int)])]
267 clickHandling :: BIO.Board Int Tile (Player,GUICell) -> IO ()
268 clickHandling board = do
269 state <- newEmptyMVar
271 (\iPos -> liftIO $ do
272 postGUIAsync $ void $ tryPutMVar state iPos
276 (\fPos -> liftIO $ do
278 mp <- boardGetPiece fPos board
279 mstate <- tryTakeMVar state
280 when (fPos `elem` validArea && isJust mp &&
281 maybe False (== fPos) mstate) $ do
282 boardSetPiece fPos (BF.second rotateGUICell $
290 mp <- boardGetPiece i board
291 when (i `elem` validArea && isJust mp && fromJust mp == Inert) $
295 fileToPixbuf :: IO [(FilePath,Pixbuf)]
296 fileToPixbuf = mapM (\f -> let f' = ("img/" ++ f) in
299 , getDataFileName f' >>=
300 \f'' -> pixbufNewFromFile f'' >>=
301 \p -> pixbufScaleSimple p hexW hexW InterpBilinear))
302 (["hexOn.png","hexOff.png","stop.svg","split.svg","absorb.svg"] ++
303 concat [["start" ++ show d ++ ".svg","ric" ++ show d ++ ".svg"]
306 actionToFile :: GUICell -> FilePath
307 actionToFile GUICell { cellAction = a
311 Inert -> "img/hexO" ++ (if ph then "n" else "ff") ++ ".png"
312 Absorb -> "img/absorb.svg"
313 Stop _ -> "img/stop.svg"
314 ChDir True _ d -> "img/start" ++ show d ++ ".svg"
315 ChDir False _ d -> "img/ric" ++ show d ++ ".svg"
316 Split _ -> "img/split.svg"