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
15 import Game.Board.BasicTurnGame
16 import Graphics.UI.Gtk hiding (Action)
17 import Graphics.UI.Gtk.Board.BoardLink
18 import Graphics.UI.Gtk.Board.TiledBoard hiding (Board)
19 import qualified Graphics.UI.Gtk.Board.TiledBoard as BIO
21 import RMCA.Global.Clock
26 data GUICell = GUICell { cellAction :: Action
31 newtype GUIBoard = GUIBoard { toGS :: GameState Int Tile Player GUICell }
33 type IOBoard = BIO.Board Int Tile (Player,GUICell)
36 data Player = Player deriving(Show)
38 rotateGUICell :: GUICell -> GUICell
39 rotateGUICell g = g { cellAction = rotateAction $ cellAction g }
40 where rotateAction (ChDir b na d) = ChDir b na (nextDir d)
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)
59 d = sqrt 3 * fromIntegral tileW / 3
64 d = 4 * fromIntegral tileW / 3
69 d = sqrt 3 * fromIntegral hexW / 2
72 (xMax,yMax) = BF.second (*2) $ neighbor N nec
74 (xMin,yMin) = BF.second (*2) swc
76 boardToTile :: [(Int,Int,Tile)]
77 boardToTile = [(x,y,Tile) | (x,y) <- range ( (xMin-1,yMin)
81 defNa = NoteAttr { naArt = NoAccent
86 ctrlPieces :: [(Int,Int,Player,GUICell)]
87 ctrlPieces = [(xMax+2,y,Player,GUICell { cellAction = action
91 | let actions = [ Absorb, Stop defNa
92 , ChDir False defNa N, ChDir True defNa N
94 -- /!\ It would be nice to find a general formula
95 -- for placing the control pieces.
96 , (y,action) <- zip [ yMin+4,yMin+8..] actions]
98 ctrlCoords :: [(Int,Int)]
99 ctrlCoords = map (\(x,y,_,_) -> (x,y)) ctrlPieces
101 boardToPiece :: [PlayHead] -> Board -> [(Int,Int,Player,GUICell)]
102 boardToPiece ph = (++ ctrlPieces) . map placePiece .
103 filter (onBoard . fst) . assocs
104 where placePiece :: (Pos,Cell) -> (Int,Int,Player,GUICell)
105 placePiece ((x,y),(a,n)) = let c = GUICell { cellAction = a
107 , asPh = (x,y) `elem` phPosS
109 (x',y') = toGUICoords (x,y)
111 phPosS = map phPos ph
113 validArea :: [(Int,Int)]
114 validArea = filter (onBoard . fromGUICoords) $
115 map (\(x,y,_,_) -> (x,y)) $ boardToPiece [] $ makeBoard []
117 outGUIBoard :: (Int,Int) -> Bool
118 outGUIBoard (xf,yf) = xf < xMin || xf > xMax || yf < yMin || yf > yMax
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 game) _ 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 (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 , ReactiveFieldWrite IO [PlayHead])
209 initBoardRV board@BIO.Board { boardPieces = (GameBoard gArray) } = do
211 phMVar <- newCBMVar []
212 notBMVar <- mkClockRV 10
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 offPh :: PlayHead -> IO ()
234 let pos = toGUICoords $ phPos ph
235 piece <- boardGetPiece pos board
236 when (isJust piece) $ do
237 let (_,c) = fromJust piece
238 boardSetPiece pos (Player, c { asPh = False }) board
239 onPh :: PlayHead -> IO ()
241 let pos = toGUICoords $ phPos ph
242 piece <- boardGetPiece pos board
243 when (isJust piece) $ do
244 let (_,c) = fromJust piece
245 boardSetPiece pos (Player, c { asPh = True }) board
246 postGUIAsync $ mapM_ offPh oph
247 postGUIAsync $ mapM_ onPh lph
248 writeCBMVar phMVar lph
250 notifierP :: IO () -> IO ()
251 notifierP = installCallbackCBMVar phMVar
253 b = ReactiveFieldRead getterB notifierB
254 ph = ReactiveFieldReadWrite setterP getterP notifierP
256 setterW :: (Int,Int) -> GUICell -> IO ()
257 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)]]
265 return (b,arrW,writeOnly ph)
267 fileToPixbuf :: IO [(FilePath,Pixbuf)]
268 fileToPixbuf = mapM (\f -> let f' = ("img/" ++ f) in
271 , getDataFileName f' >>=
272 (pixbufNewFromFile >=>
273 \p -> pixbufScaleSimple p hexW hexW InterpBilinear)))
274 (["hexOn.png","hexOff.png","stop.svg","split.svg","absorb.svg"] ++
275 concat [["start" ++ show d ++ ".svg","ric" ++ show d ++ ".svg"]
278 actionToFile :: GUICell -> FilePath
279 actionToFile GUICell { cellAction = a
283 Inert -> "img/hexO" ++ (if ph then "n" else "ff") ++ ".png"
284 Absorb -> "img/absorb.svg"
285 Stop _ -> "img/stop.svg"
286 ChDir True _ d -> "img/start" ++ show d ++ ".svg"
287 ChDir False _ d -> "img/ric" ++ show d ++ ".svg"
288 Split _ -> "img/split.svg"