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
23 import RMCA.Global.Clock
26 data GUICell = GUICell { cellAction :: Action
31 rotateGUICell g = g { cellAction = rotateAction $ cellAction g }
32 where rotateAction (ChDir b na d) = ChDir b na (nextDir d)
35 newtype GUIBoard = GUIBoard { toGS :: GameState Int Tile Player GUICell }
38 data Player = Player deriving(Show)
40 -- Takes a GUI coordinate and give the corresponding coordinate on the
42 fromGUICoords :: (Int,Int) -> (Int,Int)
43 fromGUICoords (x,y) = (x,(x `mod` 2 - y) `quot` 2)
45 -- Takes coordinates from the point of view of the internal board and
46 -- translates them to GUI board coordinates.
47 toGUICoords :: (Int,Int) -> (Int,Int)
48 toGUICoords (x,y) = (x,2*(-y) + x `mod` 2)
54 tileH = round (sqrt 3 * fromIntegral tileW / 3)
57 hexW = round (4 * fromIntegral tileW / 3)
60 hexH = round (sqrt 3 * fromIntegral hexW / 2)
63 (xMax,yMax) = BF.second (*2) $ neighbor N nec
65 (xMin,yMin) = BF.second (*2) swc
67 boardToTile :: [(Int,Int,Tile)]
68 boardToTile = [(x,y,Tile) | (x,y) <- range ( (xMin-1,yMin)
72 defNa = NoteAttr { naArt = NoAccent
77 ctrlPieces :: [(Int,Int,Player,GUICell)]
78 ctrlPieces = [(xMax+2,y,Player,GUICell { cellAction = action
82 | let actions = [ Absorb, Stop defNa
83 , ChDir False defNa N, ChDir True defNa N
85 -- /!\ It would be nice to find a general formula
86 -- for placing the control pieces.
87 , (y,action) <- zip [ yMin+4,yMin+8..] actions]
89 ctrlCoord = map (\(x,y,_,_) -> (x,y)) ctrlPieces
91 boardToPiece :: [PlayHead] -> Board -> [(Int,Int,Player,GUICell)]
92 boardToPiece ph = (++ ctrlPieces) . map placePiece .
93 filter (onBoard . fst) . assocs
94 where placePiece :: (Pos,Cell) -> (Int,Int,Player,GUICell)
95 placePiece ((x,y),(a,n)) = let c = GUICell { cellAction = a
97 , asPh = (x,y) `elem` phPosS
99 (x',y') = toGUICoords (x,y)
101 phPosS = map phPos ph
103 validArea :: [(Int,Int)]
104 validArea = filter (onBoard . fromGUICoords) $
105 map (\(x,y,_,_) -> (x,y)) $ boardToPiece [] $ makeBoard []
110 naOrn = Ornaments Nothing [] NoSlide
113 initGUIBoard :: GUIBoard
114 initGUIBoard = GUIBoard GameState
115 { curPlayer' = Player
116 , boardPos = boardToTile
117 , boardPieces' = boardToPiece [] $ makeBoard []
120 instance PlayableGame GUIBoard Int Tile Player GUICell where
122 allPos (GUIBoard game) = boardPos game
123 allPieces (GUIBoard game) = boardPieces' game
125 canMove (GUIBoard game) _ (x,y)
126 | Just (_,p) <- getPieceAt game (x,y)
127 , GUICell { cellAction = Inert } <- p = False
128 | Nothing <- getPieceAt game (x,y) = False
130 canMoveTo _ _ _ (x,y) = (x,y) `elem` validArea
132 move guiBoard@(GUIBoard game) p iPos@(_,yi) (xf,yf)
133 | iPos `elem` ctrlCoord = [ RemovePiece fPos'
134 , AddPiece fPos' Player (nCell { cellAction = ctrlAction })
136 | otherwise = [ MovePiece iPos fPos'
137 , AddPiece iPos Player nCell]
139 | (xf `mod` 2 == 0 && yf `mod` 2 == 0)
140 || (xf `mod` 2 /= 0 && yf `mod` 2 /= 0) = (xf,yf)
141 | otherwise = (xf,yf+signum' (yf-yi))
144 | otherwise = signum x
145 ctrlAction = cellAction $ snd $ fromJust $ getPieceAt game iPos
147 | Just (_,GUICell { asPh = ph, repeatCount = n }) <-
148 getPieceAt game iPos = inertCell { repeatCount = n
151 | otherwise = inertCell
152 where inertCell = GUICell { cellAction = Inert
156 applyChange (GUIBoard game) (AddPiece pos@(x,y) Player piece) =
157 GUIBoard $ game { boardPieces' = bp' }
158 where bp' = (x,y,Player,piece):boardPieces' game
160 applyChange (GUIBoard game) (RemovePiece (x,y)) = GUIBoard $
161 game { boardPieces' = bp' }
162 where bp' = [p | p@(x',y',_,_) <- boardPieces' game
163 , x /= x' || y /= y']
165 applyChange guiBoard@(GUIBoard game) (MovePiece iPos fPos)
166 | Just (_,p) <- getPieceAt game iPos
167 = applyChanges guiBoard [ RemovePiece iPos
169 , AddPiece fPos Player p]
170 | otherwise = guiBoard
172 initGame :: IO (Game GUIBoard Int Tile Player GUICell)
174 pixbufs <- fileToPixbuf
175 tilePixbuf <- pixbufNew ColorspaceRgb False 8 tileW tileH
176 pixbufFill tilePixbuf 50 50 50 0
177 let pixPiece :: (Player,GUICell) -> Pixbuf
178 pixPiece (_,a) = fromJust $ lookup (actionToFile a) pixbufs
179 pixTile :: Tile -> Pixbuf
180 pixTile _ = tilePixbuf
181 visualA = VisualGameAspects { tileF = pixTile
183 , bgColor = (1000,1000,1000)
187 return $ Game visualA initGUIBoard
189 -- Initializes a readable RV for the board and an readable-writable RV
190 -- for the playheads. Also installs some handlers for pieces modification.
191 initBoardRV :: BIO.Board Int Tile (Player,GUICell)
192 -> IO ( ReactiveFieldRead IO Board
193 , ReactiveFieldReadWrite IO [PlayHead])
194 initBoardRV board@BIO.Board { boardPieces = gBoard@(GameBoard array) } = do
196 phMVar <- newCBMVar []
197 notBMVar <- mkClockRV 100
198 let getterB :: IO Board
200 (boardArray :: [((Int,Int),Maybe (Player,GUICell))]) <- getAssocs array
201 let board = makeBoard $
202 map (BF.first fromGUICoords .
203 BF.second ((\(_,c) -> (cellAction c,repeatCount c)) .
205 filter (isJust . snd) boardArray
208 notifierB :: IO () -> IO ()
209 notifierB = reactiveValueOnCanRead notBMVar
211 getterP :: IO [PlayHead]
212 getterP = readCBMVar phMVar
214 setterP :: [PlayHead] -> IO ()
216 oph <- readCBMVar phMVar
217 let phPosS = map phPos lph
218 offPh :: PlayHead -> IO ()
220 let pos = toGUICoords $ phPos ph
221 piece <- boardGetPiece pos board
222 when (isJust piece) $ do
223 let (_,c) = fromJust piece
224 boardSetPiece pos (Player, c { asPh = False }) board
225 onPh :: PlayHead -> IO ()
227 let pos = toGUICoords $ phPos ph
228 piece <- boardGetPiece pos board
229 when (isJust piece) $ do
230 let (_,c) = fromJust piece
231 boardSetPiece pos (Player, c { asPh = True }) board
232 postGUIAsync $ mapM_ offPh oph
233 postGUIAsync $ mapM_ onPh lph
234 writeCBMVar phMVar lph
236 notifierP :: IO () -> IO ()
237 notifierP = installCallbackCBMVar phMVar
239 b = ReactiveFieldRead getterB notifierB
240 ph = ReactiveFieldReadWrite setterP getterP notifierP
243 clickHandling :: BIO.Board Int Tile (Player,GUICell) -> IO ()
244 clickHandling board = do
245 state <- newEmptyMVar
247 (\iPos -> liftIO $ do
248 postGUIAsync $ void $ tryPutMVar state iPos
252 (\fPos -> liftIO $ do
254 mp <- boardGetPiece fPos board
255 mstate <- tryTakeMVar state
256 when (fPos `elem` validArea && isJust mp &&
257 maybe False (== fPos) mstate) $ do
258 boardSetPiece fPos (BF.second rotateGUICell $
266 mp <- boardGetPiece i board
267 when (i `elem` validArea && isJust mp && fromJust mp == Inert) $
271 fileToPixbuf :: IO [(FilePath,Pixbuf)]
272 fileToPixbuf = mapM (\f -> let f' = "img/" ++ f in uncurry (liftM2 (,))
274 , pixbufNewFromFile f' >>=
275 \p -> pixbufScaleSimple p hexW hexW InterpBilinear ))
276 (["hexOn.png","hexOff.png","stop.svg","split.svg","absorb.svg"] ++
277 concat [["start" ++ show d ++ ".svg","ric" ++ show d ++ ".svg"]
280 actionToFile :: GUICell -> FilePath
281 actionToFile GUICell { cellAction = a
285 (Inert,True) -> "img/hexOn.png"
286 (Inert,False) -> "img/hexOff.png"
287 (Absorb,_) -> "img/absorb.svg"
288 (Stop _,_) -> "img/stop.svg"
289 (ChDir True _ d,_) -> "img/start" ++ show d ++ ".svg"
290 (ChDir False _ d,_) -> "img/ric" ++ show d ++ ".svg"
291 (Split _,_) -> "img/split.svg"