Counting while Adding and Subtracting Objects from Array

Please specify version and format if asking for help, or apply optional tags above:
Twine Version:2.3.13
Story Format: 2.34.1

I’m back with another question about arrays and a dumb piece of code that I cannot figure out. I’m so sorry for the frequent questions. Y’all have been beyond helpful so far.

I’m trying to have it read out the array and then give the option to buy the item for each thing in the array. I can get it to read out the contents, and take away the right amount of money and even delete the item when the .stock hits 0.

However, some items have more than one in stock (see properties) and I’ve got it so that when the link is clicked the number in stock decreases but I can’t for the life of me get it to then add a copy of that item ONCE to the player’s array ($pcarr) and then ONLY change the value of .number after that. I currently have it going through using .includes but it’s giving me clones instead of just adding to the number. I wondered if someone could enlighten me, I feel like I’m staring it in the face and can’t figure it out.

I’m using the prototype setup.VarUtils provided by the lovey HiEv if that’s of use.

::Setup Passage::

<<set $pcarr to []>>

You enter a shop and see a table with three items.
<<set $money to 100>>

<<set $pouch to {
name: "A Strange Pouch",
description: "A small leather pouch with gold braided string as the cinch draw.",
category: "Utility",
value: 20,
cansell: true,
number: 0,
stock: 3,
}>>

<<set $wolf to {
name: "A Glass Wolf",
description: "A glass figurine of a wolf. It's teeth are so sharp you have to squint to see where the glass stops.",
category: "Cursed object",
value: 40,
cansell: true,
number: 0,
stock: 1,
}>>

<<set $mug to {
name: "A Stained Mug",
description: "A ceramic mug with an opalescent gleam on the outside and a myriad of rainbow stains on the inside.",
category: "Blessed object",
value: 70,
cansell: true,
number: 0,
stock: 1,
}>>

<<set $shoparr to []>>
<<set $shoparr.push($pouch, $wolf, $mug)>>

 
::Item Shop::
You have $money g remaining. What item would you like to buy?


TEST ITEMS To SELL
	
 <<nobr>><<for _i to 0; _i lt $shoparr.length; _i++>>
	<<set _item to $shoparr[_i]>>
 	<<capture _item>>
	<p><big><<print $shoparr[_i].name>></big></p>
	<li>Description: <<print $shoparr[_i].description>></li>
	<li>Category: <<print $shoparr[_i].category>></li>
	<li>Value: <<print $shoparr[_i].value>>g</li>
	<li>Stock: <<print $shoparr[_i].stock>></li>
	
	<<if $money lt $shoparr[_i].value>>You don't have enough gold for this<<else>><<if $shoparr[_i].cansell is false>><<else>>
			<<link "Buy Item" 'Actual Shop 3'>>
				<<set _j to $shoparr.indexOfObject(_item)>>
				<<set $money -= $shoparr[_j].value>>
				<<set $shoparr[_j].stock-->>
				
				**//This is the part where it's supposed to take the item and add it to your invintory and add to the .number if it's already there. It seems to only be reading from the else statement though//**
				<<set _pcitem to clone(_item)>>
				<<if $pcarr.includes(_pcitem)>>
					<<set _a to $pcarr.indexOfObject(_pcitem)>>
					<<set $pcarr[_a].number++>>
				<<else>>
					<<set $pcarr.push(_pcitem)>>
					<<set _a to $pcarr.indexOfObject(_pcitem)>>
					<<set $pcarr[_a].number++>>
				<<endif>>
										
				<<if $shoparr[_j].stock eq 0>><<run $shoparr.delete(_item)>><<else>><<endif>>
			<</link>>
	<<endif>><<endif>>
 	<</capture>>
 <</for>>
 <</nobr>>

 TEST PC ARRAY INVINTORY READOUT
 <<nobr>><<for _i to 0; _i lt $pcarr.length; _i++>>
	<<set _item to $pcarr[_i]>>
 	<<capture _item>>
 	<p><big><<print $pcarr[_i].name>></big></p>
	<li>Description: <<print $pcarr[_i].description>></li>
	<li>Category: <<print $pcarr[_i].category>></li>
	<li>Value: <<print $pcarr[_i].value>>g</li>
	<li>Number: <<print $pcarr[_i].number>></li>
	<<if $pcarr[_i].cansell is true>><<else>>This item cannot be sold.<<endif>>
 	<</capture>>
 <</for>>
 <</nobr>>

