Comparing version 0.1.0 to 0.2.0
546
lib/dice.js
(function (exports) { | ||
var derive = Object.create, | ||
noOp = function () {}, | ||
isArray = Array.isArray, | ||
plainSpec = { | ||
@@ -41,13 +43,7 @@ enumerable: true, | ||
function numericSort (a, b) { | ||
return a - b; | ||
} | ||
function numericSort (a, b) { return a - b; } | ||
function rollDie (sides) { | ||
return Math.floor(Math.random() * sides) + 1; | ||
} | ||
function rollDie (sides) { return Math.floor(Math.random() * sides) + 1; } | ||
function reduceSum (t, d) { | ||
return (t + d); | ||
} | ||
function reduceSum (t, d) { return (t + d); } | ||
//----------------------------------------------------------------------- | ||
@@ -114,3 +110,3 @@ // Dice | ||
this.rolled = true; | ||
this._applyBehaviors(); | ||
this._applyBehaviors("roll"); | ||
return this.total; | ||
@@ -138,3 +134,3 @@ }), | ||
delete this.rolled; | ||
this._applyBehaviors(); | ||
this._applyBehaviors("reset"); | ||
return this; | ||
@@ -161,8 +157,24 @@ }), | ||
toString: method(function () { | ||
var count = this.count, | ||
total = this.total | ||
var spec = { | ||
count: this.count, | ||
sides: this.sides, | ||
total: this.total, | ||
modifiers: [], | ||
results: this.results || [], | ||
suffix: "", | ||
addenda: "" | ||
}, | ||
mod | ||
; | ||
return (count > 1 ? count : "") + "d" + this.sides + | ||
(this.rolled ? " [" + this.results.join(", ") + "] = " + total : ""); | ||
this._applyBehaviors("toString", [spec]); | ||
mod = spec.modifiers.reduce(reduceSum, 0); | ||
return (spec.count > 1 ? spec.count : "") + "d" + spec.sides + | ||
(mod !== 0 ? (mod > 0 ? "+" + mod : mod) : "") + | ||
(this.rolled ? " [" + this.results.join(", ") + "] = " + spec.total + | ||
(spec.suffix ? spec.suffix : "") + | ||
(spec.addenda ? " (" + spec.addenda + ")" : "") : | ||
""); | ||
}), | ||
@@ -198,221 +210,299 @@ /** | ||
*/ | ||
_applyBehaviors: privateMethod(function () { | ||
this._behaviors.forEach(this._applyBehavior, this); | ||
_applyBehaviors: privateMethod(function (aspect, args) { | ||
args = args || []; | ||
args.unshift(this); | ||
this._behaviors.forEach(function (behavior) { | ||
behavior[aspect].apply(behavior, args); | ||
}, this); | ||
return this; | ||
}), | ||
/** | ||
* Apply the passed behavior to this object | ||
* | ||
* @method _applyBehavior | ||
* @private | ||
* @param {function} | ||
*/ | ||
_applyBehavior: privateMethod(function (fn) { | ||
fn.call(this); | ||
}) | ||
}); | ||
//----------------------------------------------------------------------- | ||
// Behaviors | ||
// Dice Behaviors | ||
//----------------------------------------------------------------------- | ||
behaviors = { | ||
/** | ||
* Apply a modifier to the total | ||
* | ||
* @param amt {number} | ||
* @mutates Dice#total | ||
*/ | ||
resultModifier: function (amt) { | ||
return function () { | ||
if (!this.rolled) { | ||
return; | ||
} | ||
this.total += amt; | ||
}; | ||
Dice.behaviors = behaviors = {}; | ||
function DiceBehavior () { | ||
var init = this.init; | ||
if (init && typeof init === "function") { | ||
init.apply(this, arguments); | ||
} | ||
} | ||
DiceBehavior.prototype = derive(Object.prototype, { | ||
roll: method(noOp), | ||
reset: method(noOp), | ||
toString: method(noOp) | ||
}); | ||
Dice.DiceBehavior = DiceBehavior; | ||
function createBehavior (name, props) { | ||
var ctor = function () { DiceBehavior.apply(this, arguments); }; | ||
merge((ctor.prototype = derive(DiceBehavior.prototype)), props); | ||
return (behaviors[name] = ctor); | ||
} | ||
Dice.createBehavior = createBehavior; | ||
function getBehavior (name) { | ||
var behavior = behaviors[name], | ||
proto, init, inst | ||
; | ||
if (!behavior) { | ||
// throw an error | ||
throw new Error("Unknown behavior '" + name + "'"); | ||
} | ||
else { | ||
proto = behavior.prototype; | ||
if (typeof proto.init === "function") { | ||
inst = derive(proto); | ||
behavior.apply(inst, slice(arguments, 1)); | ||
return inst; | ||
} | ||
else { | ||
return proto; | ||
} | ||
} | ||
} | ||
Dice.getBehavior = getBehavior; | ||
function makeDiceFactory (sides, behaviors) { | ||
return function (num) { | ||
var dice = new Dice(num, sides); | ||
if (behaviors) { | ||
behaviors.forEach(function (args) { | ||
dice.addBehavior( | ||
isArray(args) ? | ||
getBehavior.apply(null, args) : | ||
getBehavior(args) | ||
); | ||
}); | ||
} | ||
return dice; | ||
}; | ||
} | ||
Dice.makeDiceFactory = makeDiceFactory; | ||
//----------------------------------------------------------------------- | ||
// Bits and Pieces | ||
//----------------------------------------------------------------------- | ||
/** | ||
* Apply a modifier to the total | ||
* | ||
* @param amt {number} | ||
* @mutates Dice#total | ||
*/ | ||
createBehavior("ResultModifier", { | ||
init: function (amt) { | ||
this.amount = amt; | ||
}, | ||
/** | ||
* Apply a modifier to every die | ||
* | ||
* @param amt {number} | ||
* @mutates Dice#total | ||
* @mutates Dice#results | ||
*/ | ||
dieModifier: function (amt) { | ||
return function () { | ||
if (!this.rolled) { | ||
return; | ||
} | ||
roll: function (dice) { | ||
dice.total += this.amount; | ||
}, | ||
toString: function (dice, spec) { | ||
spec.modifiers.push(this.amount); | ||
} | ||
}); | ||
/** | ||
* Apply a modifier to every die | ||
* | ||
* @param amt {number} | ||
* @mutates Dice#total | ||
* @mutates Dice#results | ||
*/ | ||
createBehavior("DieModifier", { | ||
init: function (amt) { | ||
this.amount = amt; | ||
}, | ||
roll: function (dice) { | ||
dice.total += (this.amount * dice.count); | ||
}, | ||
toString: function (dice, spec) { | ||
spec.modifiers.push(this.amount * dice.count); | ||
} | ||
}); | ||
/** | ||
* Remove *amt* lowest dice from the results. | ||
* | ||
* @param amt {number} number of low dice to discard | ||
* @mutates Dice#total | ||
* @property Dice#lowResults {array[number]} | ||
*/ | ||
createBehavior("RemoveLowest", { | ||
init: function (amt) { | ||
this.amount = amt; | ||
}, | ||
roll: function (dice) { | ||
var results = dice.results.sort(numericSort).reverse(); | ||
dice.lowResults = results.splice(dice.count - this.amount); | ||
dice.total = results.reduce(reduceSum, 0); | ||
}, | ||
reset: function (dice) { | ||
delete dice.lowResults; | ||
}, | ||
toString: function (dice, spec) { | ||
spec.addenda += "low: [" + dice.lowResults.join(", ") + "]"; | ||
} | ||
}); | ||
this.total += amt * this.results.length; | ||
}; | ||
/** | ||
* Count the number of dice above a certain threshold | ||
* | ||
* @param amt {number} number or greater to count as a success | ||
* @mutates Dice#total | ||
* @mutates Dice#toString | ||
* @property Dice#successes {number} | ||
* @property Dice#failures {number} | ||
* @property Dice#fumbled {boolean} | ||
*/ | ||
createBehavior("SuccessDice", { | ||
init: function (min) { | ||
this.minimum = min; | ||
}, | ||
/** | ||
* Remove *amt* lowest dice from the results. | ||
* | ||
* @param amt {number} number of low dice to discard | ||
* @mutates Dice#toal | ||
* @mutates Dice#toString | ||
* @property Dice#lowResults {array[number]} | ||
*/ | ||
removeLowest: function (amt) { | ||
return function () { | ||
var dice; | ||
if (!this.rolled) { | ||
delete this.lowResults; | ||
delete this.toString; | ||
roll: function (dice) { | ||
var total = 0, | ||
failures = 0, | ||
results = dice.results, | ||
len = results.length, | ||
count = dice.count, | ||
min = this.minimum, | ||
i, d | ||
; | ||
for (i = 0; i < len; i++) { | ||
d = results[i]; | ||
if (d >= min) { | ||
total++; | ||
} | ||
else { | ||
this.lowResults = this.results.sort(numericSort).reverse(). | ||
splice(this.count - amt); | ||
this.total = this.results.reduce(reduceSum, 0); | ||
this.toString = function () { | ||
return Dice.prototype.toString.call(this) + | ||
" (low: [" + this.lowResults.join(", ") + "])"; | ||
}; | ||
// We only count ones as failures if in the original | ||
// result set | ||
else if (i < count && d === 1) { | ||
failures++; | ||
} | ||
}; | ||
}, | ||
/** | ||
* Count the number of dice above a certain threshold | ||
* | ||
* @param amt {number} number or greater to count as a success | ||
* @mutates Dice#total | ||
* @mutates Dice#toString | ||
* @property Dice#successes {number} | ||
* @property Dice#failures {number} | ||
* @property Dice#fumbled {boolean} | ||
*/ | ||
successDice: function (min) { | ||
return function () { | ||
var total, failures, count, results, i, len, d; | ||
if (!this.rolled) { | ||
delete this.successes; | ||
delete this.failures; | ||
delete this.toString; | ||
} | ||
else { | ||
total = 0; | ||
failures = 0; | ||
results = this.results; | ||
len = results.length; | ||
count = this.count; | ||
for (i = 0; i < len; i++) { | ||
d = results[i]; | ||
if (d >= min) { | ||
total++; | ||
} | ||
// We only count ones as failures if in the original | ||
// result set | ||
else if (i < count && d === 1) { | ||
failures++; | ||
} | ||
} | ||
} | ||
this.successes = total; | ||
this.failures = failures; | ||
this.total = Math.max(total - failures, 0); | ||
this.fumbled = failures > total; | ||
this.toString = function () { | ||
return (this.count ? this.count : "") + "d" + this.sides + | ||
" [" + this.results.join(", ") + "] = " + | ||
this.total + " successes" + | ||
(this.fumbled ? "!" : ""); | ||
}; | ||
} | ||
}; | ||
dice.successes = total; | ||
dice.failures = failures; | ||
dice.total = Math.max(total - failures, 0); | ||
dice.fumbled = failures > total; | ||
}, | ||
/** | ||
* Determine the amount of Stun and Body | ||
* | ||
* @mutates Dice#toString | ||
* @property Dice#body {number} number of body (die result: 1 = 0, 2-5 = 1, 6 = 2) | ||
* @property Dice#stun {number} total of dice | ||
*/ | ||
heroDice: function () { | ||
return function () { | ||
if (!this.rolled) { | ||
delete this.body; | ||
delete this.stun; | ||
delete this.toString; | ||
reset: function (dice) { | ||
delete dice.successes; | ||
delete dice.failures; | ||
delete dice.fumbled; | ||
}, | ||
toString: function (dice, spec) { | ||
spec.suffix += " successes" + (dice.fumbled ? "!" : ""); | ||
} | ||
}); | ||
/** | ||
* Determine the amount of Stun and Body | ||
* | ||
* @mutates Dice#toString | ||
* @property Dice#body {number} number of body (die result: 1 = 0, 2-5 = 1, 6 = 2) | ||
* @property Dice#stun {number} total of dice | ||
*/ | ||
createBehavior("HeroNormalDice", { | ||
roll: function (dice) { | ||
dice.body = dice.results.reduce(function (total, d) { | ||
switch (d) { | ||
case 1: | ||
break; | ||
case 6: | ||
total += 2; | ||
break; | ||
default: | ||
total += 1; | ||
} | ||
else { | ||
this.body = this.results.reduce(function (total, d) { | ||
switch (d) { | ||
case 1: | ||
break; | ||
case 6: | ||
total += 2; | ||
break; | ||
default: | ||
total += 1; | ||
} | ||
return total; | ||
}, 0); | ||
this.stun = this.total; | ||
this.toString = function () { | ||
return Dice.prototype.toString.call(this) + | ||
" stun, " + this.body + " body"; | ||
}; | ||
} | ||
}; | ||
return total; | ||
}, 0); | ||
dice.stun = dice.total; | ||
}, | ||
/** | ||
* Specify a number of dice that are *wild*. If these dice roll the | ||
* maximum amount, roll another die and add that to the total. The | ||
* wild di(c)e continue to add as long as they roll the maximum. | ||
* | ||
* @param amt {number} | ||
* @property Dice#wildDice {array[number]} results of the wild dice | ||
* @property Dice#complications {boolean} if the wild die comes up a 1 | ||
* @mutates Dice#total | ||
*/ | ||
wildDice: function (amt) { | ||
return function () { | ||
var dicesWild; | ||
reset: function (dice) { | ||
delete dice.body; | ||
delete dice.stun; | ||
}, | ||
toString: function (dice, spec) { | ||
spec.suffix += " stun, " + dice.body + " body"; | ||
} | ||
}); | ||
/** | ||
* Specify a number of dice that are *wild*. If these dice roll the | ||
* maximum amount, roll another die and add that to the total. The | ||
* wild di(c)e continue to add as long as they roll the maximum. | ||
* | ||
* @param amt {number} | ||
* @property Dice#wildDice {array[number]} results of the wild dice | ||
* @property Dice#complications {boolean} if the wild die comes up a 1 | ||
* @mutates Dice#total | ||
*/ | ||
createBehavior("WildDice", { | ||
init: function (amt) { | ||
this.amount = amt || 0; | ||
}, | ||
roll: function (dice) { | ||
var results = dice.results.slice().reverse(), | ||
dicesWild = results.slice(0, this.amount || dice.results.length), | ||
complications = 0 | ||
; | ||
if (!this.rolled) { | ||
delete this.wildDice; | ||
delete this.complication; | ||
delete this.toString; | ||
dicesWild.forEach(function (die) { | ||
var sides = dice.sides, | ||
result | ||
; | ||
if (die === sides) { | ||
do { | ||
result = rollDie(sides); | ||
dice.results.push(result); | ||
} while (result === sides); | ||
} | ||
else { | ||
dicesWild = this.results.slice().reverse(). | ||
slice(0, amt || this.results.length); | ||
dicesWild.forEach(function (die) { | ||
var sides = this.sides, | ||
result | ||
; | ||
if (die === sides) { | ||
do { | ||
result = rollDie(sides); | ||
this.results.push(result); | ||
} while (result === sides); | ||
} | ||
else if (die === 1) { | ||
if (!this.complications) { | ||
this.complications = 0; | ||
} | ||
this.complications++; | ||
} | ||
}, this); | ||
this.total = this.results.reduce(reduceSum, 0); | ||
this.toString = function () { | ||
var fails = this.complications; | ||
return Dice.prototype.toString.call(this) + | ||
(fails ? Array(fails + 1).join("!") : ""); | ||
}; | ||
else if (die === 1) { | ||
complications++; | ||
} | ||
}; | ||
}); | ||
dice.total = dice.results.reduce(reduceSum, 0); | ||
dice.complications = complications; | ||
dice.complication = complications > 0; | ||
}, | ||
reset: function (dice) { | ||
delete dice.wildDice; | ||
delete dice.complication; | ||
delete dice.complications; | ||
}, | ||
toString: function (dice, spec) { | ||
var fails = dice.complication, | ||
failures = dice.complications | ||
; | ||
spec.suffix += (fails ? Array(failures+1).join("!") : ""); | ||
} | ||
}; | ||
}); | ||
@@ -424,28 +514,18 @@ //----------------------------------------------------------------------- | ||
Dice: Dice, | ||
diceBehaviors: behaviors, | ||
/** | ||
* Utility function/Class for creating Hero System Normal Dice | ||
* | ||
* @class HeroDice | ||
* @function heroDice | ||
* @param num {number} | ||
* @returns {Dice} | ||
*/ | ||
HeroDice: function (num) { | ||
return new Dice(num, 6, [behaviors.heroDice()]); | ||
}, | ||
heroDice: makeDiceFactory(6, ["HeroNormalDice"]), | ||
/** | ||
* Utility function/Class for creating a batch of Shadowrun Dice | ||
* | ||
* @class ShadowrunDice | ||
* @function shadowrunDice | ||
* @param num {number} | ||
* @returns {Dice} | ||
*/ | ||
ShadowrunDice: function (num) { | ||
return new Dice(num, 6, [ | ||
behaviors.wildDice(), | ||
behaviors.successDice(5) | ||
]); | ||
}, | ||
shadowrunDice: makeDiceFactory(6, ["WildDice", ["SuccessDice", 5]]), | ||
/** | ||
@@ -455,3 +535,3 @@ * Utility function/Class for creating West End Game's Star Wars | ||
* | ||
* @class WEGStarWarsDice | ||
* @function d6StarWarsDice | ||
* @param num {number} | ||
@@ -461,7 +541,7 @@ * @param pips {number} | ||
*/ | ||
WEGStarWarsDice: function (num, pips) { | ||
var dice = new Dice(num, 6, [behaviors.wildDice(1)]); | ||
d6StarWarsDice: function (num, pips) { | ||
var dice = new Dice(num, 6, [getBehavior("WildDice", 1)]); | ||
if (typeof pips === "number") { | ||
dice.addBehavior(behaviors.resultModifier(pips)); | ||
dice.addBehavior(getBehavior("ResultModifier", pips)); | ||
} | ||
@@ -468,0 +548,0 @@ |
{ | ||
"name": "dice-js", | ||
"description": "Roll some dice.", | ||
"version": "0.1.0", | ||
"version": "0.2.0", | ||
"keywords": [ | ||
@@ -6,0 +6,0 @@ "dice", |
var lib = require("../"), | ||
Dice = lib.Dice, | ||
behaviors = lib.diceBehaviors | ||
behaviors = Dice.behaviors | ||
; | ||
@@ -53,3 +53,3 @@ | ||
test("Roll 3d6+2", function () { | ||
var dice = new Dice(3, 6, [behaviors.resultModifier(2)]), | ||
var dice = new Dice(3, 6, [new behaviors.ResultModifier(2)]), | ||
result, dietotal | ||
@@ -71,3 +71,3 @@ ; | ||
test("Roll 3d6 and add 1 to every die", function () { | ||
var dice = new Dice(3, 6, [behaviors.dieModifier(1)]), | ||
var dice = new Dice(3, 6, [new behaviors.DieModifier(1)]), | ||
result, dietotal | ||
@@ -93,3 +93,3 @@ ; | ||
dice.addBehavior(behaviors.removeLowest(2)); | ||
dice.addBehavior(new behaviors.RemoveLowest(2)); | ||
@@ -106,3 +106,3 @@ results = dice.roll(); | ||
test("10d6 Hero Dice", function () { | ||
var dice = new lib.HeroDice(10); | ||
var dice = lib.heroDice(10); | ||
@@ -119,3 +119,3 @@ dice.roll(); | ||
dice.addBehavior(behaviors.successDice(5)); | ||
dice.addBehavior(new behaviors.SuccessDice(5)); | ||
@@ -129,3 +129,3 @@ dice.roll(); | ||
test("6d6 Shadowrun dice", function () { | ||
var dice = new lib.ShadowrunDice(6); | ||
var dice = lib.shadowrunDice(6); | ||
dice.roll(); | ||
@@ -136,3 +136,3 @@ console.log("" + dice); | ||
test("6d6 WEG Star Wars", function () { | ||
var dice = new lib.WEGStarWarsDice(6); | ||
var dice = lib.d6StarWarsDice(6); | ||
dice.roll(); | ||
@@ -143,3 +143,3 @@ console.log("" + dice); | ||
test("4d6+2 WEG Star Wars", function () { | ||
var dice = new lib.WEGStarWarsDice(4, 2); | ||
var dice = lib.d6StarWarsDice(4, 2); | ||
dice.roll(); | ||
@@ -150,5 +150,5 @@ dice.total.should.be.above(6); | ||
("" + dice).should.match(/4d6 \[(\d+(?:, )?)+\] = \d+!?/); | ||
("" + dice).should.match(/4d6\+2 \[(\d+(?:, )?)+\] = \d+!?/); | ||
console.log("" + dice); | ||
}); | ||
}); |
21639
590