Howdy, Stranger!

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

SugarCube: Widgets inside set ...?

Hi,

me again, flooding the forum :)

I've set up a somewhat long (but descriptive) widget for printing the clear name of a variable id. So you call, say <<PrintLocation 2>> and get Greenhouse in return.

Now, of course, I could add yet another widget for what I need that would set the appropriate variable but that would split variable initialization over two places and I'd like to avoid that, if possible. Here's the problem (if it is one):

For a lengthy passage I'm going to need the clearname of the location quite a few times. Since I like to keep text and code separated as muchas possible, I tried a setup routine before going to the passage (set of passages actually but that is beside the point). In that setup passage I tried to fill a couple of handy variable names with the "printed" content so I could use something like $loc in the text. Not too disturbing and interrupting when writing and proof reading.

But ... my first attempt went like this:

<<set $loc to <<PrintLoc $passedID>> >>

And, lo and behold ... that is a construct not suported by Twine/Sugarcube. I always get a naughty error that an expression was expected and "<<" was received instead.

So unless there is a trick to get that past the parser it seems nested Macros are a no, no?

If so ... is there really no other way than to either create another "shorter" widget so that I'd have to type <<loc $passedID>>? Way more ... bleh .. when writing and proof reading. :(

The SugarCube documentation is excellent in some places and somewhat sparse in others ... did I miss a pointer system somewhere I could pass to a widget so that the variable itself could be changed (wouldn't always be $loc - that's why I use a widget - other passes would have $loc2, $loc3 and so on ...

Comments

  • note: The following example has not been tested, I don't have access to Twine atm.

    Try something like the follow:
    <<set $loc to "<<PrintLoc $passedID>>">>
    
    <<print $loc>>
    
  • You cannot directly pass one macro invocation into the argument space of another, no. Even if you could, macros do not return output, they inject it into the render buffer, so it wouldn't help you here anyway. You can print macros in various way, but that's not what you're looking for.

    Beyond that, I can't say. You haven't shown any concrete code examples of what you're trying to do and how, so I really have no idea what you're actually doing. It makes it a little difficult to offer advice.

    That said, I'd be surprised if there wasn't some way to accomplish your goals in a satisfactory manner.

    did I miss a pointer system somewhere I could pass to a widget so that the variable itself could be changed (wouldn't always be $loc - that's why I use a widget - other passes would have $loc2, $loc3 and so on ...
    There are a few ways to reference a variable so that it can be modified, however, they all start with quoting the variable when you pass it in (just as you must with <<textbox>> for example). The reason you must quote the variable is found at the top of the macro library documentation, to bypass automatic story variable substitution.

    Again, however, without a better idea of what you're doing (and what you actually need to do), I can't offer any specific advice.
  • greyelf wrote: »
    note: The following example has not been tested, I don't have access to Twine atm.

    Try something like the follow:
    <<set $loc to "<<PrintLoc $passedID>>">>
    
    <<print $loc>>
    

    That does fly right pass the parser but does not produce any output. When you print the variable or use it in text it is "". I tried to put a wrapping <<run >> around the inner widget call as well (hoping against hope that it might be something like Perls eval) but that doesn't throw an error either but also does not produce any output.
  • You cannot directly pass one macro invocation into the argument space of another, no. Even if you could, macros do not return output, they inject it into the render buffer, so it wouldn't help you here anyway. You can print macros in various way, but that's not what you're looking for.

    Beyond that, I can't say. You haven't shown any concrete code examples of what you're trying to do and how, so I really have no idea what you're actually doing. It makes it a little difficult to offer advice.

    Hm, the forum ate my first - let us try again:

    I have my stuff set up this way: Passages who control the general flow by invoking non-"value-returning" widgets (i.e. procedural widgets that operate on global variables) and have some logic themselves to determine which widgets to call and which passages to display.

    The display passages have as little code as possible to allow for better writing and proof reading. I therefore have a prep phase that sets up a set of global variables which will then be used in the display passage.

    Since debugging is sometimes easier if you have descriptive information instead of merely a numerical value I also have a set of utility widgets that produce clear text from a passed numerical value.

    So, let us say the situation before calling a display passage is that it has been determined that $actor1 is in the location, that his mood is $mood1 and that the actual location is $loc1.

    $actor1 contains the name of the character
    $loc1 contains an ID
    $mood1 contains an ID

    <<PrintLoc $loc1>> would return, say, "Library"
    <<PrintLoc $mood1>> would return, say "extremely annoyed"

    To be able to write (and proof read) more fluently there's a prep widget before that passage is called that sets up short variables. Like $a for the actor, $m for the mood, $l for the location, and so on. So the display passage it might start like:

    When $a entered the $l he was $m but the fine weather outside had a calming effect so he sat down and pondered his situation ....

    My problem is setting up the shorthand variables. I can use <<set $a to $actor1>> in the prep passage. But, as I stated initially, trying <<set $m to <<PrintMood $mood1>> >> fails.

    I can, of course set $m to $mood1 in the prep passage and use <<PrintMood $m> in the display passage. I'd rather, though, just use $m (less << and >> to worry about, less disruptive.

    I can, of course, copy the <<PrintMood>> logic into the prep widget and assign $m its textual representation. But then I have 2 things that do the same thing and if I ever have to change the mood text or the threshold I have to remember to change it in the PrintMood widget AND in the prep widget else we might get textual inconsistencies.

    I hope I managed to make it a bit clearer what I am trying to do now ...
  • edited January 2016
    Not really. I got the basic gist from your first description. This still does not show me the actual mechanics of how you're doing what you're doing. What was unclear about "concrete code examples" I cannot fathom.

    For general help, a good description of the problem/goal can be completely sufficient. For situations where you're asking questions about specific code, then you really should show some code samples or, at least, examples. In this case, you want help slotting a solution into your existing code, which puts you into the latter category.


    That said, you should be able to solve your DRY dilemma in one of, at least, two ways.


    1. You could probably use a intermediary <<widget>> or <<display>>'d passage to hold the core chunk of your ID-to-description code. That code, when executed, would set a variable/property to the required value (a non-story variable, like the setup object, would be good for this). You could then reference that variable/property to print its value, set your short variables to its value, whatever was required.

    I'd show some examples, but I'd be guessing at what you need.


    2. As an alternative, have you thought about using functions? For example, a getMood() function which returns the mood description:
    /* Print the mood. */
    <<print getMood($mood1)>>
    
    /* Set $m to the mood. */
    <<set $m to getMood($mood1)>>
    
    Naturally, that would require you to write some of your code as pure JavaScript, which may or may not be an issue for you.
  • edited January 2016
    Not really. I got the basic gist from your first description. This still does not show me the actual mechanics of how you're doing what you're doing. What was unclear about "concrete code examples" I cannot fathom.

    Um, like about 8 pages of code where the stuff is inside and which, unless heavily doctored and shortened would probably not help much :)

    1. You could probably use a intermediary <<widget>> or <<display>>'d passage to hold the core chunk of your ID-to-description code. That code, when executed, would set a variable/property to the required value (a non-story variable, like the setup object, would be good for this). You could then reference that variable/property to print its value, set your short variables to its value, whatever was required.

    I'd show some examples, but I'd be guessing at what you need.


    2. As an alternative, have you thought about using functions? For example, a getMood() function which returns the mood description:
    /* Print the mood. */
    <<print getMood($mood1)>>
    
    /* Set $m to the mood. */
    <<set $m to getMood($mood1)>>
    
    Naturally, that would require you to write some of your code as pure JavaScript, which may or may not be an issue for you.

    Not being a Java programmer and not having any clue about the DOM objects I'd say the first suggestion is out of scope for me because it would require quite some effort (with questionable rewards, considering my small problem) to use that.

    Java script functions sound more manageable as you (hopefully) do not have to dig into the hierarchy of the individual objects and classes and can (wishful thinking) just use if/else case, etc. to do your logic part and then return a value. Might be more complicated than I think but unless it is that sounds like the way to go.

    Thanks.

    And since you wanted a code example - I'll try to give one ... "::" means it's a passage/widget. I'm not doing a linear flow story - I do a step based story that has like 6 parallel strains that all execute at the same time - therefore stuff is not in ONE passage but spread over widgets, control flow passages, display passages, etc. If you know Cluedo it's a bit similar to that ...
    ::StoryInit
    <<set $actor1 to "Sir George">>
    <<set $actor2 to "Winston">>
    <<set $locations to ["Greenhouse","Salon","Porch">>
    <<set $moods to ["afraid", "neutral", "annoyed", "angry">>
    <<set $mood1 to 0>>
    <<set $mood2 to 0>>
     ....
    
    ::UtilityWidgets
    <<widget PrintMood>>
    	<<if $args[0] < 0 or $args[0] > 3>>
    		Error: Index out of bounds in PrintMood
    	<<else>>
    		<<print $moods[$args[0]]>>
    	<<endif>>
    <</widget>>
    
    <<widget PrintLoc>>
    	<<if $args[0] == 1>> Greenhouse <<endif>>
    	<<if $args[0] == 2>> Salon <<endif>>
    	...
    <</widget>>
    
    ::ControlFlow
    <!-- player character is entering a location where someone else is -->
    <<if $location1 == $playerloc>>
    	<<Prep_Display_Greenhouse 1>>
    	<<display [[Greenhouse]]>>
    <<elseif $location2 == $playerloc>>
    	.....
    <<else>>
    	Error: Location flagged as populated but no actor found in that location
    <<endif>>
    
    ::Prep_Widgets
    <<widget Prep_Display_Greenhouse>>
    <!-- do some random stuff like weather, noise, etc. check for passing actors who observe the scene, select picture ID to display with location, player + $actor1 in it + matching weather ... -->
    <<if $args[0] == 1>>
    	<<set $a to $actor1>>
    	<<set $m to $moods[$mood1]]>> <!-- whis is how I solved my <<set <<widget>> >> problem atm
    	<<set $l to <<PrintLoc $loc1>> >> <!-- this is what I WANTED to do -->
    	....
    <<endif>>
    <</widget>>
    
    ::Greenhouse
    <!-- Display Passage -->
    [img[$locPicID]]
    <<if $clue == 101>>
    	<!-- player found bloody shoeprint in front of the library
    	Ho, $a! My, you look $m, but ... nevermind ... come over here, I have something important to ask. Let's get out of this $w and sit down over there. Here ... when I searched the crime scene I also checked outside. And guess what I found in front of the $cs? A shoeprint! And ... more importantly ... a $sex shoeprint.
    <<elseif $clue == 111>>
    	<!--player found a bloody knife in the garden shed ...
    <<else>>
    	<!-- player found a bloody knife inside himself ... er, what? -->
    <<endif>>
    
  • @Harald_Schuster:
    Just a couple of things for future reference:

    a. Java and Javascript (one word) are two totally unrelated programming languages which unfortunately have similar names. In this instance you meant Javascript.

    b. The format you used for your example is known as Twee Notation, if you state that your example is using that format then you generally don't need to describe how the format works.
    The titles of the widget related passages in your example are missing the required widget tag, I hope the relevant passages in your story project are not also missing the tag.
    :: UtilityWidgets [widget]
    :: Prep_Widgets [widget]
    
  • No, that was just a quick demo-typing (unchecked and not copy'n'pasted from the project) to give an idea how things are set up, using :: to indicate that this is in a different passage/widget passage. The actual utility widget has, like, 20 widgets in it and I thought it might be better to post how things are set up instead of posting the whole stuff, 80% of which is debug related anyway.

    With SugarCube I never had to tell SugarCube where to look for a widget - just defining it somewhere and then using its name inside of << >> worked fine so far. Will look into Twee Notation, though ... won't hurt, I'm sure. :)
  • […]
    <<set $locations to ["Greenhouse","Salon","Porch">>
    <<set $moods to ["afraid", "neutral", "annoyed", "angry">>
    […]
    
    Okay. You have your locations and moods in some kind of data structure, that's good (though, those array literals have syntax errors, they're missing closing square brackets "]").

    <<widget PrintLoc>>
    	<<if $args[0] == 1>> Greenhouse <<endif>>
    	<<if $args[0] == 2>> Salon <<endif>>
    	...
    <</widget>>
    
    Wait. You have your locations in an array, why are you playing if-plinko to yield the location names rather than simply indexing the $locations array?

    <<widget Prep_Display_Greenhouse>>
    […]
    	<<set $a to $actor1>>
    	<<set $m to $moods[$mood1]]>> <!-- whis is how I solved my <<set <<widget>> >> problem atm
    	<<set $l to <<PrintLoc $loc1>> >> <!-- this is what I WANTED to do -->
    […]
    <</widget>>
    
    Why do you make it sound like indexing the $moods array is a kludge, while playing if-plinko with the locations should be a preferred solution.


    I can show you how to make a function similar to <<PrintLoc>>, which returns the appropriate location name. It wouldn't be complicated at all. Before, I do that, however, I'd really like to know why you're preferring some chain of if statements (well, <<if>> macros) over indexing the arrays you already have.

    Did you just add the arrays to workaround the macro-as-argument limitation? If so, then that was serendipitous, because that's what you should be doing. If not, then…?
  • edited January 2016
    With SugarCube I never had to tell SugarCube where to look for a widget
    The following is quoted from the SugarCube <<widget>> macro documentation. (the underline emphases is mine)
    Warning: Widgets should always be defined within non-story-path passages tagged with widget, as any widgets that are not may be lost on page reload.
  • edited January 2016
    I can show you how to make a function similar to <<PrintLoc>>, which returns the appropriate location name. It wouldn't be complicated at all. Before, I do that, however, I'd really like to know why you're preferring some chain of if statements (well, <<if>> macros) over indexing the arrays you already have.

    Did you just add the arrays to workaround the macro-as-argument limitation? If so, then that was serendipitous, because that's what you should be doing. If not, then…?

    The *should* part is, where it "hurts". See - I'm trying out what Twine/SugarCube can do and I am transcribing (or trying to) a light version of something I did a few years ago - in a completely different language.

    While most things translate 1:1 (more or less) there are some data structures that do not translate 1:1. Like arrays of hashes. It "might" be doable with DOM objects but there is quite a bit of theory behind that (and behind how the story object is set up as well) and I decided to not dig into this beforehand but to plunge ahead and see if I could get by with a somewhat more primitive approach.

    Things went well until I hit a few snags and then I had to improvise. So ... the array with the descriptions is what - in hindsight - I should have done. But I didn't realize this until I hit one of my snags. So ... there's around 2k+ lines of code by now (maybe a bit more) and that code is not set up to handle arrays via indices but to handle passed variables and go through if elseif cascades.

    Before I brew myself a few liters of tea, sit down and rewrite every portion that uses the non-arrayed style I was trying for something that would allow me to keep the "legacy" parts as they are (debugged and everything).

    Despite all that - at some time it is neccessary to not only return a text when something was queried but to also set a variable or two. Actually I had this pretty early on which might be the reason I why I went this route in the first place.
    <<if $dress == 2>>
    	bloody <<set $mood1 to 0>>
    <<endif>>
    

    Could, of course be rewritten so that the state of the dress and the drop in mood occurs somewhere else but as this can happen quite a few times (because the items do not have fixed locations where they appear) I tried to keep everything related in ONE place to that - when things went logically wrong - I'd know where to look instead of having to look at 4 or 5 code snippets where the dress can be found (in whatever state) and check if the mood was set accordingly should the dress be convered in blood.

    In addition to that I sometimes need ranges (as there are different thresholds for different things and using a single index didn't provide enough spread via rngs) so a few if those if elseif cascades also read like:
    <<widget PrintStrength>>
    	<<if $args[0] <= 15>> old and battered 
    	<<elseif $args[0] >> 15 and $args[0] <= 30>> old but sturdy looking
    	<<elseife $args[0] >> 30 and $args[0] < 50>> old but surprisingly sturdy
    	<<endif>>
    <</widget>>
    

    The above example might be a door which blocks the entry to a location and it's strength was rng'ed in the initialization phase. Now the player gets some feedback to see how tough it might be to force it open and if brute force might do or if some additional item (or items) might be needed. (Note: There's a ton of red herrings in the original version - that door might be unopenable in the end in some cases (like a steel door but, alas, the dynamite sapwning routine rolled a 0 and so we do not have the one object in the game that might open that door) but if it is unbreachable, nothing story-critical is hidden behind it. I love placing red herrings.)

    tl;dr: Yes, would I start from scratch now, I'd code a few things differently but since there's quite a bit of code in place already I'm looking for alternatives to re-use it instead of rewriting everything and going back to an exhaustive debug phase.
  • greyelf wrote: »
    With SugarCube I never had to tell SugarCube where to look for a widget
    The following is quoted from the SugarCube <<widget>> macro documentation. (the underline emphases is mine)
    Warning: Widgets should always be defined within non-story-path passages tagged with widget, as any widgets that are not may be lost on page reload.

    Sometimes I do read documentation ... I'm a whimp, I know. And since I read that, all my passages that contain widgets are properly tagged and have this nice greenish coloured title bar. :)

    Colour-coding the title bars of the passages was actually a great idea, though there is a little bug that passages that contain calculated passage transistions are shown as ending passages (dark blue) instead of the lighter shade that passages have, that have a non-calculated passage transistion.
  • While most things translate 1:1 (more or less) there are some data structures that do not translate 1:1. Like arrays of hashes. It "might" be doable with DOM objects but there is quite a bit of theory behind that (and behind how the story object is set up as well) and I decided to not dig into this beforehand but to plunge ahead and see if I could get by with a somewhat more primitive approach.
    Emphasis mine. JavaScript and the DOM have absolutely nothing to do with one another, save that the DOM has a JavaScript API which is provided by the browser to its JavaScript instance/engine.

    JavaScript is no more difficult than any other programming language. It has its warts, to be sure, but so do all languages. If you have prior experience in other languages, then picking up the basics of JavaScript should be absolutely trivial—and core data structures, like arrays and generic objects, are basics.

    Things went well until I hit a few snags and then I had to improvise. So ... the array with the descriptions is what - in hindsight - I should have done. But I didn't realize this until I hit one of my snags. So ... there's around 2k+ lines of code by now (maybe a bit more) and that code is not set up to handle arrays via indices but to handle passed variables and go through if elseif cascades.

    Before I brew myself a few liters of tea, sit down and rewrite every portion that uses the non-arrayed style I was trying for something that would allow me to keep the "legacy" parts as they are (debugged and everything).
    Understandable, though I really do encourage you to look into JavaScript. I think your various misconceptions (that it's related to Java or the DOM) are causing you to think it's harder than it is.


    Okay. With that out of the way, it's function example time (replete with commentary). Here are several versions of a sayLocation() function for your consideration. The control flow statement versions are 1-based, while the array versions are 0-based, since that's more or less what you had before—converting one to the other would be trivial.
    /*
    	This uses if…else statements.
    */
    window.sayLocation = function (id) {
    	if (id === 1) {
    		return "Greenhouse";
    	}
    	else if (id === 2) {
    		return "Salon";
    	}
    	else if (id === 3) {
    		return "Porch";
    	}
    	/* Et cetera. */
    
    	/* All conditions failed?  Return an error. */
    	return "Error: id parameter out of bounds, in sayLocation(); value: " + id;
    };
    
    /*
    	This uses a switch statement.
    */
    window.sayLocation = function (id) {
    	/*
    		Normally, you should have a break statement after every case to terminate
    		it, however, that's unnecessary here as we're using return statements.
    	*/
    	switch (id) {
    		case 1: return "Greenhouse";
    		case 2: return "Salon";
    		case 3: return "Porch";
    		/* Et cetera. */
    	}
    
    	/* All conditions failed?  Return an error. */
    	return "Error: id parameter out of bounds, in sayLocation(); value: " + id;
    };
    
    /*
    	This indexes an array which is stored within a story variable (`$locations`).
    */
    window.sayLocation = function (id) {
    	/*
    		Story variables (e.g. `$foo`) are accessible in pure JavaScript code via
    		the `State.variables` API (e.g. `State.variables.foo`).  We'll cache a
    		reference to that now, so we don't need to dereference the entire property
    		chain every time we use it, and so that it's shorter to type.
    
    		n.b. This does assume SugarCube 2.x.  For SugarCube 1.x, replace the
    		     instance of `State.variables` with `state.active.variables`.
    	*/
    	var sv = State.variables;
    
    	/*
    		Ensure `id` is within range.
    	*/
    	if (id < 0 || id >= sv.locations.length) {
    		/* Range error. */
    		return "Error: id parameter out of bounds, in sayLocation(); value: " + id;
    	}
    
    	return sv.locations[id];
    };
    
    /*
    	This indexes an array which is stored within an auto-global (`GameLocations`).
    	
    	The reason you might wish to do this is to keep constant/unchanging data out
    	of the story variables, which is best reserved for your mutable data.  I don't
    	want to oversell this, however, most times you shouldn't worry about it and
    	just use story variables.
    
    	An example of how you'd declare `GameLocations`:
    		window.GameLocations = [ "Greenhouse", "Salon", "Porch" ];
    */
    window.sayLocation = function (id) {
    	/*
    		Ensure `id` is within range.
    	*/
    	if (id < 0 || id >= GameLocations.length) {
    		/* Range error. */
    		return "Error: id parameter out of bounds, in sayLocation(); value: " + id;
    	}
    
    	return GameLocations[id];
    };
    
    Normally, I don't advocate jamming properties onto the window global. Due to unfortunate scoping issues, however, there's not a great deal of choice—at least not if we're remotely concerned about convenience.

    Also, rather than returning an error string for your range errors, you could throw an exception. Doing so will get you an error at the call site, rather than wherever the value ends up. For example:
    /*
    	Instead of returning an error value.
    */
    return "Error: id parameter out of bounds, in sayLocation(); value: " + id;
    
    /*
    	Throw an exception.
    */
    throw new RangeError("id parameter out of bounds, in sayLocation(); value: " + id);
    

    Usage examples:
    /* Print the location description for $loc1. */
    <<print sayLocation($loc1)>>
    
    /* Set $l to the location description for $loc1. */
    <<set $l to sayLocation($loc1)>>
    
  • Colour-coding the title bars of the passages was actually a great idea, though there is a little bug that passages that contain calculated passage transistions are shown as ending passages (dark blue) instead of the lighter shade that passages have, that have a non-calculated passage transistion.
    If you mean that normal passages which link to others are light blue, while normal passages which do not are dark blue, then that's less of a bug and more of a limitation. Twine 1 does not allow the story formats to denote links (and/or other references) to passages—that processing is built-in and limited (currently, Twine 2 is similar).

    Specifically for Twine 1, it only really understands wiki links (e.g. link) and anchors with a data-passage attribute (e.g. <a data-passage="link">text</a>). Other means of linking to, or referencing, passages, which are offered by some story formats are not recognized by Twine 1.

    All is not dark for SugarCube users, however. In SugarCube, all macros which accept/require a link argument may take any square bracketed link markup, namely wiki links (e.g. link) and wiki image links (e.g. [img[image][text|link]]). Twine 1 will recognize the links and make the appropriate connections on the story map. For example:
    /*
    	These will not be recognized by Twine 1's link identifier.
    */
    <<click "Storm the food court!" "Food Court">>…<</click>>
    <<click "Storm the food court!">><<goto "Food Court">><</click>>
    
    /*
    	These will be recognized by Twine 1's link identifier, so a line on the story
    	map will be drawn from the current passage to the `Food Court` passage.
    */
    <<click [[Storm the food court!|Food Court]]>>…<</click>>
    <<click "Storm the food court!">><<goto [[Food Court]]>><</click>>
    
    I used <<click>> and <<goto>> in this example, but it will work for any macro which accepts links.
  • ... snip ...
    

    Thanks for the example! It's not, that Java Script is difficult as such (nor is Java, actually) - the problem is that they operate in a hierarchy and THAT is what you have to understand. The same with css - new post incoming about that tomorrow - once you know when to use "class", when to use "id" and how an exisiting layout is organized you can edit it easily with a css reference site. Without knowing any of that you're completely lost and you know you might need to set background-color or change background but you have no clue WHERE to put it.

    It's say, the difference between being able to code in C++ or to code in C++ using Microsoft's C++ framework. You might be an expert in C++ but unless you learn about the class libraries and their hierachy, constructors and suchlike you won't be able to even create a simple "Hello World" window with Foundation Classes and/or successors.

    I would - with my past knowledge - even have managed to get the Java Script function together somehow (points to a couple of dusty books behind him), I would have stumbled over the "window." part, however, not knowing where to parent it to.

    That's what I clumsily mean when I speak about "hierarchy". In the languages I use there is no super-object above what I create. Everything I create is either its own object or has its own children. When I use SugarCube/Twine I KNOW I am actually operating within a hierarchy (browser, window in that, then a page inside that window, multiple divs into that page and one of those is which I am actually working with).

    The NICE thing about Twine/SugarCube is, that is takes care of the "what to place in which context" and provides a seemingly flat context I as a user can use. I am fully aware that I won't ever be unlocking the full potential until I learn more about the different objects involved but for what I am trying to do at the moment the "flat earth" model is working nicely. :)

    Thanks for the code snipped - I should be able to get on from there. There's lots of REFERENCE (though, sadly, not many user guide) sites that should show how to set up and address hashes, and whatever might be needed to squeeze out a few functions.

    I might be worried about "jamming things into the window global" if I had any idea what that means and what the consequences, drawbacks, and whatnot were. :)

  • @Harald_Schuster:
    I hope your PrintStrength widget was not actually cut-n-pasted from your story because there are a number of errors in it.
    1. The widget name should be wrapped in quotes.
    2. The second elseif is misspelt
    3. The elseif's are incorrectly using a >> for comparison instead of >

    A corrected version:
    <<widget "PrintStrength">>
    	<<if $args[0] <= 15>> old and battered 
    	<<elseif $args[0] > 15 and $args[0] <= 30>> old but sturdy looking
    	<<elseif $args[0] > 30 and $args[0] < 50>> old but surprisingly sturdy
    	<<endif>>
    <</widget>>
    
  • edited January 2016
    ... snip ..
    
    window.sayLocation = function (id) {
    
    ... snip ...
    	
    

    See, that little info was a lifesaver. :) I noticed that the random number generator (both of them actually) are pretty good over a large probe but have a nasty tendency to cluster in a series. So if you have, say a low probablility for A and inside A another random roll with 1 being the least likely and both A and 1 are measured against a 0- 100 roll and occur when roll <= 25, you are MUCH more like to get A1 as a result than you should be.

    In other words ... once A rolls the 1 as next roll becomes more likely than it should. Or in even other words: Once the rng "draws" a low number in a 1-100 roll it is very like to draw another low number in the following roll.

    To avoid that I need to add a few random (har, har) rolls before the once that "counts". To avoid using a static number of rolls (which introduces a predicatbility) I decided I want to do 50 rolls + the number of seconds the clock currently shows. The Javascript was superbly easy:
    <<script>>
    	function GetSeconds() {
    		var d = new Date();
    		var s = d.getSeconds();
    		return s;
    	}
    <</script>>
    

    But when I tried to access that function I got an error that the name GetSeconds was not known (or something similar). Rewriting it with your code snipped I changed this to:

    <<script>>
    window.GetSeconds = function() {
    var d = new Date();
    var n = d.getSeconds();
    return n;
    }
    <</script>>

    And things work. :)

    Stumbling a bit in the dark but I guess what I am actually doing is creating a new object in the "window" parent with the name of GetSeconds that happes to be a function (which, strangely now, has no name after function). Assuming that the surrounding SugarCube accesses the window object as well when it tries to resolve GetSeconds it finds it and I get my seconds to feed to my rng.
  • It's not, that Java Script is difficult as such (nor is Java, actually) - the problem is that they operate in a hierarchy and THAT is what you have to understand.
    For what I was referring to, which was the basics of the JavaScript language itself (e.g. basic data structures), you most certainly do not. You do not need to know anything about the DOM's APIs, SugarCube's APIs, HTML, CSS, etc simply to know about JavaScript itself. They are separate concerns, totally orthogonal.

    Don't get me wrong, you're completely correct that if, for example, you wanted to manipulate the DOM in JavaScript, then you would have to know the DOM's JavaScript APIs to do so. I'm not talking about that, however.

    If you want to use core parts of JavaScript, like data structures (e.g. arrays, generic objects, the Map object, etc), then you only need to learn about JavaScript (and as someone with programming experience, you should only need a minimal reference, the concepts are the same). Down the line you may need to expand your horizons and delve into other topics, but they are totally unnecessary to learn the basics of JavaScript itself.

    You're only hurting yourself by conflating these orthogonal concerns.

    As something of a counterpoint. The user-code scoping limitation present in SugarCube (most story formats, IIRC), which is the reason why I placed the functions on window properties, is a situation where knowledge of the the language simply isn't enough (it's also not documented very well, or at all). So, I do get that there are stumbling blocks, but let's not make a mountain range out of molehill.

    I might be worried about "jamming things into the window global" if I had any idea what that means and what the consequences, drawbacks, and whatnot were. :)
    It's a namespace issue, really. The window global is an object with various predeclared properties. It's completely possible to overwrite some of these properties, so care should be taken when adding properties to window. It's not a huge concern, but something to keep in mind.

    This is, in fact, the main reason SugarCube runs in what's known as strict mode (which is also the cause of the user-code scoping limitation). Outside of strict mode, it's all too easy for novices to automatically create properties on window completely by accident—thus, all too easy to clobber existing properties by accident.
  • But when I tried to access that function I got an error that the name GetSeconds was not known (or something similar). Rewriting it with your code snipped I changed this to:

    <<script>>
    window.GetSeconds = function() {
    var d = new Date();
    var n = d.getSeconds();
    return n;
    }
    <</script>>

    And things work. :)

    Stumbling a bit in the dark but I guess what I am actually doing is creating a new object in the "window" parent with the name of GetSeconds that happes to be a function (which, strangely now, has no name after function). Assuming that the surrounding SugarCube accesses the window object as well when it tries to resolve GetSeconds it finds it and I get my seconds to feed to my rng.
    You're on the right track. That would be the user-code scoping limitation I've mentioned. Separate user-code executions happen within their own scope, meaning functions defined and variables declared within an execution instance are only accessible within that instance.

    Declaring them as properties on the window global object gets around the limitation because it exists as part of an ancestor scope (part of the root scope, really), and is thus accessible in all descendant scopes. This holds true for any object in an scope which is an ancestor of SugarCube's main execution scope, like its own setup object (which is for authors to use for things like this). The reason I recommended using window and not setup, is that window is special. Its properties are elevated to the status of auto-global, meaning that you may access them without having to reference it as well. For example, window.GetSeconds may be accessed either as window.GetSeconds or GetSeconds, while setup.GetSeconds must be accessed as setup.GetSeconds.

    The reason the function has no name after the function keyword is that it's a function expression, rather than a function definition. Function definitions are required to have a name, function expressions are not (though they may). For example:
    /*
    	Function Definition.
    */
    function doTheThing() {
    	/* code */
    }
    /* Invoked as: doTheThing() */
    
    /*
    	Function Expression, assigned to an object property (a.k.a. a method; static
    	in this case, since it's not on the object's prototype).
    */
    someObject.doTheThing = function () {
    	/* code */
    };
    /* Invoked as: someObject.doTheThing() */
    
    /*
    	Function Expression, assigned to a variable.
    */
    var doTheThing = function () {
    	/* code */
    };
    /* Invoked as: doTheThing() */
    
    /*
    	Function Expression, given a name and assigned to a variable.
    */
    var doTheThing = function noReallyDoTheThing() {
    	/* code */
    };
    /* Invoked as: doTheThing() */
    

    Here's an IIFE, which I'm including because you'll probably see them sooner or later.
    /*
    	Immediately Invoked Function Expression (IIFE).
    */
    (function () {
    	/* code */
    })();
    
    /* Immediately Invoked Function Expression (IIFE), with parameters. */
    (function (a, b) {
    	/* code */
    })(foo, bar);
    
    IIFE's are often used to create scope, since scoping in the JavaScript version most widely supported (ES5) is done at the function level, not the block level. The next major version of JavaScript (ES6) adds block level scoping (though it won't be safe to use for a while, due to older browsers).
  • greyelf wrote: »
    @Harald_Schuster:
    I hope your PrintStrength widget was not actually cut-n-pasted from your story because there are a number of errors in it.
    1. The widget name should be wrapped in quotes.
    2. The second elseif is misspelt
    3. The elseif's are incorrectly using a >> for comparison instead of >

    A corrected version:
    <<widget "PrintStrength">>
    	<<if $args[0] <= 15>> old and battered 
    	<<elseif $args[0] > 15 and $args[0] <= 30>> old but sturdy looking
    	<<elseif $args[0] > 30 and $args[0] < 50>> old but surprisingly sturdy
    	<<endif>>
    <</widget>>
    

    Nope, it was a quick-type thing for the browser. Come to the missing < or > or whatnot ... when I type in a browser I type with a different keyboard layout than the one I use to code. And - my name might suggest this - it's a German keyboard layout and this is among the most, UTMOST, UTTERMOST, programmer-unfriendly keyboard layout imaginable.

    < and > are located between SHIFT and Y (which would be Z on a US/UK layout) so it's really hardly ever used normally (y is a letter that is almost never used in German).
    So the little finger is used to hit Shift OR Y but not the key in between - this makes << a tad of a challenge and >> even more so because you have to teach yourself to use the right hand for the SHIFT key which it is used to do only for Q and A so far. :)

    (){}[] are ALL on the number keys and while () are reachable via SHIFT, {[]} are 7-0 with ALT and CTRL pressed as well ... super handy, hm?

    Possibly one of the reasons you don't get many software programs from German companies ... the keyboard layout is really absolutely terrible compared to how conveniently [] and {} and <> are accessible on US/UK keyboards. :)

    So, may I be forgiven the odd unmatched brace or < > because on the normal layout these keys are really extremely hard to hit with a standard typing system - which is the reason I use a different layout for coding but that's not really suited for normal typing because it has a couple of letters twice and lacks a few others which might make for strange reading. :)
  • edited January 2016

    .. snip ..
    You're on the right track. That would be the user-code scoping limitation I've mentioned. Separate user-code executions happen within their own scope, meaning functions defined and variables declared within an execution instance are only accessible within that instance.

    Declaring them as properties on the window global object gets around the limitation because it exists as part of an ancestor scope (part of the root scope, really), and is thus accessible in all descendant scopes. This holds true for any object in an scope which is an ancestor of SugarCube's main execution scope, like its own setup object (which is for authors to use for things like this). The reason I recommended using window and not setup, is that window is special. Its properties are elevated to the status of auto-global, meaning that you may access them without having to reference it as well. For example, window.GetSeconds may be accessed either as window.GetSeconds or GetSeconds, while setup.GetSeconds must be accessed as setup.GetSeconds.

    ... snip ...

    Thanks for the clarification, I think I see light at the end of the tunnel (although I am not sure it's not an incoming train) ...

    I probably did something silly again and I haven't tested out all permutations, yet but what I (probably foolishly) did was:

    Not being quite sure where to place the java function (probably a special script window, but I wasn't sure) I put it inside of <<script>> <</script>> tags in the StoryInit for test purposes.

    While I can access it as <<print GetSeconds()>> in any passage I tried this, it fails hard when called inside of a widget. I get the error message that GetSeconds is not a known function.

    So ... is this because I put this in the StoryInit passage or is it that widgets and passages are different in nature and have different ways to look for variables, objects? I put the script <<script>> ... included inside the widget and removed it from StoryInit and much to my surprise I can now use it inside that widget and inside of passages and inside of other widgets ... scratches head ...
  • Please. JavaScript or ECMAScript, if you prefer its standards body name. Not Java Script, and certainly not Java. This should not be hard to remember (especially since greyelf corrected you previously in this very thread).

    I'm being a downer, I know. However, if someone doesn't step on this here and now, some poor sod is come alone months or years from now, selectively read the thread and come away thinking that JavaScript is Java's little brother or some such nonsense. Please.

    Not being quite sure where to place the java function (probably a special script window, but I wasn't sure) I put it inside of <<script>> <</script>> tags in the StoryInit for test purposes.
    In Twine 1, JavaScript setup code should go in a script-tagged passage (the Story > New menu and the right-click context menu both have entries to create script-tagged passages, or you can do it manually by simply tagging a normal passage).

    For example (using Twee notation):
    ::Muh Scripts [script]
    window.GetSeconds = function() {
    	var d = new Date();
    	var n = d.getSeconds();
    	return n;
    };
    
    Tip: You'll note I terminated the assignment with a semicolon. I suggest using them. While JavaScript does have automatic semicolon insertion (ASI) rules, they have a few gotchas which can bite you unless you are familiar with them. This is one area where I advise playing it safe.

    While I can access it as <<print GetSeconds()>> in any passage I tried this, it fails hard when called inside of a widget. I get the error message that GetSeconds is not a known function.
    Then you've done something wrong, somewhere. Just to make sure I hadn't broken anything, I setup a test based on what you said you did and it worked as intended (the widget had no issues with a call to the function).

    My test setup (using Twee notation):
    ::StoryInit
    <<script>>
    window.GetSeconds = function() {
    	var d = new Date();
    	var n = d.getSeconds();
    	return n;
    };
    <</script>>
    
    ::John H. Widget & Co. [widget]
    <<widget "getsecs">><<print GetSeconds()>><</widget>>
    
    ::Start
    <<getsecs>>
    

    Still, as noted above, you should really put your JavaScript setup code into a script-tagged passage.
  • Still, as noted above, you should really put your JavaScript setup code into a script-tagged passage.

    Nods, I moved the JavaScript (sic) function (and a few brothers and sisters that got created) into passages tagged as script and I'll try to remember to make a habit out of terminating the closing "}" with a ";".
  • edited January 2016
    Unless TheMadExile has changed how SugarCube loads script tagged passages then some care needs to be taken if you plan to have more than a single passage of this type in your story because there is no way to control the order these passage get loaded.
    This means that the Javascript in one passage should not rely on the Javascript in another passage to be loaded first, and it is generally better to only have a single script tagged passage, the same goes for stykesheet tagged passages.
  • greyelf wrote: »
    Unless TheMadExile has changed how SugarCube loads script tagged passages then some care needs to be taken if you plan to have more than a single passage of this type in your story because there is no way to control the order these passage get loaded.
    This means that the Javascript in one passage should not rely on the Javascript in another passage to be loaded first, and it is generally better to only have a single script tagged passage, the same goes for stykesheet tagged passages.

    Duly noted, thanks for that info!
Sign In or Register to comment.