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
Try something like the follow:
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.
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.
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.
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 ...
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: Naturally, that would require you to write some of your code as pure JavaScript, which may or may not be an issue for you.
Um, like about 8 pages of code where the stuff is inside and which, unless heavily doctored and shortened would probably not help much
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 ...
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.
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.
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?
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…?
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.
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:
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.
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.
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.
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. 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:
Usage examples:
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: I used <<click>> and <<goto>> in this example, but it will work for any macro which accepts links.
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.
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:
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:
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.
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.
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.
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:
Here's an IIFE, which I'm including because you'll probably see them sooner or later. 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).
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.
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 ...
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.
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): 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.
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):
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 ";".
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!