intricacy-0.3.8: Interact.hs
-- This file is part of Intricacy
-- Copyright (C) 2013 Martin Bays <mbays@sdf.org>
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of version 3 of the GNU General Public License as
-- published by the Free Software Foundation.
--
-- You should have received a copy of the GNU General Public License
-- along with this program. If not, see http://www.gnu.org/licenses/.
module Interact (interactUI) where
import Control.Monad.State
import Control.Applicative
import qualified Data.Vector as Vector
import qualified Data.Map as Map
import Data.Map (Map)
import Control.Monad.Writer
import Control.Monad.Trans.Error
import Control.Monad.Trans.Maybe
import Data.Maybe
import Data.Char
import Data.List
import Control.Concurrent.STM
import Control.Concurrent
import System.Directory
import System.FilePath
import Data.Array
import Data.Function (on)
import Hex
import Command
import Physics
import Mundanities
import AsciiLock
import GameState
import GameStateTypes
import EditGameState
import Frame
import Lock
import Cache
import Database
import Protocol
import Metagame
import ServerAddr
import MainState
import InputMode
import Maxlocksize
import InteractUtil
interactUI :: UIMonad uiM => StateT MainState uiM InteractSuccess
interactUI = do
lift $ drawMessage ""
(IMMeta==) . ms2im <$> get >>= flip when (void $ do
flag <- gets newAsync
unblock <- lift unblockInput
_ <- liftIO $ forkIO $ forever $ do
atomically $ readTVar flag >>= check >> writeTVar flag False
unblock
-- draw before testing auth, lest a timeout mean a blank screen
drawMainState
testAuth
refreshUInfoUI
tbdg <- lift $ getUIBinding IMMeta CmdTutorials
(isNothing <$> gets curAuth >>=) $ flip when $
lift $ drawMessage $ "Welcome. To play the tutorial levels, press '"++tbdg++"'."
)
setMark startMark
loop
where
loop = do
mainSt <- get
let im = ms2im mainSt
when (im == IMPlay) checkWon
when (im == IMMeta) $ (checkAsyncErrors >>) $ void.runMaybeT $
mourNameSelected >>=
flip when (lift purgeInvalidUndecls)
drawMainState
cmds <- lift $ getSomeInput im
runErrorT (mapM_ (processCommand im) cmds) >>= \x -> case x of
Left ret -> do
lift $ drawMessage ""
return ret
Right _ -> loop
-- | TODO: neater would be to use a stream (see package 'pipes') for
-- input?
getSomeInput im = do
cmds <- getInput im
if null cmds then getSomeInput im else return cmds
newtype InteractSuccess = InteractSuccess Bool
instance Error InteractSuccess where
noMsg = InteractSuccess False
processCommand :: UIMonad uiM => InputMode -> Command -> ErrorT InteractSuccess (StateT MainState uiM) ()
processCommand im CmdQuit = do
case im of
IMReplay -> throwError $ InteractSuccess False
IMPlay -> lift (gets psIsSub) >>= flip when (throwError $ InteractSuccess False)
IMEdit -> lift editStateUnsaved >>= flip unless (throwError $ InteractSuccess True)
_ -> return ()
title <- lift $ getTitle
really <- lift $ lift $ confirm $ "Really quit" ++ maybe "" (" from "++) title ++ "?"
when really $ throwError $ InteractSuccess False
processCommand im CmdForceQuit = throwError $ InteractSuccess False
processCommand IMPlay CmdOpen = do
st <- gets psCurrentState
frame <- gets psFrame
if checkSolved (frame,st) then throwError $ InteractSuccess True else lift.lift $ drawError "Locked!"
processCommand im cmd = lift $ processCommand' im cmd
processCommand' :: UIMonad uiM => InputMode -> Command -> StateT MainState uiM ()
processCommand' im CmdHelp = lift $ do
showHelp im
void $ textInput "[press a key or RMB]" 1 False True Nothing Nothing
processCommand' im CmdBind = lift $ flip (>>) (drawMessage "") $ void $ runMaybeT $ do
lift $ drawMessage "Command to bind: "
cmd <- msum $ repeat $ do
cmd <- MaybeT $ listToMaybe <$> getInput im
guard $ not.null $ describeCommand cmd
return cmd
lift $ drawMessage ("key to bind to \"" ++ describeCommand cmd ++ "\" (repeat binding to delete): ")
ch <- MaybeT getChRaw
guard $ ch /= '\ESC'
lift $ setUIBinding im cmd ch
processCommand' _ CmdSuspend = lift $ suspend
processCommand' _ CmdRedraw = lift $ redraw
processCommand' im CmdMark = void.runMaybeT $ do
guard $ im `elem` [IMEdit, IMPlay, IMReplay]
str <- MaybeT $ lift $ textInput "Mark: " 1 False True Nothing Nothing
ch <- MaybeT $ return $ listToMaybe str
guard $ ch `notElem` [startMark, '\'']
lift $ setMark ch
processCommand' im CmdJumpMark = void.runMaybeT $ do
guard $ im `elem` [IMEdit, IMPlay, IMReplay]
str <- MaybeT $ lift $ textInput
"Jump to mark (\"'\" to unjump): " 1 False True Nothing Nothing
ch <- MaybeT $ return $ listToMaybe str
lift $ jumpMark ch
processCommand' im CmdReset = jumpMark startMark
processCommand' IMMeta (CmdSelCodename mname) = void.runMaybeT $ do
name <- msum [ MaybeT $ return $ mname
, do
newCodename <- (map toUpper <$>) $ MaybeT $ lift $ textInput "Codename:" 3 False True Nothing Nothing
guard $ length newCodename == 3
return newCodename
]
guard $ validCodeName name
lift $ do
modify $ \ms -> ms { codenameStack = (name:codenameStack ms) }
invalidateUInfo name
refreshUInfoUI
processCommand' IMMeta CmdHome = void.runMaybeT $ do
ourName <- mgetOurName
lift $ do
modify $ \ms -> ms { codenameStack = (ourName:codenameStack ms) }
refreshUInfoUI
processCommand' IMMeta CmdBackCodename = do
stack <- gets codenameStack
when (length stack > 1) $ do
modify $ \ms -> ms { codenameStack = tail stack }
refreshUInfoUI
processCommand' IMMeta CmdSetServer = void.runMaybeT $ do
saddr <- gets curServer
saddrs <- liftIO $ knownServers
newSaddrstr <- MaybeT $ lift $ textInput "Set server:" 256 False False
(Just $ map saddrStr saddrs) (Just $ saddrStr saddr)
newSaddr <- MaybeT $ return $ strToSaddr newSaddrstr
modify $ \ms -> ms { curServer = newSaddr }
unless (nullSaddr newSaddr) $
msum [ void $ MaybeT $ getFreshRecBlocking RecServerInfo
, modify $ \ms -> ms { curServer = saddr }
]
processCommand' IMMeta CmdToggleCacheOnly =
not <$> gets cacheOnly >>= \c -> modify $ \ms -> ms {cacheOnly = c}
processCommand' IMMeta CmdRegister = void.runMaybeT $ do
newName <- mgetCurName
mauth <- gets curAuth
let isUs = maybe False ((==newName).authUser) mauth
confirmOrBail $ if isUs then "Really reset password?" else "Register codename "++newName++"?"
passwd <- (hashPassword newName <$>) $ MaybeT $ lift $
textInput "Enter new password:" 64 True False Nothing Nothing
lift $ if isUs then do
resp <- curServerAction $ ResetPassword passwd
case resp of
ServerAck -> do
lift $ drawMessage "New password set."
modify $ \ms -> ms {curAuth = Just $ Auth newName passwd}
ServerError err -> lift $ drawError err
_ -> lift $ drawMessage $ "Bad server response: " ++ show resp
else do
modify $ \ms -> ms {curAuth = Just $ Auth newName passwd}
resp <- curServerAction Register
case resp of
ServerAck -> do
invalidateUInfo newName
refreshUInfoUI
lift $ drawMessage "Registered!"
ServerError err -> do
lift $ drawError err
modify $ \ms -> ms {curAuth = Nothing}
_ -> lift $ drawMessage $ "Bad server response: " ++ show resp
processCommand' IMMeta CmdAuth = void.runMaybeT $ do
auth <- lift $ gets curAuth
if isJust auth then do
confirmOrBail "Log out?"
modify $ \ms -> ms {curAuth = Nothing}
else do
name <- mgetCurName
passwd <- (hashPassword name <$>) $ MaybeT $ lift $
textInput ("Enter password for "++name++":") 64 True False Nothing Nothing
lift $ do
modify $ \ms -> ms {curAuth = Just $ Auth name passwd}
resp <- curServerAction $ Authenticate
case resp of
ServerAck -> (lift $ drawMessage "Authenticated.")
ServerMessage msg -> (lift $ drawMessage $ "Server: " ++ msg)
ServerError err -> do
lift $ drawError err
modify $ \ms -> ms {curAuth = auth}
_ -> lift $ drawMessage $ "Bad server response: " ++ show resp
processCommand' IMMeta (CmdSolve midx) = void.runMaybeT $ do
name <- mgetCurName
uinfo <- mgetUInfo name
idx <- msum [ MaybeT $ return midx
, askLockIndex "Solve which lock?" "No lock to solve!" (\i -> isJust $ userLocks uinfo ! i) ]
ls <- MaybeT $ return $ lockSpec <$> userLocks uinfo ! idx
undecls <- lift (gets undeclareds)
msum [ do
undecl <- MaybeT $ return $ find (\(Undeclared _ ls' _) -> ls == ls') undecls
_ <- MaybeT $ gets curAuth
confirmOrBail "Declare existing solution?"
void.lift.runMaybeT $ -- ignores MaybeT failures
declare undecl
, do
RCLock lock <- MaybeT $ getFreshRecBlocking $ RecLock ls
soln <- solveLock lock $ Just $
"solving " ++ name ++ ":" ++ [lockIndexChar idx] ++ " (#" ++ show ls ++")"
mourName <- lift $ (authUser <$>) <$> gets curAuth
guard $ mourName /= Just name
let undecl = Undeclared soln ls (ActiveLock name idx)
msum [ do
_ <- MaybeT $ gets curAuth
confirmOrBail "Declare solution?"
declare undecl
, unless (any (\(Undeclared _ ls' _) -> ls == ls') undecls) $
modify $ \ms -> ms { undeclareds = (undecl : undeclareds ms) }
]
]
processCommand' IMMeta (CmdPlayLockSpec mls) = void.runMaybeT $ do
ls <- msum [ MaybeT $ return mls
, do
tls <- MaybeT . lift $ textInput "Lock number:" 16 False False Nothing Nothing
MaybeT . return $ fst <$> (listToMaybe $ (reads :: ReadS Int) tls)
]
RCLock lock <- MaybeT $ getFreshRecBlocking $ RecLock ls
solveLock lock $ Just $ "solving " ++ show ls
processCommand' IMMeta (CmdDeclare mundecl) = void.runMaybeT $ do
mourNameSelected >>= guard
name <- mgetCurName
undecls <- lift $ gets undeclareds
guard $ not $ null undecls
declare =<< msum [ MaybeT $ return mundecl
, if length undecls == 1
then return $ head undecls
else do
which <- MaybeT $ lift $ textInput
("Declare which solution?")
5 False True Nothing Nothing
MaybeT $ return $ msum
[ do
i <- fst <$> (listToMaybe $ (reads :: ReadS Int) which)
guard $ 0 < i && i <= length undecls
return $ undecls !! (i-1)
, listToMaybe $ [ undecl
| undecl@(Undeclared _ _ (ActiveLock name' i)) <- undecls
, or [ take (length which) (name' ++ ":" ++ [lockIndexChar i]) ==
map toUpper which
, name'==name && [lockIndexChar i] == which
] ]
]
]
processCommand' IMMeta (CmdViewSolution mnote) = void.runMaybeT $ do
note <- msum [ MaybeT $ return mnote, do
ourName <- mgetOurName
name <- mgetCurName
uinfo <- mgetUInfo name
noteses <- lift $ sequence
[ case mlockinfo of
Nothing -> return []
Just lockinfo -> (++lockSolutions lockinfo) <$> do
ns <- getNotesReadOn lockinfo
return $ if length ns < 3 then [] else ns
| mlockinfo <- elems $ userLocks uinfo ]
idx <- askLockIndex "View solution to which lock?" "No solutions to view" $ not.null.(noteses!!)
let notes = noteses!!idx
authors = map noteAuthor notes
author <- if length notes == 1
then return $ noteAuthor $ head notes
else (map toUpper <$>) $ MaybeT $ lift $
textInput ("View solution by which player? ["
++ intercalate "," (take 3 authors)
++ if length authors > 3 then ",...]" else "]")
3 False True (Just $ authors) Nothing
MaybeT $ return $ find ((==author).noteAuthor) notes
]
let ActiveLock name idx = noteOn note
uinfo <- mgetUInfo name
ls <- lockSpec <$> MaybeT (return $ userLocks uinfo ! idx)
RCLock lock <- MaybeT $ getFreshRecBlocking $ RecLock ls
RCSolution soln <- MaybeT $ getFreshRecBlocking $ RecNote note
lift.lift $ execStateT interactUI $ newReplayState (snd.reframe$lock) soln $ Just $
"viewing solution by " ++ noteAuthor note ++ " to " ++ name ++ [':',lockIndexChar idx]
processCommand' IMMeta (CmdPlaceLock midx) = void.runMaybeT $ do
mourNameSelected >>= guard
ourName <- mgetOurName
(lock,msoln) <- msum [MaybeT $ gets curLock
, do
ebdg <- lift.lift $ getUIBinding IMMeta CmdEdit
lift.lift $ drawError $ "No lock selected; '"++ebdg++"' to edit one."
mzero]
lockpath <- lift $ gets curLockPath
ourUInfo <- mgetUInfo ourName
idx <- msum [ MaybeT $ return midx
, askLockIndex ("Place " ++ show lockpath ++ " in which slot?") "bug" $ const True
]
when (isJust $ userLocks ourUInfo ! idx) $
confirmOrBail "Really retire existing lock?"
soln <- msum [ MaybeT $ return msoln,
solveLock lock $ Just $ "testing lock" ]
lift $ do
curServerActionAsync $ SetLock lock idx soln
invalidateUInfo ourName
refreshUInfoUI
processCommand' IMMeta CmdSelectLock = void.runMaybeT $ do
lockdir <- liftIO $ confFilePath "locks"
paths <- liftIO $ map (drop (length lockdir + 1)) <$> getDirContentsRec lockdir
path <- MaybeT $ lift $ textInput "Lock name:" 1024 False False (Just paths) Nothing
lift $ setLockPath path
processCommand' IMMeta CmdNextLock =
gets curLockPath >>= liftIO . nextLock True >>= setLockPath
processCommand' IMMeta CmdPrevLock =
gets curLockPath >>= liftIO . nextLock False >>= setLockPath
processCommand' IMMeta CmdNextPage =
modify $ \ms -> ms { listOffset = listOffset ms + 1 }
processCommand' IMMeta CmdPrevPage =
modify $ \ms -> ms { listOffset = max 0 $ listOffset ms - 1 }
processCommand' IMMeta CmdEdit = void.runMaybeT $ do
(lock, msoln) <- msum [ MaybeT $ gets curLock
, do
frame <- BasicFrame <$> msum [ do
_ <- gets curServer
RCServerInfo (ServerInfo size _) <- MaybeT $ getFreshRecBlocking RecServerInfo
return size
, do
sizet <- MaybeT $ lift $ textInput
("Lock size: [3-" ++ show maxlocksize ++ "]") 2 False False Nothing Nothing
size <- MaybeT $ return $ fst <$> (listToMaybe $ (reads :: ReadS Int) sizet)
guard $ 3 <= size && size <= maxlocksize
return size
]
return ((frame, baseState frame), Nothing)
]
path <- lift $ gets curLockPath
newPath <- MaybeT $ lift $ (esPath <$>) $ execStateT interactUI $ newEditState (reframe lock)
msoln (if null path then Nothing else Just path)
lift $ setLockPath newPath
processCommand' IMMeta CmdTutorials = void.runMaybeT $ do
tutdir <- liftIO $ getDataPath "tutorial"
tuts <- liftIO $ (sort . map (takeWhile (/='.')) . filter (isSuffixOf ".lock")) <$>
getDirectoryContents tutdir `catchIO` (const $ return [])
when (null tuts) $ do
lift.lift $ drawError "No tutorial levels found"
mzero
-- s <- MaybeT $ lift $ textInput ("Play which tutorial level? [1-"++show (length tuts)++"]")
-- (length (show (length tuts))) False True Nothing Nothing
-- i <- MaybeT $ return $ fst <$> (listToMaybe $ (reads :: ReadS Int) s)
-- guard $ 1 <= i && i <= length tuts
let i = 1
let dotut i = do
let name = tuts !! (i-1)
let pref = tutdir ++ [pathSeparator] ++ name
(lock,_) <- MaybeT $ liftIO $ readLock (pref ++ ".lock")
text <- liftIO $ (fromMaybe "" . listToMaybe) <$> (readStrings (pref ++ ".text") `catchIO` const (return []))
_ <- solveLock lock $ Just $ "Tutorial " ++ show i ++ ": " ++ text
if i+1 <= length tuts then do
-- confirmOrBail $ "Tutorial level completed! Play next tutorial level (" ++ show (i+1) ++ ")?"
dotut $ i+1
else lift $ do
mauth <- gets curAuth
cbdg <- lift $ getUIBinding IMMeta $ CmdSelCodename Nothing
rbdg <- lift $ getUIBinding IMMeta CmdRegister
lift $ drawMessage $ if isNothing mauth
then "Tutorial completed! To play on the server, pick a codename ('"++cbdg++
"') and register it ('"++rbdg++"')."
else "Tutorial completed!"
dotut i
processCommand' IMMeta CmdShowRetired = void.runMaybeT $ do
name <- mgetCurName
newRL <- lift (gets retiredLocks) >>= \rl -> case rl of
Nothing -> do
RCLockSpecs lss <- MaybeT $ getFreshRecBlocking $ RecRetiredLocks name
return $ Just lss
Just _ -> return Nothing
lift $ modify $ \ms -> ms {retiredLocks = newRL}
processCommand' IMPlay CmdUndo = do
st <- gets psCurrentState
stack <- gets psGameStateMoveStack
ustms <- gets psUndoneStack
unless (null stack) $ do
let (st',pm) = head stack
modify $ \ps -> ps {psCurrentState=st', psGameStateMoveStack = tail stack,
psLastAlerts = [], psUndoneStack = (st,pm):ustms}
processCommand' IMPlay CmdRedo = do
ustms <- gets psUndoneStack
case ustms of
[] -> return ()
ustm:ustms' -> do
pushPState ustm
modify $ \ps -> ps {psUndoneStack = ustms'}
processCommand' IMPlay (CmdManipulateToolAt pos) = do
board <- stateBoard <$> gets psCurrentState
wsel <- gets wrenchSelected
void.runMaybeT $ msum $ [ do
tile <- MaybeT $ return $ snd <$> Map.lookup pos board
guard $ case tile of {WrenchTile _ -> True; HookTile -> True; _ -> False}
lift $ processCommand' IMPlay $ CmdTile tile
] ++ [ do
tile <- MaybeT $ return $ snd <$> Map.lookup (d<+>pos) board
guard $ tileType tile == if wsel then WrenchTile zero else HookTile
lift $ processCommand' IMPlay $ CmdDir WHSSelected $ neg d
| d <- hexDirs ]
processCommand' IMPlay (CmdDrag pos dir) = do
board <- stateBoard <$> gets psCurrentState
wsel <- gets wrenchSelected
void.runMaybeT $ do
tile <- MaybeT $ return $ snd <$> Map.lookup pos board
guard $ case tile of {WrenchTile _ -> True; HookTile -> True; _ -> False}
lift $ processCommand' IMPlay $ CmdDir WHSSelected $ dir
board' <- stateBoard <$> gets psCurrentState
msum [ do
tile' <- MaybeT $ return $ snd <$> Map.lookup pos' board'
guard $ tileType tile' == if wsel then WrenchTile zero else HookTile
lift.lift $ warpPointer $ pos'
| pos' <- map (<+>pos) $ hexDisc 2 ]
processCommand' IMPlay cmd = do
wsel <- gets wrenchSelected
st <- gets psCurrentState
let push whs dir
| whs == WHSWrench || (whs == WHSSelected && wsel) =
Just $ WrenchPush dir
| otherwise = Just $ HookPush dir
torque whs dir
| whs == WHSHook || (whs == WHSSelected && not wsel)=
Just $ HookTorque dir
| otherwise = Nothing
(wsel', pm) =
case cmd of
CmdTile (WrenchTile _) -> (True, Nothing)
CmdTile HookTile -> (False, Nothing)
CmdTile (ArmTile _ _) -> (False, Nothing)
CmdToggle -> (not wsel, Nothing)
CmdDir whs dir -> (wsel, push whs dir)
CmdRotate whs dir -> (wsel, torque whs dir)
CmdWait -> (wsel, Just NullPM)
CmdSelect -> (wsel, Just NullPM)
_ -> (wsel, Nothing)
modify $ \ps -> ps {wrenchSelected = wsel'}
case pm of
Nothing -> return ()
Just pm' -> do
(st',alerts) <- lift $ doPhysicsTick pm' st
modify $ \ps -> ps {psLastAlerts = alerts}
pushPState (st',pm')
processCommand' IMReplay (CmdReplayBack 1) = void.runMaybeT $ do
(st',pm) <- MaybeT $ listToMaybe <$> gets rsGameStateMoveStack
lift $ modify $ \rs -> rs {rsCurrentState=st'
, rsGameStateMoveStack = tail $ rsGameStateMoveStack rs
, rsMoveStack = pm:rsMoveStack rs}
processCommand' IMReplay (CmdReplayBack n) = replicateM_ n $
processCommand' IMReplay (CmdReplayBack 1)
processCommand' IMReplay (CmdReplayForward 1) = void.runMaybeT $ do
pm <- MaybeT $ listToMaybe <$> gets rsMoveStack
lift $ do
st <- gets rsCurrentState
(st',_) <- lift $ doPhysicsTick pm st
modify $ \rs -> rs {rsCurrentState = st'
, rsGameStateMoveStack = (st,pm):rsGameStateMoveStack rs
, rsMoveStack = tail $ rsMoveStack rs}
processCommand' IMReplay (CmdReplayForward n) = replicateM_ n $
processCommand' IMReplay (CmdReplayForward 1)
processCommand' IMReplay CmdUndo = processCommand' IMReplay (CmdReplayBack 1)
processCommand' IMReplay CmdRedo = processCommand' IMReplay (CmdReplayForward 1)
processCommand' IMEdit CmdPlay = do
st <- gets $ head.esGameStateStack
frame <- gets esFrame
modify $ \es -> es {selectedPiece = Nothing}
subPlay (frame,st)
processCommand' IMEdit CmdTest = do
frame <- gets esFrame
modifyEState (\st -> snd $ canonify (frame, st))
modify $ \es -> es {selectedPiece = Nothing}
mpath <- gets esPath
st <- gets $ head.esGameStateStack
void.runMaybeT $ do
soln <- solveLock (frame,st) $ Just $ "testing " ++ fromMaybe "[unnamed lock]" mpath
lift $ modify $ \es -> es { esTested = Just (st, soln) }
processCommand' IMEdit CmdUndo = do
st:sts <- gets esGameStateStack
usts <- gets esUndoneStack
unless (null sts) $ modify $ \es -> es {esGameStateStack = sts, esUndoneStack = st:usts}
processCommand' IMEdit CmdRedo = do
usts <- gets esUndoneStack
case usts of
[] -> return ()
ust:usts' -> do
pushEState ust
modify $ \es -> es {esUndoneStack = usts'}
processCommand' IMEdit CmdUnselect =
modify $ \es -> es {selectedPiece = Nothing}
processCommand' IMEdit CmdSelect = do
selPiece <- gets selectedPiece
selPos <- gets selectedPos
st:_ <- gets esGameStateStack
let selPiece' =
if isJust selPiece
then Nothing
else liftM fst $ Map.lookup selPos $ stateBoard st
modify $ \es -> es {selectedPiece = selPiece'}
processCommand' IMEdit (CmdDir _ dir) = do
selPos <- gets selectedPos
selPiece <- gets selectedPiece
frame <- gets esFrame
case selPiece of
Nothing -> modify $ \es -> es {selectedPos = checkEditable frame selPos $ dir <+> selPos}
Just p -> doForce $ Push p dir
processCommand' IMEdit (CmdMoveTo newPos) = do
frame <- gets esFrame
modify $ \es -> es {selectedPos = truncateToEditable frame newPos}
processCommand' IMEdit (CmdDrag pos dir) = do
board <- stateBoard.head <$> gets esGameStateStack
void.runMaybeT $ do
selIdx <- MaybeT $ gets selectedPiece
idx <- MaybeT $ return $ fst <$> Map.lookup pos board
guard $ idx == selIdx
lift $ processCommand' IMEdit $ CmdDir WHSSelected $ dir
board' <- stateBoard.head <$> gets esGameStateStack
msum [ do
idx' <- MaybeT $ return $ fst <$> Map.lookup pos' board'
guard $ idx' == selIdx
lift.lift $ warpPointer $ pos'
| pos' <- [dir<+>pos, pos] ]
processCommand' IMEdit (CmdRotate _ dir) = do
selPiece <- gets selectedPiece
case selPiece of
Nothing -> return ()
Just p -> doForce $ Torque p dir
processCommand' IMEdit (CmdTile tile) = do
selPos <- gets selectedPos
drawTile selPos (Just tile) False
processCommand' IMEdit (CmdPaint tile) = do
selPos <- gets selectedPos
drawTile selPos tile True
processCommand' IMEdit (CmdPaintFromTo tile from to) = do
frame <- gets esFrame
paintTilePath tile (truncateToEditable frame from) (truncateToEditable frame to)
processCommand' IMEdit CmdMerge = do
selPos <- gets selectedPos
st:_ <- gets esGameStateStack
lift $ drawMessage "Merge in which direction?"
let getDir = do
cmd <- lift $ head <$> getSomeInput IMEdit
case cmd of
CmdDir _ mergeDir -> return $ Just mergeDir
CmdDrag _ mergeDir -> return $ Just mergeDir
CmdMoveTo _ -> getDir
_ -> return $ Nothing
mergeDir <- getDir
case mergeDir of
Just mergeDir -> modifyEState $ mergeTiles selPos mergeDir True
_ -> return ()
-- XXX: merging might invalidate selectedPiece
modify $ \es -> es {selectedPiece = Nothing}
lift $ drawMessage ""
processCommand' IMEdit CmdWait = do
st:_ <- gets esGameStateStack
(st',_) <- lift $ doPhysicsTick NullPM st
pushEState st'
processCommand' IMEdit CmdDelete = do
selPos <- gets selectedPos
selPiece <- gets selectedPiece
st:_ <- gets esGameStateStack
case selPiece of
Nothing -> drawTile selPos Nothing False
Just p -> do modify $ \es -> es {selectedPiece = Nothing}
modifyEState $ delPiece p
processCommand' IMEdit CmdWriteState = void.runMaybeT $ do
path <- lift $ gets esPath
newPath <- MaybeT $ lift $ textInput "Save lock as:" 1024 False False Nothing path
guard $ not $ null newPath
fullPath <- liftIO $ fullLockPath newPath
(liftIO (doesFileExist fullPath `catchIO` const (return True)) >>=) $ flip when $
confirmOrBail $ "Really overwrite '"++fullPath++"'?"
lift $ do
st <- gets $ head.esGameStateStack
frame <- gets esFrame
msoln <- getCurTestSoln
merr <- liftIO $ ((writeAsciiLockFile fullPath msoln $ canonify (frame, st)) >> return Nothing)
`catchIO` (return . Just . show)
modify $ \es -> es {lastSavedState = Just (st,isJust msoln)}
case merr of
Nothing -> modify $ \es -> es {esPath = Just newPath}
Just err -> lift $ drawError $ "Write failed: "++err
processCommand' _ _ = return ()
hashPassword :: String -> String -> String
-- ^ salt password
hashPassword name password = hash $ "IY" ++ name ++ password
subPlay :: UIMonad uiM => Lock -> StateT MainState uiM ()
subPlay lock =
pushEState =<< psCurrentState <$> (lift $ execStateT interactUI $ newPlayState lock Nothing True)
solveLock :: UIMonad uiM => Lock -> Maybe String -> MaybeT (StateT MainState uiM) Solution
solveLock lock title = do
(InteractSuccess solved, ps) <- lift.lift $ runStateT interactUI $ newPlayState (reframe lock) title False
guard $ solved
return $ reverse $ (map snd) $ psGameStateMoveStack ps