battleships-1.0.0: Handler/Replay.hs
----------------------------------------------------------------------------
-- |
-- Module : Handler.Replay
-- Stability : experimental
-- Portability : non-portable
--
-- Handler for displaying a replay of the game.
module Handler.Replay
( getReplayR
) where
import Import
import Prelude (last)
import Control.Monad.State
import qualified Data.Map (lookup)
import Handler.Util
import Logic.Game
import Logic.GameExt
import Logic.Render
import Logic.Types
getReplayR :: GameStateExt -> Handler Html
getReplayR gameE = withGame gameE $ \game -> defaultLayout $ do
messageRender <- getMessageRender -- needed for i18n in julius
let rules = gameRules game
history = zip [0 :: Int ..] $ reconstructHistory game
humanGrids = map (renderHumanGrid rules) history
aiGrids = map (renderAIGrid rules) history
numSteps = length history
setNormalTitle
addScript $ StaticR js_jquery_js
$(widgetFile "board")
$(widgetFile "replay") where
renderHumanGrid rules (time, (humansState, aisState)) = (time, renderSvgHtml $
renderPlayerGrid (playerFleet humansState) (playerShots aisState) ActionFire rules time)
renderAIGrid rules (time, (humansState, aisState)) = renderHumanGrid rules (time, (aisState, humansState))
-- | Generate all the player states leading up to the current game situation.
-- Given a game state, this function generates a list of pairs of PlayerStates
-- (one for the human player, one for the AI player) which are "snapshots" of
-- previous game states. A snapshot is taken whenever `turnNumber` increases.
reconstructHistory :: GameState a -> [(PlayerState, PlayerState)]
reconstructHistory g = evalState (reconstructAndCheck g) initialGameState where
initialGameState = GameState
{ currentPlayer = humanPlayer
, otherPlayer = aiPlayer
, aiState = undefined
, gameRules = gameRules g
, noviceModeOpt = undefined
, devModeOpt = undefined
, expectedAction = ActionFire
, turnNumber = 0
}
humanPlayer = let s = humanPlayerState g
in s { playerShots = []
, playerFleet = reconstructOriginalFleet (playerMoves s) (playerFleet s)
, playerMoves = []
}
aiPlayer = let s = aiPlayerState g
in s { playerShots = []
, playerFleet = reconstructOriginalFleet (playerMoves s) (playerFleet s)
, playerMoves = []
}
-- | Reconstruct game history given the current game state and check for consistency.
reconstructAndCheck :: MonadState (GameState a) m =>GameState a -> m [(PlayerState, PlayerState)]
reconstructAndCheck g = do
let hsExpected = humanPlayerState g
asExpected = aiPlayerState g
history <- reconstruct -- player shots are reversed as they are originally sorted from most recent to oldest
(reverse . playerShots $ humanPlayerState g, reverse . playerMoves $ humanPlayerState g)
(reverse . playerShots $ aiPlayerState g, reverse . playerMoves $ aiPlayerState g)
let (hs, as) = last history
if as /= asExpected || hs /= hsExpected
then error "Replay preparation: Game reconstruction unsuccessful. This shouldn't happen!"
else return history
-- | Reconstruct the game history given the player's actions.
reconstruct :: MonadState (GameState a) m
=> (TrackingList, [ShipMove]) -- ^ current player's actions
-> (TrackingList, [ShipMove]) -- ^ other players's actions
-> m [(PlayerState, PlayerState)] -- ^ list of pairs of PlayerStates for every previous game situation
reconstruct ([], _) _ = do
humansState <- gets humanPlayerState
aisState <- gets aiPlayerState
return [(humansState, aisState)]
reconstruct (fstPShots, fstPMoves) sndPState = do
humansState <- gets humanPlayerState
aisState <- gets aiPlayerState
fstPShots' <- execShots fstPShots
fstPMoves' <- execMove fstPMoves
switchRoles
states <- reconstruct sndPState (fstPShots', fstPMoves')
return $ (humansState, aisState) : states
execShots :: MonadState (GameState a) m => [Shot] -> m [Shot]
execShots [] = return []
execShots (shot:shots) = do
time <- gets turnNumber
if shotTime shot == time
then
fireAt (shotPos shot)
>>= executeShot
>> execShots shots
else return $ shot:shots
execMove :: MonadState (GameState a) m => [ShipMove] -> m [ShipMove]
execMove [] = return []
execMove (move:moves) = do
time <- gets turnNumber
case move of
ShipMove mId mDir mTime | time == mTime
-> executeMove (Just (mId, mDir)) >> return moves
_ -> return $ move:moves
-- | Reconstruct the original fleet given the moves performed.
reconstructOriginalFleet :: [ShipMove] -> Fleet -> Fleet
reconstructOriginalFleet moves = undoMoves moves . unsinkFleet
unsinkFleet :: Fleet -> Fleet
unsinkFleet = fmap unsinkShip where
unsinkShip s = s { shipDamage = fmap (const $ Time Nothing) $ shipDamage s }
undoMoves :: [ShipMove] -> Fleet -> Fleet
undoMoves ms fleet = foldl undo fleet ms where
undo f m = case Data.Map.lookup (shipMoveID m) f of
Just ship -> uncheckedMoveShip ship (reverseDirection $ shipMoveDirection m) f
Nothing -> error "Replay preparation: Irreversible ship movement. This shouldn't happen!"
reverseDirection :: Movement -> Movement
reverseDirection m = case m of
Forward -> Backward
Backward -> Forward