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 +45/−2
- Cache.hs +13/−7
- CursesUI.hs +1/−1
- Init.hs +18/−9
- Interact.hs +29/−20
- InteractUtil.hs +12/−0
- Intricacy.hs +0/−1
- MainState.hs +26/−20
- Metagame.hs +13/−29
- Mundanities.hs +4/−1
- NEWS +10/−0
- Physics.hs +10/−5
- README +12/−11
- SDLUI.hs +17/−20
- SDLUIMInstance.hs +14/−14
- Server.hs +31/−14
- Version.hs +1/−1
- initiation/GAP.lock +17/−17
- initiation/REP.lock +14/−14
- initiation/REP.text +1/−1
- intricacy.cabal +29/−30
- tutorial/01-winning.lock +7/−7
- tutorial/01-winning.text +1/−1
- tutorial/03-time.lock +15/−0
- tutorial/03-time.text +1/−0
- tutorial/03-wrench.lock +0/−13
- tutorial/03-wrench.text +0/−1
- tutorial/04-hook.lock +0/−13
- tutorial/04-hook.text +0/−1
- tutorial/04-wrench.lock +13/−0
- tutorial/04-wrench.text +1/−0
- tutorial/05-blockWrench.lock +0/−11
- tutorial/05-blockWrench.text +0/−1
- tutorial/05-hook.lock +13/−0
- tutorial/05-hook.text +1/−0
- tutorial/06-balls.lock +0/−11
- tutorial/06-balls.text +0/−1
- tutorial/06-blockWrench.lock +11/−0
- tutorial/06-blockWrench.text +1/−0
- tutorial/07-balls.lock +11/−0
- tutorial/07-balls.text +1/−0
- tutorial/07-traps.lock +0/−13
- tutorial/07-traps.text +0/−1
- tutorial/08-springs.lock +0/−17
- tutorial/08-springs.text +0/−1
- tutorial/08-traps.lock +13/−0
- tutorial/08-traps.text +1/−0
- tutorial/09-pivots.lock +0/−11
- tutorial/09-pivots.text +0/−1
- tutorial/09-springs.lock +17/−0
- tutorial/09-springs.text +1/−0
- tutorial/10-pivots.lock +11/−0
- tutorial/10-pivots.text +1/−0
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