intricacy 0.7.0.1 → 0.7.1
raw patch · 55 files changed
+532/−289 lines, 55 files
Files
- CursesUI.hs +12/−0
- CursesUIMInstance.hs +3/−0
- Debug.hs +0/−19
- Interact.hs +71/−46
- InteractUtil.hs +9/−0
- MainState.hs +67/−14
- NEWS +6/−1
- SDLGlyph.hs +51/−29
- SDLRender.hs +10/−0
- SDLUI.hs +57/−11
- SDLUIMInstance.hs +17/−1
- Server.hs +1/−1
- Version.hs +1/−1
- intricacy.cabal +4/−4
- tutorial-extra/1-springChains.lock +17/−0
- tutorial-extra/2-snookered.lock +17/−0
- tutorial-extra/3-plug.lock +17/−0
- tutorial-extra/4-plunger.lock +17/−0
- tutorial-extra/5-cogs.lock +17/−0
- tutorial-extra/5-springChains.lock +0/−17
- tutorial-extra/6-snookered.lock +0/−17
- tutorial-extra/7-plunger.lock +0/−17
- tutorial-extra/8-cogs.lock +0/−17
- tutorial/01-winning.lock +11/−0
- tutorial/01-winning.text +1/−0
- tutorial/02-tools.lock +11/−0
- tutorial/02-tools.text +1/−0
- tutorial/03-wrench.lock +13/−0
- tutorial/03-wrench.text +1/−0
- tutorial/04-hook.lock +13/−0
- tutorial/04-hook.text +1/−0
- tutorial/05-blockWrench.lock +11/−0
- tutorial/05-blockWrench.text +1/−0
- tutorial/06-balls.lock +11/−0
- tutorial/06-balls.text +1/−0
- tutorial/07-pivots.lock +11/−0
- tutorial/07-pivots.text +1/−0
- tutorial/08-springs.lock +17/−0
- tutorial/08-springs.text +1/−0
- tutorial/09-traps.lock +13/−0
- tutorial/09-traps.text +1/−0
- tutorial/1-winning.lock +0/−11
- tutorial/1-winning.text +0/−1
- tutorial/10-puzzle.lock +17/−0
- tutorial/10-puzzle.text +1/−0
- tutorial/2-tools.lock +0/−13
- tutorial/2-tools.text +0/−1
- tutorial/3-puzzle.lock +0/−17
- tutorial/3-puzzle.text +0/−1
- tutorial/4-hook.lock +0/−13
- tutorial/4-hook.text +0/−1
- tutorial/5-springs.lock +0/−17
- tutorial/5-springs.text +0/−1
- tutorial/6-plug.lock +0/−17
- tutorial/6-plug.text +0/−1
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