-- | Functions for playing the game.
module Hs2048.Game
    ( addRandomTile
    , addRandomTiles
    , hasWon
    , isOver
    , new
    , randomEmptyIndex
    , randomEmptyPoint
    , randomTile
    ) where

import           Data.Maybe       (fromJust)
import qualified Hs2048.Board     as B
import qualified Hs2048.Direction as D
import qualified Hs2048.Point     as P
import qualified Hs2048.Settings  as S
import qualified Hs2048.Tile      as T
import qualified Hs2048.Vector    as V
import qualified System.Random    as R

{- |
    Adds a random tile to the board.

    >>> addRandomTile [[Nothing], [Nothing]] (R.mkStdGen 0)
    ([[Nothing],[Just 2]],1346387765 2103410263)
-}
addRandomTile :: R.RandomGen r => B.Board -> r -> (B.Board, r)
addRandomTile b r = case p of
    Nothing -> (b, r)
    _ -> (b', r'')
  where
    b' = B.set b t (fromJust p)
    (p, r') = randomEmptyPoint b r
    (t, r'') = randomTile r'

{- |
    Adds some random tiles to the board.

    >>> addRandomTiles 2 [[Nothing], [Nothing]] (R.mkStdGen 0)
    ([[Just 2],[Just 2]],2127568003 1780294415)
-}
addRandomTiles :: R.RandomGen r => Int -> B.Board -> r -> (B.Board, r)
addRandomTiles 0 b r = (b, r)
addRandomTiles n b r = addRandomTiles (n - 1) b' r'
  where
    (b', r') = addRandomTile b r

{- |
    Determines if the game has been won. See 'Hs2048.Settings.maxTile'.

    >>> hasWon [[Just 2048]]
    True
-}
hasWon :: B.Board -> Bool
hasWon = any (any (maybe False (>= S.maxTile)))

{- |
    Determines if the game is over. The game is over if there are no available
    moves and no empty points.

    >>> isOver [[Just 2]]
    True
-}
isOver :: B.Board -> Bool
isOver b = cantMove && haveNoEmptyPoints
  where
    cantMove = not (any (B.canMove b) D.directions)
    haveNoEmptyPoints = null (B.emptyPoints b)

{- |
    Creates a new game by making an empty board and adding some random tiles to
    it. See 'Hs2048.Settings.width', 'Hs2048.Settings.height', and
    'Hs2048.Settings.tiles'.

    >>> new (R.mkStdGen 0)
    ([[Just 2,Nothing,Nothing,Nothing],[Nothing,Nothing,Nothing,Nothing],[Nothing,Nothing,Nothing,Nothing],[Nothing,Just 2,Nothing,Nothing]],2127568003 1780294415)
-}
new :: R.RandomGen r => r -> (B.Board, r)
new = addRandomTiles S.tiles (B.empty S.width S.height)

{- |
    Selects an empty index at random from a vector.

    >>> randomEmptyIndex [Nothing, Nothing] (R.mkStdGen 0)
    (Just 1,40014 40692)
-}
randomEmptyIndex :: R.RandomGen r => V.Vector -> r -> (Maybe Int, r)
randomEmptyIndex v r = if null is then (Nothing, r) else (Just i, r')
  where
    i = is !! x
    (x, r') = R.randomR (0, length is - 1) r
    is = V.emptyIndexes v

{- |
    Selects an empty point at random from a board.

    >>> randomEmptyPoint [[Nothing],[Nothing]] (R.mkStdGen 0)
    (Just (1,0),40014 40692)
-}
randomEmptyPoint :: R.RandomGen r => B.Board -> r -> (Maybe P.Point, r)
randomEmptyPoint b r = if null ps then (Nothing, r) else (Just p, r')
  where
    p = ps !! x
    (x, r') = R.randomR (0, length ps - 1) r
    ps = B.emptyPoints b

{- |
    Creates a random tile.

    >>> randomTile (R.mkStdGen 0)
    (Just 2,1601120196 1655838864)
-}
randomTile :: R.RandomGen r => r -> (T.Tile, r)
randomTile r = (Just n, r')
  where
    n = if (x :: Float) < 0.9 then 2 else 4
    (x, r') = R.random r