packages feed

intricacy 0.7.0.1 → 0.7.1

raw patch · 55 files changed

+532/−289 lines, 55 files

Files

CursesUI.hs view
@@ -162,8 +162,19 @@             ]         bindingsTable IMPlay GravRight =             [ (-7, BindingsEntry "help" [CmdHelp])+            , (-6, BindingsEntry "bind" [CmdBind Nothing])             , (7, BindingsEntry "quit" [CmdQuit])             ]+        bindingsTable IMReplay GravLeft =+            [ ( 0, BindingsEntry "wait" [CmdWait])+            , ( 4, BindingsEntry "undo, redo" [CmdUndo, CmdRedo])+            , ( 5, BindingsEntry "marks" [CmdMark, CmdJumpMark, CmdReset])+            ]+        bindingsTable IMReplay GravRight =+            [ (-7, BindingsEntry "help" [CmdHelp])+            , (-6, BindingsEntry "bind" [CmdBind Nothing])+            , (7, BindingsEntry "quit" [CmdQuit])+            ]         bindingsTable IMEdit GravLeft =             [ (-4, BindingsEntry "move" $ map (CmdDir WHSSelected) hexDirs)             , (-3, BindingsEntry "rotate"@@ -176,6 +187,7 @@             ]         bindingsTable IMEdit GravRight =             [ (-7, BindingsEntry "help" [CmdHelp])+            , (-6, BindingsEntry "bind" [CmdBind Nothing])             , (-4, BindingsEntry "place" $ map CmdTile                 [ BlockTile []                 , SpringTile Relaxed zero
CursesUIMInstance.hs view
@@ -213,6 +213,8 @@         else HelpDone showHelpPaged' from IMMeta HelpPageGame =     drawBasicHelpPage from ("INTRICACY",magenta) (metagameHelpText,magenta)+showHelpPaged' from IMMeta (HelpPageInitiated n) =+    drawBasicHelpPage from ("Initiation complete",magenta) (initiationHelpText n,magenta) showHelpPaged' from IMEdit HelpPageFirstEdit =     drawBasicHelpPage from ("Your first lock:",magenta) (firstEditHelpText,green) showHelpPaged' _ _ _ = return HelpNone@@ -442,6 +444,7 @@     getUIMousePos = return Nothing     setYNButtons = return ()     onNewMode _ = say ""+    withNoBG = id      toggleColourMode = modify $ \s -> s {monochrome = not $ monochrome s} 
− Debug.hs
@@ -1,19 +0,0 @@--- This file is part of Intricacy--- Copyright (C) 2013 Martin Bays <mbays@sdf.org>------ This program is free software: you can redistribute it and/or modify--- it under the terms of version 3 of the GNU General Public License as--- published by the Free Software Foundation, or any later version.------ You should have received a copy of the GNU General Public License--- along with this program.  If not, see http://www.gnu.org/licenses/.--module Debug where--import qualified Debug.Trace as Trace-import Text.Show.Pretty (ppShow)--prettyTrace :: Show a => a -> b -> b-prettyTrace = Trace.trace . ppShow-prettyTraceVal :: Show a => a -> a-prettyTraceVal x = prettyTrace x x
Interact.hs view
@@ -120,7 +120,7 @@ processCommand im CmdQuit = do     case im of 	IMReplay -> throwE $ InteractSuccess False-	IMPlay -> lift (or <$> sequence [gets psIsSub, null <$> gets psGameStateMoveStack])+	IMPlay -> lift (or <$> sequence [gets psIsSub, gets psSaved, null <$> gets psGameStateMoveStack]) 	    >>? throwE $ InteractSuccess False 	IMEdit -> lift editStateUnsaved >>! throwE $ InteractSuccess True 	_ -> return ()@@ -146,7 +146,7 @@             first <- not <$> liftIO hasLocks             return $ [HelpPageInput] ++ if first then [HelpPageFirstEdit] else []         _ -> return [HelpPageInput]-    let showPage p = showHelp im p >>? do+    let showPage p = withNoBG $ showHelp im p >>? do 	    void $ textInput "[press a key or RMB]" 1 False True Nothing Nothing     sequence_ $ map showPage helpPages processCommand' im (CmdBind mcmd)= lift $ (>> endPrompt) $ runMaybeT $ do@@ -168,14 +168,17 @@     when (im == IMMeta) $ modify $ \ms -> ms { retiredLocks = Nothing } processCommand' im CmdMark = void.runMaybeT $ do     guard $ im `elem` [IMEdit, IMPlay, IMReplay]-    str <- MaybeT $ lift $ textInput "Mark: " 1 False True Nothing Nothing+    str <- MaybeT $ lift $ textInput "Type character for mark: "+        1 False True Nothing Nothing     ch <- liftMaybe $ listToMaybe str     guard $ ch `notElem` [startMark, '\'']     lift $ setMark True ch processCommand' im CmdJumpMark = void.runMaybeT $ do     guard $ im `elem` [IMEdit, IMPlay, IMReplay]+    marks <- lift marksSet     str <- MaybeT $ lift $ textInput-	"Jump to mark (\"'\" to unjump): " 1 False True Nothing Nothing+	("Jump to mark [" ++ intersperse ',' marks ++ "]: ")+            1 False True (Just $ map (:[]) marks) Nothing     ch <- liftMaybe $ listToMaybe str     lift $ jumpMark ch processCommand' im CmdReset = jumpMark startMark@@ -239,34 +242,40 @@ 		modify $ \ms -> ms {curAuth = Nothing} 	    , do 		confirmOrBail "Reset password?"-		passwd <- inputPassword regName "Enter new password:"-		lift $ do-		    resp <- curServerAction $ ResetPassword passwd-		    case resp of-			ServerAck -> do-			    lift $ drawMessage "New password set."-			    modify $ \ms -> ms {curAuth = Just $ Auth regName passwd}-			ServerError err -> lift $ drawError err-			_ -> lift $ drawMessage $ "Bad server response: " ++ show resp+                void.lift.runMaybeT $ do+                    passwd <- inputPassword regName True "Enter new password:"+                    lift $ do+                        resp <- curServerAction $ ResetPassword passwd+                        case resp of+                            ServerAck -> do+                                lift $ drawMessage "New password set."+                                modify $ \ms -> ms {curAuth = Just $ Auth regName passwd}+                            ServerError err -> lift $ drawError err+                            _ -> lift $ drawMessage $ "Bad server response: " ++ show resp 	    , do 		confirmOrBail "Configure email notifications?" 		setNotifications 	    ]-	else do-	    passwd <- inputPassword regName "Enter new password:"-	    lift $ do-		modify $ \ms -> ms {curAuth = Just $ Auth regName passwd}-		resp <- curServerAction Register-		case resp of-		    ServerAck -> do-			invalidateUInfo regName-			refreshUInfoUI-			conf <- lift $ confirm "Registered! Would you like to be notified by email when someone solves your lock?"-			if conf then void $ runMaybeT setNotifications else lift $ drawMessage "Notifications disabled."-		    ServerError err -> do-			lift $ drawError err-			modify $ \ms -> ms {curAuth = Nothing}-		    _ -> lift $ drawMessage $ "Bad server response: " ++ show resp+	else msum [ do+                mgetUInfo regName+                lift.lift $ drawError "Sorry, this codename is already taken."+            , do+                confirmOrBail $ "Register codename " ++ regName ++ "?"+                passwd <- inputPassword regName True "Enter new password:"+                lift $ do+                    modify $ \ms -> ms {curAuth = Just $ Auth regName passwd}+                    resp <- curServerAction Register+                    case resp of+                        ServerAck -> do+                            invalidateUInfo regName+                            refreshUInfoUI+                            conf <- lift $ confirm "Registered! Would you like to be notified by email when someone solves your lock?"+                            if conf then void $ runMaybeT setNotifications else lift $ drawMessage "Notifications disabled."+                        ServerError err -> do+                            lift $ drawError err+                            modify $ \ms -> ms {curAuth = Nothing}+                        _ -> lift $ drawMessage $ "Bad server response: " ++ show resp+            ]      where setNotifications = do 	    address <- MaybeT $ lift $ textInput "Enter address, or leave blank to disable notifications:" 128 False False Nothing Nothing@@ -285,7 +294,7 @@ 	modify $ \ms -> ms {curAuth = Nothing}     else do 	name <- mgetCurName-	passwd <- inputPassword name $ "Enter password for "++name++":"+	passwd <- inputPassword name False $ "Enter password for "++name++":" 	lift $ do 	    modify $ \ms -> ms {curAuth = Just $ Auth name passwd} 	    resp <- curServerAction $ Authenticate@@ -441,7 +450,7 @@ 	    ] 	return (baseLock size, Nothing)     not <$> liftIO hasLocks >>? do-        lift.lift $ showHelp IMEdit HelpPageFirstEdit >>? do+        lift.lift $ withNoBG $ showHelp IMEdit HelpPageFirstEdit >>? do             void $ textInput                 "[Press a key or RMB to continue; you can review this help later with '?']"                 1 False True Nothing Nothing@@ -476,13 +485,14 @@ 		rbdg <- lift $ getUIBinding IMMeta (CmdRegister False) 		if isNothing mauth 		    then do-			let showPage p = lift $ showHelp IMMeta p >>? do-				void $ textInput-				    "[Tutorial completed! Press a key or RMB to continue; you can review this help later with '?']"-				    1 False True Nothing Nothing-			showPage HelpPageGame+			let showPage p prompt = lift $ withNoBG $ showHelp IMMeta p >>? do+				void $ textInput prompt 1 False True Nothing Nothing+                        showPage (HelpPageInitiated 1) "[Tutorial complete! Press a key or RMB to continue]"+                        showPage (HelpPageInitiated 2) "[Press a key or RMB to continue]"+                        showPage (HelpPageInitiated 3) "[Press a key or RMB to continue]"+			--showPage HelpPageGame "[Press a key or RMB to continue; you can review this information later with '?']" 			lift $ drawMessage $ -			    "Tutorial completed! To play on the server, pick a codename ('"++cbdg+++			    "To join the game: pick a codename ('"++cbdg++ 			    "') and register it ('"++rbdg++"')." 		    else lift $ drawMessage $ "Tutorial completed!"     @@ -534,14 +544,24 @@     board <- stateBoard <$> gets psCurrentState     wsel <- gets wrenchSelected     void.runMaybeT $ do-	tile <- liftMaybe $ snd <$> Map.lookup pos board-	guard $ case tile of {WrenchTile _ -> True; HookTile -> True; _ -> False}+	tp <- liftMaybe $ tileType . snd <$> Map.lookup pos board+        msum [ guard $ tp == HookTile+            , do+                guard $ tp == WrenchTile zero+                board' <- lift $ stateBoard . fst . runWriter . physicsTick (WrenchPush dir) <$> gets psCurrentState+                msum $ [ do+                        tp' <- liftMaybe $ tileType . snd <$> Map.lookup pos' board'+                        guard $ tp' == WrenchTile zero+                    | d <- [0,1,2]+                    , let pos' = d *^ dir +^ pos ]+                    ++ [ (lift.lift $ warpPointer pos) >> mzero ]+            ] 	lift $ processCommand' IMPlay $ CmdDir WHSSelected $ dir 	board' <- stateBoard <$> gets psCurrentState 	msum [ do-		tile' <- liftMaybe $ snd <$> Map.lookup pos' board'-		guard $ tileType tile' == if wsel then WrenchTile zero else HookTile-		lift.lift $ warpPointer $ pos'+		tp' <- liftMaybe $ tileType . snd <$> Map.lookup pos' board'+		guard $ tp' == if wsel then WrenchTile zero else HookTile+		lift.lift $ warpPointer pos' 	    | pos' <- map (+^pos) $ hexDisc 2 ]  processCommand' IMPlay cmd = do@@ -719,10 +739,15 @@ 	    Just err -> lift $ drawError $ "Write failed: "++err processCommand' _ _ = return () -inputPassword :: UIMonad uiM => Codename -> String -> MaybeT (MainStateT uiM) String-inputPassword name prompt = do+inputPassword :: UIMonad uiM => Codename -> Bool -> String -> MaybeT (MainStateT uiM) String+inputPassword name confirm prompt = do     pw <- MaybeT $ lift $ textInput prompt 64 True False Nothing Nothing     guard $ not $ null pw+    when confirm $ do+        pw' <- MaybeT $ lift $ textInput "Confirm password:" 64 True False Nothing Nothing+        when (pw /= pw') $ do+            lift.lift $ drawError "Passwords don't match!"+            mzero     RCPublicKey publicKey <- MaybeT $ getFreshRecBlocking RecPublicKey     encryptPassword publicKey name pw @@ -756,20 +781,20 @@  subPlay :: UIMonad uiM => Lock -> MainStateT uiM () subPlay lock =-    pushEState =<< psCurrentState <$> (execSubMainState $ newPlayState lock Nothing False True)+    pushEState =<< psCurrentState <$> (execSubMainState $ newPlayState lock Nothing False True False)  solveLock,solveLockTut :: UIMonad uiM => Lock -> Maybe String -> MaybeT (MainStateT uiM) Solution solveLock = solveLock' False solveLockTut = solveLock' True solveLock' isTut lock title = do-    (InteractSuccess solved, ps) <- lift $ runSubMainState $ newPlayState (reframe lock) title isTut False+    (InteractSuccess solved, ps) <- lift $ runSubMainState $ newPlayState (reframe lock) title isTut False False     guard $ solved     return $ reverse $ (map snd) $ psGameStateMoveStack ps  solveLockSaving :: UIMonad uiM => LockSpec -> Maybe SavedPlayState -> Bool -> Lock -> Maybe String -> MaybeT (MainStateT uiM) Solution solveLockSaving ls msps isTut lock title = do     (InteractSuccess solved, ps) <- lift $ runSubMainState $-	((maybe newPlayState restorePlayState) msps) (reframe lock) title isTut False+	((maybe newPlayState restorePlayState) msps) (reframe lock) title isTut False True     if solved 	then do 	    unless isTut $ lift $ modify $ \ms -> ms { partialSolutions = Map.delete ls $ partialSolutions ms }
InteractUtil.hs view
@@ -130,6 +130,15 @@  startMark = '^' +marksSet :: UIMonad uiM => MainStateT uiM [Char]+marksSet = do+    mst <- get+    return $ case ms2im mst of+        IMEdit -> Map.keys $ esMarks mst+        IMPlay -> Map.keys $ psMarks mst+        IMReplay -> Map.keys $ rsMarks mst+        _ -> []+ jumpMark :: UIMonad uiM => Char -> MainStateT uiM () jumpMark ch = do     mst <- get
MainState.hs view
@@ -67,6 +67,7 @@     getUIMousePos :: m (Maybe HexPos)     setYNButtons :: m ()     onNewMode :: InputMode -> m ()+    withNoBG :: m () -> m ()     suspend,redraw :: m ()      doUI :: m a -> IO (Maybe a)@@ -87,6 +88,7 @@ 	, psTitle::Maybe String 	, psIsTut::Bool 	, psIsSub::Bool+	, psSaved::Bool 	, psMarks::Map Char MainState 	}     | ReplayState@@ -132,8 +134,8 @@  type MainStateT = StateT MainState -data HelpPage = HelpPageInput | HelpPageGame | HelpPageFirstEdit-    deriving (Eq, Ord, Show, Enum)+data HelpPage = HelpPageInput | HelpPageGame | HelpPageInitiated Int | HelpPageFirstEdit+    deriving (Eq, Ord, Show)  ms2im :: MainState -> InputMode ms2im mainSt = case mainSt of@@ -142,7 +144,7 @@     EditState {} -> IMEdit     MetaState {} -> IMMeta -newPlayState (frame,st) title isTut sub = PlayState st frame [] False False [] [] title isTut sub Map.empty+newPlayState (frame,st) title isTut sub saved = PlayState st frame [] False False [] [] title isTut sub saved Map.empty newReplayState st soln title = ReplayState st [] soln [] title Map.empty newEditState (frame,st) msoln mpath = EditState [st] [] frame mpath     ((\s->(st,s))<$>msoln) (Just (st, isJust msoln)) Nothing (PHS zero) (PHS zero) Map.empty@@ -169,12 +171,12 @@ savePlayState ps = SavedPlayState (getMoves ps) $ Map.map getMoves $ psMarks ps     where getMoves = reverse . map snd . psGameStateMoveStack -restorePlayState :: SavedPlayState -> Lock -> (Maybe String) -> Bool -> Bool -> MainState-restorePlayState (SavedPlayState pms markPMs) (frame,st) title isTut sub =+restorePlayState :: SavedPlayState -> Lock -> (Maybe String) -> Bool -> Bool -> Bool -> MainState+restorePlayState (SavedPlayState pms markPMs) (frame,st) title isTut sub saved =     (stateAfterMoves pms) { psMarks = Map.map stateAfterMoves markPMs }     where 	stateAfterMoves pms = let (stack,st') = applyMoves st pms-	    in (newPlayState (frame, st') title isTut sub) { psGameStateMoveStack = stack }+	    in (newPlayState (frame, st') title isTut 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)@@ -461,26 +463,77 @@     , "and you must discover the secret flaws in the locks designed by your colleagues."     , ""     , "You may put forward up to three prototype locks. They will guard the secrets you discover."-    , "If you pick a colleague's lock, the rules require that you have a note written on your solution."-    , "A note proves that a solution was found, while revealing no more of its details than necessary."-    , "Composing notes is a tricky and ritual-bound art of its own, performed by independent experts."-    , "To declare your success, you must secure your note behind a lock of your own."+    , "If you pick a colleague's lock, the rules require that a note is written on your solution."+    , "A note proves that a solution was found, while revealing no more details than necessary."+    --, "Composing notes is a tricky and ritual-bound art of its own, performed by independent experts."+    , "To declare your solution, you must secure your note behind a lock of your own."     , "If you are able to unlock a lock, you automatically read all the notes it secures."     , "If you read three notes on a lock, you will piece together the secrets of unlocking that lock."     , ""     , "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 each of their locks for which either:"-    , "you have declared a note on the lock which the lock's owner has not read,"-    , "or you have read three notes on the lock."-    , "Relative esteem ranges from +3 (best) to -3 (worst), and is calculated as"-    , "the number of their locks which win you a point minus the number of your locks which win them one."+    , "you have solved the lock and declared a note which the lock's owner has not read, or"+    , "you have read three notes on the lock."+    , "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."+    , ""     , "If the secrets to one of your locks become widely disseminated, you may wish to replace it."     , "However: once replaced, a lock is \"retired\", and the notes it secured are read by everyone."     ] +initiationHelpText :: Int -> [String]+initiationHelpText 1 =+    [ ""+    , "So."+    , ""+    , "It seems your levels of manual and mental dexterity are adequate for picking locks."+    , "Whether you also possess the deviousness required for their design, remains to be seen."+    , ""+    , "Nonetheless, we welcome you to our number. As for what exactly it is that you are joining..."+    , "perhaps you think you have worked it all out already, but let me explain."+    , ""+    , "But first: for reasons that will become clear,"+    , "our members are known exclusively by pseudonyms - by tradition, a triplet of letters or symbols."+    , "I am eager to hear what codename you will choose for yourself; do be thinking about it."+    ]+initiationHelpText 2 =+    [ ""+    , "Now."+    , ""+    , "As you fatefully determined, every lock permitted in the city has a fatal hidden flaw."+    , "Those whose duties require it are entrusted with the secrets required to pick these locks."+    , "Those who unauthorisedly discover said secrets... come to us."+    , ""+    , "Our task is to produce the superficially secure locks necessary for this system:"+    , "locks pickable with minimal tools, but with this fact obscured by their mechanical complexity."+    , ""+    , "To push the our designs to ever new heights of intricacy, we run a ritual game."+    , "You are to be its newest player."+    ]+initiationHelpText 3 =+    [ ""+    , "Each player designs locks, and each player attempts to solve the locks designed by the others."+    , ""+    , "You may put forward up to three prototype locks."+    , "They will guard the secrets you discover: when you pick a colleague's lock,"+    , "you may declare the fact by placing notes on its solution behind one of your locks."+    , "As long as the owner of the lock you picked is unable to read your notes,"+    , "you score a point against them."+    , ""+    , "If you find a lock too difficult or trivial for you to pick yourself,"+    , "you may find that reading other players' notes on it will lead you to a solution."+    , ""+    , "The finer details of the rules can wait."+    , "Go now; choose a codename, explore the locks we have set,"+    , "and begin your own experiments in the ever-rewarding art of lock design."+    ]+initiationHelpText _ = []++ firstEditHelpText :: [String] firstEditHelpText =     [ "Design a lock to protect your secrets."+    , ""     , "It must be possible to pick your lock by pulling a sprung bolt from the hole in the top-right,"     , "but you should place blocks, springs, pivots, and balls to make this as difficult as possible."     , ""
NEWS view
@@ -1,7 +1,12 @@ This is an abbreviated summary; see the git log for gory details. +0.7.1:+    Rework tutorial and intro.+    Animate movement.+    Prevent dragging wrench off-path from generating a move.+    Various UI improvements. 0.7.0.1:-    Fix compile error on older ghc versions+    Fix compile error on older ghc versions. 0.7:     Encrypt passwords when communicating with server         (in previous versions, they were salted and hashed but not encrypted).
SDLGlyph.hs view
@@ -38,6 +38,9 @@  data Glyph     = TileGlyph Tile Pixel+    | SpringGlyph HexDir HexDir SpringExtension HexDir Pixel+    | PivotGlyph TorqueDir HexDir Pixel+    | ArmGlyph TorqueDir HexDir Pixel     | BlockedArm HexDir TorqueDir Pixel     | TurnedArm HexDir TorqueDir Pixel     | BlockedBlock Tile HexDir Pixel@@ -53,6 +56,7 @@     | UseSoundsButton Bool     | WhsButtonsButton (Maybe WrHoSel)     | FullscreenButton Bool+    | DisplacedGlyph HexDir Glyph     | UnfreshGlyph     deriving (Eq, Ord, Show) @@ -137,11 +141,13 @@ 	    -- Maybe we could truncate the spring glyphs to a hex? 	    TileGlyph (BlockTile adjs) _ -> null adjs 	    TileGlyph (SpringTile extn dir) _ -> False+            SpringGlyph _ _ _ _ _ -> False 	    FilledHexGlyph _ -> False 	    HollowGlyph _ -> False 	    BlockedBlock _ _ _ -> False 	    BlockedPush _ _ -> False 	    CollisionMarker -> False+            DisplacedGlyph _ _ -> False 	    _ -> True  renderGlyph :: Glyph -> RenderM ()@@ -159,38 +165,13 @@ 		]  renderGlyph (TileGlyph (SpringTile extn dir) col) =-    thickLinesR points 1 $ brightness col-	where-            n :: Int-	    n = 3*case extn of-		Stretched -> 1-		Relaxed -> 2-		Compressed -> 4-	    brightness = if extn == Relaxed then dim else bright-	    dir' = if dir == zero then hu else dir-	    s = corner (hextant dir' - 1)-	    off = corner (hextant dir')-	    e = corner (hextant dir' - 3)-	    points = [ b +^ (fi i / fi n) **^ (e -^ s)-		| i <- [0..n]-		, i`mod`3 /= 1-		, let b = if i`mod`3==0 then s else off ]+    renderGlyph $ SpringGlyph zero zero extn dir col  renderGlyph (TileGlyph (PivotTile dir) col) = do-    rimmedCircleR zero (7/8) col $ bright col-    when (dir /= zero)-	$ aaLineR from to $ bright col-    return ()-	where-	    from = (7/8) **^ edge (neg dir)-	    to = (7/8) **^ edge dir+    renderGlyph $ PivotGlyph 0 dir col  renderGlyph (TileGlyph (ArmTile dir _) col) =-    thickLineR from to 1 col-	where-	    dir' = if dir == zero then hu else dir-	    from = edge $ neg dir'-	    to = innerCorner dir'+    renderGlyph $ ArmGlyph 0 dir col  renderGlyph (TileGlyph HookTile col) =     rimmedCircleR zero (7/8) col $ bright col@@ -213,6 +194,44 @@ renderGlyph (TileGlyph BallTile col) =     rimmedCircleR zero (7/8) (faint col) (obscure col) +renderGlyph (SpringGlyph rootDisp endDisp extn dir col) =+    thickLinesR points 1 $ brightness col+	where+            n :: Int+	    n = 3*case extn of+		Stretched -> 1+		Relaxed -> 2+		Compressed -> 4+	    brightness = if extn == Relaxed then dim else bright+	    dir' = if dir == zero then hu else dir+	    s = corner (hextant dir' - 1) +^ innerCorner endDisp+	    off = corner (hextant dir') +^ innerCorner endDisp+	    e = corner (hextant dir' - 3) +^ innerCorner rootDisp+	    points = [ b +^ (fi i / fi n) **^ (e -^ s)+		| i <- [0..n]+		, i`mod`3 /= 1+		, let b = if i`mod`3==0 then s else off ]++renderGlyph (PivotGlyph rot dir col) = do+    rimmedCircleR zero (7/8) col $ bright col+    when (dir /= zero)+	$ aaLineR from to $ bright col+    return ()+	where+	    from = rotFVec th c $ (7/8) **^ edge (neg dir)+	    to = rotFVec th c $ (7/8) **^ edge dir+            c = FVec 0 0+            th = - fi rot * pi / 12++renderGlyph (ArmGlyph rot dir col) =+    thickLineR from to 1 col+	where+	    dir' = if dir == zero then hu else dir+	    from = rotFVec th c $ edge $ neg dir'+	    to = rotFVec th c $ innerCorner dir'+            c = (2 **^ edge (neg dir'))+            th = - fi rot * pi / 12+ renderGlyph (BlockedArm armdir tdir col) =     aaLineR from to col 	where@@ -315,6 +334,9 @@ 	    | dir <- hexDirs ] 	corners' = map (((2/3)**^) . corner) [0..5] +renderGlyph (DisplacedGlyph dir glyph) =+    displaceRender (innerCorner dir) $ renderGlyph glyph+ renderGlyph (UnfreshGlyph) = do     let col = bright red     renderGlyph (HollowInnerGlyph col)@@ -338,7 +360,7 @@     where 	colAt v@(HexVec hx hy hz) = let 		[r,g,b] = map (\h -> fi $ ((0xff*)$ 5 + abs h)`div`maxR) [hx,hy,hz]-		a = fi $ (0x90 * (maxR - abs (hexLen v)))`div`maxR+		a = fi $ (0x70 * (maxR - abs (hexLen v)))`div`maxR 	    in rgbaToPixel (r,g,b,a)  drawBlocked :: GameState -> PieceColouring -> Bool -> Force -> RenderM ()
SDLRender.hs view
@@ -230,6 +230,7 @@ 	    | dir == hu = [2/3, 0] 	    | dir == hv = [-1/3, -ylen] 	    | dir == hw = [-1/3, ylen]+            | dir == zero = [0,0] 	    | not (isHexDir dir) = error "innerCorner: not a hexdir" 	    | otherwise = map (\z -> -z) $ f $ neg dir @@ -244,6 +245,15 @@ 	    | not (isHexDir dir) = error "edge: not a hexdir" 	    | otherwise = map (\z -> -z) $ f $ neg dir +rotFVec :: Float -> FVec -> FVec -> FVec+rotFVec th (FVec cx cy) v@(FVec x y)+    | th == 0 = v+    | otherwise = FVec (cx + c*dx-s*dy) (cy + s*dx+c*dy)+    where+        dx = x-cx+        dy = y-cy+        c = cos th+        s = sin th  black = Pixel 0x01000000 white = Pixel 0xffffff00
SDLUI.hs view
@@ -87,7 +87,7 @@ type UIM = StateT UIState IO nullUIState = UIState 0 0 Nothing Nothing emptyCachedGlyphs Nothing Map.empty Map.empty []     defaultUIOptions Nothing Map.empty Nothing Nothing 0 0 Nothing Nothing Nothing-    (zero,False) Nothing Nothing False (PHS zero) Map.empty 0 Nothing 25+    (zero,False) Nothing Nothing False (PHS zero) Map.empty 0 Nothing 50 #ifdef SOUND     Map.empty #endif@@ -101,9 +101,10 @@     , showButtonText::Bool     , useSounds::Bool     , uiAnimTime::Word32+    , shortUiAnimTime::Word32     }     deriving (Eq, Ord, Show, Read)-defaultUIOptions = UIOptions False ShowBlocksBlocking Nothing True False True True 100+defaultUIOptions = UIOptions False ShowBlocksBlocking Nothing True False True True 100 20  modifyUIOptions :: (UIOptions -> UIOptions) -> UIM () modifyUIOptions f = modify $ \s -> s { uiOptions = f $ uiOptions s }@@ -161,7 +162,6 @@ 	IMPlay -> 	    [ markGroup ] 	    ++ whsBGs mwhs mode-	    ++ [ singleButton tr CmdOpen 1 [("open", hu+^neg hw)] ] 	IMReplay -> [ markGroup ] 	IMMeta -> 	    [ singleButton serverPos CmdSetServer 0 [("server",7*^neg hu)]@@ -566,23 +566,24 @@ 	    -- to the right states: 	    let (globalAlerts,transitoryAlerts) = partition isGlobalAlert alerts 		splitAlerts frameAs (AlertIntermediateState st' : as) =-		    (frameAs,st') : splitAlerts [] as+		    (frameAs,st',True) : splitAlerts [] as 		splitAlerts frameAs (a:as) = 		    splitAlerts (a:frameAs) as-		splitAlerts frameAs [] = [(frameAs,st)]+		splitAlerts frameAs [] = [(frameAs,st,False)] 		isGlobalAlert (AlertAppliedForce _) = False 		isGlobalAlert (AlertIntermediateState _) = False 		isGlobalAlert _ = True 	    let animAlertedStates = nub $ 		    let ass = splitAlerts [] transitoryAlerts-		    in if last ass == ([],st) then ass else ass ++ [([],st)]+		    in if last ass == ([],st,False) then ass else ass ++ [([],st,False)] 	    let frames = length animAlertedStates-	    let (drawAlerts',drawSt) = animAlertedStates !! animFrameToDraw+	    let (drawAlerts',drawSt,isIntermediate) = animAlertedStates !! animFrameToDraw 	    let drawAlerts = drawAlerts' ++ globalAlerts 	    -- let drawAlerts = takeWhile (/= AlertIntermediateState drawSt) alerts 	    nextIsSet <- isJust <$> gets nextAnimFrameAt 	    when (not nextIsSet && frames > animFrameToDraw+1) $ do-		time <- uiAnimTime <$> gets uiOptions+		time <- (if isIntermediate then uiAnimTime else shortUiAnimTime) <$>+                    gets uiOptions 		modify $ \ds -> ds { nextAnimFrameAt = Just $ now + time }  	    let board = stateBoard drawSt@@ -594,9 +595,54 @@ 	    modify $ \ds -> ds { dispLastCol = colouring } 	    gsSurf <- liftM fromJust $ gets gsSurface 	    renderToMainWithSurf gsSurf $ do+                let tileGlyphs = fmap (ownedTileGlyph colouring highlight) board++                    applyAlert (AlertAppliedForce f@(Torque idx tdir)) =+                        let poss = case getpp drawSt idx of+                                PlacedPiece pos (Pivot arms) -> pos : map (+^pos) arms+                                PlacedPiece pos (Hook arm _) -> [arm+^pos]+                                _ -> []+                            rotateGlyph (TileGlyph (ArmTile dir _) col) =+                                ArmGlyph (-tdir) dir col+                            rotateGlyph (TileGlyph (PivotTile dir) col) =+                                PivotGlyph (-tdir) dir col+                        in flip (foldr . Map.adjust $ rotateGlyph) poss+                    applyAlert (AlertAppliedForce f@(Push idx dir)) =+                        displaceFootprint . displaceSprings+                        where+                            displace = DisplacedGlyph $ neg dir+                            displaceSpringGlyph isRoot (TileGlyph (SpringTile extn sdir) col) =+                                displaceSpringGlyph isRoot $ SpringGlyph zero zero extn sdir col+                            displaceSpringGlyph isRoot (SpringGlyph rdisp edisp extn sdir col)+                                | isRoot = SpringGlyph (neg dir) edisp extn sdir col+                                | otherwise = SpringGlyph rdisp (neg dir) extn sdir col+                            displaceSpringGlyph _ glyph = glyph+                            displaceFootprint =+                                flip (foldr . Map.adjust $ displace) $+                                    plPieceFootprint $ getpp drawSt idx++                            displaceSpring isRoot c@(Connection root end (Spring sdir _))+                                | dir `elem` [sdir,neg sdir] =+                                    Map.adjust (displaceSpringGlyph isRoot) $+                                        if isRoot+                                            then sdir +^ locusPos drawSt root+                                            else neg sdir +^ locusPos drawSt end+                                | isRoot =+                                    flip (foldr . Map.adjust $ displace) $+                                        connectionFootPrint drawSt c+                                | otherwise = id+                            displaceSpring _ _ = id++                            displaceSprings =+                                (flip (foldr $ displaceSpring True) $ springsRootAtIdx drawSt idx) .+                                (flip (foldr $ displaceSpring False) $ springsEndAtIdx drawSt idx)+                    applyAlert _ = id++                    applyAlerts = flip (foldr applyAlert) drawAlerts+ 		erase 		sequence_ [ drawAt glyph pos |-		    (pos,glyph) <- Map.toList $ fmap (ownedTileGlyph colouring highlight) board+		    (pos,glyph) <- Map.toList $ applyAlerts tileGlyphs 		    ]  		when (showBlocks uiopts /= ShowBlocksNone) $ sequence_@@ -609,8 +655,8 @@ 		    -- AlertResistedForce force <- drawAlerts ] 		    ++ [ drawAt CollisionMarker pos 			| AlertCollision pos <- drawAlerts ]-		    ++ [ drawApplied drawSt colouring force-			| AlertAppliedForce force <- drawAlerts ]+		    -- ++ [ drawApplied drawSt colouring force+		    --    | AlertAppliedForce force <- drawAlerts ] 	    vidSurf <- liftIO getVideoSurface 	    liftIO $ blitSurface gsSurf Nothing vidSurf Nothing 
SDLUIMInstance.hs view
@@ -70,7 +70,8 @@ 	    drawShortMouseHelp mode 	    refresh 	where-	drawMainState' (PlayState { psCurrentState=st, psLastAlerts=alerts, wrenchSelected=wsel, psIsTut=isTut }) = do+        drawMainState' (PlayState { psCurrentState=st, psLastAlerts=alerts,+                wrenchSelected=wsel, psIsTut=isTut, psSolved=solved }) = do 	    canUndo <- null <$> gets psGameStateMoveStack 	    canRedo <- null <$> gets psUndoneStack 	    lift $ do@@ -90,6 +91,10 @@ 			, isTool p 			] 		registerUndoButtons canUndo canRedo+                registerButton (periphery 0) CmdOpen (if solved then 2 else 0) $+                    [("open", hu+^neg hw)] ++ if solved && isTut+                        then [("Click-->",9*^neg hu)]+                        else [] 	drawMainState' (ReplayState { rsCurrentState=st, rsLastAlerts=alerts } ) = do 	    canUndo <- null <$> gets rsGameStateMoveStack 	    canRedo <- null <$> gets rsMoveStack@@ -597,12 +602,23 @@     showHelp IMMeta HelpPageGame = do         renderToMain $ drawBasicHelpPage ("INTRICACY",red) (metagameHelpText,purple)         return True+    showHelp IMMeta (HelpPageInitiated n) = do+        renderToMain $ drawBasicHelpPage ("Initiation complete",purple) (initiationHelpText n,red)+        return True     showHelp IMEdit HelpPageFirstEdit = do         renderToMain $ drawBasicHelpPage ("Your first lock:",purple) (firstEditHelpText,green)         return True     showHelp _ _ = return False      onNewMode mode = modify (\ds -> ds{needHoverUpdate=True}) >> say ""++    withNoBG m = do+        bg <- gets bgSurface+        modify $ \uiState -> uiState{bgSurface=Nothing}+        m+        isNothing <$> gets bgSurface >>?+            modify (\uiState -> uiState{bgSurface=bg})+  drawShortMouseHelp mode = do     mwhs <- gets $ whsButtons.uiOptions
Server.hs view
@@ -177,7 +177,7 @@  showRequest :: ClientRequest -> String showRequest (ClientRequest ver mauth act) = show ver ++ " "-    ++ maybe "" (\(Auth name pw) -> "Auth:" ++ name ++ ":" ++ take 4 pw) mauth ++ " "+    ++ maybe "" (\(Auth name _) -> "Auth:" ++ name) mauth ++ " "     ++ showAction act showAction :: Action -> String showAction (SetLock lock idx soln) = "SetLock " ++ show idx ++ " lock:"
Version.hs view
@@ -11,4 +11,4 @@ module Version where  version :: String-version = "0.7.0.1"+version = "0.7.1"
intricacy.cabal view
@@ -1,5 +1,5 @@ name:                intricacy-version:             0.7.0.1+version:             0.7.1 synopsis:            A game of competitive puzzle-design homepage:            http://mbays.freeshell.org/intricacy license:             GPL-3@@ -9,7 +9,7 @@ -- copyright:            category:            Game build-type:          Simple-cabal-version:       >=1.8+cabal-version:       >=1.18 data-files: VeraMoBd.ttf tutorial/*.lock tutorial/*.text sounds/*.ogg extra-doc-files: README BUILD NEWS tutorial-extra/*.lock tutorial-extra/README extra-source-files: Main_stub.h@@ -101,7 +101,7 @@    ghc-options: -fno-warn-tabs   other-modules: AsciiLock, BinaryInstances, BoardColouring, Cache, Command,-      CursesRender, CursesUI, CursesUIMInstance, CVec, Database, Debug,+      CursesRender, CursesUI, CursesUIMInstance, CVec, Database,       EditGameState, Frame, GameState, GameStateTypes, GraphColouring, Hex, Init,       InputMode, Interact, InteractUtil, KeyBindings, Lock, MainState,       Maxlocksize, Metagame, Mundanities, Physics, Protocol, SDLGlyph,@@ -126,5 +126,5 @@   main-is:  Server.hs   ghc-options: -fno-warn-tabs   other-modules: AsciiLock, BinaryInstances, BoardColouring, CVec, Database,-      Debug, Frame, GameState, GameStateTypes, GraphColouring, Hex, Lock,+      Frame, GameState, GameStateTypes, GraphColouring, Hex, Lock,       Maxlocksize, Metagame, Mundanities, Physics, Protocol, Util, Version
+ tutorial-extra/1-springChains.lock view
@@ -0,0 +1,17 @@+        % % % % % % % % %          +       %                 % % % %   +      %                   %     %  +     %                     %     % +    % S S S S " S S S S & & & & & %+   %   o -   "   # # #       %   % +  %   /         #     %       % %  + %             #     %       [ %   +%           #       %       [   %  + %           " S S %       [   %   +  % S S S S " "   %       #   %    + % %           % % S S S S " %     +% * '                       %      + % @ %                     %       +  % % %                   %        +       %                 %         +        % % % % % % % % %          
+ tutorial-extra/2-snookered.lock view
@@ -0,0 +1,17 @@+        " " " " " " " " "          +       " S S S # S S S % " " " "   +      "       #         % "     "  +     "       #     &     % "     " +    "       # O O   &     % % % % "+   "       # O O   & "     % "   " +  "       #       & " " "     " "  + "   & & & & & & & % C C # #   "   +" & &       & & & . o         % "  + " O O O O O & & o - '       % "   +  " O O O O   &   . o         "    + " " O O O   & & O % \       "     +" * ' O O   & &   %   o     "      + " @ " O   &     # Z #     "       +  " " "         # # Z     "        +       "   # # # # # Z   "         +        " " " " " " " " "          
+ tutorial-extra/3-plug.lock view
@@ -0,0 +1,17 @@+        " " " " " " " " "          +       " #             ( " " " "   +      "   #           (   "     "  +     "   # #         (     " #   " +    "   #   #       (       #     "+   "         #     (       # "   " +  "   & &   %     (         # " "  + "   & & & & Z   (         # # "   +"   # C C & & Z (       # #   # "  + "   & & & & & #             7 "   +  "   & & & & & ]           7 "    + " "     & & &   ] %       7 "     +" * '   o \ O   # " %     7 "      + " @ "   ` o     #   %   7 "       +  " " "   #   %   #   % 7 "        +       "     % %       % "         +        " " " " " " " " "          
+ tutorial-extra/4-plunger.lock view
@@ -0,0 +1,17 @@+        " " " " " " " " "          +       "     #       # # " " " "   +      "   #   #     # #   "     "  +     "     #   ]   # #     " %   " +    "   #   # % % # # & & & %     "+   "     #   ]   % #   7   % "   " +  "   #   # " " # '   7   % % " "  + "     #   ]   " o % %   '   % "   +"       # % % # '   7 . o     % "  + "   #   ]   % o   7         1 "   +  "   # " " # ' # #         1 "    + " "   #   " o   7         # "     +" * '     % '   7           "      + " @ "   % o % %           "       +  " " " %     7           "        +       "     7           "         +        " " " " " " " " "          
+ tutorial-extra/5-cogs.lock view
@@ -0,0 +1,17 @@+        " " " " " " " " "          +       " # # # # [ # # # " " " "   +      " # # # # [ # S S % "     "  +     " # # # # [       % % "     " +    " # # # # [           % % %   "+   " #   \   % % % %     %   "   " +  "       o - ' % [     . o   " "  + "   # # / . o   [ # #     `   "   +"   # # # # \ ` # # [     . o   "  + "   # #   ' o - ' [         ` "   +  "     . o / . o %       #   "    + " "       ` #   ` %     #   "     +" * '     # # # % % % % #   "      + " @ "   # # #   % # # #   "       +  " " "   # # % % # # #   "        +       "                 "         +        " " " " " " " " "          
− tutorial-extra/5-springChains.lock
@@ -1,17 +0,0 @@-        % % % % % % % % %          -       %                 % % % %   -      %                   %     %  -     %                     %     % -    % S S S S " S S S S & & & & & %-   %   o -   "   # # #       %   % -  %   /         #     %       % %  - %             #     %       [ %   -%           #       %       [   %  - %           " S S %       [   %   -  % S S S S " "   %       #   %    - % %           % % S S S S " %     -% * '                       %      - % @ %                     %       -  % % %                   %        -       %                 %         -        % % % % % % % % %          
− tutorial-extra/6-snookered.lock
@@ -1,17 +0,0 @@-        " " " " " " " " "          -       " S S S # S S S % " " " "   -      "       #         % "     "  -     "       #     &     % "     " -    "       # O O   &     % % % % "-   "       # O O   & "     % "   " -  "       #       & " " "     " "  - "   & & & & & & & % C C # #   "   -" & &       & & & . o         % "  - " O O O O O & & o - '       % "   -  " O O O O   &   . o         "    - " " O O O   & & O % \       "     -" * ' O O   & &   %   o     "      - " @ " O   &     # Z #     "       -  " " "         # # Z     "        -       "   # # # # # Z   "         -        " " " " " " " " "          
− tutorial-extra/7-plunger.lock
@@ -1,17 +0,0 @@-        " " " " " " " " "          -       "     #       # # " " " "   -      "   #   #     # #   "     "  -     "     #   ]   # #     " %   " -    "   #   # % % # # & & & %     "-   "     #   ]   % #   7   % "   " -  "   #   # " " # '   7   % % " "  - "     #   ]   " o % %   '   % "   -"       # % % # '   7 . o     % "  - "   #   ]   % o   7         1 "   -  "   # " " # ' # #         1 "    - " "   #   " o   7         # "     -" * '     % '   7           "      - " @ "   % o % %           "       -  " " " %     7           "        -       "     7           "         -        " " " " " " " " "          
− tutorial-extra/8-cogs.lock
@@ -1,17 +0,0 @@-        " " " " " " " " "          -       " # # # # [ # # # " " " "   -      " # # # # [ # S S % "     "  -     " # # # # [       % % "     " -    " # # # # [           % % %   "-   " #   \   % % % %     %   "   " -  "       o - ' % [     . o   " "  - "   # # / . o   [ # #     `   "   -"   # # # # \ ` # # [     . o   "  - "   # #   ' o - ' [         ` "   -  "     . o / . o %       #   "    - " "       ` #   ` %     #   "     -" * '     # # # % % % % #   "      - " @ "   # # #   % # # #   "       -  " " "   # # % % # # #   "        -       "                 "         -        " " " " " " " " "          
+ tutorial/01-winning.lock view
@@ -0,0 +1,11 @@+       " " " " " "       +      "           " " "  +     "             "   " +    " S S S S S S # # # "+   "             #   " " +  "                   "  + " "                 "   +" * '               "    + " @ "             "     +  " " "           "      +       " " " " " "       
+ tutorial/01-winning.text view
@@ -0,0 +1,1 @@+drag your tools to pull the bolt aside, then press 'O'
+ tutorial/02-tools.lock view
@@ -0,0 +1,11 @@+       " " " " " "       +      " # # # # # " " "  +     " # # # # # # "   " +    " S S S S S S % % % "+   " # #         %   " " +  " #     # # # # #   "  + " "   # # # # # #   "   +" * '   # #         "    + " @ "   #   # # # "     +  " " "     # # # "      +       " " " " " "       
+ tutorial/02-tools.text view
@@ -0,0 +1,1 @@+the wrench can fit where the hook gets stuck
+ tutorial/03-wrench.lock view
@@ -0,0 +1,13 @@+       " " " " " " "       +      "     #       " " "  +     " S S # #   %   "   " +    "             % % % % "+   "   o   # S S S S % " " +  "     `             # "  + "   % % % % % %   # #   " +  " %   %               "  + " "   % % %     #     "   +" * '     % %         "    + " @ " %   % o       "     +  " " " %   /     # "      +       " " " " " " "       
+ tutorial/03-wrench.text view
@@ -0,0 +1,1 @@+the wrench moves in a straight line until blocked
+ tutorial/04-hook.lock view
@@ -0,0 +1,13 @@+       " " " " " " "       +      " %   # #   % " " "  +     " %     Z #   % " # " +    " % %   % Z     % #   "+   " %   # % % Z     # " " +  " % %   Z % % Z     # "  + " % % %   Z % % %     # " +  " % % %   % % %   % # "  + " "   % ]     %   # # "   +" * '     ]   % %   # "    + " @ "   # #   % % 7 "     +  " " "   % % % % 7 "      +       " " " " " " "       
+ tutorial/04-hook.text view
@@ -0,0 +1,1 @@+turn hook with mouse wheel to pull springs aside; numpad and vi keys also supported
+ tutorial/05-blockWrench.lock view
@@ -0,0 +1,11 @@+       " " " " " "       +      " #         " " "  +     " #   #   %   "   " +    " #   # S S % % % % "+   "                 " " +  "   # #   #   # #   "  + " "   #   # #   #   "   +" * '               "    + " @ "   # # # #   "     +  " " "           "      +       " " " " " "       
+ tutorial/05-blockWrench.text view
@@ -0,0 +1,1 @@+the hook can be used to guide the wrench
+ tutorial/06-balls.lock view
@@ -0,0 +1,11 @@+       " " " " " "       +      " & & S S # " " "  +     " & & &     # "   " +    " & & & &   # # # # "+   "         &   #   " " +  "     #   & &   #   "  + " " O #     &       "   +" * ' # #     O #   "    + " @ " #   O     # "     +  " " " #       # "      +       " " " " " "       
+ tutorial/06-balls.text view
@@ -0,0 +1,1 @@+balls can be pushed around to allow or block movement
+ tutorial/07-pivots.lock view
@@ -0,0 +1,11 @@+       " " " " " "       +      " \     # # " " "  +     "   o -   \ ' " # " +    " % /   "   o - #   "+   " % %   " "   # # " " +  " % % %   " O   # # "  + " "         . o - 7 "   +" * ' . o - &   ` 7 "    + " @ " O     & & & "     +  " " " # #   & & "      +       " " " " " "       
+ tutorial/07-pivots.text view
@@ -0,0 +1,1 @@+push arms to rotate them around their pivots
+ tutorial/08-springs.lock view
@@ -0,0 +1,17 @@+        " " " " " " " " "          +       " # # # # # # # # " " " "   +      " # #               "     "  +     " # #   % % % %   #   " # # " +    " # # #   % % %     # # #     "+   "               %     # # "   " +  "   # #   # # #   %     # # " "  + "     ]   " S S #   %       # "   +"       ]   " % % % % % %     # "  + "       ]   " ] % % % % %   7 "   +  "       ] % " ] % % % %   7 "    + " "     % % % " " % % S S & "     +" * '     % Z   o - % % % % "      + " @ "       Z / & C C C C "       +  " " "       Z & & & &   "        +       "       Z & & &   "         +        " " " " " " " " "          
+ tutorial/08-springs.text view
@@ -0,0 +1,1 @@+A spring's length can double when stretched, and halve when compressed
+ tutorial/09-traps.lock view
@@ -0,0 +1,13 @@+       & & & & & & &       +      &             & & &  +     & % O % S S "   &   & +    &         #   " " " " &+   & "         O   " " & & +  & " "   # #     # # # &  + & "                   # & +  & " " "         # # # &  + & &   " } # #   #     &   +& * '     % # #   #   &    + & @ &     O       # &     +  & & &     # # # # &      +       & & & & & & &       
+ tutorial/09-traps.text view
@@ -0,0 +1,1 @@+if you make a mistake, you can undo moves ('X') or reset to the start ('^')
− tutorial/1-winning.lock
@@ -1,11 +0,0 @@-       " " " " " "       -      "           " " "  -     "             "   " -    " S S S S S S # # # "-   "             #   " " -  "                   "  - " "                 "   -" * '               "    - " @ "             "     -  " " "           "      -       " " " " " "       
− tutorial/1-winning.text
@@ -1,1 +0,0 @@-drag your tools to pull the bolt aside, then press 'O'
+ tutorial/10-puzzle.lock view
@@ -0,0 +1,17 @@+        " " " " " " " " "          +       "                 " " " "   +      "   # S S S S S %   "     "  +     "     O   "       %   "     " +    " S S & & & o -   % % % % % % "+   "   & &   #   #           "   " +  "         # #   # #   # # # " "  + "         #   #             # "   +"               # # #   # # # # "  + "             # #             "   +  "             #         O   "    + " "   #     %   # #   # # # "     +" * '     % % %           # "      + " @ "   O % } % % % o   # "       +  " " " % O % "     /   # "        +       " %     O       # "         +        " " " " " " " " "          
+ tutorial/10-puzzle.text view
@@ -0,0 +1,1 @@+your final test!
− tutorial/2-tools.lock
@@ -1,13 +0,0 @@-       " " " " " " "       -      "     #       " " "  -     " S S # #   %   "   " -    "             % % % % "-   "   o   # S S S S % " " -  "     `             # "  - "   % % % % % %   # #   " -  " %   %               "  - " "   % % %     #     "   -" * '     % %         "    - " @ " %   % o       "     -  " " " %   /     # "      -       " " " " " " "       
− tutorial/2-tools.text
@@ -1,1 +0,0 @@-the wrench can fit where the hook can't, but keeps moving until it hits something
− tutorial/3-puzzle.lock
@@ -1,17 +0,0 @@-        & & & & & & & & &          -       &                 & & & &   -      &   # S S S S S "   &     &  -     &     O   %       "   &     & -    & S S # # # o -   " " " " " " &-   &   # #   %   #           &   & -  &         % %   # #   # # # & &  - &         %   %             # &   -&               % % %   # # # # &  - &             % %             &   -  &             %         O   &    - & &         "   % %   % % % &     -& * ' " " " " "     \     % &      - & @ &         " " " o   % &       -  & & &         "       % &        -       &         "     % &         -        & & & & & & & & &          
− tutorial/3-puzzle.text
@@ -1,1 +0,0 @@-if you make a mistake, you can undo moves ('X') or reset to the start ('^')
− tutorial/4-hook.lock
@@ -1,13 +0,0 @@-       & & & & & & &       -      &     # #   % & & &  -     &       Z #   % & # & -    &   " " " Z     % #   &-   &   # C C " Z     # & & -  &   %   " " " Z     # &  - &   7   " " " " "     # & -  & 7   " " " " "   % # &  - & &   " " " " "   # # &   -& * ' " " " " " "   # &    - & @ & " " " " " " 7 &     -  & & & " " " " " 7 &      -       & & & & & & &       
− tutorial/4-hook.text
@@ -1,1 +0,0 @@-turn hook with mouse wheel to pull springs aside; numpad and vi keys also supported
− tutorial/5-springs.lock
@@ -1,17 +0,0 @@-        " " " " " " " " "          -       " # # # # #   # # " " " "   -      " # #       #       "     "  -     " # #   % %       #   " # # " -    " # # #   % S S #   # # #     "-   "                       # "   " -  "   & &   % % %         # # " "  - "     ]   " S S %           # "   -"       ]   " # # #     #     # "  - "       ]   " ] # #   # #   7 "   -  "       ] # " ] #   # #   7 "    - " "     # # # " " # # S S % "     -" * '     # Z   o - # # # # "      - " @ "       Z / % C C C C "       -  " " "       Z % % % %   "        -       "       Z % % %   "         -        " " " " " " " " "          
− tutorial/5-springs.text
@@ -1,1 +0,0 @@-A spring's length can double when stretched, and halve when compressed
− tutorial/6-plug.lock
@@ -1,17 +0,0 @@-        " " " " " " " " "          -       " #             ( " " " "   -      "   #           (   "     "  -     "   # #         (     " #   " -    "   #   #       (       #     "-   "         #     (       # "   " -  "   & &   %     (         # " "  - "   & & & & Z   (         # # "   -"   # C C & & Z (       # #   # "  - "   & & & & & #             7 "   -  "   & & & & & ]           7 "    - " "     & & &   ] %       7 "     -" * '   o \ O   # " %     7 "      - " @ "   ` o     #   %   7 "       -  " " "   #   %   #   % 7 "        -       "     % %       % "         -        " " " " " " " " "          
− tutorial/6-plug.text
@@ -1,1 +0,0 @@-compress the central springs with both tools at once