Last active
May 21, 2020 11:02
-
-
Save Akjosch/df444197e6ac9694f97487aa7b53878b to your computer and use it in GitHub Desktop.
Random event engine for SugarCube/TweeGo
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
:: Backup Event [event] | |
/* | |
PRIORITY: Number.MIN_SAFE_INTEGER | |
*/ | |
<div class="error-view"><span class="error">No valid events found. Write some?</span></div> |
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
setup.GameEvent = (function() { | |
const _target = Symbol("target"); | |
const _trigger = Symbol("trigger definition"); | |
const _weight = Symbol("weight definition"); | |
const _priority = Symbol("priority definition"); | |
const _triggerFunc = Symbol("trigger function"); | |
const _weightFunc = Symbol("weight function"); | |
const _priorityFunc = Symbol("priority function"); | |
class GameEvent { | |
/** | |
* @param {string} target | |
* @param {string=} trigger | |
* @param {string=} weight | |
* @param {string=} priority | |
*/ | |
constructor(target, trigger, weight, priority) { | |
if(arguments.length < 1) { | |
throw new TypeError("Target has to be specified"); | |
} | |
this[_target] = String(target); | |
if(!this[_target]) { | |
throw new TypeError("Target has to be a non-empty string"); | |
} | |
this[_trigger] = trigger; | |
this[_weight] = weight; | |
this[_priority] = priority; | |
if(trigger) { | |
this[_triggerFunc] = new Function("c", "return !!(" + trigger + ")"); | |
} | |
if(weight) { | |
this[_weightFunc] = new Function("c", "return Number(" + weight + ")"); | |
} | |
if(priority) { | |
this[_priorityFunc] = new Function("c", "return Math.round(" + priority + ")"); | |
} | |
} | |
get target() { return this[_target]; } | |
get trigger() { return this[_trigger]; } | |
get weight() { return this[_weight]; } | |
get priority() { return this[_priority]; } | |
/** | |
* @param {any=} context | |
* @returns {boolean} - true if the event is possible in this context | |
*/ | |
isPossible(context) { | |
try { | |
return this[_triggerFunc] ? this[_triggerFunc](context || {}) : true; | |
} catch(ex) { | |
console.log(ex); | |
return false; | |
} | |
} | |
/** | |
* @param {any=} context | |
* @returns {number} | |
*/ | |
calcWeight(context) { | |
try { | |
return this[_weightFunc] ? this[_weightFunc](context || {}) : 1.0; | |
} catch(ex) { | |
console.log(ex); | |
return 0.0; | |
} | |
} | |
/** | |
* @param {any=} context | |
* @returns {number} | |
*/ | |
calcPriority(context) { | |
try { | |
return this[_priorityFunc] ? this[_priorityFunc](context || {}) : 0; | |
} catch(ex) { | |
console.log(ex); | |
return Number.MIN_SAFE_INTEGER; | |
} | |
} | |
} | |
return GameEvent; | |
})(); | |
/** | |
* @property {GameEvent[]} | |
*/ | |
setup.events = Story.lookup("tags", "event") | |
.map(function(p) { | |
var triggersRE = /^TRIGGER:\s*(\S.*?)$/m; | |
var weightsSE = /^WEIGHT:\s*(\S.*?)$/m; | |
var prioritiesSE = /^PRIORITY:\s*(\S.*?)$/m; | |
var trigger = triggersRE.exec(p.text); | |
var weight = weightsSE.exec(p.text); | |
var priority = prioritiesSE.exec(p.text); | |
return new setup.GameEvent(p.title, trigger ? trigger[1] : "", weight ? weight[1] : "", priority ? priority[1] : ""); | |
}); | |
/** | |
* Return an array of possible events and their current weights given the context. | |
* @param {any=} context | |
*/ | |
setup.pickEvent = function(context) { | |
var possibleEvents = setup.events | |
.filter((ev) => ev.isPossible(context)) | |
.map((ev) => ({ event: ev, weight: ev.calcWeight(context), prio: ev.calcPriority(context) })) | |
.sort((d1, d2) => (d1.prio - d2.prio)); | |
if(possibleEvents.length > 0) { | |
const maxPrio = possibleEvents.last().prio; | |
possibleEvents = possibleEvents.filter((evData) => evData.prio === maxPrio); | |
} | |
return possibleEvents; | |
} |
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
:: Orcs Attack! [event] | |
/* | |
TRIGGER: c.isNight() && c.partyHealth() > 25 && c.isAreaTag("wilderness") && c.isAreaTag("orc") | |
WEIGHT: 5 + (c.partyHealth() / 30) | |
PRIORITY: 0 | |
*/ | |
This is an example event. |
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
/** | |
* @param {any[]} weights | |
* @param {() => number} [rnd] | |
*/ | |
function weightedRandom(weights, rnd) { | |
if(!weights || !weights.length) { | |
return undefined; | |
} | |
rnd = rnd || Math.random; | |
var fallback = weights.last(); | |
var pick = rnd() * weights.reduce((sum, ch) => sum + ch.weight, 0); | |
return (weights.find((ch) => (pick -= ch.weight) <= 0) || fallback); | |
} | |
setup.weightedRandom = weightedRandom; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment