Password/Keypad For Twine

I am using Harlowe 3.0.2/Twine 2.3.1 download. I have been wanting to create a keypad password for a safe. My original idea was to create a box like the following…

</span>]|box>[ [[1]] [[2]] [[3]]]
|box>[ [[4]] [[5]] [[6]]]
|box>[ [[7]] [[8]] [[9]]]
	  |box>[ [[0]]]

But then I realised that I would have to manually create a new keypad for every correct button pressed. I wanted to create an eight character password, meaning I would need eight versions of the keypad. I thought to use the if/else statements in the Twine Cookbook but realised it would need exactly the same amount of empty passages needed.

So is there a way to create a password without needing to manually create so many passages? Thanks in advance.

The following solution makes use of three Passages:

1 The Passage that contains the visual grid of ‘buttons’, in my example it is named Keypad but you could name it whatever you like as long as you alter the references to it. It uses a HTML <table> to layout the buttons, the (link-repeat:) macro to allow those ‘buttons’ to be selected multiple times without needing to ‘refresh’ the grid, and the (display:) macro to ‘execute’ the same code for each ‘button’ selected.
The HTML comment at the top of this Passage’s contents explains which Story Variables ($keycode and $guess) and Hidden Hook (correct) are required for the Keypad to work correctly. It also mentions the optional Hidden Hook (wrong) and Named Hook (entered) that can be used to extend the Keypad’s functionality.

{
<!--
	Keypad.

	requires:
		keycode		Story variable which contains the Correct answer.
		guess		Story variable which contains the current sequences of key presses.
		correct		Hidden Hook to show/execute when guess equals keycode.
	
	optional:
		wrong		Hidden Hook to show/execute when guess does not equal keycode.
		entered		Named Hook to display current guess in.
-->
<table id="keypad">
	<tr>
		<td>(link-repeat: "1")[{(set: _keypress to "1")(display: "Keypad Keypressed")}]</td>
		<td>(link-repeat: "2")[{(set: _keypress to "2")(display: "Keypad Keypressed")}]</td>
		<td>(link-repeat: "3")[{(set: _keypress to "3")(display: "Keypad Keypressed")}]</td>
	</tr>
	<tr>
		<td>(link-repeat: "4")[{(set: _keypress to "4")(display: "Keypad Keypressed")}]</td>
		<td>(link-repeat: "5")[{(set: _keypress to "5")(display: "Keypad Keypressed")}]</td>
		<td>(link-repeat: "6")[{(set: _keypress to "6")(display: "Keypad Keypressed")}]</td>
	</tr>
	<tr>
		<td>(link-repeat: "7")[{(set: _keypress to "7")(display: "Keypad Keypressed")}]</td>
		<td>(link-repeat: "8")[{(set: _keypress to "8")(display: "Keypad Keypressed")}]</td>
		<td>(link-repeat: "9")[{(set: _keypress to "9")(display: "Keypad Keypressed")}]</td>
	</tr>
	<tr>
		<td></td>
		<td>(link-repeat: "0")[{(set: _keypress to "0")(display: "Keypad Keypressed")}]</td>
		<td></td>
	</tr>
</table>
}

2 The Passage which contains the code that is executed each time a ‘button’ is selected, in my example it is named Keypad Keypressed but you can change this too. It uses (if:) macros to determine the length of the guess and if that guess was correct or not, the (replace:) macro the optional Named Hook (if it exists), and the (show:) macro to ‘execute’ the contents of the ‘correct’ (and optional ‘wrong’) Hidden Hook as needed.

(if: $guess's length < $keycode's length)[
	(set: $guess to it + _keypress)
	(replace: ?entered)[$guess]
	(if: $guess's length is $keycode's length)[
		(if: $guess is $keycode)[
			(show: ?correct)
		]
		(else:)[
			(show: ?wrong)
		]
	]
]

3 The Passage in which you want the Keypad to be displayed, in my example this was named Guess Code. You need to initialise the required Story Variables ($keycode and $guess), display the ‘Keypad’ grid, and define the Hidden Hook (correct) who’s contents will be executed when the guess equals the keycode.

Enter the 8 number passcode
(set: $keycode to "87654321")\
(set: $guess to "")\
(display: "Keypad")
|correct)[
	You guessed correctly.
]

The following is a variation of the above Passage that includes both of the optional ‘Keypad’ options.

Enter the 8 number passcode
(set: $keycode to "87654321")\
(set: $guess to "")\
(display: "Keypad")
|entered>[]
|correct)[
	You guessed correctly.
]
|wrong)[
	You guessed wrong!
	(link-goto: "Try Again", "Guess Code")
]

