Revision of Twine: Improved back and forward buttons in Sugarcane from Sat, 05/18/2013 - 23:22

This script allows Sugarcane to use HTML5 history management instead of URL hash strings to keep track of the game state. This means that various non-deterministic state changes (like random numbers, player input, state changes inside <<replace>> macros, etc.) will be properly remembered when you use the browser's Back button. This code also updates <<back>> and <<return>> and the Rewind menu.

Note: due to a conflict in the Twine engine, I've had to include Transition CSS with this script. So, you must also include one of the CSS transitions on that page. The default transition CSS code is as follows:

.transition-in{opacity:0;position:absolute}.passage:not(.transition-out){transition:1s;-webkit-transition:1s}
.transition-out{opacity:0;position:absolute}

Note 2: IE9 users will be given the old hashchange functionality anyway.

(function(){var hasPushState=(typeof window.history.pushState=="function");
History.prototype.display=function(d,b,a){var c=tale.get(d);if(a!="back"){this.history.unshift({passage:c,variables:clone(this.history[0].variables)});
this.history[0].hash=this.save();if(hasPushState&&this.history&&this.history.length>2){window.history.pushState(this.history,document.title);
}}this.history[0].hash=this.save();var e=c.render();e.style.visibility="visible";
if(a!="offscreen"){var p=$("passages");for(var j=0;j<p.childNodes.length;
j+=1){var q=p.childNodes[j];q.classList.add("transition-out");
setTimeout(function(){if(q.parentNode){q.parentNode.removeChild(q);
}},1000);}e.classList.add("transition-in");setTimeout(function(){e.classList.remove("transition-in");
},1);p.appendChild(e);}if((a=="quietly")||(a=="offscreen")){e.style.visibility="visible";
}if(a!="offscreen"){document.title=tale.title+": "+c.title;this.hash=this.save();
if(!hasPushState){this.hash=this.save();window.location.hash=this.hash;
}window.scroll(0,0);}return e;};History.prototype.restart=function(){window.location.reload();
};macros["return"]=macros.back={handler:function(a,b,e){var el,d="";
var steps=1;if(e[0]){if(e[1]=="steps"){if(isNaN(e[0])){throwError(a,"parameter before 'steps' must be a number.");
return;}else{if(e[0]<state.history.length){d=state.history[e[0]].passage.title;
steps=e[0];}}}else{if(tale.get(e[0]).id==undefined){throwError(a,"The "+e[0]+" passage does not exist");
return;}for(var c=0;c<state.history.length;c++){if(state.history[c].passage.title==e[0]){d=e[0];
steps=c;break;}}}}else{d=state.history[1].passage.title;}if(!d){return;
}else{el=document.createElement("a");el.className="return";el.onclick=function(){if(b=="back"){if(hasPushState){window.history.back();
return;}while(steps>=0){if(state.history.length>1){state.history.shift();
}steps--;}}state.display(d);};el.href="javascript:void(0)";el.innerHTML="<b>«</b> "+b[0].toUpperCase()+b.slice(1);
a.appendChild(el);}}};Interface.buildSnapback=function(){var c=false;
removeChildren(document.getElementById("snapbackMenu"));for(var a=state.history.length-1;
a>=0;a--){if(state.history[a].passage&&state.history[a].passage.tags.indexOf("bookmark")!=-1){var b=document.createElement("div");
b.pos=a;b.onclick=function(){var p=this.pos;var n=state.history[p].passage.title;
window.history.go(-(p+1));while(p>=0){if(state.history.length>1){state.history.shift();
}p--;}state.display(n);};b.innerHTML=Passage.prototype.excerpt.call(state.history[a].passage);
document.getElementById("snapbackMenu").appendChild(b);c=true;
}}if(!c){var b=document.createElement("div");b.innerHTML="<i>No passages available</i>";
document.getElementById("snapbackMenu").appendChild(b);}};History.prototype.init=function(){if(!this.restore()){this.display("Start",null);
}if(!hasPushState){this.hash=window.location.hash;this.interval=window.setInterval(function(){a.watchHash.apply(a);
},250);}};window.onpopstate=function(e){console.log("   Popped");
console.log(e.state);if(e.state===null){return;}if(e.state&&e.state.length>0){state.history=e.state;
}else{state=new History();state.init();}state.display(state.history[0].passage.title,null,"back");
};if(hasPushState){clearInterval(state.interval);}}());

Feel free to report any bugs to @webbedspace.

pensive-mosquitoes