Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

How to Create Loops in Twine

edited February 2014 in Workshop
It's come up a couple different times in the forum, but there's not been a super-simple example of basic loops in Twine.

Loops are basic fundamental programming and can be used for many, many different things in Twine. I'll leave their use to your imagination.

In Twine, loops are passages. To run a loop, you display a loop passage. In versions of Twine earlier than 1.4, or for passages with titles of more than one word, passages are displayed like this:
<<display 'Passage Name'>>
In Twine 1.4+ for passages with single-word titles, we can simply do this:
<<Passage>>
Part of what makes a loop passage a loop is that it displays itself. It "loops" from its end back to its beginning over and over.

For example:

[quote]Passage

<<Passage>>

As can probably be guessed, displaying Passage inside Passage will cause an infinite loop in a "hall of mirrors." Eventually, it will crash with the error, "Too much recursion."

So, we need to add a counter so that it displays itself only as many times we want it to do so.

Let's take a look at how to do that.

[quote]Start

<<loop>>

We're done!

Again, "<<loop>>" is not a predefined macro. It is the name of a passage. It can be whatever you want to name the passage that executes the loop. It will work the same if <<display 'loop'>> is used.

Now, let's look at the passage titled "loop."

[quote]loop

<<if $i lt 10>>

Hello!

<<set $i = $i + 1>>

<<loop>>

<<else>>

<<set $i = 0>>

<<endif>>

First of all, in Twine 1.4+, all variables are set to '0' by default. So, since we have not set $i, its value is '0.'

When the loop begins, the if conditional branch checks to see if the generic variable $i is less than 10. You can set the number higher or lower, but it will continue to "loop" until the variable reaches that number.

At first, the variable is 0, which is less than 10, so it will execute the code. In this case, the code is "Hello!<<set $i = $i + 1>><<loop>>" so it will print "Hello!" on the screen, then increase the generic variable, $i, by 1. Then, it will display the "loop" passage again from the top.

The "loop" passage will display ten times, thus printing "Hello!" ten times and increasing $i by 1 each time.

At that point, the if conditional branch will return false, because $i is equal to 10 and thus not less than 10. The passage will then set $i back to '0' (that's the "<<else>><<set $i = 0>><<endif>>" code) so the loop can be ran again if needed.

Of course, if you change "<<if $i lt 10>>" to "<<if $i lt 20>>", it will print "Hello!" 20 times.

That's it! Pretty simple, basic stuff, but it can be used for powerful effects. :)

Attached is an example TWS and HTML file.

Hope this helps someone! :)

Comments

  • In my original passage I have:
    <<set $huntLoop to 1>>\
    <<set $availPop to 10>>\

    <<huntLinks>>
    Then, in my "huntLinks" passage, I have:
    <<if $huntLoop <= $availPop >>\
    [[<<$huntLoop>>|hunt][$hunters = $huntLoop; $availPop -= $huntLoop]]
    <<set $huntLoop += 1>>\
    <<huntLinks>>
    <<endif>>\
    Each link is supposed show a number (1 to 10) and when clicked, set $hunters to the value shown, and subtract that from $availPop.

    It prints a list of links that are numbers 1 to 10 as I intended. However, it seems Twine throws whatever the last value of $huntLoop was into the expressions for EVERY link. ALL of the links set $hunters to 11, and $availPop to -1...

    I've run into this problem before, before setter links existed in Twine. I'm guessing L knows exactly why Twine does this -- any workarounds, or bug fixes?
  • Could we think of a way to create a more generic and versatile loop macro? As it is, we need to write one specific loop macro every time we want to do something different. Can't we think of writing one that would be more generic? Like a true "while" loop.

    <<while CONDITION>>
    the looped code
    <<endwhile>>
  • Firstly, what narF said. That would be awesome.

    Secondly, I know I should be able to figure this out myself, but I'm having trouble wrapping my head around it. At various points in my game I want to set a variable to a value from an array (I figured that part out! :P), and then loop until that value doesn't match either value chosen the last 2 times this happened. I think I need to store a variable for the last one and the one before and check the current one against them in a loop, just struggling to actually code it.

    Hope this makes sense, it's 3:20am here and I'm a little fuzzy. :) Thanks for reading!
  • I think you might want to pass those two values to an array too. But a bit of your code would help with advice, I think :)
  • mostly wrote:
    At various points in my game I want to set a variable to a value from an array (I figured that part out! :P), and then loop until that value doesn't match either value chosen the last 2 times this happened. I


    Right now, I don't have time to give you a full answer, but remember there is "neq", meaning "not equal to."

    So, "do this until $a is neq $b or $c". Something like that should get you going.

    If not, I'll be around tonight. :)
  • Thanks peeps, I just figured it out! It was pretty simple, but here it is in case anyone else tries to do it when their brain is asleep. I've used cheeses to avoid spoilers. This is what I have:

    In my StoryInit (in Sugarcube, or Start in Sugarcane) passage:
    <<set $cheeses = ["edam", "double gloucester", "cheddar", "wensleydale", "brie", "gorgonzola", "kraft slice"] >>
    Then in my CheeseTime passage:
    <<if $lastcheese != 0>><<set $beforecheese = $lastcheese>><<endif>>
    <<if $currentcheese != 0>><<set $lastcheese = $currentcheese>><<endif>>
    <<ChooseCheese>>
    And in the ChooseCheese passage:
    <<set $currentcheese to $cheeses.random()>>
    <<if $currentcheese is $lastcheese or $currentcheese is $beforecheese>>
    <<choosecheese>>
    <<endif>>
  • Sharpe wrote:
    So, "do this until $a is neq $b or $c". Something like that should get you going.

    No!  "do this until $a is neq $b or $a is neq $c"
  • I'd hoped that was gathered.
  • It is such a common mistake, I think it best spelt out properly each time.

    In my opinion the best way to loop in Twine is in JavaScript.

    An alternative CheeseTime:
    <<if $lastcheese != 0>><<set $beforecheese = $lastcheese>><<endif>>
    <<if $currentcheese != 0>><<set $lastcheese = $currentcheese>><<endif>>
    <<set do {
    $currentcheese to $cheeses.random();
    } while ($currentcheese == $lastcheese || $currentcheese == $beforecheese)>>
    <<print $currentcheese>>
    Or do it as a macro in a script passage (but note all these variables are local to the script:
    var cheeses = ["edam", "double gloucester", "cheddar", "wensleydale", "brie", "gorgonzola", "kraft slice"];

    macros.cheesetime = {
    handler: function(place, macroName, params, parser) {
    if (state.active.variables.lastcheese != 0) state.active.variables.beforecheese = state.active.variables.lastcheese;
    if (state.active.variables.currentcheese != 0) state.active.variables.lastcheese = state.active.variables.currentcheese;
    do {
    state.active.variables.currentcheese = cheeses.random();
    } while (state.active.variables.currentcheese == state.active.variables.lastcheese || state.active.variables.currentcheese == state.active.variables.beforecheese);
    new Wikifier(place, state.active.variables.currentcheese);
    },
    };
  • I agree that breaking into JavaScript is often the most concise way to handle loops, but for people who don't know JS, Twine loops are quick, easy and legible.
Sign In or Register to comment.