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)-- (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 oldphMVar <- newCBMVar []
198 notBMVar <- mkClockRV 100
199 let getterB :: IO Board
201 (boardArray :: [((Int,Int),Maybe (Player,GUICell))]) <- getAssocs array
202 let board = makeBoard $
203 map (BF.first fromGUICoords .
204 BF.second ((\(_,c) -> (cellAction c,repeatCount c)) .
206 filter (isJust . snd) boardArray
209 notifierB :: IO () -> IO ()
210 notifierB = reactiveValueOnCanRead notBMVar
212 getterP :: IO [PlayHead]
213 getterP = readCBMVar phMVar
215 setterP :: [PlayHead] -> IO ()
217 readCBMVar phMVar >>= writeCBMVar oldphMVar
218 writeCBMVar phMVar lph
219 oph <- readCBMVar oldphMVar
220 let phPosS = map phPos lph
221 offPh :: PlayHead -> IO ()
223 let pos = toGUICoords $ phPos ph
224 piece <- boardGetPiece pos board
225 when (isJust piece) $ do
226 let (_,c) = fromJust piece
227 boardSetPiece pos (Player, c { asPh = pos `elem` phPosS }) board
228 onPh :: PlayHead -> IO ()
230 let pos = toGUICoords $ phPos ph
231 piece <- boardGetPiece pos board
232 when (isJust piece) $ do
233 let (_,c) = fromJust piece
234 boardSetPiece pos (Player, c { asPh = True }) board
238 notifierP :: IO () -> IO ()
239 notifierP = installCallbackCBMVar phMVar
241 b = ReactiveFieldRead getterB notifierB
242 ph = ReactiveFieldReadWrite setterP getterP notifierP
245 clickHandling :: BIO.Board Int Tile (Player,GUICell) -> IO ()
246 clickHandling board = do
247 state <- newEmptyMVar
249 (\iPos -> liftIO $ do
250 tryPutMVar state iPos
254 (\fPos -> liftIO $ do
255 mp <- boardGetPiece fPos board
256 mstate <- tryTakeMVar state
257 when (fPos `elem` validArea && isJust mp &&
258 maybe False (== fPos) mstate) $ do
259 boardSetPiece fPos (BF.second rotateGUICell $
267 mp <- boardGetPiece i board
268 when (i `elem` validArea && isJust mp && fromJust mp == Inert) $
272 fileToPixbuf :: IO [(FilePath,Pixbuf)]
273 fileToPixbuf = mapM (\f -> let f' = "img/" ++ f in uncurry (liftM2 (,))
275 , pixbufNewFromFile f' >>=
276 \p -> pixbufScaleSimple p hexW hexW InterpBilinear ))
277 (["hexOn.png","hexOff.png","stop.svg","split.svg","absorb.svg"] ++
278 concat [["start" ++ show d ++ ".svg","ric" ++ show d ++ ".svg"]
281 actionToFile :: GUICell -> FilePath
282 actionToFile GUICell { cellAction = a
286 (Inert,True) -> "img/hexOn.png"
287 (Inert,False) -> "img/hexOff.png"
288 (Absorb,_) -> "img/absorb.svg"
289 (Stop _,_) -> "img/stop.svg"
290 (ChDir True _ d,_) -> "img/start" ++ show d ++ ".svg"
291 (ChDir False _ d,_) -> "img/ric" ++ show d ++ ".svg"
292 (Split _,_) -> "img/split.svg"