Place the following CSS within your project’s Story Stylesheet area, it adds some styling to the <table> element used to display the ‘button grid’.

#keypad { 
    border-spacing: 10px;
    border-collapse: separate;
	display: inline-block;
}
#keypad td {
	width: 38px;
	height: 38px;
	text-align: center;
	border: 1px solid white;
}
#keypad td tw-hook, #keypad td tw-hook tw-link {
    vertical-align: middle;
	display: inline-block;
	width: 100%;
	height: 100%;
}
#keypad td tw-hook tw-collapsed tw-hook {
	display: none;
}
#keypad tr:last-of-type td:not(:nth-child(2)) {
	border: none
}

It works perfectly on its own, but I’m starting to experience slowdown when I add it to the rest of the coding in my passage, stalling for several seconds after a key is pressed. Any ideas?

(set: $timertwo to 300)(display: "Timer")
[[Check your notebook|Possible Codes]]

(align:"<===")|nox>[Safe Security] (align:"<===")[<span class="hides">[[Reset Button?|Correct]]</span>](align:"===>")|vox>[ [Model Number] ] (align:"===>")[<span class="hides">[[Reset Button?|Wrong]]</span>]
(set: $keycode to "87654321")\
(set: $guess to "")\
(display: "Keypad")
|entered>[]
|correct)[
	You guessed correctly.
	(link-goto:"Correct",)
]
|wrong)[
	You guessed wrong!
	(link-goto: "Try Again",)](align:"<===")|lox>[ [Serial Number] ](align:"<===")[<span class="hides">[[Reset Button?|Wrong]]</span>]
(click: ?link)[(go-to: "Correct|Reset Button?")]
(click: ?link)[(go-to: "Wrong|Reset Button?")]

This is the Stylesheet.

tw-passage[tags~="middle"] {
   text-align: center; 

}

tw-include[type="startup"]{
	display: none;
}
tw-sidebar {
  	display:none;
}

tw-passage {
    font-family: literaturnaya;
}

.hidden tw-link, .hidden .enchantment-link {
	color: transparent;
}

tw-include[title="Hidden Link Setup"] {
	display: none;
}

tw-hook[name="box"] {
	border: 3px solid blue;
}

tw-hook[name="nox"] {
	border: 3px solid green;
}

tw-hook[name="vox"] {
	border: 3px solid yellow;
}

tw-hook[name="lox"] {
	border: 3px solid red;
}

tw-hook[name="border"] {
	border: 3px solid white;
}

/* twine-user-stylesheet #1: "User Style" */
tw-include[type="startup"], tw-hook[name="workarea"] {
	display: none;
}
tw-hook[name="map"] {
    font-family: monospace; 
	line-height: 1.75;
	font-size: 16pt;
}

#keypad { 
    border-spacing: 10px;
    border-collapse: separate;
	display: inline-block;
}
#keypad td {
	width: 38px;
	height: 38px;
	text-align: center;
	border: 1px solid white;
}
#keypad td tw-hook, #keypad td tw-hook tw-link {
    vertical-align: middle;
	display: inline-block;
	width: 100%;
	height: 100%;
}
#keypad td tw-hook tw-collapsed tw-hook {
	display: none;
}
#keypad tr:last-of-type td:not(:nth-child(2)) {
	border: none
}

background:

  1. Each instance of a (click:) family macro waits until all of the HTML elements generated by the processing of the ‘current’ Passage have been added to the current web-page (rendered) before it scans the entirety of that page (it’s Document Object Model) looking for every occurrence of its target text or Named Hook, and the scanning process of all of those (click:) instances may be repeated each time the DOM is dynamically changed.

  2. Each time the link associated with a (link-repeat:) macro is selected it results in additional (custom) HTML elements being dynamically added to the DOM.

  3. I’m going to assume your “Timer” Passage involves the usage of a (live:) macro, which when triggered often results in the DOM being dynamically altered.

Try replacing your (click:) macro usage with one of (link:) family of macros, and see if that makes a difference.

note: Based on the related documentation your (link-goto: "Try Again",) call includes an invalid coma, assuming you want the Link Text to be the same as the Target Passage Name.

I deleted the the two lines…

(click: ?link)[(go-to: "Correct|Reset Button?")]
(click: ?link)[(go-to: "Wrong|Reset Button?")]

They were redundant as their function was already being taken care of by the [[ ]] lines in the body of the passage.

Thanks for pointing out which Macro was the one slowing the system down!