Maybe something like this?

<<set _pcitem to clone(_item)>>
<<set _a to $pcarr.indexOfObject(_pcitem)>>
<<if _a === -1>>
	<<set _pcitem.number++>>
	<<set $pcarr.push(_pcitem)>>
<<else>>
	<<set $pcarr[_a].number++>>
<<endif>> 

Thank you for the assist but it’s still cloning the item in the receiving inventory. The number is printing one every time instead of counting up. I think it’s an issue with cloning it every loop but I’m not sure what to do about it.

Oh, I think I might see what’s going wrong.

So it looks like _item is taken from the list of shop items and _pcarr is what you’re carrying, right? .indexOfObject() that HiEv wrote compares values between objects to see if they’re identical and returns -1 if there are no matches.

I think what’s happening is that the item in the store array has a number value of 0, but as soon as you add the item to your character inventory, you increase the number by one. Because of that, _item will never match the items in _pcarr.

I’m not sure a good way around this. To be honest, this is why I usually use object lists with key names instead of pushing items into an array, especially if you plan on only having unique entries anyway.

So I’d do something like:

<<set pouch = {
	id: "pouch", 
	name: "A strange pouch", 
	number: 0
}>>
<<set $pcarr = {}>>

<<for _i in $shoparr>>

	/% ... %/

	<<set _item to $shoparr[_i]>>

	/% ... %/

	<<if !(_item.id in $pcarr)>>
		<<set $pcarr[_item.id] = clone(_item)>>
	<<endif>>
	<<set $pcarr[_item.id].number++>>

If the item “name” property is unique to each item, then you could use the .findIndex() method like this instead:

<<set _idx = $pcarr.findIndex(function (element) { return element.name === this }, _pcitem.name)>>

That would return the index of the first element in the $pcarr array which has a .name property set to the same name as _pcitem.name, or -1 if no match is found.

Hope that helps! :slight_smile:

EDIT: Fixed the code and added a link.

1 Like

I think so but I’m confused about a couple of things in the line you’ve provided. I’m entirely self taught so I understand I have some significant gaps in my knowledge. I’ve plunked it in and it evaluates the first time correctly and adds the item to $pcarr. However, on the second click I get an alert that says:

<<set>> bad evaluation: el is not defined.

Am I supposed to fill in “this” with the name to check or sub something instead of “element” and replace “el”.name with something else?

I think element should have been el or vice versa. They should be the same variable name.

<<set _idx = $pcarr.findIndex(function (el) { return el.name === this }, _pcitem.name)>> 
1 Like

Oops. Yup, that’s the fix. I’ve corrected my code above and linked to the info on the .findIndex() method as well.

Ok, last thing. It all is functioning except that the .number property of the in the $pcarr changes to 1 but clicking again doesn’t make it add another. This is the code that’s executing the command presently.

				<<set _pcitem to clone(_item)>>
				<<set _idx = $pcarr.findIndex(function (element) { return element.name === this }, _pcitem.name)>>
				<<if _idx is -1>>
					<<set $pcarr.push(_pcitem)>>
				<<endif>>
				<<set _pcitem.number++>>

I’m not sure what’s wrong with it but the <<set _pcitem.number++>> in the <<else>> doesn’t evaluate. The one outside the <> statement does but only once. I know it isn’t my $pcarr loop mishandling the info because I had it print out the number of the pouch outside of either loop and it changes the first click, but not the second or third.

You’ll probably have to change it to be similar to my original solution since we’re back to relying on the index. So…

<<set _idx to $pcarr.findIndex(function (element) { return element.name === this }, _item.name)>>
<<if _idx is -1>>
	<<set _pcitem to clone(_item)>>
	<<set _pcitem.number++>>
	<<set $pcarr.push(_pcitem)>>
<<else>>
	<<set $pcarr[_idx].number++>>
<<endif>> 

That did it! Holy crow thank you so much!! I’m a teacher and I’m running a creative writing elective on interactive fiction and games. I’m trying to come up with a bunch of essentially copy and paste systems for middle school kids so they can make something a little more complex without having to learn the nitty gritty of the code yet. On behalf of all my super excited kids, thank you both so much for your help!

1 Like