Skip to content

Instantly share code, notes, and snippets.

@Villadelfia
Last active October 9, 2022 08:19
Show Gist options
  • Save Villadelfia/781097f9a87e2e350d03 to your computer and use it in GitHub Desktop.
Save Villadelfia/781097f9a87e2e350d03 to your computer and use it in GitHub Desktop.
Currency Exchange
// Globals, utility functions. {{{
var titleStyleR = "font-family: Georgia; font-size: large; font-weight: normal"+
"; text-align: center; vertical-align: middle; padding: 5px 0px; margin-to"+
"p: 0.2em; border: 1px solid #000; border-radius: 10px 10px 0px 0px; color"+
": #FFF; background-color: #400;";
var titleStyleG = "font-family: Georgia; font-size: large; font-weight: normal"+
"; text-align: center; vertical-align: middle; padding: 5px 0px; margin-to"+
"p: 0.2em; border: 1px solid #000; border-radius: 10px 10px 0px 0px; color"+
": #FFF; background-color: #040;";
var titleStyleB = "font-family: Georgia; font-size: large; font-weight: normal"+
"; text-align: center; vertical-align: middle; padding: 5px 0px; margin-to"+
"p: 0.2em; border: 1px solid #000; border-radius: 10px 10px 0px 0px; color"+
": #FFF; background-color: #004;";
var rowStyle = " padding: 5px; border-left: 1px solid #000; border-right: 1px "+
"solid #000; border-radius: 0px; ";
var lastRowStyle = " padding: 5px; border-left: 1px solid #000; border-bottom:"+
" 1px solid #000; border-right: 1px solid #000; border-radius: 0px 0px 10p"+
"x 10px; ";
var oddRow = " background-color: #CEC7B6; color: #000;";
var evenRow = " background-color: #B6AB91; color: #000;";
var titleTagR = "<div style =\"" + titleStyleR + "\">";
var titleTagG = "<div style =\"" + titleStyleG + "\">";
var titleTagB = "<div style =\"" + titleStyleB + "\">";
var oddTag = "<div style =\"" + rowStyle + oddRow + "\">";
var evenTag = "<div style =\"" + rowStyle + evenRow + "\">";
var lastOddTag = "<div style =\"" + lastRowStyle + oddRow + "\">";
var lastEvenTag = "<div style =\"" + lastRowStyle + evenRow + "\">";
var endTag = "</div>";
var sendNextMessageToGm = false;
String.prototype.contains = function(str, startIndex) {
return ''.indexOf.call(this, str, startIndex) !== -1;
};
String.prototype.startsWith = function(str) {
return this.slice(0, str.length) == str;
};
String.prototype.endsWith = function(str) {
return this.slice(-str.length) == str;
};
var sendFormatted = function(message, msg) {
var toGm = sendNextMessageToGm;
sendNextMessageToGm = false;
var msgcontent = message;
var msgcontent = msgcontent.replace(new RegExp("__B__", 'ig'), "<b>");
var msgcontent = msgcontent.replace(new RegExp("__EB__", 'ig'), "</b>");
var msgcontent = msgcontent.replace(new RegExp("__C__", 'ig'), "<div style"+
"=\"text-align: center;\">");
var msgcontent = msgcontent.replace(new RegExp("__EC__", 'ig'), "</div>");
var msgcontent = msgcontent.replace(new RegExp("__I__", 'ig'), "<i>");
var msgcontent = msgcontent.replace(new RegExp("__EI__", 'ig'), "</i>");
var msgcontent = msgcontent.replace(new RegExp("__S__", 'ig'), "<small>");
var msgcontent = msgcontent.replace(new RegExp("__ES__", 'ig'), "</small>");
var msgcontent = msgcontent.replace(new RegExp("__U__", 'ig'), "<u>");
var msgcontent = msgcontent.replace(new RegExp("__EU__", 'ig'), "</u>");
var msgcontent = msgcontent.replace(new RegExp("__NAME__", 'ig'), msg.who);
var msgcontent = msgcontent.replace(new RegExp("__BR__", 'ig'), "<br/>");
var messagercv = msgcontent.split("|||");
if(messagercv.length < 2) return;
var ctr = 0;
var changed = 1;
var title = titleTagB;
if(messagercv[0] == "R") {
changed = 0;
title = titleTagR;
++ctr;
} else if(messagercv[0] == "G") {
changed = 0;
title = titleTagG;
++ctr;
} else if(messagercv[0] == "B") {
changed = 0;
title = titleTagB;
++ctr;
} else if(messagercv[0].contains("#")) {
changed = 0;
var titleStyle = messagercv[0].split(";");
var newTitleTag = titleTagB;
if(titleStyle.length < 2) {
newTitleTag = newTitleTag.replace("#004", messagercv[0]);
} else {
newTitleTag = newTitleTag.replace("#004", titleStyle[0]);
newTitleTag = newTitleTag.replace("#FFF", titleStyle[1]);
}
title = newTitleTag;
++ctr;
}
var message = "";
message = message + title + messagercv[ctr] + endTag;
++ctr;
while(ctr < messagercv.length) {
var last = ctr == messagercv.length - 1;
var tag = "";
if(last)
tag = (ctr % 2 == changed ? lastOddTag : lastEvenTag); else
tag = (ctr % 2 == changed ? oddTag : evenTag);
message = message + tag + messagercv[ctr] + endTag;
++ctr;
}
if(toGm)
sendChat(msg.who, "/w gm " + message);
else
sendChat(msg.who, message);
};
var sendError = function(error, source) {
sendFormatted('R|||Error|||' + error, {who: source});
};
// }}}
// Currency Exchange {{{
// Implements the commands:
// !xe
// - Prints info on usage
// !xeset a equals x b
// - Sets the exchange rate so that 1 a = x b. If x = 0, it means conversion
// is not possible.
// !xeconvert x a to b
// - Converts x of currency a to currency b. It will try and find the
// shortest path from a to b.
// !xedelete x
// - Deletes currency x, and all references to it. If this leaves a currency
// without references, that currency will be deleted as well!
// !xeexport
// !xeimport JSON
// - Export and import the state of the system.
// !xereset
// - Resets the entire currency system.
on("chat:message", function(msg) {
if(msg.type != "api") return;
if(!msg.content.startsWith("!xe")) return;
msg = _.clone(msg);
msg.content = msg.content.toLowerCase();
var sender = msg.who.split(" ")[0];
var args = msg.content.split(' ');
state.xe = state.xe || {};
var gmState = function(msg) {
if(typeof isGM !== 'undefined' && _.isFunction(isGM)) {
return isGM(msg.playerid);
} else {
return msg.who.contains('(GM)');
}
};
var xeHelp = function() {
sendFormatted(
"B|||Currency Exchange|||"+
"__I__Player Functions__EI__|||"+
"Convert X of currency A to currency B:__BR____B__!xeconvert X"+
" A to B__EB__|||"+
"__I__GM Functions__EI__|||"+
"Set exchange rate so that 1 A equals X B:__BR____B__!xeset A "+
"equals X B__EB__|||"+
"Delete currency X and all references to it, also deletes any "+
"currencies that don't have any conversions due to this deleti"+
"on:__BR____B__!xedelete X__EB__|||"+
"Export the state of the system to JSON:__BR____B__!xeexport__"+
"EB__|||"+
"Import a JSON state:__BR____B__!xeimport JSON__EB__|||"+
"Reset the system:__BR____B__!xereset__EB__",
{who: 'XE'});
};
var shortestPath = function(edges, numVertices, startVertex) {
var done = new Array(numVertices);
done[startVertex] = true;
var pathLengths = new Array(numVertices);
var predecessors = new Array(numVertices);
for (var i = 0; i < numVertices; i++) {
pathLengths[i] = edges[startVertex][i];
if(edges[startVertex][i] != Infinity) {
predecessors[i] = startVertex;
}
}
pathLengths[startVertex] = 0;
for (var i = 0; i < numVertices - 1; i++) {
var closest = -1;
var closestDistance = Infinity;
for (var j = 0; j < numVertices; j++) {
if (!done[j] && pathLengths[j] < closestDistance) {
closestDistance = pathLengths[j];
closest = j;
}
}
done[closest] = true;
for (var j = 0; j < numVertices; j++) {
if (!done[j] && edges[closest]) {
var possiblyCloserDistance = pathLengths[closest] +
edges[closest][j];
if (possiblyCloserDistance < pathLengths[j]) {
pathLengths[j] = possiblyCloserDistance;
predecessors[j] = closest;
}
}
}
}
return { "startVertex": startVertex,
"pathLengths": pathLengths,
"predecessors": predecessors };
};
var constructPath = function(shortestPathInfo, endVertex) {
var path = [];
while (endVertex != shortestPathInfo.startVertex) {
path.unshift(endVertex);
endVertex = shortestPathInfo.predecessors[endVertex];
}
return path;
};
var xeSet = function() {
var working = args.split('equals');
if(working.length != 2) return;
var from = working[0].trim();
working = working[1].trim().split(' ');
if(working.length < 2) return;
var to = working.slice(1).join(' ').trim();
var value = parseFloat(working[0].trim());
if(isNaN(value)) return;
state.xe[from] = state.xe[from] || {};
state.xe[to] = state.xe[to] || {};
state.xe[from][to] = value;
var inverseSet = false;
if(value != 0 && typeof state.xe[to][from] == 'undefined') {
state.xe[to][from] = 1/value;
inverseSet = true;
}
sendFormatted('G|||Conversion set|||1 ' + from + ' is now worth ' +
value + ' ' + to + '.', {who: 'XE'});
if(inverseSet) {
sendFormatted('G|||Conversion set|||1 ' + to + ' is now worth ' +
1/value + ' ' + from + '.', {who: 'XE'});
}
};
var xeConvert = function() {
var working = args.split(' ');
if(working.length < 2) return;
var value = parseFloat(working[0].trim());
if(isNaN(value)) return;
working = working.slice(1).join(' ').trim().split('to');
if(working.length != 2) return;
var from = working[0].trim();
var to = working[1].trim();
if(typeof state.xe[from] == 'undefined' ||
typeof state.xe[to] == 'undefined') return;
var out = value;
var arrow = ' \u21d2 ';
// Try direct first:
if(typeof state.xe[from][to] != 'undefined' &&
state.xe[from][to] != 0) {
out = value * state.xe[from][to];
sendFormatted('B|||Conversion|||' + value + ' ' + from + ' equals '
+ out + ' ' + to + '.|||Converted via ' + from + arrow +
to + '.', {who: 'XE'});
return;
}
// Not possible, dijkstra to the rescue!.
var inf = Infinity;
var currencies = [];
// Get list of currencies
for(a in state.xe) {
if(!state.xe.hasOwnProperty(a)) continue;
currencies.push(a);
}
// Create adjacency matrix.
var matrix = new Array(currencies.length);
for(var i = 0; i < currencies.length; ++i) {
matrix[i] = new Array(currencies.length);
matrix[i][i] = 1;
}
for(var i = 0; i < currencies.length; ++i) {
for(var j = 0; j < currencies.length; ++j) {
if(i == j) continue;
var a = state.xe[currencies[i]][currencies[j]];
if(typeof a == 'undefined' || a == 0)
matrix[i][j] = inf;
else
matrix[i][j] = 1;
}
}
var data = shortestPath(matrix, currencies.length,
currencies.indexOf(from));
if(data.pathLengths[currencies.indexOf(to)] == inf) {
sendError('There is no suitable conversion from ' + from + ' to ' +
to + '.', 'XE');
return;
}
var path = constructPath(data, currencies.indexOf(to));
for(var i = 0; i < path.length; ++i) {
path[i] = currencies[path[i]];
}
var conversionPath = from;
for(var i = 0; i < path.length; ++i) {
if(i == 0) {
out = value * state.xe[from][path[i]];
} else {
out *= state.xe[path[i-1]][path[i]];
}
conversionPath += arrow + path[i];
}
sendFormatted('B|||Conversion|||' + value + ' ' + from + ' equals '
+ out + ' ' + to + '.|||Converted via ' + conversionPath + '.',
{who: 'XE'});
};
var xeDelete = function() {
if(args == '') return;
if(typeof state.xe[args] == 'undefined') return;
delete state.xe[args];
for(prop in state.xe) {
if(!state.xe.hasOwnProperty(prop)) continue;
delete state.xe[prop][args];
if(Object.keys(state.xe[prop]).length == 0)
delete state.xe[prop];
}
sendFormatted('R|||Deleted|||Deleted currency ' + args + '.',
{who: 'XE'});
};
var xeExport = function() {
sendChat('XE', '/w gm To import type: <br /><b>!xeimport ' +
JSON.stringify(state.xe) + '</b>');
};
var xeImport = function() {
var data;
try {
data = JSON.parse(args);
} catch(e) {
sendChat('XE', '/w gm Syntax Error in import string.');
return;
}
state.xe = data;
sendChat('XE', '/w gm Imported JSON data.');
}
var xeReset = function() {
state.xe = {};
sendChat('XE', '/w gm System has been reset.');
};
switch(args[0]) {
case '!xe':
args = msg.content.replace('!xe', '').trim();
xeHelp();
break;
case '!xeset':
if(!gmState(msg)) return;
args = msg.content.replace('!xeset', '').trim();
xeSet();
break;
case '!xeconvert':
args = msg.content.replace('!xeconvert', '').trim();
xeConvert();
break;
case '!xedelete':
if(!gmState(msg)) return;
args = msg.content.replace('!xedelete', '').trim();
xeDelete();
break;
case '!xeexport':
if(!gmState(msg)) return;
args = msg.content.replace('!xeexport', '').trim();
xeExport();
break;
case '!xeimport':
if(!gmState(msg)) return;
args = msg.content.replace('!xeimport', '').trim();
xeImport();
break;
case '!xereset':
if(!gmState(msg)) return;
args = msg.content.replace('!xereset', '').trim();
xeReset();
break;
}
});
// }}}
// vim: fdm=marker
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment