summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormbays <>2017-11-15 22:28:00 (GMT)
committerhdiff <hdiff@hdiff.luite.com>2017-11-15 22:28:00 (GMT)
commiteb1bff1b15e165a2eed9566763b2eab9640c0e18 (patch)
tree3a592031027120c8a37fbee0f732ab3501660864
parentf7a66f634231b801876e9c12b1e92265c74e610e (diff)
version 0.7.10.7.1
-rw-r--r--CursesUI.hs12
-rw-r--r--CursesUIMInstance.hs3
-rw-r--r--Debug.hs19
-rw-r--r--Interact.hs117
-rw-r--r--InteractUtil.hs9
-rw-r--r--MainState.hs81
-rw-r--r--NEWS7
-rw-r--r--SDLGlyph.hs80
-rw-r--r--SDLRender.hs10
-rw-r--r--SDLUI.hs68
-rw-r--r--SDLUIMInstance.hs18
-rw-r--r--Server.hs2
-rw-r--r--Version.hs2
-rw-r--r--intricacy.cabal8
-rw-r--r--tutorial-extra/1-springChains.lock (renamed from tutorial-extra/5-springChains.lock)0
-rw-r--r--tutorial-extra/2-snookered.lock (renamed from tutorial-extra/6-snookered.lock)0
-rw-r--r--tutorial-extra/3-plug.lock (renamed from tutorial/6-plug.lock)0
-rw-r--r--tutorial-extra/4-plunger.lock (renamed from tutorial-extra/7-plunger.lock)0
-rw-r--r--tutorial-extra/5-cogs.lock (renamed from tutorial-extra/8-cogs.lock)0
-rw-r--r--tutorial/01-winning.lock (renamed from tutorial/1-winning.lock)0
-rw-r--r--tutorial/01-winning.text (renamed from tutorial/1-winning.text)0
-rw-r--r--tutorial/02-tools.lock11
-rw-r--r--tutorial/02-tools.text1
-rw-r--r--tutorial/03-wrench.lock (renamed from tutorial/2-tools.lock)0
-rw-r--r--tutorial/03-wrench.text1
-rw-r--r--tutorial/04-hook.lock13
-rw-r--r--tutorial/04-hook.text (renamed from tutorial/4-hook.text)0
-rw-r--r--tutorial/05-blockWrench.lock11
-rw-r--r--tutorial/05-blockWrench.text1
-rw-r--r--tutorial/06-balls.lock11
-rw-r--r--tutorial/06-balls.text1
-rw-r--r--tutorial/07-pivots.lock11
-rw-r--r--tutorial/07-pivots.text1
-rw-r--r--tutorial/08-springs.lock17
-rw-r--r--tutorial/08-springs.text (renamed from tutorial/5-springs.text)0
-rw-r--r--tutorial/09-traps.lock13
-rw-r--r--tutorial/09-traps.text (renamed from tutorial/3-puzzle.text)0
-rw-r--r--tutorial/10-puzzle.lock17
-rw-r--r--tutorial/10-puzzle.text1
-rw-r--r--tutorial/2-tools.text1
-rw-r--r--tutorial/3-puzzle.lock17
-rw-r--r--tutorial/4-hook.lock13
-rw-r--r--tutorial/5-springs.lock17
-rw-r--r--tutorial/6-plug.text1
44 files changed, 419 insertions, 176 deletions
diff --git a/CursesUI.hs b/CursesUI.hs
index 0bc062e..6482f06 100644
--- a/CursesUI.hs
+++ b/CursesUI.hs
@@ -162,6 +162,17 @@ drawBindingsTables mode frame | mode `elem` [ IMEdit, IMPlay ] = do
]
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 =
@@ -176,6 +187,7 @@ drawBindingsTables mode frame | mode `elem` [ IMEdit, IMPlay ] = do
]
bindingsTable IMEdit GravRight =
[ (-7, BindingsEntry "help" [CmdHelp])
+ , (-6, BindingsEntry "bind" [CmdBind Nothing])
, (-4, BindingsEntry "place" $ map CmdTile
[ BlockTile []
, SpringTile Relaxed zero
diff --git a/CursesUIMInstance.hs b/CursesUIMInstance.hs
index 086e566..e63d80c 100644
--- a/CursesUIMInstance.hs
+++ b/CursesUIMInstance.hs
@@ -213,6 +213,8 @@ showHelpPaged' from mode HelpPageInput = do
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 @@ instance UIMonad (StateT UIState IO) where
getUIMousePos = return Nothing
setYNButtons = return ()
onNewMode _ = say ""
+ withNoBG = id
toggleColourMode = modify $ \s -> s {monochrome = not $ monochrome s}
diff --git a/Debug.hs b/Debug.hs
deleted file mode 100644
index 7f187d5..0000000
--- a/Debug.hs
+++ /dev/null
@@ -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
diff --git a/Interact.hs b/Interact.hs
index 2ba2fcc..034bb80 100644
--- a/Interact.hs
+++ b/Interact.hs
@@ -120,7 +120,7 @@ processCommand :: UIMonad uiM => InputMode -> Command -> ExceptT InteractSuccess
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 @@ processCommand' im CmdHelp = lift $ do
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 @@ processCommand' im CmdClear = do
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 @@ processCommand' IMMeta (CmdRegister _) = void.runMaybeT $ do
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 @@ processCommand' IMMeta CmdAuth = void.runMaybeT $ do
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 @@ processCommand' IMMeta CmdEdit = void.runMaybeT $ do
]
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 @@ processCommand' IMMeta CmdTutorials = void.runMaybeT $ do
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 @@ processCommand' IMPlay (CmdDrag pos dir) = do
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 @@ processCommand' IMEdit CmdWriteState = void.runMaybeT $ do
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 @@ setSelectedPos pos = do
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 }
diff --git a/InteractUtil.hs b/InteractUtil.hs
index ee66f1b..18518c7 100644
--- a/InteractUtil.hs
+++ b/InteractUtil.hs
@@ -130,6 +130,15 @@ declare undecl@(Undeclared soln ls al) = do
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
diff --git a/MainState.hs b/MainState.hs
index b597a4a..ddea46a 100644
--- a/MainState.hs
+++ b/MainState.hs
@@ -67,6 +67,7 @@ class (Applicative m, MonadIO m) => UIMonad m where
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 @@ data MainState
, psTitle::Maybe String
, psIsTut::Bool
, psIsSub::Bool
+ , psSaved::Bool
, psMarks::Map Char MainState
}
| ReplayState
@@ -132,8 +134,8 @@ data MainState
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 @@ ms2im mainSt = case mainSt of
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 :: MainState -> SavedPlayState
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 @@ metagameHelpText =
, "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."
, ""
diff --git a/NEWS b/NEWS
index ff947b0..83b5586 100644
--- a/NEWS
+++ b/NEWS
@@ -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).
diff --git a/SDLGlyph.hs b/SDLGlyph.hs
index 0ac9cf2..3bd7dad 100644
--- a/SDLGlyph.hs
+++ b/SDLGlyph.hs
@@ -38,6 +38,9 @@ data ShowBlocks = ShowBlocksBlocking | ShowBlocksAll | ShowBlocksNone
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 @@ data Glyph
| UseSoundsButton Bool
| WhsButtonsButton (Maybe WrHoSel)
| FullscreenButton Bool
+ | DisplacedGlyph HexDir Glyph
| UnfreshGlyph
deriving (Eq, Ord, Show)
@@ -137,11 +141,13 @@ renderGlyphCaching gl = do
-- 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 (BlockTile adjs) col) =
]
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 (WrenchTile mom) col) = do
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 @@ renderGlyph (FullscreenButton fs) = do
| 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 @@ drawBasicBG maxR = sequence_ [ drawAtRel (HollowGlyph $ colAt v) v | v <- hexDis
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 ()
diff --git a/SDLRender.hs b/SDLRender.hs
index 29cb84b..0f242d7 100644
--- a/SDLRender.hs
+++ b/SDLRender.hs
@@ -230,6 +230,7 @@ innerCorner dir = FVec x y
| 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 @@ edge dir = FVec x y
| 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
diff --git a/SDLUI.hs b/SDLUI.hs
index 416d2bb..156e938 100644
--- a/SDLUI.hs
+++ b/SDLUI.hs
@@ -87,7 +87,7 @@ data UIState = UIState { scrHeight::Int, scrWidth::Int
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 @@ data UIOptions = UIOptions
, 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 @@ getButtons mode = do
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 @@ drawMainGameState' args@(DrawArgs highlight colourFixed alerts st uiopts) = do
-- 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 @@ drawMainGameState' args@(DrawArgs highlight colourFixed alerts st uiopts) = do
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 @@ drawMainGameState' args@(DrawArgs highlight colourFixed alerts st uiopts) = do
-- 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
diff --git a/SDLUIMInstance.hs b/SDLUIMInstance.hs
index 4d08874..50ffd78 100644
--- a/SDLUIMInstance.hs
+++ b/SDLUIMInstance.hs
@@ -70,7 +70,8 @@ instance UIMonad (StateT UIState IO) where
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 @@ instance UIMonad (StateT UIState IO) where
, 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,6 +602,9 @@ instance UIMonad (StateT UIState IO) where
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
@@ -604,6 +612,14 @@ instance UIMonad (StateT UIState IO) where
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
showBT <- showButtonText <$> gets uiOptions
diff --git a/Server.hs b/Server.hs
index 49ea950..cac0c9e 100644
--- a/Server.hs
+++ b/Server.hs
@@ -177,7 +177,7 @@ handler dbpath delay logh mfeedPath hdl addr = handle ((\e -> return ()) :: Some
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:"
diff --git a/Version.hs b/Version.hs
index d093f8f..e795d1c 100644
--- a/Version.hs
+++ b/Version.hs
@@ -11,4 +11,4 @@
module Version where
version :: String
-version = "0.7.0.1"
+version = "0.7.1"
diff --git a/intricacy.cabal b/intricacy.cabal
index d07907c..d5a9190 100644
--- a/intricacy.cabal
+++ b/intricacy.cabal
@@ -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 @@ maintainer: mbays@sdf.org
-- 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 @@ executable intricacy
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 @@ executable intricacy-server
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
diff --git a/tutorial-extra/5-springChains.lock b/tutorial-extra/1-springChains.lock
index a1bc8eb..a1bc8eb 100644
--- a/tutorial-extra/5-springChains.lock
+++ b/tutorial-extra/1-springChains.lock
diff --git a/tutorial-extra/6-snookered.lock b/tutorial-extra/2-snookered.lock
index fcc33b8..fcc33b8 100644
--- a/tutorial-extra/6-snookered.lock
+++ b/tutorial-extra/2-snookered.lock
diff --git a/tutorial/6-plug.lock b/tutorial-extra/3-plug.lock
index 4756391..4756391 100644
--- a/tutorial/6-plug.lock
+++ b/tutorial-extra/3-plug.lock
diff --git a/tutorial-extra/7-plunger.lock b/tutorial-extra/4-plunger.lock
index 536b07a..536b07a 100644
--- a/tutorial-extra/7-plunger.lock
+++ b/tutorial-extra/4-plunger.lock
diff --git a/tutorial-extra/8-cogs.lock b/tutorial-extra/5-cogs.lock
index 7b2c972..7b2c972 100644
--- a/tutorial-extra/8-cogs.lock
+++ b/tutorial-extra/5-cogs.lock
diff --git a/tutorial/1-winning.lock b/tutorial/01-winning.lock
index 24a8eae..24a8eae 100644
--- a/tutorial/1-winning.lock
+++ b/tutorial/01-winning.lock
diff --git a/tutorial/1-winning.text b/tutorial/01-winning.text
index 20617c1..20617c1 100644
--- a/tutorial/1-winning.text
+++ b/tutorial/01-winning.text
diff --git a/tutorial/02-tools.lock b/tutorial/02-tools.lock
new file mode 100644
index 0000000..d1f5e5a
--- /dev/null
+++ b/tutorial/02-tools.lock
@@ -0,0 +1,11 @@
+ " " " " " "
+ " # # # # # " " "
+ " # # # # # # " "
+ " S S S S S S % % % "
+ " # # % " "
+ " # # # # # # "
+ " " # # # # # # "
+" * ' # # "
+ " @ " # # # # "
+ " " " # # # "
+ " " " " " "
diff --git a/tutorial/02-tools.text b/tutorial/02-tools.text
new file mode 100644
index 0000000..08d8c71
--- /dev/null
+++ b/tutorial/02-tools.text
@@ -0,0 +1 @@
+the wrench can fit where the hook gets stuck
diff --git a/tutorial/2-tools.lock b/tutorial/03-wrench.lock
index 3b04659..3b04659 100644
--- a/tutorial/2-tools.lock
+++ b/tutorial/03-wrench.lock
diff --git a/tutorial/03-wrench.text b/tutorial/03-wrench.text
new file mode 100644
index 0000000..47d36ec
--- /dev/null
+++ b/tutorial/03-wrench.text
@@ -0,0 +1 @@
+the wrench moves in a straight line until blocked
diff --git a/tutorial/04-hook.lock b/tutorial/04-hook.lock
new file mode 100644
index 0000000..61a3904
--- /dev/null
+++ b/tutorial/04-hook.lock
@@ -0,0 +1,13 @@
+ " " " " " " "
+ " % # # % " " "
+ " % Z # % " # "
+ " % % % Z % # "
+ " % # % % Z # " "
+ " % % Z % % Z # "
+ " % % % Z % % % # "
+ " % % % % % % % # "
+ " " % ] % # # "
+" * ' ] % % # "
+ " @ " # # % % 7 "
+ " " " % % % % 7 "
+ " " " " " " "
diff --git a/tutorial/4-hook.text b/tutorial/04-hook.text
index db81e0a..db81e0a 100644
--- a/tutorial/4-hook.text
+++ b/tutorial/04-hook.text
diff --git a/tutorial/05-blockWrench.lock b/tutorial/05-blockWrench.lock
new file mode 100644
index 0000000..176460b
--- /dev/null
+++ b/tutorial/05-blockWrench.lock
@@ -0,0 +1,11 @@
+ " " " " " "
+ " # " " "
+ " # # % " "
+ " # # S S % % % % "
+ " " "
+ " # # # # # "
+ " " # # # # "
+" * ' "
+ " @ " # # # # "
+ " " " "
+ " " " " " "
diff --git a/tutorial/05-blockWrench.text b/tutorial/05-blockWrench.text
new file mode 100644
index 0000000..d3647d6
--- /dev/null
+++ b/tutorial/05-blockWrench.text
@@ -0,0 +1 @@
+the hook can be used to guide the wrench
diff --git a/tutorial/06-balls.lock b/tutorial/06-balls.lock
new file mode 100644
index 0000000..323ce79
--- /dev/null
+++ b/tutorial/06-balls.lock
@@ -0,0 +1,11 @@
+ " " " " " "
+ " & & S S # " " "
+ " & & & # " "
+ " & & & & # # # # "
+ " & # " "
+ " # & & # "
+ " " O # & "
+" * ' # # O # "
+ " @ " # O # "
+ " " " # # "
+ " " " " " "
diff --git a/tutorial/06-balls.text b/tutorial/06-balls.text
new file mode 100644
index 0000000..1a9db2c
--- /dev/null
+++ b/tutorial/06-balls.text
@@ -0,0 +1 @@
+balls can be pushed around to allow or block movement
diff --git a/tutorial/07-pivots.lock b/tutorial/07-pivots.lock
new file mode 100644
index 0000000..aad90be
--- /dev/null
+++ b/tutorial/07-pivots.lock
@@ -0,0 +1,11 @@
+ " " " " " "
+ " \ # # " " "
+ " o - \ ' " # "
+ " % / " o - # "
+ " % % " " # # " "
+ " % % % " O # # "
+ " " . o - 7 "
+" * ' . o - & ` 7 "
+ " @ " O & & & "
+ " " " # # & & "
+ " " " " " "
diff --git a/tutorial/07-pivots.text b/tutorial/07-pivots.text
new file mode 100644
index 0000000..da72cd0
--- /dev/null
+++ b/tutorial/07-pivots.text
@@ -0,0 +1 @@
+push arms to rotate them around their pivots
diff --git a/tutorial/08-springs.lock b/tutorial/08-springs.lock
new file mode 100644
index 0000000..2b01cfb
--- /dev/null
+++ b/tutorial/08-springs.lock
@@ -0,0 +1,17 @@
+ " " " " " " " " "
+ " # # # # # # # # " " " "
+ " # # " "
+ " # # % % % % # " # # "
+ " # # # % % % # # # "
+ " % # # " "
+ " # # # # # % # # " "
+ " ] " S S # % # "
+" ] " % % % % % % # "
+ " ] " ] % % % % % 7 "
+ " ] % " ] % % % % 7 "
+ " " % % % " " % % S S & "
+" * ' % Z o - % % % % "
+ " @ " Z / & C C C C "
+ " " " Z & & & & "
+ " Z & & & "
+ " " " " " " " " "
diff --git a/tutorial/5-springs.text b/tutorial/08-springs.text
index 056cd9a..056cd9a 100644
--- a/tutorial/5-springs.text
+++ b/tutorial/08-springs.text
diff --git a/tutorial/09-traps.lock b/tutorial/09-traps.lock
new file mode 100644
index 0000000..53a5b25
--- /dev/null
+++ b/tutorial/09-traps.lock
@@ -0,0 +1,13 @@
+ & & & & & & &
+ & & & &
+ & % O % S S " & &
+ & # " " " " &
+ & " O " " & &
+ & " " # # # # # &
+ & " # &
+ & " " " # # # &
+ & & " } # # # &
+& * ' % # # # &
+ & @ & O # &
+ & & & # # # # &
+ & & & & & & &
diff --git a/tutorial/3-puzzle.text b/tutorial/09-traps.text
index 563dac8..563dac8 100644
--- a/tutorial/3-puzzle.text
+++ b/tutorial/09-traps.text
diff --git a/tutorial/10-puzzle.lock b/tutorial/10-puzzle.lock
new file mode 100644
index 0000000..9a87789
--- /dev/null
+++ b/tutorial/10-puzzle.lock
@@ -0,0 +1,17 @@
+ " " " " " " " " "
+ " " " " "
+ " # S S S S S % " "
+ " O " % " "
+ " S S & & & o - % % % % % % "
+ " & & # # " "
+ " # # # # # # # " "
+ " # # # "
+" # # # # # # # "
+ " # # "
+ " # O "
+ " " # % # # # # # "
+" * ' % % % # "
+ " @ " O % } % % % o # "
+ " " " % O % " / # "
+ " % O # "
+ " " " " " " " " "
diff --git a/tutorial/10-puzzle.text b/tutorial/10-puzzle.text
new file mode 100644
index 0000000..fda59dd
--- /dev/null
+++ b/tutorial/10-puzzle.text
@@ -0,0 +1 @@
+your final test!
diff --git a/tutorial/2-tools.text b/tutorial/2-tools.text
deleted file mode 100644
index 6dc8250..0000000
--- a/tutorial/2-tools.text
+++ /dev/null
@@ -1 +0,0 @@
-the wrench can fit where the hook can't, but keeps moving until it hits something
diff --git a/tutorial/3-puzzle.lock b/tutorial/3-puzzle.lock
deleted file mode 100644
index 58a9289..0000000
--- a/tutorial/3-puzzle.lock
+++ /dev/null
@@ -1,17 +0,0 @@
- & & & & & & & & &
- & & & & &
- & # S S S S S " & &
- & O % " & &
- & S S # # # o - " " " " " " &
- & # # % # & &
- & % % # # # # # & &
- & % % # &
-& % % % # # # # &
- & % % &
- & % O &
- & & " % % % % % &
-& * ' " " " " " \ % &
- & @ & " " " o % &
- & & & " % &
- & " % &
- & & & & & & & & &
diff --git a/tutorial/4-hook.lock b/tutorial/4-hook.lock
deleted file mode 100644
index 0bd9ac8..0000000
--- a/tutorial/4-hook.lock
+++ /dev/null
@@ -1,13 +0,0 @@
- & & & & & & &
- & # # % & & &
- & Z # % & # &
- & " " " Z % # &
- & # C C " Z # & &
- & % " " " Z # &
- & 7 " " " " " # &
- & 7 " " " " " % # &
- & & " " " " " # # &
-& * ' " " " " " " # &
- & @ & " " " " " " 7 &
- & & & " " " " " 7 &
- & & & & & & &
diff --git a/tutorial/5-springs.lock b/tutorial/5-springs.lock
deleted file mode 100644
index eb34892..0000000
--- a/tutorial/5-springs.lock
+++ /dev/null
@@ -1,17 +0,0 @@
- " " " " " " " " "
- " # # # # # # # " " " "
- " # # # " "
- " # # % % # " # # "
- " # # # % S S # # # # "
- " # " "
- " & & % % % # # " "
- " ] " S S % # "
-" ] " # # # # # "
- " ] " ] # # # # 7 "
- " ] # " ] # # # 7 "
- " " # # # " " # # S S % "
-" * ' # Z o - # # # # "
- " @ " Z / % C C C C "
- " " " Z % % % % "
- " Z % % % "
- " " " " " " " " "
diff --git a/tutorial/6-plug.text b/tutorial/6-plug.text
deleted file mode 100644
index eac1226..0000000
--- a/tutorial/6-plug.text
+++ /dev/null
@@ -1 +0,0 @@
-compress the central springs with both tools at once