A set of classes for parsing, evaluating, and formatting die roll strings.
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

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