0 votes
by (1.3k points)

So, some code:

<<widget "listingRand">>
	<<if $args[0] === "$pcPiercings">>
		<<set `$args[0]` = setup.pcGetPiercingDescs()>>
	<<elseif $args[0] === "$pcTattoos">>
		<<set `$args[0]` = setup.pcGetTattooDescs()>>
	<<if $args[1] > `$args[0]`.length>>
		<<set $args[1] = `$args[0]`.length>>
	<<set `$args[0]` = `$args[0]`.randomMany($args[1])>>

See where I have `$args[0]`, that's where I'd ideally like to evaluate the variable name that $args[0] supposedly holds. This doesn't work, but is there a way to do this? It must be possible, right? But now? Is it ($args[0) or something? Or a totally different approach?

1 Answer

0 votes
by (68.6k points)
selected by
Best answer
How are you calling the widget?
by (1.3k points)
Like this: <<listingPrint "$pcPiercings">>

But, could I use <<listingPrint $pcPiercings>> instead?

Oh, that was the printing one.

The other is similar: <<listingRand "$pcPiercings" `random(2, setup.pcPiercingCount())`>>
by (68.6k points)

I should have asked this before, but I didn't, so I have to ask now.  Mea culpa.

What are you actually attempting to accomplish here?  Print a random slice of descriptions?  Some things are obvious by simply looking at the widget, others less so.

For example, with the last <<set>> it seems like you're attempting to overwrite the variable, which AFAIK contains an object, with a new array of randomly selected members from the array generated by pulling the descriptions of the sub-objects.  I sincerely doubt that's what you really want to do here—if for no other reason than you'd be overwriting the original object.

by (1.3k points)

Oh right. So, I'll show you the data I'm dealing with:

I set up this in some passage:

<<set $pcPierced.ears.scale = 1>>
<<set $pcPierced.ears.desc = "''your ears pierced,'' the little silver sleepers catching the light">>
<<set $pcPierced.navel.scale = 1>>
<<set $pcPierced.navel.desc = "''your navel pierced,'' the jewelry there hanging down from it, glittery and gorgeous">>
<<set $pcPierced.tongue.scale = 1>>
<<set $pcPierced.tongue.desc = "''your tongue pierced,'' the silvery stud often making an appearance whenever you might open your mouth">>
<<set $pcPierced.face.scale = 2>>
<<set $pcPierced.face.desc = "''your lip pierced,'' a silver ring on one side to catch attention, //and your eyebrow too,// another silvery ring">>

<<if setup.pcIsPierced()>><<listingRand "$pcPiercings" 3>><</if>>

All that is just for testing, not as I'll be using it (quite) in the game. But, for testing formats, it's good.

I have this defined in StoryInit:

