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 }
39 data Player = Player deriving(Show)
41 -- Takes a GUI coordinate and give the corresponding coordinate on the
43 fromGUICoords :: (Int,Int) -> (Int,Int)
44 fromGUICoords (x,y) = (x,(x `mod` 2 - y) `quot` 2)
46 -- Takes coordinates from the point of view of the internal board and
47 -- translates them to GUI board coordinates.
48 toGUICoords :: (Int,Int) -> (Int,Int)
49 toGUICoords (x,y) = (x,2*(-y) + x `mod` 2)
55 tileH = round (sqrt 3 * fromIntegral tileW / 3)
58 hexW = round (4 * fromIntegral tileW / 3)
61 hexH = round (sqrt 3 * fromIntegral hexW / 2)
64 (xMax,yMax) = BF.second (*2) $ neighbor N nec
66 (xMin,yMin) = BF.second (*2) swc
68 boardToTile :: [(Int,Int,Tile)]
69 boardToTile = [(x,y,Tile) | (x,y) <- range ( (xMin-1,yMin)
73 defNa = NoteAttr { naArt = NoAccent
78 ctrlPieces :: [(Int,Int,Player,GUICell)]
79 ctrlPieces = [(xMax+2,y,Player,GUICell { cellAction = action
83 | let actions = [ Absorb, Stop defNa
84 , ChDir False defNa N, ChDir True defNa N
86 -- /!\ It would be nice to find a general formula
87 -- for placing the control pieces.
88 , (y,action) <- zip [ yMin+4,yMin+8..] actions]
90 ctrlCoords :: [(Int,Int)]
91 ctrlCoords = map (\(x,y,_,_) -> (x,y)) ctrlPieces
93 boardToPiece :: [PlayHead] -> Board -> [(Int,Int,Player,GUICell)]
94 boardToPiece ph = (++ ctrlPieces) . map placePiece .
95 filter (onBoard . fst) . assocs
96 where placePiece :: (Pos,Cell) -> (Int,Int,Player,GUICell)
97 placePiece ((x,y),(a,n)) = let c = GUICell { cellAction = a
99 , asPh = (x,y) `elem` phPosS
101 (x',y') = toGUICoords (x,y)
103 phPosS = map phPos ph
105 validArea :: [(Int,Int)]
106 validArea = filter (onBoard . fromGUICoords) $
107 map (\(x,y,_,_) -> (x,y)) $ boardToPiece [] $ makeBoard []
109 outGUIBoard :: (Int,Int) -> Bool
110 outGUIBoard (xf,yf) = xf < xMin || xf > xMax || yf < yMin || yf > yMax
115 naOrn = Ornaments Nothing [] NoSlide
118 initGUIBoard :: GUIBoard
119 initGUIBoard = GUIBoard GameState
120 { curPlayer' = Player
121 , boardPos = boardToTile
122 , boardPieces' = boardToPiece [] $ makeBoard []
125 instance PlayableGame GUIBoard Int Tile Player GUICell where
127 allPos (GUIBoard game) = boardPos game
128 allPieces (GUIBoard game) = boardPieces' game
130 canMove (GUIBoard game) _ (x,y)
131 | Just (_,p) <- getPieceAt game (x,y)
132 , GUICell { cellAction = Inert } <- p = False
133 | Nothing <- getPieceAt game (x,y) = False
135 canMoveTo _ _ _ fPos = fPos `elem` validArea
138 move guiBoard@(GUIBoard game) p iPos@(_,yi) fPos@(xf,yf)
139 | iPos `elem` ctrlCoords = [ RemovePiece fPos'
140 , AddPiece fPos' Player
141 (nCell { cellAction = ctrlAction }) ]
142 | outGUIBoard fPos = [ RemovePiece iPos
143 , AddPiece iPos Player nCell ]
144 | otherwise = [ MovePiece iPos fPos'
145 , AddPiece iPos Player nCell ]
147 | (xf `mod` 2 == 0 && yf `mod` 2 == 0)
148 || (xf `mod` 2 /= 0 && yf `mod` 2 /= 0) = (xf,yf)
149 | otherwise = (xf,yf+signum' (yf-yi))
152 | otherwise = signum x
153 ctrlAction = cellAction $ snd $ fromJust $ getPieceAt game iPos
155 | Just (_,GUICell { asPh = ph, repeatCount = n }) <-
156 getPieceAt game iPos = inertCell { repeatCount = n
159 | otherwise = inertCell
160 where inertCell = GUICell { cellAction = Inert
164 applyChange (GUIBoard game) (AddPiece pos@(x,y) Player piece) =
165 GUIBoard $ game { boardPieces' = bp' }
166 where bp' = (x,y,Player,piece):boardPieces' game
168 applyChange (GUIBoard game) (RemovePiece (x,y)) = GUIBoard $
169 game { boardPieces' = bp' }
170 where bp' = [p | p@(x',y',_,_) <- boardPieces' game
171 , x /= x' || y /= y']
173 applyChange guiBoard@(GUIBoard game) (MovePiece iPos fPos)
174 | Just (_,p) <- getPieceAt game iPos
175 = applyChanges guiBoard [ RemovePiece iPos
177 , AddPiece fPos Player p]
178 | otherwise = guiBoard
180 initGame :: IO (Game GUIBoard Int Tile Player GUICell)
182 pixbufs <- fileToPixbuf
183 tilePixbuf <- pixbufNew ColorspaceRgb False 8 tileW tileH
184 pixbufFill tilePixbuf 50 50 50 0
185 let pixPiece :: (Player,GUICell) -> Pixbuf
186 pixPiece (_,a) = fromJust $ lookup (actionToFile a) pixbufs
187 pixTile :: Tile -> Pixbuf
188 pixTile _ = tilePixbuf
189 visualA = VisualGameAspects { tileF = pixTile
191 , bgColor = (1000,1000,1000)
195 return $ Game visualA initGUIBoard
197 -- Initializes a readable RV for the board and an readable-writable RV
198 -- for the playheads. Also installs some handlers for pieces modification.
199 initBoardRV :: BIO.Board Int Tile (Player,GUICell)
200 -> IO ( ReactiveFieldRead IO Board
201 , ReactiveFieldReadWrite IO [PlayHead])
202 initBoardRV board@BIO.Board { boardPieces = gBoard@(GameBoard array) } = do
204 phMVar <- newCBMVar []
205 notBMVar <- mkClockRV 100
206 let getterB :: IO Board
208 (boardArray :: [((Int,Int),Maybe (Player,GUICell))]) <- getAssocs array
209 let board = makeBoard $
210 map (BF.first fromGUICoords .
211 BF.second ((\(_,c) -> (cellAction c,repeatCount c)) .
213 filter (isJust . snd) boardArray
216 notifierB :: IO () -> IO ()
217 notifierB = reactiveValueOnCanRead notBMVar
219 getterP :: IO [PlayHead]
220 getterP = readCBMVar phMVar
222 setterP :: [PlayHead] -> IO ()
224 oph <- readCBMVar phMVar
225 let phPosS = map phPos lph
226 offPh :: PlayHead -> IO ()
228 let pos = toGUICoords $ phPos ph
229 piece <- boardGetPiece pos board
230 when (isJust piece) $ do
231 let (_,c) = fromJust piece
232 boardSetPiece pos (Player, c { asPh = False }) board
233 onPh :: 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 = True }) board
240 postGUIAsync $ mapM_ offPh oph
241 postGUIAsync $ mapM_ onPh lph
242 writeCBMVar phMVar lph
244 notifierP :: IO () -> IO ()
245 notifierP = installCallbackCBMVar phMVar
247 b = ReactiveFieldRead getterB notifierB
248 ph = ReactiveFieldReadWrite setterP getterP notifierP
251 clickHandling :: BIO.Board Int Tile (Player,GUICell) -> IO ()
252 clickHandling board = do
253 state <- newEmptyMVar
255 (\iPos -> liftIO $ do
256 postGUIAsync $ void $ tryPutMVar state iPos
260 (\fPos -> liftIO $ do
262 mp <- boardGetPiece fPos board
263 mstate <- tryTakeMVar state
264 when (fPos `elem` validArea && isJust mp &&
265 maybe False (== fPos) mstate) $ do
266 boardSetPiece fPos (BF.second rotateGUICell $
274 mp <- boardGetPiece i board
275 when (i `elem` validArea && isJust mp && fromJust mp == Inert) $
279 fileToPixbuf :: IO [(FilePath,Pixbuf)]
280 fileToPixbuf = mapM (\f -> let f' = ("img/" ++ f) in
283 , getDataFileName f' >>=
284 \f'' -> pixbufNewFromFile f'' >>=
285 \p -> pixbufScaleSimple p hexW hexW InterpBilinear))
286 (["hexOn.png","hexOff.png","stop.svg","split.svg","absorb.svg"] ++
287 concat [["start" ++ show d ++ ".svg","ric" ++ show d ++ ".svg"]
290 actionToFile :: GUICell -> FilePath
291 actionToFile GUICell { cellAction = a
295 Inert -> "img/hexO" ++ (if ph then "n" else "ff") ++ ".png"
296 Absorb -> "img/absorb.svg"
297 Stop _ -> "img/stop.svg"
298 ChDir True _ d -> "img/start" ++ show d ++ ".svg"
299 ChDir False _ d -> "img/ric" ++ show d ++ ".svg"
300 Split _ -> "img/split.svg"