intfiction.org

The Interactive Fiction Community Forum
It is currently Mon Oct 23, 2017 6:45 am

All times are UTC - 6 hours [ DST ]




Post new topic Reply to topic  [ 6 posts ] 
Author Message
 Post subject: Ramus rocks!
PostPosted: Mon Mar 19, 2012 9:33 pm 
Offline
User avatar

Joined: Fri Jan 27, 2012 2:34 pm
Posts: 271
Location: Boston
There are about three things you need to know to make something in Ramus, and I put them together into a little template file. This because I got tangled up editing the demo.

The absolute minimum you need to use Ramus is here:

Rant: show
Code:
<!DOCTYPE html>
<!-- copyright and authorship info go here. Legal, if you want.



 -->
<html>
 <head>
  <meta http-equiv="Content-type" content="text/html; charset=utf-8">
  <title>Document Title</title>
  <style type="text/css">  <!-- Edit these parameters to change the look
                        and feel of your document. -->
html { Background-color: #442200; }
body {
        Opacity: 0.95;
   Font-size: 1.1em;
   Width: 80ex;
   Margin: 5ex auto;
   Padding: 5ex;
   Background-color: #885522;
   Color: white;
   Border: 1px solid black;
   Border-right-width: 2px;
   Border-bottom-width: 2px;
   -webkit-box-shadow: 3px 3px 3px #999999;
   -moz-box-shadow: 3px 3px 3px #999999;
   Box-shadow: 3px 3px 3px #999999;
   Font-family: serif;
   Position: relative;
}
a { Color: #000000; }
a.external { Font-variant: small-caps; }
a.external:visited { Color: #990066; }
h1, h2, h3, h4, h5, h6 { Font-weight: bold; Font-family: sans-serif; }
h1 { Font-size: 1.6em; Border-bottom: 1px solid black; Text-align: center; }
h2 { Font-size: 1.5em; }
h3 { Font-size: 1.4em; }
h4 { Font-size: 1.3em; }
h5 { Font-size: 1.2em; }
h6 { Font-size: 1.1em; }
#transcript table { Margin: 1em auto; }
#transcript th, #transcript td {
   Margin: 0; Padding: 0.5em;
   Border: 1px solid #808080; Border-collapse: collapse;
}
#transcript th { Color: #ffffff; Background-color: #808080; Font-family: serif; }
#transcript hr { Color: #808080; }
address { Font-size: 0.9em; Text-align: center; }
dt { Font-weight: bold; }
  </style>
 </head>

 <body>
 
 
  <div id="transcript">
   This document requires Javascript.
  </div>

  <!--The above message is displayed by default.  Then, when JavaScript kicks
  in, it overwrites the "need JS" message.
 
  I really suggest you keep this div element first, before the story element.
  The reason is that if you slip up and have a stray, unclosed italic tag, with
  this element at the end of the file that formatting will run through to the
  transcript message, the formatting of which will then apply to your whole
  game.
 
  Keep this element first, and such formatting errors will be sequential in
  output as in the html file (this file here), and therefore easier to spot.-->

 
  <div id="story" style="Display: none;">
   <div id="start">   
   <p>This document uses <a href="http://ramus.notimetoplay.org"><b>Ramus</a>!</b>
   --A nifty little JavaScript program created
   by <a href="http://felix.plesoianu.ro/">Felix PleČ™oianu</a></p>
   
   <p>This text is the text that starts the document!  From here
   on, you're on your own.</p>
   <p>Make a link <a rel="node2">like this</a> or <a rel="node3">
   like this</a>.</p>
   </div>
   
   <div id="node2">
   Click through to the Ramus website for more detailed info.</p>
   </div>
   
   <div id="node3">
   [?do clear_links();?]
   <p>Use the above line to clear past links, so the player can't
   back up.</p>
   </div>
   
   
<!--    COOKBOOK

[?do clear_links();?]
      ...will clear all prior links.  Use this, for example, if you
      list exits as links, and when a player clicks through one it no
      longer makes sense for him to click through another; or if the
      links are mutually exclusive choices of any kind.  You put this
      command in the line after the initial <div id="whatever> line.
      
  <div id="nodename">
  <p>This is some text to put in your node! </p>
  </div>
      ...is the basic data structure for a node.  The <div>..</div>
      tags contain the node information.  <p>..</p> are a good idea
      to keep your formatting clean.  But if you want the text to
      add on to the previous paragraph and not to create a new one,
      then don't use the initial <p>, and similar for not ending paragraphs.

  nodetext nodetext lalala lala <a rel="newnodenametag">link text</a> node
  text nodetext la la la
      ..."newnodenametag" has to match the <div id="--this--"> in order
      for the link to work.
-->   

  </div> <!--story-->

  <!-- GEARWORKS.  You shouldn't need to edit below this line. -->

  <script type="text/javascript">
var transcript = null;
var templates = {};

window.onload = function () {
   transcript = document.getElementById("transcript");
   
   transcript.innerHTML = "";
   transcript.appendChild(render_content(get_elements("start")));
   var links = transcript.getElementsByTagName("a");
   setup_links(links);
};

function get_elements(ids) {
        ids = ids.split(" ");
        var elements = [];
        for (var i = 0; i < ids.length; i++) {
                var elt = document.getElementById(ids[i]);
                if (elt) elements.push(elt);
        }
        return elements;
}

function render_content(elements) {
   var turn = document.createElement("div");
   turn.className = "turn";
   for (var i = 0; i < elements.length; i++) {
                var elt = elements[i];
                if (!templates[elt.id])
                        templates[elt.id] = Mold.bake(elt.innerHTML);
                Mold.castAppend(turn, templates[elt.id]);
   }
   return turn;
}

function setup_links(links) {
   for (var i = 0; i < links.length; i++) {
      var a = links[i];
      if (!a.href) a.href = "#"; else a.className = "external";
      if (a.rel) a.onclick = function () {
                        this.parentNode.replaceChild(this.firstChild, this);
         var turn = render_content(get_elements(this.rel));
         setup_links(turn.getElementsByTagName("a"));
                        turn.style.opacity = 0;
         transcript.appendChild(turn);
         //transcript.lastChild.scrollIntoView();
         smoothScrollTo(transcript.lastChild);
                        emile(turn, "Opacity: 1;", {duration: 1000});
         return false;
      };
   }
}

function clear_links() {
        var links = transcript.getElementsByTagName("a");
       
        for (var i = 0; i < links.length; ) {
                var a = links[i];
                if (a.rel)
                        a.parentNode.replaceChild(a.firstChild, a);
                else
                        i++;
        }
}

function clear_text() {
        transcript.innerHTML = "";
}

// emile.js (c) 2009 Thomas Fuchs
// Licensed under the terms of the MIT license.

(function(emile, container){
  var parseEl = document.createElement('div'),
    props = ('backgroundColor borderBottomColor borderBottomWidth borderLeftColor borderLeftWidth '+
    'borderRightColor borderRightWidth borderSpacing borderTopColor borderTopWidth bottom color fontSize '+
    'fontWeight height left letterSpacing lineHeight marginBottom marginLeft marginRight marginTop maxHeight '+
    'maxWidth minHeight minWidth opacity outlineColor outlineOffset outlineWidth paddingBottom paddingLeft '+
    'paddingRight paddingTop right textIndent top width wordSpacing zIndex').split(' ');

  function interpolate(source,target,pos){ return (source+(target-source)*pos).toFixed(3); }
  function s(str, p, c){ return str.substr(p,c||1); }
  function color(source,target,pos){
    var i = 2, j, c, tmp, v = [], r = [];
    while(j=3,c=arguments[i-1],i--)
      if(s(c,0)=='r') { c = c.match(/\d+/g); while(j--) v.push(~~c[j]); } else {
        if(c.length==4) c='#'+s(c,1)+s(c,1)+s(c,2)+s(c,2)+s(c,3)+s(c,3);
        while(j--) v.push(parseInt(s(c,1+j*2,2), 16)); }
    while(j--) { tmp = ~~(v[j+3]+(v[j]-v[j+3])*pos); r.push(tmp<0?0:tmp>255?255:tmp); }
    return 'rgb('+r.join(',')+')';
  }
 
  function parse(prop){
    var p = parseFloat(prop), q = prop.replace(/^[\-\d\.]+/,'');
    return isNaN(p) ? { v: q, f: color, u: ''} : { v: p, f: interpolate, u: q };
  }
 
  function normalize(style){
    var css, rules = {}, i = props.length, v;
    parseEl.innerHTML = '<div style="'+style+'"></div>';
    css = parseEl.childNodes[0].style;
    while(i--) if(v = css[props[i]]) rules[props[i]] = parse(v);
    return rules;
  } 
 
  container[emile] = function(el, style, opts, after){
    el = typeof el == 'string' ? document.getElementById(el) : el;
    opts = opts || {};
    var target = normalize(style), comp = el.currentStyle ? el.currentStyle : getComputedStyle(el, null),
      prop, current = {}, start = +new Date, dur = opts.duration||200, finish = start+dur, interval,
      easing = opts.easing || function(pos){ return (-Math.cos(pos*Math.PI)/2) + 0.5; };
    for(prop in target) current[prop] = parse(comp[prop]);
    interval = setInterval(function(){
      var time = +new Date, pos = time>finish ? 1 : (time-start)/dur;
      for(prop in target)
        el.style[prop] = target[prop].f(current[prop].v,target[prop].v,easing(pos)) + target[prop].u;
      if(time>finish) { clearInterval(interval); opts.after && opts.after(); after && setTimeout(after,1); }
    },10);
  }
})('emile', this);

var Mold = {};

// Evaluate something in a relatively clean environment, to prevent
// name clashing.
Mold.cleanEval = function(__string) {
  return window.eval(__string);
};

(function() {
  function splitTemplate(template) {
    var parts = [];
    function addString(string) {
      if (string.length)
        parts.push(string);
    }

    while (true) {
      var open = template.search(/[\[<]\?/);
      if (open == -1) {
        addString(template);
        break;
      }
      else {
        addString(template.slice(0, open));
        var close = template.indexOf("?" + (template.charAt(open) == "<" ? ">" : "]"), open + 2);
        if (close == -1) throw new Error("'<?' without matching '?>' in template.");
        var content = template.slice(open + 2, close), match = content.match(/^([\w\.]+)(?:\s+((?:\r|\n|.)+))?$/);
        if (!match) throw new Error("Template command ('" + content + "') does not follow 'command [arguments]' format.");
        parts.push({command: match[1], args: match[2]});
        template = template.slice(close + 2);
      }
    }

    return parts;
  }

  var snippets, snippet;
  Mold.addSnippet = function addSnippet(f) {
    if (snippets) {
      snippets.push(f);
      return snippets.length - 1;
    }
  };

  var labels;
  Mold.setLabel = function setLabel(name) {
    return function(node) {
      if (!labels) labels = {};

      if (forDepth > 0) {
        var array = labels[name] || (labels[name] = []);
        if (array.push) array.push(node);
      }
      else {
        labels[name] = node;
      }
    };
  };

  Mold.forDepth = 0;
  var casting = false;

  var HTMLspecial = {"<": "&lt;", "&": "&amp;", "\"": "&quot;"};
  Mold.escapeHTML = function escapeHTML(text) {
    return String(text).replace(/[<&\"]/g, function(ch) {return HTMLspecial[ch];});
  };
  var JSspecial = {"\"": "\\\"", "\\": "\\\\", "\f": "\\f", "\b": "\\b",
                   "\n": "\\n", "\t": "\\t", "\r": "\\r", "\v": "\\v"};
  function escapeString(text) {
    return String(text).replace(/[\"\\\f\b\n\t\r\v]/g, function(ch) {return JSspecial[ch];});
  }

  Mold.attachEvent = null;
  Mold._attachEvent = function(node, eventName, func) {
    var wrapped = function(event) {func(event || window.event, node);};
    if (eventName == "enter") {
      var origFunc = func;
      func = function(event, node){if ((event.charCode || event.keyCode) == 13) origFunc(event, node);};
      eventName = "keydown";
    }

    if (Mold.attachEvent)
      Mold.attachEvent(node, eventName, wrapped);
    else if (node.addEventListener)
      node.addEventListener(eventName, wrapped, false);
    else
      node.attachEvent("on" + eventName, wrapped);
  };

  Mold.forEach = function forEach(array, f) {
    for (var i = 0; i < array.length; i++)
      f(array[i], i);
  };
  var hop = Object.prototype.hasOwnProperty;
  Mold.forEachIn = function forEachIn(obj, f) {
    var i = 0;
    for (var n in obj) {
      if (hop.call(obj, n))
        f(n, obj[n], i++);
    }
  };

  var custom = {};
  Mold.define = function(name, func) {
    custom[name] = func;
  };
  Mold.dispatchCustom = function(name, arg, output) {
    if (!custom.hasOwnProperty(name))
      throw new Error("Unrecognised template command: '" + name + "'.");
    output.push(custom[name](arg, output));
  };

  Mold.bake = function bake(template) {
    var parts = splitTemplate(template);
    var func = ["[function templateFunction($arg, __output){\nvar __out = __output || [];\n"];
    var stack = [], match;

    while (parts.length) {
      var cur = parts.shift();
      if (typeof cur == "string") {
        func.push("__out.push(\"" + escapeString(cur) + "\");\n");
        continue;
      }
      switch (cur.command) {

      case "text": case "t":
        func.push("__out.push(Mold.escapeHTML(" + cur.args + "));\n");
        break;
      case "html": case "h":
        func.push("__out.push(" + cur.args + ");\n");
        break;
      case "do": case "d":
        func.push(cur.args + "\n");
        break;

      case "if":
        stack.push("if");
        func.push("if (" + cur.args + ") {\n");
        break;
      case "elif":
        if (stack[stack.length - 1] != "if") throw new Error("'elif' without matching 'if' in template.");
        func.push("} else if (" + cur.args + ") {\n");
        break;
      case "else":
        if (stack[stack.length - 1] != "if") throw new Error("'else' without matching 'if' in template.");
        func.push("} else {\n");
        break;
      case "if.":
        if (stack.pop() != "if") throw new Error("'if.' without matching 'if' in template.");
        func.push("}\n");
        break;

      case "for":
        stack.push("for");
        if (match = cur.args.match(/^([\w\$_]+)(?:,\s*([\w\$_]+))?\s+in\s+((?:\r|\n|.)+)$/))
          func.push("Mold.forDepth++;\nMold.forEachIn(" + match[3] + ", function(" + match[1] + ", " +
                    (match[2] || "$dummy") + ", $i) {\n");
        else if (match = cur.args.match(/^([\w\$_]+)\s+((?:\r|\n|.)+)$/))
          func.push("Mold.forDepth++;\nMold.forEach(" + match[2] + ", function(" + match[1] + ", $i) {\n");
        else
          throw new Error("Malformed arguments to 'for' form in template -- expected variable name followed by expression.");
        break;
      case "for.":
        if (stack.pop() != "for") throw new Error("'for.' without matching 'for' in template.");
        func.push("});\nMold.forDepth--;\n");
        break;

      case "event":
        if (match = cur.args.match(/^(\w+)\s+((?:\r|\n|.)+)$/))
          func.push("__out.push(\"<var class=\\\"__mold \" + Mold.addSnippet(function(__node){Mold._attachEvent(__node, \"" +
                    match[1] + "\", function($event, $node) {\n" + match[2] + "\n});}) + \"\\\"></var>\");\n");
        else
          throw new Error("Malformed arguments to 'event' form in template -- expected event name followed by handler body.");
        break;
      case "run": case "r":
        func.push("__out.push(\"<var class=\\\"__mold \" + Mold.addSnippet(function($node){" + cur.args + "}) + \"\\\"></var>\");\n");
        break;
      case "label": case "l":
        func.push("__out.push(\"<var class=\\\"__mold \" + Mold.addSnippet(Mold.setLabel(\"" + escapeString(cur.args) +
                  "\")) + \"\\\"></var>\");\n");
        break;
      case "call":
        var f = cur.args, arr = f.indexOf("->"), arg = "null";
        if (arr != -1) {arg = f.slice(arr + 2); f = f.slice(0, arr);}
        func.push(f + "(" + arg + ", __out);");
        break;

      default:
        func.push("Mold.dispatchCustom(\"" + escapeString(cur.command) + "\", " + (/^\s*$/.test(cur.args) ? "null" : cur.args) + ", __out);\n");
      }
    }
    if (stack.length) throw new Error("Unclosed blocks in template (" + stack.join() + ").");

    func.push("return __output ? \"\" : __out.join(\"\");\n}]");
    // The brackets are there to work around some weird IE6 behaviour.
    return Mold.cleanEval(func.join(""))[0];
  };

  Mold.cast = function cast(target, mold, arg) {
    if (casting) throw new Error("Mold.cast must not be called recursively.");

    snippets = [], snippet = 0, labels = null, forDepth = 0, casting = true;
    try {
      target.innerHTML = mold(arg);
      var varTags = target.getElementsByTagName("VAR"), array = [];
      // Copy tags into array -- FF modifies the varTags collection when you delete nodes in it.
      for (var i = 0; i < varTags.length; i++)
        array.push(varTags[i]);
      for (var i = 0; i < array.length; i++) {
        var varTag = array[i], match = varTag.className.match(/^__mold (\d+)$/);
        if (match) {
          var prev = varTag.previousSibling;
          while (prev && prev.nodeType == 3) prev = prev.previousSibling;
          snippets[match[1]](prev || varTag.parentNode);
          varTag.parentNode.removeChild(varTag);
        }
      }

      var result = labels;
    }
    finally {
      labels = snippets = null;
      casting = false;
    }
    return result;
  };

  Mold.castAppend = function castAppend(target, mold, arg) {
    var temp = target.ownerDocument.createElement("DIV");
    var result = Mold.cast(temp, mold, arg);
    while (temp.firstChild)
      target.appendChild(temp.firstChild);
    return result;
  };
})();

// Based on the tutorial at
// http://www.itnewb.com/v/Creating-the-Smooth-Scroll-Effect-with-JavaScript

function currentYPosition() {
   // Firefox, Chrome, Opera, Safari
   if (window.pageYOffset) return window.pageYOffset;
   // Internet Explorer 6 - standards mode
   if (document.documentElement && document.documentElement.scrollTop)
      return document.documentElement.scrollTop;
   // Internet Explorer 6, 7 and 8
   if (document.body.scrollTop) return document.body.scrollTop;
   return 0;
}

function elementYPosition(elt) {
   var y = elt.offsetTop;
   while (elt.offsetParent && elt.offsetParent != document.body) {
      elt = elt.offsetParent;
      y += node.offsetTop;
   }
   return y;
}

function smoothScrollTo(element) {
   var startY = currentYPosition();
   var stopY = elementYPosition(element);
   var distance = Math.abs(startY - stopY);
   if (distance < 100) {
      scrollTo(0, stopY);
      return;
   }
   var speed = Math.round(distance / 100);
   if (speed > 20) speed = 20;
   var step = Math.round(distance / 25);
   var leapY = stopY > startY ? startY + step : startY - step;
   var timer = 0;
   if (stopY > startY) {
      for (var i = startY; i < stopY; i += step) {
         setTimeout(
            "window.scrollTo(0, " + leapY + ")",
            timer * speed);
         leapY += step;
         if (leapY > stopY) leapY = stopY;
         timer++;
      }
   } else {
      for (var i = startY; i > stopY; i -= step) {
         setTimeout(
            "window.scrollTo(0, " + leapY + ")",
            timer * speed);
         leapY -= step;
         if (leapY < stopY) leapY = stopY;
         timer++;
      }
   }
}
  </script>
 </body>
</html>


Save this to a .htm or .html file, edit it with a text editor, and open it with your browser to go.


Conrad.

_________________
http://tiltedcandle.wordpress.com


Last edited by conradcook on Mon Mar 19, 2012 9:51 pm, edited 1 time in total.

Top
 Profile Send private message  
Reply with quote  
 Post subject: Re: Ramus rocks!
PostPosted: Mon Mar 19, 2012 9:41 pm 
Offline

Joined: Tue Mar 09, 2010 2:34 pm
Posts: 5082
Location: Burlington, VT
Thanks Conrad! And thanks to Felix P. too (who if I'm not mistaken is not the Felix who posts here under that name) - Ramus looks very nice, and easier to learn than Undum. I suppose I might have to learn a little Javascript if I want to do any state tracking in it, but it seems easier than diving straight into the Undum examples.


Top
 Profile Send private message  
Reply with quote  
 Post subject: Re: Ramus rocks!
PostPosted: Tue Mar 20, 2012 12:43 am 
Offline
User avatar

Joined: Fri Jan 27, 2012 2:34 pm
Posts: 271
Location: Boston
Guys,

Find attached a little interactive story I've just cranked out using Ramus. This is to show what you can do with the three moves illustrated by the earlier template, and hopefully to entertain.


Conrad.


Attachments:
Unicorn_Story.zip [278.76 KiB]
Downloaded 171 times

_________________
http://tiltedcandle.wordpress.com
Top
 Profile Send private message  
Reply with quote  
 Post subject: Re: Ramus rocks!
PostPosted: Tue Mar 20, 2012 2:08 am 
Offline
User avatar

Joined: Wed Nov 30, 2011 2:43 am
Posts: 384
Cool. But are there any particular reasons (religious or otherwise) you're not using JQuery?


Top
 Profile Send private message  
Reply with quote  
 Post subject: Re: Ramus rocks!
PostPosted: Tue Mar 20, 2012 8:28 am 
Offline
User avatar

Joined: Fri Jan 27, 2012 2:34 pm
Posts: 271
Location: Boston
No idea what JQuery is. What, did I code it badly?

Conrad.

_________________
http://tiltedcandle.wordpress.com


Top
 Profile Send private message  
Reply with quote  
 Post subject: Re: Ramus rocks!
PostPosted: Tue Mar 20, 2012 9:59 am 
Offline
User avatar

Joined: Fri Jan 27, 2012 2:34 pm
Posts: 271
Location: Boston
Reading the google I find:

Code:
The best featuring for jQuery is the effects you can accomplish, with less code than what it would take with JavaScript. Most common jQuery effects are drop down menus, drag and drop elements, animations and form validation.


Also a Wikipedia thing I don't entirely fathom and a web page for JQ itself.

--I did not write the JavaScript code. That was done by Felix. There's a link to his personal page and to the Ramus project page in that file.

As I understand the technology tree -- I might be entirely wrong; such things aren't really on my radar -- Inform 7 was modified to be clickable through a nifty thing called Vorple. OTOH, there was something called Undum, which also made for clickable IF. But both were pretty complex. So Felix made Ramus, with greatly simplified options, in order to greatly simplify document production.

I have no idea why he wrote the JS code in the way he did.


Conrad.

_________________
http://tiltedcandle.wordpress.com


Top
 Profile Send private message  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 6 posts ] 

All times are UTC - 6 hours [ DST ]


Who is online

Users browsing this forum: No registered users and 2 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group