A set of classes for parsing, evaluating, and formatting die roll strings.
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

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