import Control.Monad import Data.Array import Data.Maybe import Data.Tree import System.Random import Haste import Haste.Concurrent import Haste.DOM import Haste.Events import Haste.Graphics.Canvas bnds = ((0,0), (7,7)); sz = 40 data Event = Mo (Int, Int) | Ke Int data State = Won | Play deriving Eq data Game = Game { board :: Array (Int, Int) Int , state :: State , player :: Int , selection :: Maybe (Int, Int) , anim :: Maybe (Int, ((Int, Int), (Int, Int))) , lastMove :: ((Int, Int), (Int, Int)) } initRow y | y <= 1 = -1 | y >= 6 = 1 | True = 0 initBoard = array bnds [(i, initRow y) | i@(x,y) <- range bnds] initGame = Game initBoard Play 1 Nothing Nothing undefined score game = if state game == Won then player game * (-1024) else (-1) * sum [(board game)!i | i <- range bnds] omitWith op ((g, ns):nss) = let omit pot [] = [] omit pot ((g, ns):nss) | or $ map (`op` pot) ns = omit pot nss | otherwise = (g, last ns) : omit (last ns) nss in (g, last ns) : omit (last ns) nss maximize' :: Tree Game -> [(Game, Int)] maximize' (Node leaf []) = [(undefined, score leaf)] maximize' (Node _ kids) = omitWith (<=) $ [(rootLabel k, map snd $ minimize' k) | k <- kids] maximize = last . maximize' minimize' :: Tree Game -> [(Game, Int)] minimize' (Node leaf []) = [(undefined, score leaf)] minimize' (Node _ kids) = omitWith (>=) $ [(rootLabel k, map snd $ maximize' k) | k <- kids] best game ms = lastMove $ fst $ maximize $ prune 4 $ Node game (map (gameTree . move game) ms) gameTree = unfoldTree (\x -> (x, nextNodes x)) nextMoves game = if state game == Play then [(i, dst) | i <- range bnds, (board game)!i == player game, dst <- movesFrom i game] else [] nextNodes game = map (move game) $ nextMoves game prune 0 (Node a _) = Node a [] prune n (Node a kids) = Node a $ map (prune (n - 1)) kids box :: Int -> Int -> Int -> Int -> Picture () -- Why is this needed? box x y dx dy = fill $ rect (fromIntegral x, fromIntegral y) (fromIntegral (x+dx), fromIntegral (y+dy)) sqColor False = RGB 191 191 191 sqColor True = RGB 255 255 255 drawB pic x y = draw pic (fromIntegral x, fromIntegral y) playerName 1 = "White" playerName (-1) = "Black" movesFrom (x, y) game = let b = board game p = player game in [i1 | dx <- [-1, 0, 1], let i1 = (x + dx, y - p), inRange bnds i1, b!i1 /= p, dx /= 0 || b!i1 == 0] move game (i0, i1@(_, y1)) = let p = player game nextBoard = board game // [(i0, 0), (i1, p)] nextState = if (p == 1 && y1 == 0) || (p == -1 && y1 == 7) then Won else Play in Game nextBoard nextState (if nextState == Won then p else -p) Nothing Nothing (i0, i1) shuffleIO [] = return [] shuffleIO xs = getStdRandom (randomR (0, length xs - 1)) >>= \n -> let (a, b:bs) = splitAt n xs in (b:) <$> shuffleIO (a ++ bs) main = withElems ["canvas", "message"] $ \[canvasE, msg] -> do Just canvas <- fromElem canvasE whitePiece <- createCanvas sz sz renderOnTop whitePiece $ color (RGB 255 255 255) $ fill $ circle (20, 20) 10 renderOnTop whitePiece $ color (RGB 0 0 0) $ stroke $ circle (20, 20) 11 blackPiece <- createCanvas sz sz renderOnTop blackPiece $ color (RGB 0 0 0) $ fill $ circle (20, 20) 11 fromCan <- createCanvas sz sz render fromCan $ color (RGB 127 15 15) $ sequence_ [ box 0 0 5 40, box 0 0 40 5, box 35 0 40 40, box 0 35 40 40 ] toCan <- createCanvas sz sz render toCan $ color (RGBA 0 191 0 0.3) $ box 0 0 sz sz boardCan <- createCanvas 320 320 sequence_ $ [renderOnTop boardCan $ color (sqColor (mod (x + y) 2 == 0)) $ box (x*sz) (y*sz) sz sz | (x, y) <- range bnds] buf <- createCanvas 320 320 ev <- newEmptyMVar void $ canvasE `onEvent` MouseDown $ \m -> concurrent $ putMVar ev $ Mo $ mouseCoords m void $ documentBody `onEvent` KeyDown $ \k -> concurrent $ putMVar ev $ Ke $ keyCode k let renderPiece c p (x,y) = renderOnTop c $ draw (if p == 1 then whitePiece else blackPiece) (fromIntegral x, fromIntegral y) drawGame game = do sequence_ $ (render buf $ draw boardCan (0, 0)) : [renderPiece buf p (x*sz, y*sz) | i@(x, y) <- range bnds, let p = (board game)!i, p /= 0] render canvas $ draw buf (0, 0) setProp msg "innerHTML" $ playerName (player game) ++ case state game of Play -> " to move" Won -> " wins" loop game = if isNothing $ anim game then let sel0 = selection game in do e <- takeMVar ev case e of Mo (bx, by) -> when (state game == Play) $ let i@(x, y) = (div bx sz, div by sz) sel = if (board game)!i == player game then Just i else Nothing in when (inRange bnds i) $ do render canvas $ draw buf (0, 0) if sel0 == Nothing then do unless (sel == Nothing) $ do renderOnTop canvas $ drawB fromCan (x*sz) (y*sz) sequence_ [renderOnTop canvas $ drawB toCan (x1*sz) (y1*sz) | (x1, y1) <- movesFrom i game] loop game { selection = sel } else if i `elem` movesFrom (fromJust sel0) game then loop game { anim = Just (0, (fromJust sel0, i)) } else loop game { selection = Nothing } Ke 113 -> drawGame initGame >> loop initGame _ -> loop game else let Just (frame, m@((x0, y0), (x1, y1))) = anim game in if frame == 8 then let game1 = move game m in do drawGame game1 if state game1 == Play && player game1 == -1 then do wait 1 -- Delay for redraw. ms <- liftIO $ shuffleIO $ nextMoves game1 loop game1 { anim = Just (0, best game1 ms) } else loop game1 else let f x0 x1 frame = x0 * sz + (x1 - x0) * sz * frame `div` 8 in do drawGame game { board = board game // [((x0, y0), 0)] } renderPiece canvas (player game) (f x0 x1 frame, f y0 y1 frame) void $ setTimer (Once 20) $ loop game { anim = Just (frame + 1, m) } concurrent $ forkIO $ drawGame initGame >> loop initGame
Breakthrough
Breakthrough was invented by Dan Troyka [Rules].