dicelang
Version:
JavaScript interpreter of the Roll20 dice language
536 lines (535 loc) • 21.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var Constants_1 = require("../Common/Constants");
var Dice_1 = require("./Dice");
var DiceMod = (function () {
function DiceMod(modExpr) {
this._successes = null;
this._failures = null;
this._exploding = null;
this._compounding = null;
this._penetrating = null;
this._keepDrop = null;
this._reroll = [];
this._sort = null;
var originalExpr = modExpr;
if (modExpr) {
modExpr = modExpr.trim();
}
var result;
var explodeRegExp = DiceMod.explodeModRegExpr;
while (result = explodeRegExp.exec(modExpr)) {
this._parseExplodeResult(result);
modExpr = modExpr.replace(result[0], '');
}
var keepDropRegExp = DiceMod.keepDropModRegExp;
while (result = keepDropRegExp.exec(modExpr)) {
this._parseKeepDropResult(result);
modExpr = modExpr.replace(result[0], '');
}
var rerollRegExp = DiceMod.rerollModRegExp;
while (result = rerollRegExp.exec(modExpr)) {
this._parseRerollResult(result);
modExpr = modExpr.replace(result[0], '');
}
var successesRegExp = DiceMod.successesModRegExpr;
while (result = successesRegExp.exec(modExpr)) {
this._parseSucessesResult(result);
modExpr = modExpr.replace(result[0], '');
}
var sortRegExp = DiceMod.sortModRegExp;
while (result = sortRegExp.exec(modExpr)) {
this._parseSortResult(result);
modExpr = modExpr.replace(result[0], '');
}
if (modExpr && modExpr.length > 0) {
throw new Error("\"" + originalExpr + "\" is not a valid modifier: \"" + modExpr + "\" could not be parsed.");
}
}
DiceMod.comparePointToString = function (cp) {
switch (cp) {
case '<':
return 'less than or equal to';
case '>':
return 'greater than or equal to';
case '=':
return 'equal to';
default:
throw new Error("A compare point must be \"<\", \">\", or \"=\", but got \"" + cp + "\".");
}
};
DiceMod.comparePoint = function (cp, n, value) {
switch (cp) {
case '<':
return value <= n;
case '>':
return value >= n;
case '=':
return value === n;
default:
throw new Error("A compare point must be \"<\", \">\", or \"=\", but got \"" + cp + "\".");
}
};
Object.defineProperty(DiceMod, "maxN", {
get: function () {
return Constants_1.MAX_JS_INT;
},
enumerable: true,
configurable: true
});
Object.defineProperty(DiceMod, "successesModRegExpr", {
get: function () {
return /([\<\=\>])(\d+)(?:f([\<\=\>])?(\d+))?/i;
},
enumerable: true,
configurable: true
});
Object.defineProperty(DiceMod, "explodeModRegExpr", {
get: function () {
return /(![!p]?)(?:([\<\=\>])?(\d+)|(?![\<\=\>]))/i;
},
enumerable: true,
configurable: true
});
Object.defineProperty(DiceMod, "keepDropModRegExp", {
get: function () {
return /([kd])([lh])?(\d+)/i;
},
enumerable: true,
configurable: true
});
Object.defineProperty(DiceMod, "rerollModRegExp", {
get: function () {
return /r(o)?(?:([\<\=\>])?(\d+)|(?![\<\=\>]))/i;
},
enumerable: true,
configurable: true
});
Object.defineProperty(DiceMod, "sortModRegExp", {
get: function () {
return /s([ad])?/i;
},
enumerable: true,
configurable: true
});
DiceMod.sortAscComparator = function (a, b) {
return a - b;
};
DiceMod.sortDesComparator = function (a, b) {
return b - a;
};
DiceMod.checkN = function (n) {
if (n < 0 || n > DiceMod.maxN) {
throw new Error("The value of n must be between 0 and " + DiceMod.maxN + " inclusive (got " + n + ")");
}
};
DiceMod.prototype.rolled = function (roll, result, dice) {
var rolls = this._applyHotModifiers(roll, dice);
rolls.forEach(function (r) { return result.push(r); });
};
DiceMod.prototype.modResult = function (result) {
return this._applySettledModifiers(result);
};
Object.defineProperty(DiceMod.prototype, "successesProperties", {
get: function () {
return this._successes === null ? null : {
cp: this._successes.cp,
n: this._successes.n,
f: this._failures === null ? null : {
cp: this._failures.cp,
n: this._failures.n,
},
};
},
enumerable: true,
configurable: true
});
Object.defineProperty(DiceMod.prototype, "successes", {
get: function () {
return this._successes === null ? '' : "" + this._successes.cp + this._successes.n + (this._failures === null ? '' : "f" + this._failures.cp + this._failures.n);
},
enumerable: true,
configurable: true
});
Object.defineProperty(DiceMod.prototype, "successesPlaintext", {
get: function () {
return this._successes === null ? "" : "Count rolls " + DiceMod.comparePointToString(this._successes.cp) + " " + this._successes.n + (this._failures === null ? '' : " minus rolls " + DiceMod.comparePointToString(this._failures.cp) + " " + this._failures.n) + " as successes.";
},
enumerable: true,
configurable: true
});
Object.defineProperty(DiceMod.prototype, "explodingProperties", {
get: function () {
return this._exploding === null ? null : {
cp: this._exploding.cp,
n: this._exploding.n,
};
},
enumerable: true,
configurable: true
});
Object.defineProperty(DiceMod.prototype, "exploding", {
get: function () {
return this._exploding === null ? '' : "!" + (this._exploding.n === null ? '' : "" + this._exploding.cp + this._exploding.n);
},
enumerable: true,
configurable: true
});
Object.defineProperty(DiceMod.prototype, "explodingPlaintext", {
get: function () {
if (this._exploding === null) {
return '';
}
var explodeOn;
if (this._exploding.n === null) {
explodeOn = 'the maximum value';
}
else {
explodeOn = "rolls " + DiceMod.comparePointToString(this._exploding.cp) + " " + this._exploding.n;
}
return this._exploding === null ? "" : "Explode on " + explodeOn + ".";
},
enumerable: true,
configurable: true
});
Object.defineProperty(DiceMod.prototype, "compoundingProperties", {
get: function () {
return this._compounding === null ? null : {
cp: this._compounding.cp,
n: this._compounding.n,
};
},
enumerable: true,
configurable: true
});
Object.defineProperty(DiceMod.prototype, "compounding", {
get: function () {
return this._compounding === null ? '' : "!!" + (this._compounding.n === null ? '' : "" + this._compounding.cp + this._compounding.n);
},
enumerable: true,
configurable: true
});
Object.defineProperty(DiceMod.prototype, "compoundingPlaintext", {
get: function () {
if (this._compounding === null) {
return '';
}
var explodeOn;
if (this._compounding.n === null) {
explodeOn = 'the maximum value';
}
else {
explodeOn = "rolls " + DiceMod.comparePointToString(this._compounding.cp) + " " + this._compounding.n;
}
return this._compounding === null ? "" : "Compound on " + explodeOn + ".";
},
enumerable: true,
configurable: true
});
Object.defineProperty(DiceMod.prototype, "penetratingProperties", {
get: function () {
return this._penetrating === null ? null : {
cp: this._penetrating.cp,
n: this._penetrating.n,
};
},
enumerable: true,
configurable: true
});
Object.defineProperty(DiceMod.prototype, "penetrating", {
get: function () {
return this._penetrating === null ? '' : "!p" + (this._penetrating.n === null ? '' : "" + this._penetrating.cp + this._penetrating.n);
},
enumerable: true,
configurable: true
});
Object.defineProperty(DiceMod.prototype, "penetratingPlaintext", {
get: function () {
if (this._penetrating === null) {
return '';
}
var explodeOn;
if (this._penetrating.n === null) {
explodeOn = 'the maximum value';
}
else {
explodeOn = "rolls " + DiceMod.comparePointToString(this._penetrating.cp) + " " + this._penetrating.n;
}
return this._penetrating === null ? "" : "Penetrate on " + explodeOn + ".";
},
enumerable: true,
configurable: true
});
Object.defineProperty(DiceMod.prototype, "keepDropProperties", {
get: function () {
return this._keepDrop === null ? null : {
kd: this._keepDrop.kd,
lh: this._keepDrop.lh,
n: this._keepDrop.n,
};
},
enumerable: true,
configurable: true
});
Object.defineProperty(DiceMod.prototype, "keepDrop", {
get: function () {
return this._keepDrop === null ? '' : "" + this._keepDrop.kd + this._keepDrop.lh + this._keepDrop.n;
},
enumerable: true,
configurable: true
});
Object.defineProperty(DiceMod.prototype, "keepDropPlaintext", {
get: function () {
return this._keepDrop === null ? "" : (this._keepDrop.kd === 'k' ? 'Keep' : 'Drop') + " the " + (this._keepDrop.lh === 'l' ? 'lowest' : 'highest') + " " + this._keepDrop.n + " roll" + (this._keepDrop.n > 1 ? 's' : '') + ".";
},
enumerable: true,
configurable: true
});
Object.defineProperty(DiceMod.prototype, "rerollProperties", {
get: function () {
var result = [];
this._reroll.forEach(function (rr) { return result.push({
cp: rr.cp,
o: rr.o,
n: rr.n,
}); });
return result;
},
enumerable: true,
configurable: true
});
Object.defineProperty(DiceMod.prototype, "reroll", {
get: function () {
return this._reroll.length < 1 ? '' : this._reroll.map(function (x) { return "r" + (x.o ? 'o' : '') + (x.n === null ? "" : "" + x.cp + x.n); }).join("");
},
enumerable: true,
configurable: true
});
Object.defineProperty(DiceMod.prototype, "rerollPlaintext", {
get: function () {
var rerolls;
rerolls = this._reroll.map(function (x) { return "" + (x.n === null ? 'the lowest value' : "values " + DiceMod.comparePointToString(x.cp) + " " + x.n) + (x.o ? " only once" : ""); });
if (rerolls.length > 1) {
rerolls[rerolls.length - 1] = "and " + rerolls[rerolls.length - 1];
}
return this._reroll.length === 0 ? "" : "Reroll on " + rerolls.join(', ') + ".";
},
enumerable: true,
configurable: true
});
Object.defineProperty(DiceMod.prototype, "sortProperties", {
get: function () {
return this._sort === null ? null : {
ad: this._sort.ad,
};
},
enumerable: true,
configurable: true
});
Object.defineProperty(DiceMod.prototype, "sort", {
get: function () {
return this._sort === null ? '' : "s" + this._sort.ad;
},
enumerable: true,
configurable: true
});
Object.defineProperty(DiceMod.prototype, "sortPlaintext", {
get: function () {
return this._sort === null ? "" : "Sort " + (this._sort.ad === 'a' ? 'ascending' : 'descending') + ".";
},
enumerable: true,
configurable: true
});
DiceMod.prototype.toString = function () {
return "" + this.penetrating + this.compounding + this.exploding + this.keepDrop + this.reroll + this.successes + this.sort;
};
DiceMod.prototype.toStringPlaintext = function () {
var mods = [
this.penetratingPlaintext,
this.compoundingPlaintext,
this.explodingPlaintext,
this.keepDropPlaintext,
this.rerollPlaintext,
this.successesPlaintext,
this.sortPlaintext,
];
return "" + mods.join(' ');
};
DiceMod.prototype._parseExplodeResult = function (result) {
var n = result[3] === undefined ? null : parseInt(result[3], 10);
DiceMod.checkN(n);
var cp = result[2] || '=';
var parsed = { cp: cp, n: n };
switch (result[1]) {
case '!':
if (this._exploding !== null) {
throw new Error("Exploding already set as \"" + this.exploding + "\" but parsed \"" + result[0] + "\" as well.");
}
this._exploding = parsed;
break;
case '!!':
if (this._compounding !== null) {
throw new Error("Compounding already set as \"" + this.compounding + "\" but parsed \"" + result[0] + "\" as well.");
}
this._compounding = parsed;
break;
case '!p':
if (this._penetrating !== null) {
throw new Error("Penetrating already set as \"" + this.penetrating + "\" but parsed \"" + result[0] + "\" as well.");
}
this._penetrating = parsed;
break;
default:
throw new Error("explodeModRegExp should only match \"!\", \"!!\", or \"!p\" but it matched \"" + result[1] + "\".");
}
};
DiceMod.prototype._parseKeepDropResult = function (result) {
if (this._keepDrop !== null) {
throw new Error("Keep/Drop already set as \"" + this.keepDrop + "\" but parsed \"" + result[0] + "\" as well.");
}
var n = parseInt(result[3], 10);
DiceMod.checkN(n);
var kd = result[1];
var lh = result[2] || (result[1] === 'k' ? 'h' : 'l');
this._keepDrop = { kd: kd, lh: lh, n: n };
};
DiceMod.prototype._parseRerollResult = function (result) {
var n = result[3] === undefined ? null : parseInt(result[3], 10);
DiceMod.checkN(n);
var o = result[1] !== undefined;
var cp = result[2] === undefined ? '=' : result[2];
if (this._reroll.filter(function (x) { return x.cp === cp && x.n === n; }).length > 0) {
throw new Error("Reroll on \"" + cp + n + "\" is already set but parsed \"" + result[0] + "\" as well.");
}
this._reroll.push({ o: o, cp: cp, n: n });
};
DiceMod.prototype._parseSucessesResult = function (result) {
if (this._successes !== null) {
throw new Error("Successes/failures already set as \"" + this.successes + "\" but parsed \"" + result[0] + "\" as well.");
}
var sn = parseInt(result[2], 10);
DiceMod.checkN(sn);
var scp = result[1] || '=';
if (result[4]) {
var fn = parseInt(result[4], 10);
DiceMod.checkN(fn);
var fcp = result[3] || '=';
this._failures = {
cp: fcp,
n: fn,
};
}
this._successes = {
cp: scp,
n: sn,
};
};
DiceMod.prototype._parseSortResult = function (result) {
if (this._sort !== null) {
throw new Error("Sort already set as \"" + this.sort + "\" but parsed \"" + result[0] + "\" as well.");
}
var ad = result[1] || 'a';
this._sort = { ad: ad };
};
DiceMod.prototype._applyHotModifiers = function (roll, dice) {
var _this = this;
var queue = [{ value: roll }];
var result = [];
var shouldExplode = function (value) {
return _this._exploding !== null && DiceMod.comparePoint(_this._exploding.cp, _this._exploding.n === null ? dice.d : _this._exploding.n, value);
};
var shouldPenetrate = function (value) {
return _this._penetrating !== null && DiceMod.comparePoint(_this._penetrating.cp, _this._penetrating.n === null ? dice.d : _this._penetrating.n, value);
};
var shouldReroll = function (value, rerolled) {
if (_this._reroll !== null) {
for (var _i = 0, _a = _this._reroll; _i < _a.length; _i++) {
var reroll = _a[_i];
var o = reroll.o;
var cp = reroll.cp ? reroll.cp : '=';
var n = reroll.n ? reroll.n : dice.minRoll;
if (!(o && rerolled) && DiceMod.comparePoint(cp, n, value)) {
return true;
}
}
}
return false;
};
while (queue.length > 0) {
var next = queue.pop();
if (shouldExplode(next.value)) {
queue.push({ value: Dice_1.Dice.roll(dice.d)[0] });
}
if (shouldPenetrate(next.value)) {
queue.push({ value: Dice_1.Dice.roll(dice.d)[0], penetrated: true });
}
var compoundedValue = this._applyCompounding(next.value, dice);
if (shouldReroll(compoundedValue, next.rerolled)) {
queue.push({ value: Dice_1.Dice.roll(dice.d)[0], rerolled: true });
}
else {
result.push(compoundedValue - (next.penetrated ? 1 : 0));
}
}
return result;
};
DiceMod.prototype._applySettledModifiers = function (result) {
var _this = this;
this._applyKeepDrop(result);
if (this._sort !== null) {
result.sort(this._sort.ad === 'a' ? DiceMod.sortAscComparator : DiceMod.sortDesComparator);
}
if (this._successes !== null) {
return result.reduce(function (acc, val) {
if (DiceMod.comparePoint(_this._successes.cp, _this._successes.n, val)) {
++acc;
}
if (_this._failures !== null && DiceMod.comparePoint(_this._failures.cp, _this._failures.n, val)) {
--acc;
}
return acc;
}, 0);
}
else {
return result.reduce(function (acc, val) { return acc + val; }, 0);
}
};
DiceMod.prototype._applyCompounding = function (roll, dice) {
var _this = this;
var shouldCompound = function (value) {
return _this._compounding !== null && DiceMod.comparePoint(_this._compounding.cp, _this._compounding.n === null ? dice.d : _this._compounding.n, value);
};
var compoundRoll = roll;
while (shouldCompound(compoundRoll)) {
if (roll >= Constants_1.MAX_JS_INT) {
throw new Error('Exceeded maximum roll value while applying Compounding modifier.');
}
compoundRoll = Dice_1.Dice.roll(dice.d)[0];
roll += compoundRoll;
}
return roll;
};
DiceMod.prototype._applyKeepDrop = function (result) {
if (this._keepDrop !== null) {
result.sort(DiceMod.sortAscComparator);
switch ("" + this._keepDrop.kd + this._keepDrop.lh) {
case 'kh':
result.splice(0, result.length - this._keepDrop.n);
break;
case 'kl':
result.splice(this._keepDrop.n);
break;
case 'dh':
result.splice(result.length - this._keepDrop.n);
break;
case 'dl':
result.splice(0, this._keepDrop.n);
break;
default:
throw new Error("Keep/Drop may be one of \"kh\", \"kl\", \"dh\" or \"dl\", but got \"" + this._keepDrop.kd + this._keepDrop.lh + "\".");
}
}
};
return DiceMod;
}());
exports.DiceMod = DiceMod;