packages feed

intricacy 0.8.0.1 → 0.8.1

raw patch · 53 files changed

+437/−321 lines, 53 filesdep +network-simpledep ~binarydep ~bytestringdep ~cryptonite

Dependencies added: network-simple

Dependency ranges changed: binary, bytestring, cryptonite, memory, mtl, network-fancy, random, time, transformers

Files

AsciiLock.hs view
@@ -16,6 +16,7 @@ import           Control.Applicative import           Control.Arrow       ((&&&)) import           Control.Monad+import           Data.Char           (toUpper) import           Data.Function       (on) import           Data.List import           Data.Map            (Map)@@ -33,6 +34,7 @@ import           Hex import           Lock import           Mundanities+import           Physics import           Util  @@ -234,16 +236,57 @@ minmax :: Ord a => [a] -> (a,a) minmax = minimum &&& maximum +solutionToAscii :: Solution -> String+solutionToAscii = map pmToAscii++dirChar :: HexVec -> Char+dirChar dir+    | dir == hu = 'l'+    | dir == neg hu = 'h'+    | dir == hv = 'y'+    | dir == neg hv = 'n'+    | dir == hw = 'b'+    | dir == neg hw = 'u'+    | otherwise = '.'+pmToAscii :: PlayerMove -> Char+pmToAscii (HookPush dir)    = dirChar dir+pmToAscii (WrenchPush dir)  = toUpper $ dirChar dir+pmToAscii (HookTorque 1)    = '+'+pmToAscii (HookTorque (-1)) = '-'+pmToAscii _                 = '.'++solutionOfAscii :: String -> Maybe Solution+solutionOfAscii = mapM pmOfAscii++pmOfAscii :: Char -> Maybe PlayerMove+pmOfAscii 'l' = Just $ HookPush hu+pmOfAscii 'y' = Just $ HookPush hv+pmOfAscii 'b' = Just $ HookPush hw+pmOfAscii 'h' = Just . HookPush $ neg hu+pmOfAscii 'n' = Just . HookPush $ neg hv+pmOfAscii 'u' = Just . HookPush $ neg hw+pmOfAscii 'L' = Just $ WrenchPush hu+pmOfAscii 'Y' = Just $ WrenchPush hv+pmOfAscii 'B' = Just $ WrenchPush hw+pmOfAscii 'H' = Just . WrenchPush $ neg hu+pmOfAscii 'N' = Just . WrenchPush $ neg hv+pmOfAscii 'U' = Just . WrenchPush $ neg hw+pmOfAscii '+' = Just $ HookTorque 1+pmOfAscii '-' = Just . HookTorque $ -1+pmOfAscii '.' = Just NullPM+pmOfAscii _   = Nothing+ readAsciiLockFile :: FilePath -> IO (Maybe Lock, Maybe Solution) readAsciiLockFile path = fromLines <$> readStrings path     where fromLines lines = fromMaybe (lockOfAscii lines, Nothing) $ do             guard $ length lines > 2             let (locklines, [header,solnLine]) = splitAt (length lines - 2) lines             guard $ isPrefixOf "Solution:" header-            return (lockOfAscii locklines, tryRead solnLine)+            return (lockOfAscii locklines,+                solutionOfAscii solnLine `mplus` tryRead solnLine)  writeAsciiLockFile :: FilePath -> Maybe Solution -> Lock -> IO () writeAsciiLockFile path msoln lock = do     writeStrings path $ lockToAscii lock ++ case msoln of         Nothing   -> []-        Just soln -> ["Solution:", show soln]+        Just soln -> ["Solution:", solutionToAscii soln]
Cache.hs view
@@ -8,6 +8,8 @@ -- You should have received a copy of the GNU General Public License -- along with this program.  If not, see http://www.gnu.org/licenses/. +{-# LANGUAGE LambdaCase #-}+ module Cache where  import           Control.Applicative@@ -20,7 +22,7 @@ import qualified Data.ByteString            as BS import qualified Data.ByteString.Lazy       as BL import           Data.Maybe-import           Network.Fancy+import           Network.Simple.TCP         (connect, recv, send) import           System.Directory import           System.FilePath import           System.IO@@ -78,14 +80,18 @@ makeRequest saddr _ | nullSaddr saddr =     return $ ServerError "No server set." makeRequest saddr@(ServerAddr host port) request =-    handle (return . ServerError . (show::SomeException -> String)) $-        withStream (IP host port) makeRequest'+    handle (return . ServerError . (show::SomeException -> String)) $ do+        connect host (show port) makeRequest'             `catchIO` const (return $ ServerError $ "Cannot connect to "++saddrStr saddr++"!")     where-        makeRequest' hdl = do-            BS.hPut hdl $ BL.toStrict $ encode request-            hFlush hdl-            decode . BL.fromStrict <$> BS.hGetContents hdl+        makeRequest' (sock,_) = do+            send sock . BL.toStrict $ encode request+            decode . BL.fromStrict <$> recvAll sock+        recvAll sock =+            recv sock 4096 >>= \case+                Nothing -> return BS.empty+                Just b  -> BS.append b <$> recvAll sock+  knownServers :: IO [ServerAddr] knownServers = ignoreIOErr $ do
CursesUI.hs view
@@ -314,7 +314,7 @@     drawMsgLine     refresh -drawTitle (Just title) = do+drawTitle (Just (title,n)) = do     (h,w) <- liftIO Curses.scrSize     drawStrCentred a0 white (CVec 0 (w`div`2)) title drawTitle Nothing = return ()
Init.hs view
@@ -30,10 +30,17 @@ import           Util import           Version -data Opt = LockSize Int | ForceCurses | Help | Version+data Opt+    = DataDir FilePath+    | LockSize Int+    | ForceCurses+    | Help+    | Version     deriving (Eq, Ord, Show)+ options =-    [ Option ['c'] ["curses"] (NoArg ForceCurses) "force curses UI"+    [ Option ['d'] ["datadir"] (ReqArg DataDir "PATH") "user data and conf directory (default: ~/.intricacy)"+    , Option ['c'] ["curses"] (NoArg ForceCurses) "force curses UI"     , Option ['s'] ["locksize"] (ReqArg (LockSize . read) "SIZE") "locksize"     , Option ['h'] ["help"] (NoArg Help) "show usage information"     , Option ['v'] ["version"] (NoArg Version) "show version information"@@ -49,22 +56,24 @@         (o,n,[])   -> return (o,n)         (_,_,errs) -> ioError (userError (concat errs ++ usage)) -setup :: IO (Maybe Lock,[Opt],Maybe String)+setup :: IO (Maybe (Lock, Maybe Solution), [Opt], Maybe String) setup = do     argv <- getArgs     (opts,args) <- parseArgs argv     when (Help `elem` opts) $ putStr usage >> exitSuccess     when (Version `elem` opts) $ putStrLn version >> exitSuccess     let size = fromMaybe 8 $ listToMaybe [ size | LockSize size <- opts ]+    mapM_ (setEnv "INTRICACY_PATH") [ dir | DataDir dir <- opts ]+     curDir <- getCurrentDirectory     (fromJust <$>) $ runMaybeT $ msum         [ do             path <- liftMaybe ((curDir </>) <$> listToMaybe args)             msum [ do-                    lock <- reframe.fst <$> MaybeT (readLock path)-                    return (Just lock,opts,Just path)-                , return (Just $ baseLock size, opts, Just path) ]-        , return (Nothing,opts,Nothing) ]+                    (lock, msoln) <- MaybeT (readLock path)+                    return (Just (reframe lock, msoln), opts, Just path)+                , return (Just (baseLock size, Nothing), opts, Just path) ]+        , return (Nothing, opts, Nothing) ]  main' :: (UIMonad s, UIMonad c) =>         Maybe (s MainState -> IO (Maybe MainState)) ->@@ -72,8 +81,8 @@ main' msdlUI mcursesUI = do     (mlock,opts,mpath) <- setup     initMState <- case mlock of-            Just lock -> return $ newEditState lock Nothing mpath-            Nothing   -> initMetaState+            Just (lock, msoln) -> return $ newEditState lock msoln mpath+            Nothing            -> initMetaState     void $ runMaybeT $ msum [ do             finalState <- msum                 [ do
Interact.hs view
@@ -146,7 +146,7 @@     title <- lift getTitle     (lift . lift . confirm) ("Really quit"             ++ (if im == IMEdit then " without saving" else "")-            ++ maybe "" (" from "++) title ++ "?")+            ++ maybe "" ((" from "++) . fst) title ++ "?")         >>? throwE $ InteractSuccess False processCommand im CmdForceQuit = throwE $ InteractSuccess False processCommand IMPlay CmdOpen = do@@ -196,7 +196,7 @@         MaybeT . lift $ gets (Map.lookup v . initLocks)     lift $ do         (InteractSuccess solved, ps) <- lift . runSubMainState $-            maybe newPlayState restorePlayState partial (reframe lock) (Just desc) Nothing False True+            maybe newPlayState restorePlayState partial (reframe lock) [] (Just desc) Nothing False True         let updateLock initLock = initLock { initLockSolved = initLockSolved initLock || solved                 , initLockPartial = Just $ savePlayState ps }         lift . modify $ \is -> is { initLocks = Map.adjust updateLock v $ initLocks is }@@ -411,7 +411,7 @@             liftMaybe $ readMay tls         ]     RCLock lock <- MaybeT $ getFreshRecBlocking $ RecLock ls-    solveLock lock $ Just $ "solving " ++ show ls+    solveLock lock Nothing $ Just $ "solving " ++ show ls  processCommand' IMMeta (CmdDeclare mundecl) = void.runMaybeT $ do     guard =<< mourNameSelected@@ -482,7 +482,7 @@         askLockIndex ("Place " ++ show lockpath ++ " in which slot?") "bug" $ const True     when (isJust $ userLocks ourUInfo ! idx) $         confirmOrBail "Really retire existing lock?"-    soln <- (liftMaybe msoln `mplus`) $ solveLock lock $ Just "testing lock"+    soln <- (liftMaybe msoln `mplus`) $ solveLock lock Nothing $ Just "testing lock"     lift $ curServerActionAsyncThenInvalidate         (SetLock lock idx soln)         (Just (SomeCodenames [ourName]))@@ -545,12 +545,12 @@     unless (null stack) $ do         let (st',pm) = head stack         modify $ \ps -> ps {psCurrentState=st', psGameStateMoveStack = tail stack,-            psLastAlerts = [], psUndoneStack = (st,pm):ustms}+            psLastAlerts = [], psUndoneStack = pm:ustms} processCommand' IMPlay CmdRedo = do     ustms <- gets psUndoneStack     case ustms of         [] -> return ()-        ustm@(_,pm):ustms' -> do+        pm:ustms' -> do             st <- gets psCurrentState             (st',alerts) <- lift $ doPhysicsTick pm st             pushPState (st',pm)@@ -654,8 +654,12 @@     modify $ \es -> es {selectedPiece = Nothing}     mpath <- gets esPath     st <- gets esGameState+    curSoln <- runMaybeT $ do+        (st', soln) <- MaybeT $ gets esTested+        guard $ st' == st+        return soln     void.runMaybeT $ do-        soln <- solveLock (frame,st) $ Just $ "testing " ++ fromMaybe "[unnamed lock]" mpath+        soln <- solveLock (frame,st) curSoln $ Just $ "testing " ++ fromMaybe "[unnamed lock]" mpath         lift $ modify $ \es -> es { esTested = Just (st, soln) } processCommand' IMEdit CmdUndo = do     st <- gets esGameState@@ -704,9 +708,18 @@             | pos' <- [dir+^pos, pos] ] processCommand' IMEdit (CmdRotate _ dir) = do     selPiece <- gets selectedPiece+    st <- gets esGameState     case selPiece of         Nothing -> return ()-        Just p  -> doForce $ Torque p dir+        Just p  ->+            let PlacedPiece _ pc = getpp st p+                torquable = case pc of+                    Hook _ _ -> True+                    Pivot _  -> True+                    _        -> False+            in if torquable+                then doForce $ Torque p dir+                else adjustSpringTension p dir processCommand' IMEdit (CmdTile tile) = do     selPos <- gets selectedPos     drawTile selPos (Just tile) False@@ -786,15 +799,10 @@ -- replay it to authenticate as the user. encryptPassword :: UIMonad uiM =>     PublicKey -> String -> String -> MaybeT (MainStateT uiM) String-encryptPassword publicKey name password = msum-    [ MaybeT . liftIO .-        handle (\(e :: SomeException) -> return Nothing) $ do+encryptPassword publicKey name password = MaybeT . liftIO .+            handle (\(e :: SomeException) -> return Nothing) $ do         Right c <- encrypt (defaultOAEPParams SHA256) publicKey . CS.pack $ hashed         return . Just . CS.unpack $ c-    , confirmOrBail-        "Failed to encrypt password - send unencrypted?"-        >> return hashed-    ]     where hashed = hash $ "IY" ++ name ++ password  setSelectedPosFromMouse :: UIMonad uiM => MainStateT uiM ()@@ -807,12 +815,13 @@  subPlay :: UIMonad uiM => Lock -> MainStateT uiM () subPlay lock =-    pushEState . psCurrentState =<< execSubMainState (newPlayState lock Nothing Nothing True False)+    pushEState . psCurrentState =<< execSubMainState (newPlayState lock [] Nothing Nothing True False) -solveLock :: UIMonad uiM => Lock -> Maybe String -> MaybeT (MainStateT uiM) Solution+solveLock :: UIMonad uiM => Lock -> Maybe Solution -> Maybe String -> MaybeT (MainStateT uiM) Solution solveLock = solveLock' Nothing-solveLock' tutLevel lock title = do-    (InteractSuccess solved, ps) <- lift $ runSubMainState $ newPlayState (reframe lock) title tutLevel False False+solveLock' tutLevel lock mSoln title = do+    (InteractSuccess solved, ps) <- lift $ runSubMainState $+        newPlayState (reframe lock) (fromMaybe [] mSoln) title tutLevel False False     guard solved     return . reverse $ snd <$> psGameStateMoveStack ps @@ -820,7 +829,7 @@ solveLockSaving ls msps tutLevel lock title = do     let isTut = isJust tutLevel     (InteractSuccess solved, ps) <- lift $ runSubMainState $-        maybe newPlayState restorePlayState msps (reframe lock) title tutLevel False True+        maybe newPlayState restorePlayState msps (reframe lock) [] title tutLevel False True     if solved         then do             unless isTut . lift . modify $ \ms -> ms { partialSolutions = Map.delete ls $ partialSolutions ms }
InteractUtil.hs view
@@ -26,6 +26,7 @@ import           Command import           EditGameState import           Frame+import           GameState import           GameStateTypes import           Hex import           InputMode@@ -66,6 +67,17 @@         let from' = hexVec2HexDirOrZero (to-^from) +^ from         when (inEditable frame from') $ drawTile from' tile True         paintTilePath frame tile from' to++adjustSpringTension :: UIMonad uiM => PieceIdx -> Int -> MainStateT uiM ()+adjustSpringTension p dl = do+    st <- gets esGameState+    let updateConn c@(Connection r e@(p',_) (Spring d l))+            | p' == p+            , let c' = Connection r e (Spring d $ l + dl)+            , springExtensionValid st c'+            = c'+            | otherwise = c+    pushEState $ st { connections = updateConn <$> connections st }  pushEState :: UIMonad uiM => GameState -> MainStateT uiM () pushEState st = do
Intricacy.hs view
@@ -40,7 +40,6 @@ #else     (Nothing :: (Maybe (CursesUI.UIM MainState -> IO (Maybe MainState)))) #endif- #ifdef MAIN_CURSES     (Just (doUI::CursesUI.UIM MainState -> IO (Maybe MainState))) #else
MainState.hs view
@@ -40,6 +40,7 @@ import           Hex import           InputMode import           Lock+import           Maxlocksize import           Metagame import           Mundanities import           Physics@@ -87,7 +88,7 @@         , wrenchSelected       :: Bool         , psSolved             :: Bool         , psGameStateMoveStack :: [(GameState, PlayerMove)]-        , psUndoneStack        :: [(GameState, PlayerMove)]+        , psUndoneStack        :: [PlayerMove]         , psTitle              :: Maybe String         , psTutLevel           :: Maybe Int         , psIsSub              :: Bool@@ -154,7 +155,7 @@     InitState {}   -> IMInit     MetaState {}   -> IMMeta -newPlayState (frame,st) title tutLevel sub saved = PlayState st frame [] False False [] [] title tutLevel sub saved Map.empty+newPlayState (frame,st) pms title tutLevel sub saved = PlayState st frame [] False False [] pms title tutLevel sub saved Map.empty newReplayState st soln title = ReplayState st [] soln [] title Map.empty newEditState (frame,st) msoln mpath = EditState st [] [] frame mpath     ((st,)<$>msoln) (Just (st, isJust msoln)) Nothing (PHS zero) (PHS zero) Map.empty@@ -187,8 +188,8 @@ initTutProgress = TutProgress False 1 Nothing  wrenchOnlyTutLevel, noUndoTutLevel :: Maybe Int -> Bool-wrenchOnlyTutLevel = (`elem` (Just <$> [1..3]))-noUndoTutLevel = (`elem` (Just <$> [1..6]))+wrenchOnlyTutLevel = (`elem` (Just <$> [1..4]))+noUndoTutLevel = (`elem` (Just <$> [1..7]))  data InitLock = InitLock     { initLockName    :: String@@ -216,12 +217,12 @@ savePlayState ps = SavedPlayState (getMoves ps) $ Map.map getMoves $ psMarks ps     where getMoves = reverse . map snd . psGameStateMoveStack -restorePlayState :: SavedPlayState -> Lock -> Maybe String -> Maybe Int -> Bool -> Bool -> MainState-restorePlayState (SavedPlayState pms markPMs) (frame,st) title tutLevel sub saved =+restorePlayState :: SavedPlayState -> Lock -> [PlayerMove] -> Maybe String -> Maybe Int -> Bool -> Bool -> MainState+restorePlayState (SavedPlayState pms markPMs) (frame,st) redoPms title tutLevel sub saved =     (stateAfterMoves pms) { psMarks = Map.map stateAfterMoves markPMs }     where         stateAfterMoves pms = let (stack,st') = applyMoves st pms-            in (newPlayState (frame, st') title tutLevel sub saved) { psGameStateMoveStack = stack }+            in (newPlayState (frame, st') redoPms title tutLevel sub saved) { psGameStateMoveStack = stack }         applyMoves st pms = foldl tick ([],st) pms         tick :: ([(GameState,PlayerMove)],GameState) -> PlayerMove -> ([(GameState,PlayerMove)],GameState)         tick (stack,st) pm = ((st,pm):stack,fst . runWriter $ physicsTick pm st)@@ -250,7 +251,7 @@             desc <- MaybeT $ listToMaybe <$> readStrings (initDataDir </> name ++ ".text")             lock <- (fst <$>) . MaybeT $ readLock (initDataDir </> name ++ ".lock")             solved <- lift . (fromMaybe False <$>) . readReadFile $ initConfDir </> name ++ ".solved"-            partial <- lift . readReadFile $ initConfDir </> name ++ ".partial"+            partial <- lift . (fromMaybe Nothing <$>) . readReadFile $ initConfDir </> name ++ ".partial"             return $ InitLock name desc lock solved partial     initLocks <- Map.mapMaybe id <$> mapM readInitLock namesMap     return (tut,initLocks)@@ -288,19 +289,26 @@     writeServerSolns saddr ms writeMetaState _ = return () -getTitle :: UIMonad uiM => MainStateT uiM (Maybe String)+getTitle :: UIMonad uiM => MainStateT uiM (Maybe (String, Int)) getTitle = get >>= title . ms2im     where     title IMEdit = do         mpath <- gets esPath         unsaved <- editStateUnsaved         isTested <- isJust <$> getCurTestSoln-        return $ Just $ "editing " ++ fromMaybe "[unnamed lock]" mpath ++-            (if isTested then " (Tested)" else "") ++-            (if unsaved then " [+]" else "    ")-    title IMPlay = gets psTitle-    title IMReplay = gets rsTitle+        height <- gets $ aboveFrame . esFrame+        return $ Just ("editing " ++ fromMaybe "[unnamed lock]" mpath +++                (if isTested then " (Tested)" else "") +++                (if unsaved then " [+]" else "    "),+            height+            )+    title IMPlay = do+        height <- gets $ aboveFrame . psFrame+        gets $ ((, height) <$>) . psTitle+    title IMReplay = gets $ ((, maxHeight) <$>) . rsTitle     title _ = return Nothing+    aboveFrame frame = min maxHeight $ 2 + frameSize frame+    maxHeight = maxlocksize + 1  editStateUnsaved :: UIMonad uiM => MainStateT uiM Bool editStateUnsaved = (isNothing <$>) $ runMaybeT $ do@@ -502,18 +510,17 @@     guard $ ourName /= name     uinfo <- mgetUInfo name     ourUInfo <- mgetUInfo ourName-    let (neg,pos) = (countPoints ourUInfo uinfo, countPoints uinfo ourUInfo)+    let (pos,neg) = (countUnaccessedBy ourUInfo name, countUnaccessedBy uinfo ourName)     return (pos-neg,(pos,neg))     where-        countPoints mugu masta = length $ filter (maybe False winsPoint) $ getAccessInfo mugu masta+        countUnaccessedBy ui name = length $ filter isNothing $ getAccessInfo ui name  accessedAL :: (UIMonad uiM) => ActiveLock -> MainStateT uiM Bool accessedAL (ActiveLock name idx) = (isJust <$>) $ runMaybeT $ do     ourName <- mgetOurName     guard $ ourName /= name     uinfo <- mgetUInfo name-    ourUInfo <- mgetUInfo ourName-    guard $ isJust $ getAccessInfo uinfo ourUInfo !! idx+    guard $ isJust $ getAccessInfo uinfo ourName !! idx  getNotesReadOn :: UIMonad uiM => LockInfo -> MainStateT uiM [NoteInfo] getNotesReadOn lockinfo = (fromMaybe [] <$>) $ runMaybeT $ do@@ -561,8 +568,7 @@     , ""     , "The game judges players relative to each of their peers. There are no absolute rankings."     , "You win a point of esteem against another player for one of their locks"-    , "if you have solved the lock and declared a note which remains unread by the lock's owner,"-    , "or if you have read three notes by other players on the lock."+    , "if you have declared a solution to it, or if you have read three notes on it."     , "You also win a point for each empty lock slot if you can unlock all full slots."     , "Relative esteem is the points you win minus the points they win; +3 is best, -3 is worst."     , ""
Metagame.hs view
@@ -39,43 +39,21 @@  initLockInfo ls = LockInfo ls False [] [] [] -data NoteInfo = NoteInfo {noteAuthor::Codename, noteBehind::Maybe ActiveLock, noteOn::ActiveLock}-    deriving (Eq, Ord, Show, Read)--data ActiveLock = ActiveLock {lockOwner::Codename, lockIndex :: LockIndex}+data AccessedReason = AccessedPrivy | AccessedEmpty | AccessedPub     deriving (Eq, Ord, Show, Read) -data AccessedReason = AccessedPrivyRead | AccessedPrivySolved {noteReadByOwner::Bool} | AccessedEmpty | AccessedPub-    deriving (Eq, Ord, Show, Read)-winsPoint :: AccessedReason -> Bool-winsPoint (AccessedPrivySolved True) = False-winsPoint _                          = True--getAccessInfo :: UserInfo -> UserInfo -> [Maybe AccessedReason]-getAccessInfo accessedUInfo accessorUInfo =-    let accessor = codename accessorUInfo-        mlinfos = elems $ userLocks accessedUInfo+getAccessInfo :: UserInfo -> Codename -> [Maybe AccessedReason]+getAccessInfo ui name =+    let mlinfos = elems $ userLocks ui         accessedSlot = maybe accessedAllExisting accessedLock         accessedAllExisting = all (maybe True accessedLock) mlinfos-        accessedLock linfo = public linfo || accessor `elem` accessedBy linfo+        accessedLock linfo = public linfo || name `elem` accessedBy linfo     in map (maybe             (if accessedAllExisting then Just AccessedEmpty else Nothing)-            (\linfo -> if public linfo then Just AccessedPub else-                if not $ accessedLock linfo then Nothing else Just $-                    if countRead accessorUInfo linfo >= notesNeeded then AccessedPrivyRead-                        else AccessedPrivySolved $ not.null $-                            [ n-                            | n <- lockSolutions linfo-                            , noteAuthor n == accessor-                            , isNothing (noteBehind n) || n `elem` notesRead accessedUInfo ]))+            (\linfo -> if public linfo then Just AccessedPub+                else if accessedLock linfo then Just AccessedPrivy else Nothing))         mlinfos -countRead :: UserInfo -> LockInfo -> Int-countRead reader tlock = fromIntegral $ length-        $ filter (\n -> (isNothing (noteBehind n) || n `elem` notesRead reader)-            && noteAuthor n /= codename reader)-        $ lockSolutions tlock- data UserInfoDelta     = AddRead NoteInfo     | DelRead NoteInfo@@ -89,6 +67,12 @@     | AddSolution NoteInfo     | AddAccessed Codename     | SetPublic+    deriving (Eq, Ord, Show, Read)++data NoteInfo = NoteInfo {noteAuthor::Codename, noteBehind::Maybe ActiveLock, noteOn::ActiveLock}+    deriving (Eq, Ord, Show, Read)++data ActiveLock = ActiveLock {lockOwner::Codename, lockIndex :: LockIndex}     deriving (Eq, Ord, Show, Read)  data Undeclared = Undeclared Solution LockSpec ActiveLock
Mundanities.hs view
@@ -13,7 +13,7 @@ import           Control.Arrow import           Control.Monad import           Control.Monad.Catch    (MonadMask, catch, handle)-import           Control.Monad.IO.Class (MonadIO)+import           Control.Monad.IO.Class (MonadIO, liftIO) import qualified Data.ByteString        as BS import qualified Data.ByteString.Char8  as BSC import           Data.List@@ -31,6 +31,9 @@  ignoreIOErrAlt :: (MonadIO m, MonadMask m, Alternative f) => m (f a) -> m (f a) ignoreIOErrAlt = handle ((\_ -> return empty) :: (Monad m, Alternative f) => IOError -> m (f a))++warnIOErrAlt :: (MonadIO m, MonadMask m, Alternative f) => m (f a) -> m (f a)+warnIOErrAlt = handle ((\e -> liftIO (print e) >> return empty) :: (MonadIO m, Alternative f) => IOError -> m (f a))  unlessIOErr :: (MonadIO m, MonadMask m) => m Bool -> m Bool unlessIOErr = (fromMaybe False <$>) . ignoreIOErrAlt . (Just <$>)
NEWS view
@@ -1,5 +1,15 @@ This is an abbreviated summary; see the git log for gory details. +0.8.1:+    Revise tutorial and initiation levels REP and GAP+    Disallow overextended/compressed springs+    Allow editing spring tension by turning+    Add compact solution format+    Revert scoring change introduced in 0.6+    Add --datadir option+    Fix loading partial initiation solutions+    Fix server locking+ 0.8:     Add single-player introductory subgame ("initiation").     Improve UI variously.
Physics.hs view
@@ -135,10 +135,15 @@                             then (f /= f',[])                             else let cols = collisions st' (forceIdx f) (forceIdx f')                                 in (not $ null cols, cols) ]]+                badConns = [ c | c <- connections st'+                    , not $ springExtensionValid st' c ]             in do                 tell $ map AlertBlockingForce $ concat inconsistencies                 tell $ map AlertCollision cols-                return $ if null inconsistencies then []+                tell $ map AlertCollision $ concat+                    [ locusPos st' <$> [r, e]+                    | Connection r e _ <- badConns]+                return $ if null inconsistencies && null badConns then []                     else if i==j then [i]                     else if dominates i j then [j]                     else if dominates j i then [i]@@ -240,8 +245,8 @@     bumps ++ springTransmissions     where         idx = forceIdx force-        bumps = [ ForceChoice $ map (transmittedForce st idx' cpos) dirs |-            idx' <- ppidxs st+        bumps = [ ForceChoice $ map (transmittedForce st idx' cpos) dirs+            | idx' <- ppidxs st             , idx' /= idx             , cpos <- collisionsWithForce st force idx'             , let dirs = case force of@@ -252,8 +257,8 @@                               arm = cpos -^ placedPos (getpp st idx) ]         springTransmissions =             case force of-                 Push _ dir -> [ ForceChoice [Push idx' dir] |-                        c@(Connection (ridx,_) (eidx,_) (Spring sdir _)) <- conns+                 Push _ dir -> [ ForceChoice [Push idx' dir]+                        | c@(Connection (ridx,_) (eidx,_) (Spring sdir _)) <- conns                         , let root = idx == ridx                         , let end = idx == eidx                         , root || end
README view
@@ -42,18 +42,17 @@ the bit with the 3-letter codenames and the three lock slots and notes and so on. If you haven't seen anything of the kind... keep solving those locks!) -A player accesses a lock slot when one of the following holds-    (i) the player has read three notes on the lock in the slot;-    (ii) the player has solved the lock in the slot, and declared the solution;-    (iii) there's no lock in the slot, and the player has accessed all the slots-	which do have locks in them.- Scoring is always relative - each player has a score relative to each other player. That score is the number of the second player's lock slots to which the first player has access, minus the number of the first player's lock slots-to which the second player has access; but a point is not awarded for-case (ii) if the owner of the lock has read the note.+to which the second player has access. +A player accesses a lock slot when one of the following holds+    * the player has solved the lock in the slot, and declared the solution;+    * the player has read three notes on the lock in the slot;+    * there's no lock in the slot, and the player has accessed all the slots+	which do have locks in them.+ A player reads every note in every lock the player accesses. When a lock is replaced ('retired'), each note secured by the lock becomes 'public', and is read by every player.@@ -123,8 +122,9 @@ 	ball southeast will be tried instead.     * A spring connecting two blocks transmits a push on one block to the 	other unless the direction is such that the push is tending to-	compress/extend the spring and the spring isn't already fully-	compressed/extended.+        compress resp. extend the spring and the spring is currently extended +        resp. compressed, or the force is a player force and the spring isn't +        already fully compressed resp. extended.  A force is _resisted_ if it is a push on a pivot or on a block which isn't the     end of a spring, or it's a torque on a block, or it's a force on a hook@@ -142,7 +142,8 @@  Two forces are _inconsistent_ when     * they act on the same piece in different directions, or-    * applying both at once results in overlapping pieces.+    * applying both at once results in overlapping pieces, or+    * applying both at once results in an overextended/overcompressed spring.  Two force groups are _inconsistent_ iff there is some force from the first     which is inconsistent with some force from the second.
SDLUI.hs view
@@ -211,7 +211,8 @@  data AccessedInfo = AccessedSolved | AccessedPublic | AccessedReadNotes | AccessedUndeclared     deriving (Eq, Ord, Show)-data Selectable = SelOurLock+data Selectable+    = SelOurLock     | SelTut Bool     | SelInitLock HexVec Bool     | SelLock ActiveLock@@ -234,7 +235,7 @@     | SelPrivyHeader     | SelNotesHeader     | SelToolHook-    | SelToolWrench+    | SelToolWrench Bool     deriving (Eq, Ord, Show)  registerSelectable :: HexVec -> Int -> Selectable -> UIM ()@@ -249,9 +250,9 @@ clearSelectables = modify $ \ds -> ds {registeredSelectables = Map.empty} clearButtons = modify $ \ds -> ds {contextButtons = []} -registerUndoButtons :: Bool -> Bool -> UIM ()-registerUndoButtons noUndo noRedo = do-    unless noUndo $ registerButton (periphery 2+^hu) CmdUndo 0 [("undo",hu+^neg hw)]+registerUndoButtons :: Bool -> UIM ()+registerUndoButtons noRedo = do+    registerButton (periphery 2+^hu) CmdUndo 0 [("undo",hu+^neg hw)]     unless noRedo $ registerButton (periphery 2+^hu+^neg hv) CmdRedo 2 [("redo",hu+^neg hw)]  commandOfSelectable IMInit (SelTut _) _ = Just . CmdSolveInit $ Just zero@@ -310,24 +311,16 @@     "Random set of players. Colours show relative esteem, bright red (-3) to bright green (+3)." helpOfSelectable (SelScoreLock (Just name) Nothing _) = Just $     "Your lock. "++name++" can not not unlock it."-helpOfSelectable (SelScoreLock (Just name) (Just AccessedPrivyRead) _) = Just $-    "Your lock. "++name++" has read three notes on it: -1 to relative esteem."-helpOfSelectable (SelScoreLock (Just name) (Just (AccessedPrivySolved False)) _) = Just $-    "Your lock. "++name++" has declared a note on it which you have not read: -1 to relative esteem."-helpOfSelectable (SelScoreLock (Just name) (Just (AccessedPrivySolved True)) _) = Just $-    "Your lock. "++name++" has declared a note on it, but you have read that note."+helpOfSelectable (SelScoreLock (Just name) (Just AccessedPrivy) _) = Just $+    "Your lock. "++name++" can unlock it: -1 to relative esteem." helpOfSelectable (SelScoreLock (Just name) (Just AccessedPub) _) = Just     "Your lock. Its secrets have been publically revealed: -1 to relative esteem." helpOfSelectable (SelScoreLock (Just name) (Just AccessedEmpty) _) = Just $     "Your empty lock slot. "++name++" can unlock all your locks: -1 to relative esteem." helpOfSelectable (SelScoreLock Nothing Nothing (ActiveLock name _)) = Just $     name++"'s lock. You can not unlock it."-helpOfSelectable (SelScoreLock Nothing (Just AccessedPrivyRead) (ActiveLock name _)) = Just $-    name++"'s lock. You have read three notes on it: +1 to relative esteem."-helpOfSelectable (SelScoreLock Nothing (Just (AccessedPrivySolved False)) (ActiveLock name _)) = Just $-    name++"'s lock. You have declared a note on it which "++name++" has not read: +1 to relative esteem."-helpOfSelectable (SelScoreLock Nothing (Just (AccessedPrivySolved True)) (ActiveLock name _)) = Just $-    name++"'s lock. You have declared a note on it, but "++name++" has read your note."+helpOfSelectable (SelScoreLock Nothing (Just AccessedPrivy) (ActiveLock name _)) = Just $+    name++"'s lock. You can unlock it: +1 to relative esteem." helpOfSelectable (SelScoreLock Nothing (Just AccessedPub) (ActiveLock name _)) = Just $     name++"'s lock. Its secrets have been publically revealed: +1 to relative esteem." helpOfSelectable (SelScoreLock Nothing (Just AccessedEmpty) (ActiveLock name _)) = Just $@@ -360,7 +353,8 @@     "Notes on this lock declared by players who picked the lock." helpOfSelectable SelNotesHeader = Just     "Notes secured by this lock. These notes are read by everyone who can unlock the lock."-helpOfSelectable SelToolWrench = Just "The wrench, one of your lockpicking tools. Click and drag to move."+helpOfSelectable (SelToolWrench False) = Just "The wrench, one of your lockpicking tools. Click and drag to move."+helpOfSelectable (SelToolWrench True) = Just "The wrench, currently in motion. It will keep moving until it is blocked." helpOfSelectable SelToolHook = Just "The hook, one of your lockpicking tools. Click and drag to move, use mousewheel to turn."  cmdAtMousePos pos@(mPos,central) im selMode = do@@ -523,8 +517,11 @@ -- ^ XXX only peripheries 0,2,3,5 are guaranteed to be on-screen! --messageLineStart = (maxlocksize+1)*^hw messageLineCentre = ((maxlocksize+1)`div`2)*^hw +^ ((maxlocksize+1+1)`div`2)*^neg hv-titlePos = (maxlocksize+1)*^hv +^ ((maxlocksize+1)`div`2)*^hu +-- XXX HACK: the n=8 line is crowded+titlePos n = titlePos' $ if n == 8 then 9 else n+    where titlePos' n = n*^hv +^ (n`div`2)*^hu+ screenWidthHexes,screenHeightHexes :: Int screenWidthHexes = 32 screenHeightHexes = 26@@ -904,7 +901,7 @@ clearMsg :: UIM () clearMsg = modify $ \s -> s { message = Nothing } -drawTitle (Just title) = renderToMain $ renderStrColAtCentre messageCol title titlePos+drawTitle (Just (title,n)) = renderToMain $ renderStrColAtCentre messageCol title (titlePos n) drawTitle Nothing      = return ()  say = setMsgLine messageCol
SDLUIMInstance.hs view
@@ -100,12 +100,12 @@     getUIBinding mode cmd = ($cmd) <$> getBindingStr mode      initUI = (isJust <$>) . runMaybeT $ do-        catchIOErrorMT $ SDL.init+        let toInit = [InitVideo] #ifdef SOUND-            [InitVideo,InitAudio]-#else-            [InitVideo]+                ++ [InitAudio] #endif+        catchIOErrorMT $ SDL.init toInit+        liftIO (SDL.wasInit [InitVideo]) >>= guard . (InitVideo `elem`)         catchIOErrorMT TTF.init         lift $ do             readUIConfigFile@@ -118,7 +118,7 @@             initAudio             readBindings         where-            catchIOErrorMT m = MaybeT . liftIO . ignoreIOErrAlt $ m >> return (Just ())+            catchIOErrorMT m = MaybeT . liftIO . warnIOErrAlt $ m >> return (Just ())      endUI = do         writeUIConfigFile@@ -444,7 +444,6 @@ drawMainState' :: MainState -> MainStateT UIM () drawMainState' PlayState { psCurrentState=st, psLastAlerts=alerts,         wrenchSelected=wsel, psTutLevel=tutLevel, psSolved=solved } = do-    canUndo <- gets (null . psGameStateMoveStack)     canRedo <- gets (null . psUndoneStack)     let isTut = isJust tutLevel     lift $ do@@ -458,27 +457,28 @@             centre <- gets dispCentre             sequence_                 [ registerSelectable (pos -^ centre) 0 $-                   if isWrench p then SelToolWrench else SelToolHook+                    case p of+                        Wrench v -> SelToolWrench $ v /= zero+                        _        -> SelToolHook                 | not $ lb || rb                 , PlacedPiece pos p <- Vector.toList $ placedPieces st                 , isTool p]         unless (noUndoTutLevel tutLevel) $ do-            registerUndoButtons canUndo canRedo+            registerUndoButtons canRedo             registerButtonGroup markButtonGroup         registerButton (periphery 0) CmdOpen (if solved then 2 else 0) $             ("open", hu+^neg hw) : [("Press-->",9*^neg hu) | solved && isTut] drawMainState' ReplayState { rsCurrentState=st, rsLastAlerts=alerts } = do-    canUndo <- gets (null . rsGameStateMoveStack)     canRedo <- gets (null . rsMoveStack)     lift $ do         drawMainGameState [] False alerts st-        registerUndoButtons canUndo canRedo+        registerUndoButtons canRedo         renderToMain $ drawCursorAt Nothing drawMainState' EditState { esGameState=st, esGameStateStack=sts, esUndoneStack=undostack,         selectedPiece=selPiece, selectedPos=selPos } = lift $ do     drawMainGameState (maybeToList selPiece) True [] st     renderToMain $ drawCursorAt $ if isNothing selPiece then Just selPos else Nothing-    registerUndoButtons (null sts) (null undostack)+    registerUndoButtons (null undostack)     when (isJust selPiece) $ mapM_ registerButtonGroup         [ singleButton (periphery 2 +^ 3*^hw+^hv) CmdDelete 0 [("delete",hu+^neg hw)]         , singleButton (periphery 2 +^ 3*^hw) CmdMerge 1 [("merge",hu+^neg hw)]@@ -620,7 +620,7 @@         us <- liftMaybe ourName         ourUInfo <- mgetUInfo us         selUInfo <- mgetUInfo sel-        let accesses = map (uncurry getAccessInfo) [(ourUInfo,selUInfo),(selUInfo,ourUInfo)]+        let accesses = map (uncurry getAccessInfo) [(ourUInfo,sel),(selUInfo,us)]         let posLeft = scoresPos +^ hw +^ neg hu         let posRight = posLeft +^ 3*^hu         size <- snd <$> (lift.lift) getGeom@@ -635,7 +635,7 @@                 , let accessed = head accesses !! i                 , let col                         | accessed == Just AccessedPub = dim pubColour-                        | maybe False winsPoint accessed = dim $ scoreColour $ -3+                        | isJust accessed = dim $ scoreColour $ -3                         | otherwise = obscure $ scoreColour 3 ]             fillArea (posRight+^hw) (map (posRight+^) [zero,hw,neg hv])                 [ \pos -> lift (registerSelectable pos 0 (SelScoreLock Nothing accessed $ ActiveLock sel i)) >>@@ -644,7 +644,7 @@                 , let accessed = accesses !! 1 !! i                 , let col                         | accessed == Just AccessedPub = obscure pubColour-                        | maybe False winsPoint accessed = dim $ scoreColour 3+                        | isJust accessed = dim $ scoreColour 3                         | otherwise = obscure $ scoreColour $ -3 ]         (posScore,negScore) <- MaybeT $ (snd<$>) <$> getRelScoreDetails sel         lift.lift $ sequence_
Server.hs view
@@ -56,8 +56,9 @@  import qualified Crypto.Argon2              as A2 import           Crypto.Hash.Algorithms     (SHA256 (..))-import           Crypto.PubKey.RSA          (generate)+import           Crypto.PubKey.RSA          (generate, generateBlinder) import           Crypto.PubKey.RSA.OAEP     (decrypt, defaultOAEPParams)+import           Crypto.PubKey.RSA.Types    (private_n)  import           Network.Mail.Mime          (plainPart) import qualified Network.Mail.SMTP          as SMTP@@ -224,13 +225,17 @@             GetRandomNames _ -> ReadMode             _                -> ReadWriteMode -    -- check solutions prior to write-locking database:-    withDBLock dbpath ReadMode (runExceptT checkRequest) >>=-        either (return . ServerError) (const $-        withDBLock dbpath lockMode $ runExceptT handleRequest' >>=-            either (return . ServerError) return)+    -- Check solutions prior to write-locking database.+    -- Slightly awkward, because we have to drop the read lock before+    -- acquiring the write lock, so need to check preconditions again once we+    -- have the write lock.+    withDBLock dbpath ReadMode (runExceptT $ checkRequest Nothing) >>=+        either (return . ServerError) (\mCheckedLock ->+            withDBLock dbpath lockMode $+                runExceptT (checkRequest mCheckedLock >> handleRequest') >>=+                    either (return . ServerError) return)     where-        checkRequest = do+        checkRequest mCheckedLock = do             when (pv /= protocolVersion) $ throwE "Bad protocol version"             case action of                 DeclareSolution soln ls target idx -> do@@ -247,7 +252,10 @@                         throwE "That's your lock!"                     behindLock <- getALock behind                     when (public behindLock) $ throwE "Your lock is cracked!"-                    unless (checkSolution lock soln) $ throwE "Bad solution"+                    case mCheckedLock of+                        Nothing -> unless (checkSolution lock soln) $ throwE "Bad solution"+                        Just lock' -> unless (lock == lock') $ throwE "Lock changed!"+                    return $ Just lock                 SetLock lock@(frame,_) idx soln -> do                     ServerInfo serverSize _ <- getServerInfo                     when (frame /= BasicFrame serverSize) $ throwE $@@ -258,8 +266,11 @@                             `catchE` const (return (RCLockHashes []))                     let hashed = hash $ show lock                     when (hashed `elem` hashes) $ throwE "Lock has already been used"-                    unless (checkSolution lock soln) $ throwE "Bad solution"-                _ -> return ()+                    case mCheckedLock of+                        Nothing -> unless (checkSolution lock soln) $ throwE "Bad solution"+                        Just lock' -> unless (lock == lock') $ throwE "Lock changed!"+                    return $ Just lock+                _ -> return Nothing         handleRequest' =             case action of                 UndefinedAction -> throwE "Request not recognised by this server"@@ -406,9 +417,13 @@         decryptPassword :: String -> ExceptT String IO String         decryptPassword pw = do             RCSecretKey secretKey <- getRecordErrored RecSecretKey-            ExceptT . return . bimap show CS.unpack .-                decrypt Nothing (defaultOAEPParams SHA256) secretKey . CS.pack $ pw-                -- <=intricacy-0.6.2 sends the hashed password unencrypted+            blinder <- liftIO . generateBlinder $ private_n secretKey+            ExceptT . return . bimap+                    (\err -> show err ++ "; try deleting ~/.intricacy/cache ?")+                    CS.unpack .+                decrypt (Just blinder) (defaultOAEPParams SHA256) secretKey . CS.pack $ pw+            -- XXX: <=intricacy-0.6.2 sends the hashed password unencrypted,+            -- but we don't support that anymore         convertLegacyPW :: Codename -> IO ()         convertLegacyPW name = void . runExceptT $ do             RCPasswordLegacy legacyPw <- getRecordErrored (RecPasswordLegacy name)@@ -508,7 +523,9 @@             info <- getCurrUserInfo name             tlock <- getCurrALock target             unless (name `elem` accessedBy tlock || public tlock || name == lockOwner target) $ do-                when (countRead info tlock == notesNeeded) $+                let countRead = fromIntegral $ length $+                        filter (\n -> isNothing (noteBehind n) || n `elem` notesRead info) $ lockSolutions tlock+                when (countRead == notesNeeded) $                     accessLock name target tlock         checkSuffPubNotes al@(ActiveLock name idx) = do             lock <- getCurrALock al
Version.hs view
@@ -11,4 +11,4 @@ module Version where  version :: String-version = "0.8.0.1"+version = "0.8.1"
initiation/GAP.lock view
@@ -1,17 +1,17 @@-        & & & & & & & & &          -       &                 & & & &   -      &             % %   &     &  -     & # C C C C % %   %   & #   & -    &                 % # # #     &-   &                 %   7   &   & -  & $ $ S S S " O O %   7     & &  - &             #   " % %       &   -& $ S S S S S %   " C C C C C D &  - &         # %   % O           &   -  &             % Z           &    - & &           %   Z         &     -& * '             # #       &      - & @ &         # #         &       -  & & &                   &        -       &                 &         -        & & & & & & & & &          +        " " " " " " " " "          +       "                 " " " "   +      " & C C C C % % %   "     "  +     " & C C C C % % % %   " #   " +    "       #   % % % % # # #     "+   "           o -   %   7   "   " +  "             `   %   7     " "  + " % % % % % % %   # % %       "   +" $ S S S S S #   # C C C C C D "  + " % % % % % #   % O           "   +  "             % Z           "    + " "           %   Z         "     +" * '             # #       "      + " @ "         # #         "       +  " " "                   "        +       "                 "         +        " " " " " " " " "          
initiation/REP.lock view
@@ -1,17 +1,17 @@         " " " " " " " " "          -       " } #             " " " "   -      " & }   # # S S %   "     "  -     "   & }       & & % % "     " -    "     & }     &       % % %   "-   " %     & }   &           "   " -  " [ % $ # & } &             " "  - " [         & &           o   "   -" # # # # # o - O         / ` % "  - "   % #   /     O       %   % "   -  " % # %         O     %     "    - " "   % %         O   %     "     -" * '     %         O %     "      - " @ "     % # # # # O   # "       -  " " "     #             "        +       " }               " " " "   +      " & }     # S S %   "     "  +     " # & }       & & % % "     " +    " 9 " & }     &       % % %   "+   " % 9 # & }   &           "   " +  "   % 9 " & } &             " "  + "     % 9 # & &           o   "   +"       % 9 \ ' O         / ` # "  + "       %   o   O       #   # "   +  " % % S # /     O     #     "    + " "   %   #       O   #     "     +" * '     %         O #     "      + " @ "     % % % % % O   # "       +  " " "       %           "                "           #     "                  " " " " " " " " "          
initiation/REP.text view
@@ -1,1 +1,1 @@-Do the shuffle.+Do the shuffle
intricacy.cabal view
@@ -1,6 +1,6 @@ cabal-version:      >=1.10 name:               intricacy-version:            0.8.0.1+version:            0.8.1 license:            GPL-3 license-file:       COPYING maintainer:         mbays@sdf.org@@ -103,22 +103,22 @@         default-extensions: DoAndIfThenElse         build-depends:             base >=4.3 && <5,-            mtl >=2.1.3.1 && <2.3,-            transformers >=0.3.0.0 && <0.6,+            mtl >=2.1.3.1 && <2.4,+            transformers >=0.3.0.0 && <0.8,             stm >=2.1 && <2.6,             directory >=1.0 && <1.4,             exceptions >=0.8.3 && <0.11,             filepath >=1.0 && <1.5,-            time >=1.2 && <1.10,-            bytestring ==0.10.*,+            time >=1.2 && <1.14,+            bytestring >=0.10 && <0.12,             array >=0.3 && <0.6,             containers >=0.4 && <0.7,             vector >=0.9 && <0.13,-            binary >=0.5 && <0.9,-            network-fancy >=0.1.5 && <0.3,+            binary >=0.5 && <0.11,+            network-simple >=0.3 && <0.5,             safe >=0.3.18 && <0.4,-            memory >= 0.11 && <0.16,-            cryptonite >= 0.16 && <0.28+            memory >=0.11 && <0.18,+            cryptonite >=0.16 && <0.31          if !impl(ghc >=8.0)             build-depends: semigroups ==0.18.*@@ -133,7 +133,7 @@                 cpp-options:   -DSOUND                 build-depends:                     SDL-mixer ==0.6.*,-                    random >=1.0 && <1.2+                    random >=1.0 && <1.4              if os(windows)                 extra-libraries:@@ -148,21 +148,20 @@                     extra-libraries: SDL_mixer              if os(osx)-                cpp-options:   -DAPPLE-                c-sources:     c_main.c-                include-dirs:  /usr/include/SDL /usr/local/include/SDL-                ghc-options:   -no-hs-main-                build-depends: network-fancy <0.2.1+                cpp-options:  -DAPPLE+                c-sources:    c_main.c+                include-dirs: /usr/include/SDL /usr/local/include/SDL+                ghc-options:  -no-hs-main          if flag(curses)-            cpp-options:   -DMAIN_CURSES+            cpp-options:       -DMAIN_CURSES+            pkgconfig-depends: ncursesw -any             other-modules:                 CursesRender                 CursesUI                 CursesUIMInstance -            build-depends: hscurses ==1.4.*-            pkgconfig-depends: ncursesw+            build-depends:     hscurses ==1.4.*          if flag(sdl)             cpp-options:   -DMAIN_SDL@@ -205,31 +204,31 @@         default-extensions: DoAndIfThenElse         build-depends:             base >=4.3 && <5,-            mtl >=2.2 && <2.3,-            transformers >=0.4 && <0.6,+            mtl >=2.2 && <2.4,+            transformers >=0.4 && <0.8,             stm >=2.1 && <2.6,             directory >=1.0 && <1.4,             exceptions >=0.8.3 && <0.11,             filepath >=1.0 && <1.5,-            time >=1.5 && <1.10,-            bytestring ==0.10.*,+            time >=1.5 && <1.14,+            bytestring >=0.10 && <0.12,             array >=0.3 && <0.6,             containers >=0.4 && <0.7,             vector >=0.9 && <0.13,-            binary >=0.5 && <0.9,+            binary >=0.5 && <0.11,             network-fancy >=0.1.5 && <0.3,             safe >=0.3.18 && <0.4,-            random >=1.0 && <1.3,+            random >=1.0 && <1.4,             pipes >=4 && <4.4,             feed >=1.1 && <1.4,-            xml-conduit >=1.0 && < 1.10,+            xml-conduit >=1.0 && <1.10,             email-validate >=1.0 && <2.4,-            text >=0.1 && < 1.3,+            text >=0.1 && <1.3,             text-short ==0.1.*,-            mime-mail >=0.4.4 && < 0.6,-            smtp-mail >=0.1.4.1 && < 0.4,-            memory >= 0.11 && <0.16,-            cryptonite >= 0.16 && <0.28,+            mime-mail >=0.4.4 && <0.6,+            smtp-mail >=0.1.4.1 && <0.4,+            memory >=0.11 && <0.18,+            cryptonite >=0.16 && <0.31,             argon2 ==1.3.*          if !impl(ghc >=8.0)
tutorial/01-winning.lock view
@@ -1,11 +1,11 @@        " " " " " "              "           " " "        "             "   " -    " S S S S S S # # # "-   "             #   " " -  "                   "  - " "                 "   -" * '               "    - " @ "             "     -  " " "           "      +    " S S S S # # # # # "+   " %       #       " " +  " % % % %         % "  + " "       % % %   % "   +" * '             % "    + " @ " %         % "     +  " " " % % % % % "              " " " " " "       
tutorial/01-winning.text view
@@ -1,1 +1,1 @@-drag your tools to pull the bolt aside, then press 'O'+drag your tools to push the bolt aside, then press 'O'
+ tutorial/03-time.lock view
@@ -0,0 +1,15 @@+        & & & & & & & &        +       &   O           &       +      &   % % % % % % % & & &  +     &   %               & ~ & +    & " c c c c c c c c # ~   &+   &   ]               ~ ~ & & +  & %   ]             ~ 1   &  + &   % % # %     '   ~ 1     & +  &     %   %   o   ~ #     &  + & &   %   %     ` ~   #   &   +& * '     %       %   #   &    + & @ & % %       %   #   &     +  & & &           5 #   &      +       &           #   &       +        & & & & & & & &        
+ tutorial/03-time.text view
@@ -0,0 +1,1 @@+time passes when you move
− tutorial/03-wrench.lock
@@ -1,13 +0,0 @@-       " " " " " " "       -      "     #       " " "  -     " S S # #   %   "   " -    "             % % % % "-   "   o   # S S S S % " " -  "     `             # "  - "   % % % % % %   # #   " -  " %   %               "  - " "   % % %     #     "   -" * '     % %         "    - " @ " %   % o       "     -  " " " %   /     # "      -       " " " " " " "       
− tutorial/03-wrench.text
@@ -1,1 +0,0 @@-the wrench moves in a straight line until blocked
− tutorial/04-hook.lock
@@ -1,13 +0,0 @@-       " " " " " " "       -      " %   # #   % " " "  -     " %     Z #   % " # " -    " % %   % Z     % #   "-   " %   # % % Z     # " " -  " % %   Z % % Z     # "  - " % % %   Z % % %     # " -  " % % %   % % %   % # "  - " "   % ]     %   # # "   -" * '     ]   % %   # "    - " @ "   # #   % % 7 "     -  " " "   % % % % 7 "      -       " " " " " " "       
− tutorial/04-hook.text
@@ -1,1 +0,0 @@-turn hook with mouse wheel to pull springs aside; numpad and vi keys also supported
+ tutorial/04-wrench.lock view
@@ -0,0 +1,13 @@+       " " " " " " "       +      "     #       " " "  +     " S S # #   %   "   " +    "             % % % % "+   "   o   # S S S S % " " +  "     `             # "  + "   % % % % % %   # #   " +  " %   %               "  + " "   % % %     #     "   +" * '     % %         "    + " @ " %   % o       "     +  " " " %   /     # "      +       " " " " " " "       
+ tutorial/04-wrench.text view
@@ -0,0 +1,1 @@+the wrench moves in a straight line until blocked
− tutorial/05-blockWrench.lock
@@ -1,11 +0,0 @@-       " " " " " "       -      " #         " " "  -     " #   #   %   "   " -    " #   # S S % % % % "-   "                 " " -  "   # #   #   # #   "  - " "   #   # #   #   "   -" * '               "    - " @ "   # # # #   "     -  " " "           "      -       " " " " " "       
− tutorial/05-blockWrench.text
@@ -1,1 +0,0 @@-the hook can be used to guide the wrench
+ tutorial/05-hook.lock view
@@ -0,0 +1,13 @@+       " " " " " " "       +      " %   # #   % " " "  +     " %     Z #   % " # " +    " % %   % Z     % #   "+   " %   # % % Z     # " " +  " % %   Z % % Z     # "  + " % % %   Z % % %     # " +  " % % %   % % %   % # "  + " "   % ]     %   # # "   +" * '     ]   % %   # "    + " @ "   # #   % % 7 "     +  " " "   % % % % 7 "      +       " " " " " " "       
+ tutorial/05-hook.text view
@@ -0,0 +1,1 @@+turn hook with mouse wheel (or keys) to pull springs aside
− tutorial/06-balls.lock
@@ -1,11 +0,0 @@-       " " " " " "       -      " & & S S # " " "  -     " & & &     # "   " -    " & & & &   # # # # "-   "         &   #   " " -  "     #   & &   #   "  - " " O #     &       "   -" * ' # #     O #   "    - " @ " #   O     # "     -  " " " #       # "      -       " " " " " "       
− tutorial/06-balls.text
@@ -1,1 +0,0 @@-balls can be pushed around to allow or block movement
+ tutorial/06-blockWrench.lock view
@@ -0,0 +1,11 @@+       " " " " " "       +      " #         " " "  +     " #   #   %   "   " +    " #   # S S % % % % "+   "                 " " +  "   # #   #   # #   "  + " "   #   # #   #   "   +" * '               "    + " @ "   # # # #   "     +  " " "           "      +       " " " " " "       
+ tutorial/06-blockWrench.text view
@@ -0,0 +1,1 @@+the hook can be used to guide the wrench
+ tutorial/07-balls.lock view
@@ -0,0 +1,11 @@+       " " " " " "       +      " & & S S # " " "  +     " & & &     # "   " +    " & & & &   # # # # "+   "         &   #   " " +  "     #   & &   #   "  + " " O #     &       "   +" * ' # #     O #   "    + " @ " #   O     # "     +  " " " #       # "      +       " " " " " "       
+ tutorial/07-balls.text view
@@ -0,0 +1,1 @@+balls can be pushed around to allow or block movement
− tutorial/07-traps.lock
@@ -1,13 +0,0 @@-       & & & & & & &       -      &             & & &  -     & % O % S S "   &   & -    &         #   " " " " &-   & "         O   " " & & -  & " "   # #     # # # &  - & "                   # & -  & " " "         # # # &  - & &   " } # #   #     &   -& * '     % # #   #   &    - & @ &     O       # &     -  & & &     # # # # &      -       & & & & & & &       
− tutorial/07-traps.text
@@ -1,1 +0,0 @@-if you make a mistake, you can undo moves ('X') or reset to the start ('^')
− tutorial/08-springs.lock
@@ -1,17 +0,0 @@-        " " " " " " " " "          -       " # # # # # # # # " " " "   -      " # #               "     "  -     " # #   % % % %   #   " # # " -    " # # #   % % %     # # #     "-   "               %     # # "   " -  "   # #   # # #   %     # # " "  - "     ]   " S S #   %       # "   -"       ]   " % % % % % %     # "  - "       ]   " ] % % % % %   7 "   -  "       ] % " ] % % % %   7 "    - " "     % % % " " % % S S & "     -" * '     % Z   o - % % % % "      - " @ "       Z / & C C C C "       -  " " "       Z & & & &   "        -       "       Z & & &   "         -        " " " " " " " " "          
− tutorial/08-springs.text
@@ -1,1 +0,0 @@-A spring's length can double when stretched, and halve when compressed
+ tutorial/08-traps.lock view
@@ -0,0 +1,13 @@+       & & & & & & &       +      &             & & &  +     & % O % S S "   &   & +    &         #   " " " " &+   & "         O   " " & & +  & " "   # #     # # # &  + & "                   # & +  & " " "         # # # &  + & &   " } # #   #     &   +& * '     % # #   #   &    + & @ &     O       # &     +  & & &     # # # # &      +       & & & & & & &       
+ tutorial/08-traps.text view
@@ -0,0 +1,1 @@+if you make a mistake, you can undo ('X') or reset ('^')
− tutorial/09-pivots.lock
@@ -1,11 +0,0 @@-       " " " " " "       -      " \     # # " " "  -     "   o -   \ ' " # " -    " % /   "   o - #   "-   " % %   " "   # # " " -  " % % %   " O   # # "  - " "         . o - 7 "   -" * ' . o - &   ` 7 "    - " @ " O     & & & "     -  " " " # #   & & "      -       " " " " " "       
− tutorial/09-pivots.text
@@ -1,1 +0,0 @@-push arms to rotate them around their pivots
+ tutorial/09-springs.lock view
@@ -0,0 +1,17 @@+        " " " " " " " " "          +       " # # # # # # # # " " " "   +      " # #               "     "  +     " # #   % % % %   #   " # # " +    " # # #   % % %     # # #     "+   "               %     # # "   " +  "   # #   # # #   %     # # " "  + "     ]   " S S #   %       # "   +"       ]   " % % % % % %     # "  + "       ]   " ] % % % % %   7 "   +  "       ] % " ] % % % %   7 "    + " "     % % % " " % % S S & "     +" * '     % Z   o - % % % % "      + " @ "       Z / & C C C C "       +  " " "       Z & & & &   "        +       "       Z & & &   "         +        " " " " " " " " "          
+ tutorial/09-springs.text view
@@ -0,0 +1,1 @@+A spring's length can double when stretched, and halve when compressed
+ tutorial/10-pivots.lock view
@@ -0,0 +1,11 @@+       " " " " " "       +      " \     # # " " "  +     "   o -   \ ' " # " +    " % /   "   o - #   "+   " % %   " "   # # " " +  " % % %   " O   # # "  + " "         . o - 7 "   +" * ' . o - &   ` 7 "    + " @ " O     & & & "     +  " " " # #   & & "      +       " " " " " "       
+ tutorial/10-pivots.text view
@@ -0,0 +1,1 @@+push arms to rotate them around their pivots