Changing Datamaps in for loops

So when player clicks “buy” _item’s number should go up by one, but it isn’t… why? Printing _item’s number works fine, by the way, so it isn’t being set incorrectly in the datamap.

(for: each _item, ...$products)[
	(print: (upperfirst: _item's name + "s")): $(print: _item's cost)
<br>
(link-repeat: "Buy")[
	(if: $dollars >= _item's cost)[
		(set: _item's number to it + 1)
		(set: $dollars to it - _item's cost)
	]
]
<br><br>
]

note: You didn’t supply an example of the data structure contained within your $products variable, so I will assume it is something like…

(set: $products to (a:
	(dm: "name", "Apple", "cost", 10, "number", 0),
	(dm: "name", "Banana", "cost", 8, "number", 0),
	(dm: "name", "Pear", "cost", 6, "number", 0)
))

warning: for simplistically sake, the following information may not be 100% technically correct.

There are a couple of conceptual issues with your example:

  1. Harlowe has interesting scoping rules when it comes to temporary variables and associated hooks (of macros like (for:) and (link-repeat:)), often such hooks are given their own unique “copy” of current state of all known temporary variables which means that changes made to the value of those variables may not effect the original instance of that temporary variable.
  2. Whenever you use the (set:) macro to assign/update a value to/of a property of an existing collection object in Harlowe the macro first clones the original referenced object before doing the property assignment. This cloning process will break object referential integrity if the original object instance was referenced by more that one variable.

So based on the above information it is possible/likely that:

  1. the “item” object being referenced by the _item variable used in the (for:) macro’s associated hook and the “item” object being referenced by the _item variable used in the (link-repeat:) macro may not be the same “item” object to begin with.
  2. the above two _item variables will definitely not be referencing the same “item” object after the (set:) macro has been called.
  3. the resulting “item” object being referenced by either of the above _item variables is not the same “item” object contained within the $products variable.

The way you get around this scoping/cloning issue is to always update the original collection directly, in your case the collection contained within the $products variable.

The following uses the (range:) macro to generate a set of integer indexes that can be used to reference each element within the $products collection, and uses that same index to directly update elements of that same collection.

(for: each _index, ...(range: 1, $products's length))[
	(set: _item to $products's (_index))\
	(print: (upperfirst: _item's name + "s")): $(print: _item's cost)
	(link-repeat: "Buy")[{
		(if: $dollars >= _item's cost)[
			(set: $products's (_index)'s number to it + 1)
			(set: $dollars to it - _item's cost)
		]
	}]
]

note: The above example uses a _item temporary variable to help shorten all but one reference to the elements of the collection within the $products variable, this temporary variable can be removed if you prefer but you will then need to fully reference the $products variable each time…

(for: each _index, ...(range: 1, $products's length))[
	(print: (upperfirst: $products's (_index)'s name + "s")): $(print: $products's (_index)'s cost)
	(link-repeat: "Buy")[{
		(if: $dollars >= $products's (_index)'s cost)[
			(set: $products's (_index)'s number to it + 1)
			(set: $dollars to it - $products's (_index)'s cost)
		]
	}]
]
1 Like

Excellent answer, thank you so much. It works perfectly. And I noticed that shadow variable in the debug mode! I’m glad you provided an explanation for it.