Howdy, Stranger!

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

HOW do you create and use custom macros? (+ ranting)

Hi... anyone...?

I am using Twine 2 (linux) with SugarCube 2 (yes, installed).

I'm not very experienced with
- Twine (discovered a month or less)
- nor forum posting*
- nor coding (well I AM an IT student but 1. I'll say I have experience after at least 5 years)
(- nor proper english writing, vive la France.)


[Ranting part - ok to skip]

* I feel like it's the first time I NEED to post, usually I easily find what I'm looking in answers to other people's questions like many others do I guess. But the past 4 days or so I've been trying to learn how to Twine and it feels... hard, frustrating, confusing (i.e. http://twinery.org/forum/discussion/2853/what-do-i-consider-when-choosing-1-x-or-2-x or https://www.reddit.com/r/twinegames/comments/2rgikc/is_there_any_comprehensive_guide_yet_for_twine_2/)...

I've been wondering if I am the only person that feels this way : when you search for something Twine-related most of the time you end up either with something "outdated", or not the format/version you're explicitly asking for (in my case SugarCube 2 for Twine 2) so also useless, or just the same generic results no matter what you type in the bar :

- Twine wiki, which I don't like (confusing, not extensive nor concise)
- Harlowe's reference, which I like (and had me use Harlowe but then I went to SugarCube but... I can't remember why)
- Adam Hammond'guides (I prefer reading pdf) - neat and concise and clear but very "short", I wish they'd go "deeper"
- the "Motoslave site", which I quite like - neat, clear, concise, quite extensive (maybe one of the reasons why I switched to SugarCube)

Sometimes I feel like I should just give up but I've seen people MAKING GREAT THINGS with Twine so it's possible.

Overall I feel like there's several "introduction to...", "getting started...", "quick guide..." but once you've passed that (or feel like you did) and want something more, you just stumble on some people's blog who created great macros and give the code but you lack the intermediary explanations between the "beginner's guide" and the "pro's custom macro" to understand what's really going on... and I don't want to copy and paste, I want to understand how to build my own things...
That's a question inside the question : How/where did they find that missing step to learn? OR (drama mode) How many YEARS of failure does it take?

[end of Ranting part]


[Question Part]


I've decided to create a custom macro called "scoreDisplay".
GOAL : pass X and Y to scoreDisplay, X being the number of correct answers and Y incorrect ones
so that scoreDisplay... displays the percentage of correct answer AND displays it in a certain color depending on how good it is (i.e. red for a low percentage).

[process part - you can also skip]
I've tried using this : http://www.motoslave.net/sugarcube/2/docs/api-macro.html (Macro.add)
to understand how I should do but... well it says "Example of a very simple <<if>>/<<elseif>>/<<else>> macro." and it looks like chinese to me so now I feel like the dumbest person in the world.
So I found http://eturnerx.blogspot.fr/2012/12/how-to-create-custom-macros-in-twine.html which explains more but is dated/ not for the versions I'm using so I'm still confused....
Tired of always finding dated / various different ways of doing macros I gave up and decided to stick with the SugarCube 2 guide and bump my head on my keyboard until it works (yes, sometimes it DOES).
[end of process part]

Here's what I came up with (which of course does NOT work) and why in comment :
the GOAL is simplified : display the sum of a + b
/* Later on I'd like to pass a and b as arguments but right now I don't know how I should do that since "Macro handlers are called with no arguments" - motoslave says and then "but with this set to an execution context object." - motoslave also says and I'm still trying to figure out what that means (remember the "it looks like chinese to me so now I feel like the dumbest person in the world"). */

1) Story JavaScript :
/* testMacro */
Macro.add("testMacro", { // I use the motoslave way to declare a new macro
handler: function(){ // this part seems common to all ways so it can't be wrong..?
try {
var a = 10;
var b = 5;
document.getElementById("test").innerHTML = a + b; // I tell it to display the sum in the "test" span
} catch () {return this.error("error");} // I intend to check how to do precise error handling properly
} // handler function
} // definition
); // macro

2) StoryPassage ("Test") :

/* place to display the result */
<span id="test"></span>
/* calling the macro */
<<testMacro>>

What I get is the catch error message and I'd like to understand what's wrong.
(And after that :
Error: macro <<testMacro>> does not exist)

