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
24 data GUICell = GUICell { cellAction :: Action
29 rotateGUICell g = g { cellAction = rotateAction $ cellAction g }
30 where rotateAction (ChDir b na d) = ChDir b na (nextDir d)
33 newtype GUIBoard = GUIBoard { toGS :: GameState Int Tile Player GUICell }
35 type IOBoard = BIO.Board 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 ctrlCoords :: [(Int,Int)]
90 ctrlCoords = map (\(x,y,_,_) -> (x,y)) ctrlPieces
92 boardToPiece :: [PlayHead] -> Board -> [(Int,Int,Player,GUICell)]
93 boardToPiece ph = (++ ctrlPieces) . map placePiece .
94 filter (onBoard . fst) . assocs
95 where placePiece :: (Pos,Cell) -> (Int,Int,Player,GUICell)
96 placePiece ((x,y),(a,n)) = let c = GUICell { cellAction = a
98 , asPh = (x,y) `elem` phPosS
100 (x',y') = toGUICoords (x,y)
102 phPosS = map phPos ph
104 validArea :: [(Int,Int)]
105 validArea = filter (onBoard . fromGUICoords) $
106 map (\(x,y,_,_) -> (x,y)) $ boardToPiece [] $ makeBoard []
108 outGUIBoard :: (Int,Int) -> Bool
109 outGUIBoard (xf,yf) = xf < xMin || xf > xMax || yf < yMin || yf > yMax
114 naOrn = Ornaments Nothing [] NoSlide
118 inertCell = GUICell { cellAction = Inert
123 initGUIBoard :: GUIBoard
124 initGUIBoard = GUIBoard GameState
125 { curPlayer' = Player
126 , boardPos = boardToTile
127 , boardPieces' = boardToPiece [] $ makeBoard []
130 instance PlayableGame GUIBoard Int Tile Player GUICell where
132 allPos (GUIBoard game) = boardPos game
133 allPieces (GUIBoard game) = boardPieces' game
135 canMove (GUIBoard game) _ (x,y)
136 | Just (_,p) <- getPieceAt game (x,y)
137 , GUICell { cellAction = Inert } <- p = False
138 | Nothing <- getPieceAt game (x,y) = False
140 canMoveTo _ _ _ fPos = fPos `elem` validArea
143 move (GUIBoard game) _ iPos@(_,yi) fPos@(xf,yf)
144 | outGUIBoard iPos && outGUIBoard fPos = []
145 | outGUIBoard fPos = [ RemovePiece iPos
146 , AddPiece iPos Player nCell ]
147 | iPos `elem` ctrlCoords = [ RemovePiece fPos'
148 , AddPiece fPos' Player
149 (nCell { cellAction = ctrlAction }) ]
150 | otherwise = [ MovePiece iPos fPos'
151 , AddPiece iPos Player nCell ]
153 | (xf `mod` 2 == 0 && yf `mod` 2 == 0)
154 || (xf `mod` 2 /= 0 && yf `mod` 2 /= 0) = (xf,yf)
155 | otherwise = (xf,yf+signum' (yf-yi))
158 | otherwise = signum x
159 ctrlAction = cellAction $ snd $ fromJust $ getPieceAt game iPos
161 | Just (_,GUICell { asPh = ph, repeatCount = n }) <-
162 getPieceAt game iPos = inertCell { repeatCount = n
165 | otherwise = inertCell
167 applyChange (GUIBoard game) (AddPiece (x,y) Player piece) =
168 GUIBoard $ game { boardPieces' = bp' }
169 where bp' = (x,y,Player,piece):boardPieces' game
171 applyChange (GUIBoard game) (RemovePiece (x,y)) = GUIBoard $
172 game { boardPieces' = bp' }
173 where bp' = [p | p@(x',y',_,_) <- boardPieces' game
174 , x /= x' || y /= y']
176 applyChange guiBoard@(GUIBoard game) (MovePiece iPos fPos)
177 | Just (_,p) <- getPieceAt game iPos
178 = applyChanges guiBoard [ RemovePiece iPos
180 , AddPiece fPos Player p]
181 | otherwise = guiBoard
183 initGame :: IO (Game GUIBoard Int Tile Player GUICell)
185 pixbufs <- fileToPixbuf
186 tilePixbuf <- pixbufNew ColorspaceRgb False 8 tileW tileH
187 pixbufFill tilePixbuf 50 50 50 0
188 let pixPiece :: (Player,GUICell) -> Pixbuf
189 pixPiece (_,a) = fromJust $ lookup (actionToFile a) pixbufs
190 pixTile :: Tile -> Pixbuf
191 pixTile _ = tilePixbuf
192 visualA = VisualGameAspects { tileF = pixTile
194 , bgColor = (1000,1000,1000)
198 return $ Game visualA initGUIBoard
200 -- Initializes a readable RV for the board and an readable-writable RV
201 -- for the playheads. Also installs some handlers for pieces modification.
202 initBoardRV :: BIO.Board Int Tile (Player,GUICell)
203 -> IO ( ReactiveFieldRead IO Board
204 , Array Pos (ReactiveFieldWrite IO GUICell)
205 , ReactiveFieldReadWrite IO [PlayHead])
206 initBoardRV board@BIO.Board { boardPieces = (GameBoard gArray) } = do
208 phMVar <- newCBMVar []
209 notBMVar <- mkClockRV 100
210 let getterB :: IO Board
212 (boardArray :: [((Int,Int),Maybe (Player,GUICell))]) <- getAssocs gArray
213 let board = makeBoard $
214 map (BF.first fromGUICoords .
215 BF.second ((\(_,c) -> (cellAction c,repeatCount c)) .
217 filter (isJust . snd) boardArray
220 notifierB :: IO () -> IO ()
221 notifierB = reactiveValueOnCanRead notBMVar
223 getterP :: IO [PlayHead]
224 getterP = readCBMVar phMVar
226 setterP :: [PlayHead] -> IO ()
228 oph <- readCBMVar phMVar
229 let offPh :: PlayHead -> IO ()
231 let pos = toGUICoords $ phPos ph
232 piece <- boardGetPiece pos board
233 when (isJust piece) $ do
234 let (_,c) = fromJust piece
235 boardSetPiece pos (Player, c { asPh = False }) board
236 onPh :: PlayHead -> IO ()
238 let pos = toGUICoords $ phPos ph
239 piece <- boardGetPiece pos board
240 when (isJust piece) $ do
241 let (_,c) = fromJust piece
242 boardSetPiece pos (Player, c { asPh = True }) board
243 postGUIAsync $ mapM_ offPh oph
244 postGUIAsync $ mapM_ onPh lph
245 writeCBMVar phMVar lph
247 notifierP :: IO () -> IO ()
248 notifierP = installCallbackCBMVar phMVar
250 b = ReactiveFieldRead getterB notifierB
251 ph = ReactiveFieldReadWrite setterP getterP notifierP
253 setterW :: (Int,Int) -> GUICell -> IO ()
254 setterW i g = postGUIAsync $ boardSetPiece i (Player,g) board
256 arrW :: Array Pos (ReactiveFieldWrite IO GUICell)
257 arrW = array (minimum validArea, maximum validArea)
258 [(i, ReactiveFieldWrite (setterW i))
259 | i <- (validArea :: [(Int,Int)])]
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
275 , getDataFileName f' >>=
276 \f'' -> pixbufNewFromFile f'' >>=
277 \p -> pixbufScaleSimple p hexW hexW InterpBilinear))
278 (["hexOn.png","hexOff.png","stop.svg","split.svg","absorb.svg"] ++
279 concat [["start" ++ show d ++ ".svg","ric" ++ show d ++ ".svg"]
282 actionToFile :: GUICell -> FilePath
283 actionToFile GUICell { cellAction = a
287 Inert -> "img/hexO" ++ (if ph then "n" else "ff") ++ ".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"