I like to gather all the relevant methods into a (static) object, typically called window.Stat or window.Stats or similar, and use it to access the values and write my stat/skill check methods. This way, I can later on refactor how I store the stats in the game or add things like stat-modifying effects without worrying that anything which uses the stat values or makes stat checks needs to be changed. A typical implementation (cut down to just the Stat.check(...) method and its helpers) looks about like this.
window.Stat = {};
/*
* Returns the statistic value (a finite number) of the given stat,
* modified by currently active effects and whatnot,
* for the given character, or undefined if the character doesn't
* possess the stat or the arguments are invalid.
*
* The value for "stat" can be anything the game recognises as a
* valued statistic, so attributes, skills, levels, height
* or any computed value; or it can be limited to "real" statistics
* or any subset of the above. The only important consideration is
* that calling Stat.get(...) works the same for everything;
* PCs, NPCs and mechanic traps alike.
*/
Stat.get = function(character, stat) {
/* Implementation is game specific */
};
/*
* What the Stat.check(...) call returns when some check
* automatically fails (for example, because the character
* doesn't have the stat).
*/
Stat.RESULT_AUTOFAILURE = {
roll1: 0, roll2: 0, result: -100, success: false, failure: true,
};
/*
* What the Stat.check(...) call returns when some check
* automatically succeeds (for example, because the character
* isn't actually opposed by anyone).
*/
Stat.RESULT_AUTOSUCCESS = {
roll1: 0, roll2: 0, result: 100, success: true, failure: false,
};
/*
* The Stat.check(...) function can be called in two different ways:
*
* Stat.check(character, character's stat, opponent, opponent's counter-stat)
* - Opposed check against an opponent
*
* Stat.check(character, character's stat, flat value)
* - Unopposed check against some flat value to beat
*
* It returns an object with the following properties:
*
* # result
* - positive if the character's roll+stat value is better
* than the opponent's (or the flat value), negative if
* it's worse
* # roll1, roll2
* - the two rolls used to determine the value; roll2
* is 0 if there is only a flat value to beat;
* both values will be 0 if there is nothing to roll
* # success
* - a boolean value; true if the check succeeded for the character
* # failure
* - a boolean value; true if the check failed for the character
*
* This object can be further enhanced with crits and fumbles
* or other data gathered during the check process, as required.
*/
Stat.check = function(character, stat, opponent, counter) {
var dice = {}; /* this will be our return value */
var characterStat = Stat.get(character, stat);
if(!_.isDefined(characterStat)) {
return Stat.RESULT_AUTOFAILURE;
}
dice.roll1 = random(1, 8);
var counterStat = undefined;
if(!_.isDefined(counter) && _.isFinite(opponent)) {
/* We were called with a simple number - a check against a static value */
dice.roll2 = 0;
counterStat = opponent;
} else {
/* We were likely called with a counter-character and their stat */
dice.roll2 = random(1, 8);
counterStat = Stat.get(opponent, counter);
}
if(!_.isDefined(counterStat)) {
return Stat.RESULT_AUTOSUCCESS;
}
dice.result = (characterStat + dice.roll1) - (counterStat + dice.roll2);
/* This makes it so a tie is neither a success nor a failure */
dice.success = (dice.result > 0);
dice.failure = (dice.result < 0);
return dice;
};
And you can now call the method and use its result, either right away or saving it in a temporary variable and checking what the values are. So to move a boulder ...
<<link "Try moving the boulder [STR]">>
<<if Stat.check($PC, "STR", 10).success>>
<<goto "Boulder moved">>
<<else>>
<<goto "Boulder doesn't bulge">>
<</if>>
<</link>>
And to attack an enemy, keeping the result in a variable so we can reference it in the next passages and do something with it ...
<<link "Hit her with your axe">>
<<set $lastAttackCheck = Stat.check($PC, "DEX", $enemy, "DEX")>>
<<if $lastAttackCheck.success>>
<<goto "Attack connects">>
<<elseif $lastAttackCheck.failure>>
<<goto "Attack misses">>
<<else>>
<<goto "No opening for an attack found">>
<</if>>
<</link>>