Skip to content

Instantly share code, notes, and snippets.

@mildmojo
Created March 4, 2018 19:36
Show Gist options
  • Save mildmojo/40db613f21b67fa22802d29f6aeb9969 to your computer and use it in GitHub Desktop.
Save mildmojo/40db613f21b67fa22802d29f6aeb9969 to your computer and use it in GitHub Desktop.
More logic operators for bitsy. &&, ||, !==, &&!, and ||!
/* Operator logic is at the bottom of this script; need to declare the toolkit first. */
/*
================================
SCRIPT HOOKS TOOLKIT (@mildmojo)
================================
HOW TO USE:
1. Paste this whole file in script tags at the bottom of your Bitsy
exported game HTML, after the last /script> tag.
*/
(function scriptHooksToolkit(globals) {
// Allow multiple copies of this script to work in one HTML file.
globals.queuedInjectScripts = globals.queuedInjectScripts || [];
globals.queuedBeforeScripts = globals.queuedBeforeScripts || {};
globals.queuedAfterScripts = globals.queuedAfterScripts || [];
globals.superFuncs = globals.superFuncs || {};
globals.injectsDone = globals.injectsDone || false;
var queuedInjectScripts = globals.queuedInjectScripts;
var queuedBeforeScripts = globals.queuedBeforeScripts;
var queuedAfterScripts = globals.queuedAfterScripts;
var superFuncs = globals.superFuncs;
var injectsDone = globals.injectsDone;
var oldStartFunc = startExportedGame;
globals.startExportedGame = function doAllInjections() {
// Only do this once.
globals.startExportedGame = oldStartFunc;
if (injectsDone) {
return oldStartFunc();
}
globals.injectsDone = true;
// Rewrite scripts and hook everything up.
doInjects();
hookBefores();
hookAfters();
// Start the game. If original `startExportedGame` wasn't overwritten, call it.
if (startExportedGame === oldStartFunc) {
oldStartFunc.apply(this, arguments);
} else {
startExportedGame.apply(this, arguments);
}
};
// Examples: inject('names.sprite', 'console.dir(names)');
// inject('names.sprite', 'console.dir(names);', 'console.dir(sprite);');
// inject('names.sprite', ['console.dir(names)', 'console.dir(sprite);']);
globals.inject = function(searchString, codeFragments) {
var args = [].slice.call(arguments);
codeFragments = _flatten(args.slice(1));
queuedInjectScripts.push({
searchString: searchString,
codeFragments: codeFragments
});
};
// Ex: before('load_game', function run() { alert('Loading!'); });
// before('show_text', function run(text) { return text.toUpperCase(); });
// before('show_text', function run(text, done) { done(text.toUpperCase()); });
globals.before = function(targetFuncName, beforeFn) {
queuedBeforeScripts[targetFuncName] = queuedBeforeScripts[targetFuncName] || [];
queuedBeforeScripts[targetFuncName].push(beforeFn);
};
// Ex: after('load_game', function run() { alert('Loaded!'); });
globals.after = function(targetFuncName, afterFn) {
queuedAfterScripts.push({
targetFuncName: targetFuncName,
runFunc: afterFn
});
};
function doInjects() {
queuedInjectScripts.forEach(function(injectScript) {
_inject(injectScript.searchString, injectScript.codeFragments);
});
_reinitEngine();
}
function hookBefores() {
Object.keys(queuedBeforeScripts).forEach(function(targetFuncName) {
var superFn = globals[targetFuncName];
var beforeFuncs = queuedBeforeScripts[targetFuncName];
globals[targetFuncName] = function() {
var self = this;
var args = [].slice.call(arguments);
var i = 0;
runBefore.call(self);
// Iterate thru sync & async functions. Run each, finally run original.
function runBefore() {
// Update args if provided.
if (arguments.length > 0) {
args = [].slice.call(arguments);
}
// All outta before functions? Finish by running the original.
if (i === beforeFuncs.length) {
return superFn.apply(self, args);
}
// Assume before funcs that accept more args than the original are
// async and have a spot for an async callback.
if (beforeFuncs[i].length > superFn.length) {
beforeFuncs[i++].apply(self, args.concat(runBefore));
} else {
var newArgs = beforeFuncs[i++].apply(self, args) || args;
runBefore.apply(self, newArgs);
}
}
};
});
}
function hookAfters() {
queuedAfterScripts.forEach(function(afterScript) {
_after(afterScript.targetFuncName, afterScript.runFunc);
});
}
function _inject(searchString, codeToInject) {
var args = [].slice.call(arguments);
codeToInject = _flatten(args.slice(1)).join("\n");
// find the relevant script tag
var scriptTags = document.getElementsByTagName('script');
var scriptTag;
var code;
for (var i = 0; i < scriptTags.length; ++i) {
scriptTag = scriptTags[i];
var matchesSearch = scriptTag.textContent.indexOf(searchString) !== -1;
var isCurrentScript = scriptTag === document.currentScript;
if (matchesSearch && !isCurrentScript) {
code = scriptTag.textContent;
break;
}
}
// error-handling
if (!code) {
throw 'Couldn\'t find "' + searchString + '" in script tags';
}
// modify the content
code = code.replace(searchString, searchString + codeToInject);
// replace the old script tag with a new one using our modified code
scriptTag.remove();
scriptTag = document.createElement('script');
scriptTag.textContent = code;
document.head.appendChild(scriptTag);
}
function _after(targetFuncName, afterFn) {
var superFn = globals[targetFuncName];
globals[targetFuncName] = function() {
superFn.apply(this, arguments);
afterFn.apply(this, arguments);
};
}
function _reinitEngine() {
// recreate the script and dialog objects so that they'll be
// referencing the code with injections instead of the original
globals.scriptModule = new Script();
globals.scriptInterpreter = scriptModule.CreateInterpreter();
globals.dialogModule = new Dialog();
globals.dialogRenderer = dialogModule.CreateRenderer();
globals.dialogBuffer = dialogModule.CreateBuffer();
}
function _flatten(list) {
if (!Array.isArray(list)) {
return list;
}
return list.reduce(function(fragments, arg) {
return fragments.concat(_flatten(arg));
}, []);
}
})(window);
/*
=====================================
ADD CONDITIONAL OPERATORS (@mildmojo)
=====================================
Adds conditional logic operators:
- !== (not equal to)
- && (and)
- || (or)
- &&! (and not)
- ||! (or not)
Examples: candlecount > 5 && haslighter == 1
candlecount > 5 && papercount > 1 && isIndoors
haslighter == 1 || hasmatches == 1
candlecount > 5 && candlecount !== 666
candlecount > 5 &&! droppedlighter
droppedlighter ||! hasmatches
NOTE: The combining operators (&&, ||, &&!, ||!) have lower precedence than
all other math and comparison operators, so it might be hard to write
tests that mix and match these new operators and have them evaluate
correctly. If you're using multiple `&&` and `||` operators in one
condition, be sure to test every possibility to make sure it behaves
the way you want.
*/
(function(globals) {
'use strict';
inject('operatorMap.set("-", subExp);',
'operatorMap.set("!==", notEqExp);',
'operatorMap.set("&&", andExp);',
'operatorMap.set("||", orExp);',
'operatorMap.set("&&!", andNotExp);',
'operatorMap.set("||!", orNotExp);');
inject('var operatorSymbols = ["-", "+", "/", "*", "<=", ">=", "<", ">", "=="];',
'operatorSymbols.unshift("!==", "&&", "||", "&&!", "||!");');
globals.andExp = function andExp(environment,left,right,onReturn) {
right.Eval(environment,function(rVal){
left.Eval(environment,function(lVal){
onReturn( lVal && rVal );
});
});
};
globals.orExp = function orExp(environment,left,right,onReturn) {
right.Eval(environment,function(rVal){
left.Eval(environment,function(lVal){
onReturn( lVal || rVal );
});
});
};
globals.notEqExp = function notEqExp(environment,left,right,onReturn) {
right.Eval(environment,function(rVal){
left.Eval(environment,function(lVal){
onReturn( lVal !== rVal );
});
});
};
globals.andNotExp = function andNotExp(environment,left,right,onReturn) {
right.Eval(environment,function(rVal){
left.Eval(environment,function(lVal){
onReturn( lVal && !rVal );
});
});
};
globals.orNotExp = function orNotExp(environment,left,right,onReturn) {
right.Eval(environment,function(rVal){
left.Eval(environment,function(lVal){
onReturn( lVal || !rVal );
});
});
};
})(window);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment