A set of classes for parsing, evaluating, and formatting die roll strings.
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

SA_DiceEvaluator.m 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. //
  2. // SA_DiceEvaluator.m
  3. //
  4. // Copyright (c) 2016 Said Achmiz.
  5. //
  6. // This software is licensed under the MIT license.
  7. // See the file "LICENSE" for more information.
  8. #import "SA_DiceEvaluator.h"
  9. #import "SA_DiceBag.h"
  10. #import "SA_DiceParser.h"
  11. #import "SA_DiceExpression.h"
  12. #import "SA_DiceExpressionStringConstants.h"
  13. #import "SA_Utility.h"
  14. /**************************/
  15. #pragma mark Defined values
  16. /**************************/
  17. #define DEFAULT_MAX_DIE_COUNT 1000 // One thousand
  18. #define DEFAULT_MAX_DIE_SIZE 10000 // Ten thousand
  19. /***************************************************/
  20. #pragma mark - SA_DiceEvaluator class implementation
  21. /***************************************************/
  22. @implementation SA_DiceEvaluator {
  23. SA_DiceBag *_diceBag;
  24. NSUInteger _maxDieCount;
  25. NSUInteger _maxDieSize;
  26. }
  27. /************************/
  28. #pragma mark - Properties
  29. /************************/
  30. -(NSUInteger) maxDieCount {
  31. return _maxDieCount;
  32. }
  33. -(void) setMaxDieCount:(NSUInteger)maxDieCount {
  34. if (maxDieCount > NSUIntegerMax / _maxDieSize) {
  35. _maxDieCount = NSUIntegerMax / _maxDieSize;
  36. } else {
  37. _maxDieCount = maxDieCount;
  38. }
  39. }
  40. -(NSUInteger) maxDieSize {
  41. return _maxDieSize;
  42. }
  43. -(void) setMaxDieSize:(NSUInteger)maxDieSize {
  44. if (maxDieSize > [_diceBag biggestPossibleDieSize] ||
  45. maxDieSize > NSIntegerMax / _maxDieCount) {
  46. _maxDieSize = ([_diceBag biggestPossibleDieSize] < (NSIntegerMax / _maxDieCount)) ? [_diceBag biggestPossibleDieSize] : NSIntegerMax / _maxDieCount;
  47. } else {
  48. _maxDieSize = maxDieSize;
  49. }
  50. }
  51. /**************************/
  52. #pragma mark - Initializers
  53. /**************************/
  54. -(instancetype) init {
  55. if (self = [super init]) {
  56. _maxDieCount = DEFAULT_MAX_DIE_COUNT;
  57. _maxDieSize = DEFAULT_MAX_DIE_SIZE;
  58. _diceBag = [SA_DiceBag new];
  59. }
  60. return self;
  61. }
  62. /****************************/
  63. #pragma mark - Public methods
  64. /****************************/
  65. -(SA_DiceExpression *) resultOfExpression:(SA_DiceExpression *)expression {
  66. // Check to see if the expression is erroneous (i.e. the parser has judged
  67. // that it is malformed, etc.). If so, decline to evaluate the expression;
  68. // return (a copy of) it, unchanged.
  69. if (expression.errorBitMask != 0) {
  70. return [expression copy];
  71. }
  72. /*
  73. Even if an expression is not erroneous (i.e. if it has no syntax errors),
  74. it may still not be possible to evaluate it. For example, ‘5d0’ is a
  75. perfectly well-formed die string, and will yield an expression tree as follows:
  76. @{SA_DB_TERM_TYPE : SA_DB_TERM_TYPE_ROLL_COMMAND,
  77. SA_DB_ROLL_COMMAND : SA_DB_ROLL_COMMAND_SUM,
  78. SA_DB_ROLL_DIE_COUNT : @{SA_DB_TERM_TYPE : SA_DB_TERM_TYPE_VALUE,
  79. SA_DB_VALUE : @(5)
  80. },
  81. SA_DB_ROLL_DIE_SIZE : @{SA_DB_TERM_TYPE : SA_DB_TERM_TYPE_VALUE,
  82. SA_DB_VALUE : @(0)
  83. }
  84. }
  85. This is, of course, an illegal expression; we can’t roll a die of size 0
  86. (a die with zero sides?).
  87. If we encounter such an illegal expression, we add an appropriate error to
  88. the term. We are not required to set a value for the SA_DB_RESULT key in
  89. such a case.
  90. */
  91. switch (expression.type) {
  92. case SA_DiceExpressionTerm_OPERATION: {
  93. return [self resultOfExpressionDescribingOperation:expression];
  94. break;
  95. }
  96. case SA_DiceExpressionTerm_ROLL_COMMAND: {
  97. return [self resultOfExpressionDescribingRollCommand:expression];
  98. break;
  99. }
  100. case SA_DiceExpressionTerm_ROLL_MODIFIER: {
  101. return [self resultOfExpressionDescribingRollModifier:expression];
  102. break;
  103. }
  104. case SA_DiceExpressionTerm_VALUE:
  105. default: {
  106. return [self resultOfExpressionDescribingValue:expression];
  107. break;
  108. }
  109. }
  110. }
  111. /****************************/
  112. #pragma mark - Helper methods
  113. /****************************/
  114. -(SA_DiceExpression *) resultOfExpressionDescribingValue:(SA_DiceExpression *)expression {
  115. SA_DiceExpression *result = [expression copy];
  116. result.result = result.value;
  117. return result;
  118. }
  119. -(SA_DiceExpression *) resultOfExpressionDescribingRollCommand:(SA_DiceExpression *)expression {
  120. SA_DiceExpression *result = [expression copy];
  121. // For now, only sum and exploding sum (i.e., sum but with exploding dice)
  122. // are supported Other sorts of roll commands may be added later.
  123. switch (result.rollCommand) {
  124. case SA_DiceExpressionRollCommand_SUM:
  125. case SA_DiceExpressionRollCommand_SUM_EXPLODING: {
  126. // First, recursively evaluate the expressions that represent the
  127. // die count and (for standard dice) the die size.
  128. result.dieCount = [self resultOfExpression:result.dieCount];
  129. if (result.dieType == SA_DiceExpressionDice_STANDARD)
  130. result.dieSize = [self resultOfExpression:result.dieSize];
  131. // Evaluating those expressions may have generated an error(s);
  132. // propagate any errors up to the current term.
  133. result.errorBitMask |= result.dieCount.errorBitMask;
  134. if (result.dieType == SA_DiceExpressionDice_STANDARD)
  135. result.errorBitMask |= result.dieSize.errorBitMask;
  136. // If indeed we’ve turned up errors, return.
  137. if (result.errorBitMask != 0)
  138. return result;
  139. // Evaluating the two sub-expressions didn’t generate errors; this means
  140. // that we have successfully generated values for both the die count and
  141. // (for standard dice) the die size...
  142. NSInteger dieCount = result.dieCount.result.integerValue;
  143. NSInteger dieSize = 0;
  144. if (result.dieType == SA_DiceExpressionDice_STANDARD)
  145. dieSize = result.dieSize.result.integerValue;
  146. // ... but, the resulting values of the expressions may make it
  147. // impossible to evaluate the roll command. Check to see whether the die
  148. // count and die size have legal values.
  149. if (dieCount < 0) {
  150. result.errorBitMask |= SA_DiceExpressionError_DIE_COUNT_NEGATIVE;
  151. } else if (dieCount > _maxDieCount) {
  152. result.errorBitMask |= SA_DiceExpressionError_DIE_COUNT_EXCESSIVE;
  153. }
  154. // Die type only matters for standard dice, not for Fudge dice.
  155. if (result.dieType == SA_DiceExpressionDice_STANDARD) {
  156. if (dieSize < 1) {
  157. result.errorBitMask |= SA_DiceExpressionError_DIE_SIZE_INVALID;
  158. } else if (dieSize > _maxDieSize) {
  159. result.errorBitMask |= SA_DiceExpressionError_DIE_SIZE_EXCESSIVE;
  160. }
  161. }
  162. // If indeed the die count or die size fall outside of their allowed
  163. // ranges, return.
  164. if (result.errorBitMask != 0)
  165. return result;
  166. // The die count and die size have legal values. We can safely roll the
  167. // requisite number of dice, and take the sum of the rolls (if needed).
  168. // NOTE: _maxDieSize is guaranteed to be no greater than the largest die
  169. // size that the SA_DiceBag can roll (this is enforced by the setter
  170. // method for the maxDieSize property), so we need not check to see
  171. // if the return value of rollDie: or rollNumber:ofDice: is valid.
  172. // We are also guaranteed that the product of _maxDieCount and
  173. // _maxDieSize is no greater than the largest unsigned value that can be
  174. // stored by whatever numeric type we specify simple value terms (terms
  175. // of type SA_DiceExpressionTerm_VALUE) to contain (likewise enforced
  176. // by the setters for both maxDieSize and maxDieCount), therefore we
  177. // need not worry about overflow here.
  178. if (dieCount == 0) {
  179. result.result = @(0);
  180. result.rolls = @[];
  181. } else {
  182. NSArray *rolls;
  183. if (result.dieType == SA_DiceExpressionDice_STANDARD) {
  184. SA_DiceRollingOptions options = (result.rollCommand == SA_DiceExpressionRollCommand_SUM_EXPLODING) ? SA_DiceRollingExplodingDice : 0;
  185. rolls = [_diceBag rollNumber:dieCount
  186. ofDice:dieSize
  187. withOptions:options];
  188. } else if (result.dieType == SA_DiceExpressionDice_FUDGE) {
  189. rolls = [_diceBag rollFudgeDice:dieCount];
  190. }
  191. result.result = [rolls valueForKeyPath:@"@sum.self"];
  192. result.rolls = rolls;
  193. }
  194. break;
  195. }
  196. default: {
  197. result.errorBitMask |= SA_DiceExpressionError_UNKNOWN_ROLL_COMMAND;
  198. break;
  199. }
  200. }
  201. return result;
  202. }
  203. -(SA_DiceExpression *) resultOfExpressionDescribingRollModifier:(SA_DiceExpression *)expression {
  204. SA_DiceExpression *result = [expression copy];
  205. switch (result.rollModifier) {
  206. case SA_DiceExpressionRollModifier_KEEP_HIGHEST:
  207. case SA_DiceExpressionRollModifier_KEEP_LOWEST: {
  208. // These roll modifiers takes the highest, or the lowest, N rolls
  209. // out of all the rolls generated by a roll command, discarding the
  210. // rest, and summing the kept ones.
  211. // First, check if the left-hand operand is a roll command (and
  212. // specifically, a simple sum; though this latter requirement may
  213. // be dropped later).
  214. // TODO: re-evaluate this ^
  215. // If the left-hand operand is not a roll-and-sum, then the KEEP
  216. // modifier cannot be applied to it. In that case, we add an error
  217. // and return the result without evaluating.
  218. if (result.leftOperand.type != SA_DiceExpressionTerm_ROLL_COMMAND ||
  219. (result.leftOperand.rollCommand != SA_DiceExpressionRollCommand_SUM &&
  220. result.leftOperand.rollCommand != SA_DiceExpressionRollCommand_SUM_EXPLODING)) {
  221. result.errorBitMask |= SA_DiceExpressionError_ROLL_MODIFIER_INAPPLICABLE;
  222. return result;
  223. }
  224. // We now know the left-hand operand is a roll command. Recursively
  225. // evaluate the expressions that represent the roll command and the
  226. // modifier value (the right-hand operand).
  227. result.leftOperand = [self resultOfExpression:result.leftOperand];
  228. result.rightOperand = [self resultOfExpression:result.rightOperand];
  229. // Evaluating the operands may have generated an error(s); propagate any
  230. // errors up to the current term.
  231. result.errorBitMask |= result.leftOperand.errorBitMask;
  232. result.errorBitMask |= result.rightOperand.errorBitMask;
  233. // If indeed we’ve turned up errors, return.
  234. if (result.errorBitMask != 0)
  235. return result;
  236. // Evaluating the operands didn’t generate any errors; this means
  237. // that on the left hand we have a set of rolls (as well as a
  238. // result, which we are ignoring), and on the right hand we have a
  239. // result, which specifies how many rolls to keep.
  240. NSArray <NSNumber *> *rolls = result.leftOperand.rolls;
  241. NSNumber *keepHowMany = result.rightOperand.result;
  242. // However, it is now possible that the “keep how many” value
  243. // exceeds the number of rolls, which would make the expression
  244. // incoherent. If so, add an error and return.
  245. if (keepHowMany.unsignedIntegerValue > rolls.count) {
  246. result.errorBitMask |= SA_DiceExpressionError_KEEP_COUNT_EXCEEDS_ROLL_COUNT;
  247. return result;
  248. }
  249. // It is also possible that the “keep how many” value is negative.
  250. // This, too, would make the expression incoherent. Likewise, add
  251. // an error and return.
  252. if (keepHowMany < 0) {
  253. result.errorBitMask |= SA_DiceExpressionError_KEEP_COUNT_NEGATIVE;
  254. return result;
  255. }
  256. // We sort the rolls array...
  257. BOOL sortAscending = (result.rollModifier == SA_DiceExpressionRollModifier_KEEP_LOWEST);
  258. result.rolls = [rolls sortedArrayUsingDescriptors:@[ [NSSortDescriptor sortDescriptorWithKey:@"integerValue"
  259. ascending:sortAscending] ]];
  260. // And the ‘result’ property of the result expression is the sum of
  261. // the first <keepHowMany> elements of the sorted rolls array.
  262. result.result = [[result.rolls subarrayWithRange:NSRangeMake(0, keepHowMany.unsignedIntegerValue)] valueForKeyPath:@"@sum.self"];
  263. break;
  264. }
  265. default: {
  266. result.errorBitMask |= SA_DiceExpressionError_UNKNOWN_ROLL_MODIFIER;
  267. break;
  268. }
  269. }
  270. return result;
  271. }
  272. -(SA_DiceExpression *) resultOfExpressionDescribingOperation:(SA_DiceExpression *)expression {
  273. SA_DiceExpression *result = [expression copy];
  274. // First, recursively evaluate the expressions that represent the
  275. // left-hand-side and right-hand-side operands.
  276. result.leftOperand = [self resultOfExpression:result.leftOperand];
  277. result.rightOperand = [self resultOfExpression:result.rightOperand];
  278. // Evaluating the operands may have generated an error(s); propagate any
  279. // errors up to the current term.
  280. result.errorBitMask |= result.leftOperand.errorBitMask;
  281. result.errorBitMask |= result.rightOperand.errorBitMask;
  282. // If indeed we’ve turned up errors, return.
  283. if (result.errorBitMask != 0)
  284. return result;
  285. // Evaluating the operands didn’t generate any errors. We have valid
  286. // operands.
  287. NSInteger leftOperand = result.leftOperand.result.integerValue;
  288. NSInteger rightOperand = result.rightOperand.result.integerValue;
  289. switch (result.operator) {
  290. case SA_DiceExpressionOperator_MINUS: {
  291. // First, we check for possible overflow...
  292. if (leftOperand > 0 && rightOperand < 0 &&
  293. NSIntegerMax + rightOperand < leftOperand) {
  294. result.errorBitMask |= SA_DiceExpressionError_INTEGER_OVERFLOW_SUBTRACTION;
  295. break;
  296. } else if (leftOperand < 0 && rightOperand > 0 &&
  297. NSIntegerMin + rightOperand > leftOperand) {
  298. result.errorBitMask |= SA_DiceExpressionError_INTEGER_UNDERFLOW_SUBTRACTION;
  299. break;
  300. }
  301. // No overflow will occur. We can perform the subtraction operation.
  302. result.result = @(leftOperand - rightOperand);
  303. break;
  304. }
  305. case SA_DiceExpressionOperator_PLUS: {
  306. // First, we check for possible overflow...
  307. if (rightOperand > 0 && leftOperand > 0 &&
  308. NSIntegerMax - rightOperand < leftOperand) {
  309. result.errorBitMask |= SA_DiceExpressionError_INTEGER_OVERFLOW_ADDITION;
  310. break;
  311. } else if(rightOperand < 0 && leftOperand < 0 && NSIntegerMin - rightOperand > leftOperand) {
  312. result.errorBitMask |= SA_DiceExpressionError_INTEGER_UNDERFLOW_ADDITION;
  313. break;
  314. }
  315. // No overflow will occur. We can perform the addition operation.
  316. result.result = @(leftOperand + rightOperand);
  317. break;
  318. }
  319. case SA_DiceExpressionOperator_TIMES: {
  320. // First, we check for possible overflow...
  321. if (( leftOperand == NSIntegerMin && ( rightOperand != 0 || rightOperand != 1 )) ||
  322. ( rightOperand == NSIntegerMin && ( leftOperand != 0 || leftOperand != 1 )) ||
  323. ( leftOperand != 0 && ( (NSIntegerMax / ABS(leftOperand)) < rightOperand ))) {
  324. if ((leftOperand > 0 && rightOperand > 0) ||
  325. (leftOperand < 0 && rightOperand < 0)) {
  326. result.errorBitMask |= SA_DiceExpressionError_INTEGER_OVERFLOW_MULTIPLICATION;
  327. } else {
  328. result.errorBitMask |= SA_DiceExpressionError_INTEGER_UNDERFLOW_MULTIPLICATION;
  329. }
  330. break;
  331. }
  332. // No overflow will occur. We can perform the multiplication operation.
  333. result.result = @(leftOperand * rightOperand);
  334. break;
  335. }
  336. default: {
  337. // We add the appropriate error. We do not set a value for the
  338. // result property.
  339. result.errorBitMask |= SA_DiceExpressionError_UNKNOWN_OPERATOR;
  340. break;
  341. }
  342. }
  343. return result;
  344. }
  345. @end