A set of classes for parsing, evaluating, and formatting die roll strings.
Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

SA_DiceEvaluator.m 15KB

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