For-loop takes very long. Can I make it more efficient?

Good evening,

for a mouse dexterity game, I have the following activity in one of my passages:

<<set $countDownTime to 0>><<timerUp>><<set $amount to 12>><<set $initialAmount to $amount>><<set $levelBase to $amount*10>>
<h2>Click on the circles between the letters, but avoid the red one!</h2>
<<set $letterArray to []>>
<<set _letters to "abcdefghijklmnopqstuvwxyz">>
<<for _i=0;_i<1000;_i++>>
	<<set _rnd to random(_letters.length)>>
	<<set _randomLetter to _letters.charAt(_rnd)>>
	<<set $letterArray.push(_randomLetter)>>				
<</for>>

<<for _i=0;_i<$amount;_i++>>\
	<<set _pos to random($letterArray.length)>>
	<<set $letterArray[_pos] to '<span class="dot" id="dot-' + _i + '"><<link "●">><<replace "#dot-' + _i + '">>
	<span class="black">●</span><</replace>><<audio "shoot" play>><<set $amount-->><<replace "#levelPoints" >>$amount out of $initialAmount<</replace>><<if $amount eq 0>><<timerStop>><<goto "levelFinished">><</if>><</link>></span>'>>
<</for>>\

<<set _posToReplace to random($letterArray.length)>>
<<set $letterArray[_posToReplace] to '<span class="red" id="boom"><<link "●">><<audio "wrong" play>><</link>></span>'>>

<<for _i=0; _i<$letterArray.length; _i++>>
	<<print $letterArray[_i]>>
<</for>>

It works as expected, but takes a VERY long time to load - so much, that Firefox asks what to do with tab. I guess this is due to the two very long “for” cycles.
Is there any thing I can do to accelerate the code?

Please use the optional tags section of the New Topic form to specific which Story Format (name & version) you are using, as answers can vary based on this information. Based on the syntax of your example I will assume you are using a version of SugarCube.

There are a number of issues with your example…

1: Each line-break within a Passage’s content will be automatically converted to a HTML <br> element. Most of the ones within your example play no useful purpose in the HTML output you are aiming to generate, and it takes time to inject each of those unnecessary <br> elements into the Document Object Model of the current page.

In this specific case can use the <<silently>> macro to absorb all the unnecessary visual output generated by your code. Another useful macro you can use to a similar outcome is <<nobr>>

2: A long String literal, like the one you are assigned to $letterArray[_posToReplace], is not meant to have a line-break within it.

/* BAD */
<<set $variable to "<span>some text</span>
<span>some more text</span>">>

/* GOOD */
<<set $variable to "<span>some text</span>" +
	"<span>some more text</span>">>

If you want a <br> element to be injected between the <<replace>> macro and the <span> element within that macro’s contents then simply add that element to the String literal.

<<set $variable to "<span>some text</span>" +
	"<br><span>some more text</span>">>

3: Each calling of the <<print>> macro results in the updating of the page’s DOM, and it takes time to do.that update. In your specific case you could use the JavaScript <array>.join() function to concatenate all the elements of the $letterArray variable to form a single String value, and then use a single <<print>> macro to inject the HTML elements contained within that string into the page’s DOM.

The following is a modify version of your example with the above changes made to it.

<<silently>>
	<<set $countDownTime to 0>>
	<<timerUp>>
	<<set $amount to 12>>
	<<set $initialAmount to $amount>>
	<<set $levelBase to $amount * 10>>
<</silently>>\
<h2>Click on the circles between the letters, but avoid the red one!</h2>
<<silently>>
	<<set $letterArray to []>>
	<<set _letters to "abcdefghijklmnopqstuvwxyz">>
	<<for _i = 0; _i < 1000; _i++>>
		<<set _rnd to random(_letters.length)>>
		<<set _randomLetter to _letters.charAt(_rnd)>>
		<<set $letterArray.push(_randomLetter)>>				
	<</for>>

	<<for _i = 0; _i < $amount; _i++>>
		<<set _pos to random($letterArray.length)>>
		<<set $letterArray[_pos] to '<span class="dot" id="dot-' + _i + '"><<link "●">><<replace "#dot-' + _i + '">>' +
			'<span class="black">●</span><</replace>><<audio "shoot" play>><<set $amount-->><<replace "#levelPoints" >>$amount out of $initialAmount<</replace>><<if $amount eq 0>><<timerStop>><<goto "levelFinished">><</if>><</link>></span>'>>
	<</for>>

	<<set _posToReplace to random($letterArray.length)>>
	<<set $letterArray[_posToReplace] to '<span class="red" id="boom"><<link "●">><<audio "wrong" play>><</link>></span>'>>
<</silently>>\
<<print $letterArray.join('<br>')>>

You could also shorten this:

	<<set $letterArray to []>>
	<<set _letters to "abcdefghijklmnopqstuvwxyz">>
	<<for _i = 0; _i < 1000; _i++>>
		<<set _rnd to random(_letters.length)>>
		<<set _randomLetter to _letters.charAt(_rnd)>>
		<<set $letterArray.push(_randomLetter)>>				
	<</for>>

to just this:

	<<set $letterArray to []>>
	<<for _i = 0; _i < 1000; _i++>>
		<<set $letterArray.push(String.fromCharCode(random(97, 122)))>>
	<</for>>

That uses ASCII values 97 through 122, which are converted to “a” through “z” using the String.fromCharCode() method.

Hope that helps! :slight_smile: