packages feed

yampa2048-0.1.0.0: src/Game.hs

{-# LANGUAGE Arrows #-}

module Game (wholeGame) where

import System.Random (StdGen)
import FRP.Yampa

import Types
import GameModel
import GameLogic

-- | Run the game that still has moves ('gameAlive'), until ('switch')
-- there are no more moves ('outOfMoves'), in which case the game is over
-- and needs to be restarted ('restartGame')
wholeGame :: StdGen -> SF GameInput GameState
wholeGame g = switch
  (gameAlive g >>> (identity &&& outOfMoves))
  (restartGame g)

-- | Detect when there are no more possible moves on the given board
outOfMoves :: SF GameState (Event GameState)
outOfMoves = proc s -> do
  lost <- edge -< isGameOver s
  let snapshot = lost `tag` s
  returnA -< snapshot

-- | Start the game using the initial game state (empty board with score 0)
-- and placing two initial tiles randomly onto the board
gameAlive :: StdGen -> SF GameInput GameState
gameAlive g = 
    let (float1, g') = random g
        (float2, g'') = random g'
        (float3, g''') = random g''
        (float4, g'''') = random g'''
    in runGame $ placeRandomTile float1 float2
               $ placeRandomTile float3 float4
                 (initialGameState g'''')

-- | When the game is lost we want to show the GameOver text for some time
-- and then restart the game
restartGame :: StdGen -> GameState -> SF GameInput GameState
restartGame g s = switch
  (gameOver s &&& after 5 ())
  (const $ wholeGame g)

-- | When we have lost the game we want to keep the board in a state that
-- the user reached and show some GameOver message over it
gameOver :: GameState -> SF a GameState
gameOver s = arr $ const $ s { status = GameOver }

-- | Run the game, keeping the internal state using dHold, updating the
-- game state based on user's input (if any)
runGame :: GameState -> SF GameInput GameState
runGame state = proc input -> do
  rec currentState <- dHold state -< gameUpdated
      gameUpdated <- arr update -< (currentState, input)

  returnA -< currentState

-- | Based on the input received either try to slide the board in the given
-- direction or do not do anything (NoEvent). If the sliding in the given
-- direction is not possible then we are once again not doing anything
-- (NoEvent)
update :: (GameState, GameInput) -> Event GameState
update (gameState, input) =
    case input of
      Event None -> NoEvent
      Event direction ->
        let newBoardScore = slideBoard direction (board gameState)
            (float1, gen') = random (gen gameState)
            (float2, gen'') = random gen'
        in if fst newBoardScore == board gameState
             then NoEvent
             else Event $ placeRandomTile float1 float2
                        $ gameState { board = fst newBoardScore
                                    , score = score gameState + snd newBoardScore
                                    , gen = gen''
                                    }
      _ -> NoEvent