A set of classes for parsing, evaluating, and formatting die roll strings.
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

SA_DiceEvaluator.m 14KB

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