1 {-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, ScopedTypeVariables,
2 TypeSynonymInstances #-}
4 module RMCA.GUI.Board where
8 import Data.Array.MArray
9 import qualified Data.Bifunctor as BF
10 import Data.Board.GameBoardIO
14 import Data.ReactiveValue
16 import Game.Board.BasicTurnGame
17 import Graphics.UI.Gtk hiding (Action)
18 import Graphics.UI.Gtk.Board.BoardLink
19 import Graphics.UI.Gtk.Board.TiledBoard hiding (Board)
20 import qualified Graphics.UI.Gtk.Board.TiledBoard as BIO
21 import RMCA.Global.Clock
24 data GUICell = GUICell { cellAction :: Action
29 newtype GUIBoard = GUIBoard { toGS :: GameState Int Tile Player GUICell }
32 data Player = Player deriving(Show)
34 -- Takes a GUI coordinate and give the corresponding coordinate on the
36 fromGUICoords :: (Int,Int) -> (Int,Int)
37 fromGUICoords (x,y) = (x,(x `mod` 2 - y) `quot` 2)
39 -- Takes coordinates from the point of view of the internal board and
40 -- translates them to GUI board coordinates.
41 toGUICoords :: (Int,Int) -> (Int,Int)
42 toGUICoords (x,y) = (x,2*(-y) + x `mod` 2)
48 tileH = round (sqrt 3 * fromIntegral tileW / 3)
51 hexW = round (4 * fromIntegral tileW / 3)
54 hexH = round (sqrt 3 * fromIntegral hexW / 2)
57 (xMax,yMax) = BF.second (*2) $ neighbor N nec
59 (xMin,yMin) = BF.second (*2) swc
61 boardToTile :: [(Int,Int,Tile)]
62 boardToTile = [(x,y,Tile) | (x,y) <- range ( (xMin-1,yMin)
66 defNa = NoteAttr { naArt = NoAccent
71 ctrlPieces :: [(Int,Int,Player,GUICell)]
72 ctrlPieces = [(xMax+2,y,Player,GUICell { cellAction = action
76 | let actions = [ Absorb, Stop defNa
77 , ChDir False defNa N, ChDir True defNa N
79 -- /!\ It would be nice to find a general formula
80 -- for placing the control pieces.
81 , (y,action) <- zip [ yMin+4,yMin+8..] actions]
83 ctrlCoord = map (\(x,y,_,_) -> (x,y)) ctrlPieces
85 boardToPiece :: [PlayHead] -> Board -> [(Int,Int,Player,GUICell)]
86 boardToPiece ph = (++ ctrlPieces) . map placePiece .
87 filter (onBoard . fst) . assocs
88 where placePiece :: (Pos,Cell) -> (Int,Int,Player,GUICell)
89 placePiece ((x,y),(a,n)) = let c = GUICell { cellAction = a
91 , asPh = (x,y) `elem` phPosS
93 (x',y') = toGUICoords (x,y)
97 validArea :: [(Int,Int)]
98 validArea = filter (onBoard . fromGUICoords) $
99 map (\(x,y,_,_) -> (x,y)) $ boardToPiece [] $ makeBoard []
104 naOrn = Ornaments Nothing [] NoSlide
107 initGUIBoard :: GUIBoard
108 initGUIBoard = GUIBoard GameState
109 { curPlayer' = Player
110 , boardPos = boardToTile
111 , boardPieces' = boardToPiece [] $
112 makeBoard [((0,0), mkCell (ChDir True na NE)),
113 ((2,1), mkCellRpt (ChDir False na NW) 3),
114 ((0,2), mkCell (ChDir False na S))]
117 instance PlayableGame GUIBoard Int Tile Player GUICell where
119 allPos (GUIBoard game) = boardPos game
120 allPieces (GUIBoard game) = boardPieces' game
122 canMove (GUIBoard game) _ (x,y)
123 | Just (_,p) <- getPieceAt game (x,y)
124 , GUICell { cellAction = Inert } <- p = False
125 | Nothing <- getPieceAt game (x,y) = False
127 canMoveTo _ _ _ (x,y) = (x,y) `elem` validArea
129 move guiBoard@(GUIBoard game) p iPos@(_,yi) (xf,yf)
130 | not (canMove guiBoard p iPos) = []
131 | not (canMoveTo guiBoard p iPos fPos') = []
132 | iPos `elem` ctrlCoord = [ RemovePiece fPos'
133 , AddPiece fPos' Player (nCell { cellAction = ctrlAction })
135 | otherwise = [ MovePiece iPos fPos'
136 , AddPiece iPos Player nCell]
138 | (xf `mod` 2 == 0 && yf `mod` 2 == 0)
139 || (xf `mod` 2 /= 0 && yf `mod` 2 /= 0) = (xf,yf)
140 | otherwise = (xf,yf)-- (xf,yf+signum' (yf-yi))
143 | otherwise = signum x
144 ctrlAction = cellAction $ snd $ fromJust $ getPieceAt game iPos
146 | Just (_,GUICell { asPh = ph, repeatCount = n }) <-
147 getPieceAt game iPos = inertCell { repeatCount = n
150 | otherwise = inertCell
151 where inertCell = GUICell { cellAction = Inert
155 applyChange (GUIBoard game) (AddPiece pos@(x,y) Player piece) =
156 GUIBoard $ game { boardPieces' = bp' }
157 where bp' = (x,y,Player,piece):boardPieces' game
159 applyChange (GUIBoard game) (RemovePiece (x,y)) = GUIBoard $
160 game { boardPieces' = bp' }
161 where bp' = [p | p@(x',y',_,_) <- boardPieces' game
162 , x /= x' || y /= y']
164 applyChange guiBoard@(GUIBoard game) (MovePiece iPos fPos)
165 | Just (_,p) <- getPieceAt game iPos
166 = applyChanges guiBoard [ RemovePiece iPos
168 , AddPiece fPos Player p]
169 | otherwise = guiBoard
171 initGame :: IO (Game GUIBoard Int Tile Player GUICell)
173 pixbufs <- fileToPixbuf
174 tilePixbuf <- pixbufNew ColorspaceRgb False 8 tileW tileH
175 pixbufFill tilePixbuf 50 50 50 0
176 let pixPiece :: (Player,GUICell) -> Pixbuf
177 pixPiece (_,a) = fromJust $ lookup (actionToFile a) pixbufs
178 pixTile :: Tile -> Pixbuf
179 pixTile _ = tilePixbuf
180 visualA = VisualGameAspects { tileF = pixTile
182 , bgColor = (1000,1000,1000)
186 return $ Game visualA initGUIBoard
188 initBoardRV :: BIO.Board Int Tile (Player,GUICell)
189 -> IO ( ReactiveFieldRead IO Board
190 , ReactiveFieldReadWrite IO [PlayHead])
191 initBoardRV board@BIO.Board { boardPieces = gBoard@(GameBoard array) } = do
192 phMVar <- newCBMVar []
193 oldphMVar <- newCBMVar []
194 notBMVar <- mkClockRV 100
195 let getterB :: IO Board
197 (boardArray :: [((Int,Int),Maybe (Player,GUICell))]) <- getAssocs array
198 let board = makeBoard $
199 map (BF.first fromGUICoords .
200 BF.second ((\(_,c) -> (cellAction c,repeatCount c)) .
202 filter (isJust . snd) boardArray
205 notifierB :: IO () -> IO ()
206 notifierB = reactiveValueOnCanRead notBMVar
208 getterP :: IO [PlayHead]
209 getterP = readCBMVar phMVar
211 setterP :: [PlayHead] -> IO ()
213 readCBMVar phMVar >>= writeCBMVar oldphMVar
214 writeCBMVar phMVar lph
215 oph <- readCBMVar oldphMVar
216 let phPosS = map phPos lph
217 offPh :: PlayHead -> IO ()
219 let pos = toGUICoords $ phPos ph
220 piece <- boardGetPiece pos board
221 when (isJust piece) $ do
222 let (_,c) = fromJust piece
223 boardSetPiece pos (Player, c { asPh = pos `elem` phPosS }) board
224 onPh :: PlayHead -> IO ()
226 let pos = toGUICoords $ phPos ph
227 piece <- boardGetPiece pos board
228 when (isJust piece) $ do
229 let (_,c) = fromJust piece
230 boardSetPiece pos (Player, c { asPh = True }) board
234 notifierP :: IO () -> IO ()
235 notifierP = installCallbackCBMVar phMVar
237 b = ReactiveFieldRead getterB notifierB
238 ph = ReactiveFieldReadWrite setterP getterP notifierP
241 fileToPixbuf :: IO [(FilePath,Pixbuf)]
242 fileToPixbuf = mapM (\f -> let f' = "img/" ++ f in uncurry (liftM2 (,))
244 , pixbufNewFromFile f' >>=
245 \p -> pixbufScaleSimple p hexW hexW InterpBilinear ))
246 (["hexOn.png","hexOff.png","stop.svg","split.svg"] ++
247 concat [["start" ++ show d ++ ".svg","ric" ++ show d ++ ".svg"]
250 actionToFile :: GUICell -> FilePath
251 actionToFile GUICell { cellAction = a
255 (Inert,True) -> "img/hexOn.png"
256 (Inert,False) -> "img/hexOff.png"
257 (Absorb,_) -> "img/stop.svg"
258 (Stop _,_) -> "img/stop.svg"
259 (ChDir True _ d,_) -> "img/start" ++ show d ++ ".svg"
260 (ChDir False _ d,_) -> "img/ric" ++ show d ++ ".svg"
261 (Split _,_) -> "img/split.svg"