Updates on iterations in for loop

Twine Version:
Sugarcube 2.37.3

So, I’m basically trying to make an inventory system that acts like an online shopping cart where the delete function kind of works: it deletes all iteration text rather than what should be the selected iteration of the for loop. It also seems to delete the first iteration but not the selected iteration.

So far what seems to be a possible solution that I have no idea how to implement is each iteration has an id selector that I then replace with empty space. Is there a way I can do that dynamically or is there a better solution for my conundrum?

Conundrum Code:

 ::JavaScript
window.Apples = {
id: "window.Apples",
InventoryName: "Apples",
TextName: "apples",
}
window.Oranges = {
id: "window.Oranges",
InventoryName: "Oranges",
TextName: "oranges",
}
window.Bananas = {
id: "window.Bananas",
InventoryName: "Bananas",
TextName: "bananas",
}
:: StoryInit
<<set $Inventory = [ ]>>
<<set $PickupableItems = [window.Apples, window.Oranges, window.Bananas]>>
::Start
Choose what direction to travel:
<<link "Left" "Directions">><<set $Direction = "Left">><</link>>
<<link "Forward" "Directions">><<set $Direction = "Forward">><</link>>
<<link "Right" "Directions">><<set $Direction = "Right">><</link>>

<<link "Look into satchel" "InventoryDisplay">><</link>>
:: Pickup Item
<<set _RandomItem = $PickupableItems.random( )>>
You found <<= _RandomItem.TextName>>!
<<link "Pick it up" "Start">><<set $Inventory.push(_RandomItem)>><</link>>
::InventoryDisplay
<<if $Inventory.length is 0 >>
Your inventory is empty.
<<else>>
<<for _i = 0; _i < $Inventory.length; _i++>>
<span id= "Item">$Inventory[_i].InventoryName</span>
<<linkreplace "Toss Item">><<run $Inventory.deleteAt(' + _i + ')>><<replace "#Item">><<nobr>><</nobr>><</replace>><</linkreplace>>
<</for>>
<</if>>
<<return "Look back up">>
::Directions
<<switch $Direction>>
<<case "Left">>
<<set _Scenery = ["garden", "forest", "field"]>>
<<set $KeepScenery = _Scenery.random( )>>
You walk into a $KeepScenery where you see something laying down on the ground.
<<include "Pickup Item">>
<<link "Look into satchel" "InventoryDisplay">><</link>>
<<case "Forward">>
<<set _Scenery = ["castle", "town", "market"]>>
<<set $KeepScenery = _Scenery.random( )>>
You walk into a $KeepScenery where you see something laying down on the ground.
<<include "Pickup Item">>
<<link "Look into satchel" "InventoryDisplay">><</link>>
<<case "Right">>
<<set _Scenery = ["fort", "barracks"]>>
<<set $KeepScenery = _Scenery.random( )>>
You walk into a $KeepScenery where you see something laying down on the ground.
<<include "Pickup Item">>
<<link "Look into satchel" "InventoryDisplay">><</link>>
<</switch>>

Adding for fixing a bug within solution: insert <<goto InventoryDisplay>> within the Toss Item link code. This allows the ability to delete the last item within inventory if you delete within the middle of the inventory list

The loop you made wraps each element in a span with the same ID (<span id= "Item">), so when you try to replace the content of #Item, it will do as told and replace the first one it finds. You can assign a different idea for each iteration, like:
<span @id= "'Item-' + _i "> (which will translate as #Item-0, #Item-1)

EDIT: scrap below, see this response Updates on iterations in for loop - #4 by manonamora
But it won’t really solve the issue, because what is inside the <<link>> macro is run after the loop is completed (so the _i won’t ever be correct).

I would suggest doing a widget instead, something like:

<<for _i = 0; _i < $Inventory.length; _i++>>
<<print  '<<'+$Inventory[_i].InventoryName+'>>'>>
<</for>>

::Widget
<<widget "Oranges">>
   <span id= "Oranges">Oranges
        <<linkr "Toss Item">><<run $Inventory.deleteFirst('Oranges')>><<remove"#Oranges">><</link>>
   </span>
<</widget>>

(and then the same thing for the other objects)

The content of your linkreplace macro is evaluated at runtime, when the loop has ended.

For this pattern (links generated from a looped collection) the content of the loop is usually wrapped in a <<capture _i>> macro (see doc here), which will make the inner macros use the saved value rather than the one read at runtime.

OMG I keep forgetting about <<capture>> being a thing…

I’ve tried the code they provided + edits for <<capture>>,

<<for _i = 0; _i < $Inventory.length; _i++>>
<<capture _i>>
<span @id= "'Item-' + _i ">$Inventory[_i].InventoryName
<<link "Toss Item">><<run $Inventory.deleteAt(_i)>><<remove`"#Item-"+ _i`>><</link>></span>
<</capture>>
<</for>>

and it works: removed the element + the correct item in the array.

2 Likes

It works! Thank you so much, this conundrum felt like I was hitting my head against the wall

Is this where capture was supposed to be used? I saw the documentation for it but didn’t see the use of it till you pointed it out here. I’ll run through the documentation again to see where else some ‘useless’ macros might actually be used

<<capture>> is considered a more advanced macro, where it’s use is pretty specific (like your case).
But it can also be used in other cases, like:

       <<set _n = 1>>
        <<button "Non-Captured Value">>
            <<run alert(_n)>>
        <</button>>
        <<capture _n>>
            <<button "Captured Value">>
                <<run alert(_n)>>
            <</button>>
        <</capture>>
        <<set _n = 4>>

Ok so this code works but there seems to be a bug where if I delete a middle item from the inventory and then attempt to delete the last item of the inventory, it doesn’t actually be removed from the inventory array. Do you know a fix to that as well?

After removing an item from the middle of a collection, the index you had for the last element won’t be valid. I’d recommend to re-render the passage so everything is reconstructed. A goto to the same passage should do nicely.

Ah, right… I only tested one at a time. But forgot that deleting more than one element at once will change the order in the array :thinking:

You might want to swap the deleteAt() with deleteWith(), where you can use a function to check the id of an object that you want to delete. Docu on deleteWith

$Inventory.deleteWith(function (val) {
	return val.InventoryName=== $Inventory[_i].InventoryName;
})

(untested)

This runs into the problem of deleting any duplicate names which isn’t something I’m really wanting to do, just the selected iteration and placement within the $Inventory array. The <<goto>> solution from n-n works the way I was hoping

1 Like