<<set $pcPierced = {
	ears:   { scale: 0, desc: "" },
	navel:  { scale: 0, desc: "" },
	tongue: { scale: 0, desc: "" },
	face:   { scale: 0, desc: "" }

And I have this defined too:

<<set $pcPiercings = []>>

So, I know the names are pretty similar, they are quite different. For testing, I'm just first loading the $pcPiercings array with data. That's the one that eventually gets overwritten. And that's desired, since it's there for just that.

I have another version of the widget I was using before moving to a more generalized approach. It's this:

<<widget "piercingsRand">>
	<<set _piercingList = setup.pcGetPiercingDescs()>>
	<<if $args[0] > _piercingList.length>>
		<<set $args[0] = _piercingList.length>>
	<<set $pcPiercings = _piercingList.randomMany($args[0])>>

So, I think that's about all you really need to see. Oh, and the javascript function:

/* Returns a new array of all piercing part descs, where scale > 0. */
setup.pcGetPiercingDescs = function () {
	var sv = State.variables;
	return Object.keys(sv.pcPierced).reduce(function (a, c) {
		return sv.pcPierced[c].scale > 0 ? a.concat(sv.pcPierced[c].desc) : a;
	}, []);

I only really use the object for seeing which descs I pull to push into the array. From there, I'm just using array functions to shuffle and then downsize. 

So, in the end, you get something like this:

[ "''your tongue pierced,'' the silvery stud often making an appearance whenever you might open your mouth", "''your ears pierced,'' the little silver sleepers catching the light" ]

What think? The only real question I have is... how to pass the variable in, and make use of it directly, as opposed to using temporary variables and then handing over the result at the end. That's pretty much how I'm doing the other version. And that one works. I wonder... if we can't get the generalized one to work too?

But, it's it not really something that's been designed, I could just go back to the old version. I just thought... I could just this for four arrays I have. To shuffle, and then print in a pretty nice list. They're all the same. Just arrays of string fragments.

by (1.3k points)
Oh, and the reason I am overwriting the variable is... because that's the list I want to preserve. The shortened one that's been randomized. It's for display purposes. But, it's reusable too, whenever the user generates a new listing. Over and over, the array preserving the last one they generate.
by (1.3k points)

And there's the other widget I use to print the array out again.

<<widget "piercingsList">>
<<if $pcPiercings.length > 0>>
<<if $pcPiercings.length == 2>>
<<print [$pcPiercings.slice(0, -1).join(', '), $pcPiercings.slice(-1)[0]].join($pcPiercings.length < 2 ? '' : ', and ');>>
<<print [$pcPiercings.slice(0, -1).join('; '), $pcPiercings.slice(-1)[0]].join($pcPiercings.length < 2 ? '' : '; and then... ');>>

That's from the old version too. They work together, since I just split up the one they were both in into two.

Oh, and sorry for the formatting. I've generally found I need to convert all the widget code into one flat line for there to not be problems with spurious spaces. If it's all in one one, the output is perfect. But, that only really counts if it's printing stuff to the screen. If it's not, then I can leave it formatted for reading.

by (1.3k points)

This is the error I get for when I try to use the widget:

Error: <<listingRand>>: errors within widget contents (Error: <<set>>: bad evaluation: invalid assignment left-hand side; Error: <<set>>: bad evaluation: invalid assignment left-hand side)

But, this is the error I get by using the printing widget:

Error: <<listingPrint>>: error within widget contents (Error: <<print>>: bad evaluation: "$args[0]".slice(...).join is not a function).

It's from this one I thought to generalize too:

<<widget "listingPrint">>
	\<<if `$args[0]`.length > 0>>
		\<<if `$args[0]`.length == 2>>
			<<print [`$args[0]`.slice(0, -1).join(', '), `$args[0]`.slice(-1)[0]].join(`$args[0]`.length < 2 ? '' : ', and ');>>
			<<print [`$args[0]`.slice(0, -1).join('; '), `$args[0]`.slice(-1)[0]].join(`$args[0]`.length < 2 ? '' : '; and then... ');>>


by (1.3k points)

OK, so last little comment, maybe.

The four arrays I wanted to use that widget on are these:

<<set $pcPiercings = []>>
<<set $pcTattoos = []>>

<<set $pcTopicalBody = []>>
<<set $pcTopicalEvent = []>>

They're slightly different, since some store things in use, and others act more like temporary storage. But they're the came. I thought I could just use:

<<listingPrint "$pcPiercings">>
<<listingPrint "$pcTattoos">>
<<listingPrint "$pcTopicalBody">>
<<listingPrint "$$pcTopicalEvent">>

But, I could just make four. It's no big deal.

by (68.6k points)

Let me touch on a couple of issues first:

  1. I really, really suggest reading, or rereading, the docs on macro arguments.  I'm not trying to be mean, however, it's painfully clear that you really have not internalized what the docs are trying to tell you—the misuse of backquote expressions alone, which have one use, makes that clear.
  2. Never modify a widget's $args array.  Doing so can have unintended, and hard to diagnose, side-effects.  You should treat it as though it were immutable—even though it currently is not.  If you need to use a modified form of one of its values, copy the value to a temporary variable and modify that.

Okay.  Assuming I understood all of that, the following should do what you want.  It makes use of the undocumented Wikifier.setValue() API, since it's probably the best fit for this situation:

<<widget "listingRand">>
		<<switch $args[0]>>
		<<case "$pcPiercings">>
			<<set _descs to setup.pcGetPiercingDescs()>>
		<<case "$pcTattoos">>
			<<set _descs to setup.pcGetTattooDescs()>>
		<<run Wikifier.setValue($args[0], _descs.randomMany($args[1]))>>


  1. There are other ways to do what Wikifier.setValue() is doing here, but they all kind of suck.  I really need to make a public version of that API.
  2. You don't need to clamp the <Array>.randomMany() method's want parameter value to the length of the array as it will only return as many elements as the array has members—i.e. it's automatically clamped.

 Usage would be the same as what you were already doing.  For example:

<<listingRand "$pcPiercings" `random(2, setup.pcPiercingCount())`>>


by (1.3k points)
Yeah, cool. Thanks for that. I did have a rather preachy blog to post about this and that. But since I already wrote it, I think I've written enough.

The take home message? The pre-defined variables you create in the StoryInit special passage are pretty damn golden. They're really dependable. Which is a good thing.

For the future, I think I'd better brush up on how to do widgets as Macro.adds again, so that most play can be done with pure javascript. I do like the various macros you've added to the sugarcube code base. I'm not even vaguely aware of the latest advances in JS development. So, they're handy, since they bring good stuff with each new release.

I'll continue to play with this and that, but mostly I wanna just finish this stuff up so I can put my mind in to story. I'm pretty close to being able to do that. Just a bit more descing and that's it. Or that's it for a start.

No doubt you'll see me here again, if I have a question or two. I feel like writing right now though, so I might go do that. Till later!