Last active
October 9, 2022 08:19
-
-
Save Villadelfia/781097f9a87e2e350d03 to your computer and use it in GitHub Desktop.
Currency Exchange
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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