htzaar-0.0.2: src/Play.hs
module Play
( play
) where
import Control.Monad
import Data.Function
import Data.List
import Data.Maybe
import Data.Word
import Graphics.Rendering.OpenGL
import Graphics.UI.SDL hiding (init, Color)
import qualified Graphics.UI.SDL as SDL
import System.Random
import Board hiding (Position)
import qualified Board as B
data State = State
{ bt :: BoardTree
, history :: [State]
, stdGen :: StdGen
, ai :: AI
, stage :: Stage
}
data Stage
= A
| B B.Position
| C Move
| D Move B.Position
| E
initState g ai = State
{ bt = boardTree startingBoard
, history = []
, stdGen = g
, ai = ai
, stage = A
}
play :: StdGen -> AI -> IO ()
play g ai = do
SDL.init [InitVideo]
setCaption "htzaar" "htzaar"
glSetAttribute glRedSize 8
glSetAttribute glGreenSize 8
glSetAttribute glBlueSize 8
glSetAttribute glAlphaSize 8
glSetAttribute glDepthSize 24
glSetAttribute glDoubleBuffer 1
setView 600 400
cullFace $= Nothing
clearColor $= Color4 (255/255) (246/255) (143/255) 0
clearDepth $= 1
depthMask $= Disabled
loop $ initState g ai
quit
setView :: Int -> Int -> IO ()
setView w h = do
setVideoMode w h 16 [OpenGL, Resizable] >> return ()
matrixMode $= Projection
loadIdentity
let r = (fromIntegral w / fromIntegral h)
ortho (-r) r (-1) 1 (-1) 1
matrixMode $= Modelview 0
viewport $= (Position 0 0, Size (fromIntegral w) (fromIntegral h))
redraw :: State -> IO ()
redraw state = do
clear [ColorBuffer, DepthBuffer]
loadIdentity
scale3 0.2 0.2 1
grid
case stage state of
A -> pieces board
B p -> highlightPosition p >> pieces board
C m -> pieces $ applyMove board m
D m p -> highlightPosition p >> pieces (applyMove board m)
E -> pieces board
flush
glSwapBuffers
where
BoardTree _ board _ = bt state
loop :: State -> IO ()
loop state = do
event <- pollEvent
state <- handler event state
when (event /= Quit) $ loop state
handler :: Event -> State -> IO State
handler event state = case event of
NoEvent -> return state
VideoExpose -> redraw state >> return state
VideoResize x y -> setView x y >> return state
MouseButtonDown x y ButtonLeft -> do
r <- clickPosition x y
s <- userSelectedPosition r state
redraw s
return s
MouseButtonDown _ _ ButtonRight -> case history state of
[] -> return state
(a:_) -> redraw a >> return a
KeyDown (key) | symKey key == SDLK_SPACE -> do
s <- userSelectedPass state
redraw s
return s
_ -> return state
mousePosition :: Word16 -> Word16 -> IO (Float, Float)
mousePosition x y = do
mm <- get $ matrix $ Just $ Modelview 0
pm <- get $ matrix $ Just Projection
vp <- get $ viewport
Vertex3 x y _ <- unProject (Vertex3 (fromIntegral x) (fromIntegral y) 0) (mm :: GLmatrix GLdouble) (pm :: GLmatrix GLdouble) vp
return (realToFrac x, realToFrac (-y))
clickPosition :: Word16 -> Word16 -> IO (Maybe B.Position)
clickPosition x y = do
(x, y) <- mousePosition x y
let (p, d) = minimumBy (compare `on` snd) [ (p, sqrt ((x - x') ^^ 2 + (y - y') ^^ 2)) | (p, (x', y')) <- boardPositions ]
return (if d < 0.4 then Just p else Nothing)
userSelectedPosition :: Maybe B.Position -> State -> IO State
userSelectedPosition Nothing s = case stage s of
E -> newGame s
_ -> return s
userSelectedPosition (Just p) s = case stage s of
A | or [ True | ((a, _), _) <- turns, a == p ] -> return s { history = s : history s, stage = B p }
B p1 | all (\ (_, a) -> a == Nothing) turns -> applyTurn ((p1, p), Nothing) s
| or [ True | (a, _) <- turns, a == (p1, p) ] -> return s { history = s : history s, stage = C (p1, p) }
C m | or [ True | (a, Just (b, _)) <- turns, a == m, b == p ] -> return s { history = s : history s, stage = D m p }
D m p1 | elem t turns -> applyTurn t s
where
t = (m, Just (p1, p))
E -> newGame s
_ -> return s
where
BoardTree _ _ branches = bt s
turns = fst $ unzip branches
userSelectedPass :: State -> IO State
userSelectedPass s = case stage s of
C m | elem (m, Nothing) turns -> applyTurn (m, Nothing) s
_ -> return s
where
BoardTree _ _ branches = bt s
turns = fst $ unzip branches
applyTurn :: Turn -> State -> IO State
applyTurn t s
| null branches' = do
putStrLn $ "white : " ++ showTurn t
putStrLn "White Wins!"
return s { history = s : history s, stage = E, bt = swapBoardTree bt', stdGen = g }
| otherwise = do
putStrLn $ "white : " ++ showTurn t
putStrLn $ "black : " ++ showTurn t'
if null branches''
then do
putStrLn "Black Wins!"
return s { history = s : history s, stage = E, bt = bt'', stdGen = g }
else do
return s { history = s : history s, stage = A, bt = bt'', stdGen = g }
where
BoardTree _ _ branches = bt s
bt'@(BoardTree _ _ branches') = swapBoardTree $ fromJust $ lookup t branches
(t', g) = strategy (ai s) bt' (stdGen s)
bt''@(BoardTree _ _ branches'') = swapBoardTree $ case lookup t' branches' of
Nothing -> error $ "Invalid AI Turn: " ++ show t'
Just a -> a
newGame :: State -> IO State
newGame s = do
putStrLn "New Game!"
return (initState (stdGen s) (ai s)) { history = s : history s }
grid :: IO ()
grid = do
color3 (128/255) (128/255) (128/255)
renderPrimitive Polygon $ do
p A1
p A5
p E8
p I5
p I1
p E1
lineWidth $= 3
preservingMatrix $ do
color3 0.1 0.1 0.1
g
rotate3 (pi / 3) 0 0 1 >> g
rotate3 (pi / 3) 0 0 1 >> g
rotate3 (pi / 3) 0 0 1 >> g
rotate3 (pi / 3) 0 0 1 >> g
rotate3 (pi / 3) 0 0 1 >> g
where
p a = vertex2 x y where (x, y) = boardPosition a
g = renderPrimitive Lines $ do
p E5 >> p E8
p F1 >> p F8
p G1 >> p G7
p H1 >> p H6
p I1 >> p I5
highlightPosition :: B.Position -> IO ()
highlightPosition p = preservingMatrix $ do
translate3 x y 0
color3 0 0 1
lineWidth $= 2
ring 0.4
where
(x, y) = boardPosition p
data PieceColor = White | Black
pieces :: Board -> IO ()
pieces (whites, blacks) = do
mapM_ (piece White) whites
mapM_ (piece Black) blacks
piece :: PieceColor -> (B.Position, Type, Int) -> IO ()
piece c (p, t, size) = preservingMatrix $ do
translate3 x y 0
scale3 0.3 0.3 1
lineWidth $= 1
stack size
where
(x, y) = boardPosition p
(chipColor, lineColor, crownColor) = case c of
White -> (color3 1 1 1, color3 0 0 0, color3 ( 60/255) ( 60/255) ( 0/255))
Black -> (color3 0 0 0, color3 1 1 1, color3 (255/255) (215/255) ( 0/255))
stack 0 = case t of
Tott -> return ()
Tzarra -> crownColor >> disc 0.25
Tzaar -> crownColor >> disc 0.75 >> chipColor >> disc 0.5 >> crownColor >> disc 0.25
stack n = do
chipColor >> disc 1
lineColor >> ring 1
when (n /= 1) $ translate3 0 0.2 0
stack $ n - 1
segments :: [Float]
segments = [0, 2 * pi / 24 .. 2 * pi] ++ [0]
disc :: Float -> IO ()
disc a = renderPrimitive TriangleFan $ vertex2 0 0 >> mapM_ (\ p -> vertex2 (a * cos p) (a * sin p)) segments
ring :: Float -> IO ()
ring a = renderPrimitive LineStrip $ mapM_ (\ p -> vertex2 (a * cos p) (a * sin p)) segments
boardPosition :: B.Position -> (Float, Float)
boardPosition a = fromJust $ lookup a boardPositions
boardPositions :: [(B.Position, (Float, Float))]
boardPositions =
[ (A1, p (-4) (-2))
, (A2, p (-4) (-1))
, (A3, p (-4) ( 0))
, (A4, p (-4) ( 1))
, (A5, p (-4) ( 2))
, (B1, p (-3) (-3))
, (B2, p (-3) (-2))
, (B3, p (-3) (-1))
, (B4, p (-3) ( 1))
, (B5, p (-3) ( 2))
, (B6, p (-3) ( 3))
, (C1, p (-2) (-3))
, (C2, p (-2) (-2))
, (C3, p (-2) (-1))
, (C4, p (-2) ( 0))
, (C5, p (-2) ( 1))
, (C6, p (-2) ( 2))
, (C7, p (-2) ( 3))
, (D1, p (-1) (-4))
, (D2, p (-1) (-3))
, (D3, p (-1) (-2))
, (D4, p (-1) (-1))
, (D5, p (-1) ( 1))
, (D6, p (-1) ( 2))
, (D7, p (-1) ( 3))
, (D8, p (-1) ( 4))
, (E1, p ( 0) (-4))
, (E2, p ( 0) (-3))
, (E3, p ( 0) (-2))
, (E4, p ( 0) (-1))
, (E5, p ( 0) ( 1))
, (E6, p ( 0) ( 2))
, (E7, p ( 0) ( 3))
, (E8, p ( 0) ( 4))
, (F1, p ( 1) (-4))
, (F2, p ( 1) (-3))
, (F3, p ( 1) (-2))
, (F4, p ( 1) (-1))
, (F5, p ( 1) ( 1))
, (F6, p ( 1) ( 2))
, (F7, p ( 1) ( 3))
, (F8, p ( 1) ( 4))
, (G1, p ( 2) (-3))
, (G2, p ( 2) (-2))
, (G3, p ( 2) (-1))
, (G4, p ( 2) ( 0))
, (G5, p ( 2) ( 1))
, (G6, p ( 2) ( 2))
, (G7, p ( 2) ( 3))
, (H1, p ( 3) (-3))
, (H2, p ( 3) (-2))
, (H3, p ( 3) (-1))
, (H4, p ( 3) ( 1))
, (H5, p ( 3) ( 2))
, (H6, p ( 3) ( 3))
, (I1, p ( 4) (-2))
, (I2, p ( 4) (-1))
, (I3, p ( 4) ( 0))
, (I4, p ( 4) ( 1))
, (I5, p ( 4) ( 2))
]
where
p :: Int -> Int -> (Float, Float)
p x y = (x', y')
where
x' = fromIntegral x * sin (pi / 3)
y' | even x = fromIntegral y
| otherwise = fromIntegral y - (fromIntegral (signum y) * 0.5)
--vertex3 :: Real a => a -> a -> a -> IO ()
vertex2 x y = vertex $ Vertex3 (toFloat x) (toFloat y) 0
--color3 :: Real a => a -> a -> a -> IO ()
color3 r g b = color $ Color3 (toFloat r) (toFloat g) (toFloat b)
--scale3 :: Real a => a -> a -> a -> IO ()
scale3 x y z = scale (toFloat x) (toFloat y) (toFloat z)
--translate3 :: Real a => a -> a -> a -> IO ()
translate3 x y z = translate $ Vector3 (toFloat x) (toFloat y) (toFloat z)
--rotate3 :: (Real a, Floating a) => a -> a -> a -> a -> IO ()
rotate3 angle x y z = rotate (toFloat $ angle * 180 / pi) $ Vector3 (toFloat x) (toFloat y) (toFloat z)
toFloat :: (Real a, Floating a) => a -> GLfloat
toFloat = realToFrac