Any help or suggestion or link to a guide is welcome !
(I'd even understand and accept "Duh, you don't make any sense at all, just. go. away.")

Comments

  • Let me answer about the errors first.

    You should have received an error right at the start saying something about an unexpected token. That's because you cannot leave the parameter list of a catch empty, which you did, so that's the problem there.

    Beyond that, you're attempting to fetch an element which is not on the page yet, because the passage is still rendering, so the call to getElementById() is going to return null, meaning the attempt to access innerHTML is going to throw an exception.


    Moving on to the more general issue.

    SugarCube's APIs are largely meant for authors/developers who know, or are learning, JavaScript and want to do more than SugarCube provides by default.

    If you do not know JavaScript and it looks like "chinese" to you, implying that you are unlikely to ever be proficient with it, then you may want to rethink using the Macro API—I'm not trying to discourage you, however, banging your head against the wall is unlikely to end well. Instead, I'd suggest trying out the built-in <<widget>> macro, which allows you to make macros using the standard macros and wiki text that you'd use normally within a story.

    For example: (goes into a widget-tagged passage)
    @'
    >>\
    <</widget>>
    
    I used temporary variables, since you don't want those scratch variables going into the history.

    Example usage:
    /* Answered 8 correct, 3 incorrect questions. */
    <<scoreDisplay 8 3>>
    
    /* Answered 6 correct, 5 incorrect questions. */
    <<scoreDisplay 6 5>>
    
    /* Answered 3 correct, 8 incorrect questions. */
    <<scoreDisplay 3 8>>
    
  • First, thanks a lot, I think I'll start by trying to make widgets to solve my problems.
    (About the parameter list of a catch empty you're right it was a previous mistake that I did not correct after pasting on this post, sorry. I'd like to add for any noob reading this that publishing and opening in browser gives more precise error messages than "play" with Twine does.)

    Buuuut I still wanted to solve that "element which is not on the page yet". I did not manage to use PassageReady or predisplay but I'll try again later, so I tried plain javascripting.

    Story Javascipt :
    function scoreDisplay(content, answer) {
    /* answer being an array with number of incorrect answers at index 0 and correct ones at 1 */
    	var score = (answer[1]*100) / (answer[0]+answer[1]);
    	if(score<50) {content.style.color = "rgba(255, 9, 0, 1)";} // red for bad
    	else if(score>50) {content.style.color = "rgba(200, 255, 0, 1)";} // green for good
      content.innerHTML = score + "%";              
    }
    
    window.onload = function () { // solves the script activating before html had loaded
    	scoreDisplay(document.getElementById('score'), Wikifier.getValue("$answer"));
    }
    

    I did not know about the Wikifier.getValue("$answer") to fetch the value of a Twine variable to use it in JavaScript. I found out thanks to another of your answers (https://twinery.org/forum/discussion/2534/interchange-javascript-and-twine-2-0-3-sugarcube-web-storage-variables)*. Actually I had never heard about wikifiers at all and that's one of the things that made me think about chinese though it does not sound chinese at all. But right know I can use the methods but I don't... get what it is. The explanations I've found so far concern Twine 1 and they don't look like they're talking about the same wikifier... moving on.

    Test Passage :
    /*42 wrong 58 right*/
    <<set $answer to [42, 58]>>
    <span id="score"></span>
    

    Which gives a nice green 58% in the right place etc... So I CAN also do things that way, but I don't know if it's the proper way, or if there is one.
    I'm considering setting the answer as answered once I'll be able to post the macro to accomplish the simple goal set . I still have hope to be able to do so by tomorrow, if not, the widget way does the trick anyway so it's fine.

    Thanks again TheMadExile for your spot-on and detailed answer(s)*.
  • edited September 2016
    Iwasabee wrote: »
    window.onload = function () { // solves the script activating before html had loaded
    	scoreDisplay(document.getElementById('score'), Wikifier.getValue("$answer"));
    }
    
    That's only going to work on the starting passage. If you're bound and determined to solve this with JavaScript and want a general solution, use a postdisplay task—postrender would work here as well, but you'd have to target the content buffer—or a macro.

    You don't need to use Wikifier.getValue() here, simply access the current variable store natively. For example:
    scoreDisplay(document.getElementById('score'), State.variables.answer);
    

    Finally, your conditions are setup so that a score of exactly 50 results in the <span> not being styled—I'm unsure if that was intentional.

    Iwasabee wrote: »
    Which gives a nice green 58% in the right place etc... So I CAN also do things that way, but I don't know if it's the proper way, or if there is one.
    A macro, however it's created, is the right fit here. What you want to do here doesn't really need post-processing of any kind.

    As an example, here's your Macro API macro:
    /*
    	<<scoreDisplay incorrect correct>>
    */
    Macro.add('scoreDisplay', {
    	handler : function () {
    		if (this.args.length < 2) {
    			var errors = [];
    			if (this.args.length < 1) { errors.push('incorrect'); }
    			if (this.args.length < 2) { errors.push('correct'); }
    			return this.error('no ' + errors.join(' or ') + ' answers specified');
    		}
    
    		var
    			incorrect = this.args[0],
    			correct   = this.args[1],
    			score     = (correct * 100) / (incorrect + correct);
    
    		jQuery('<span></span>')
    			.css('color', score < 50 ? 'rgba(255, 9, 0, 1)' : 'rgba(200, 255, 0, 1)')
    			.text(score.toFixed(1) + '%')
    			.appendTo(this.output);
    	}
    });
    
    Usage:
    /* Answered 42 incorrect and 58 correct. */
    <<scoreDisplay 42 58>>
    
Sign In or Register to comment.