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

SA_DiceEvaluator.m 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  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_DiceExpressionStringConstants.h"
  12. #import "SA_DiceErrorHandling.h"
  13. /**************************/
  14. #pragma mark Defined values
  15. /**************************/
  16. #define DEFAULT_MAX_DIE_COUNT 1000 // One thousand
  17. #define DEFAULT_MAX_DIE_SIZE 10000 // Ten thousand
  18. /***************************************************/
  19. #pragma mark - SA_DiceEvaluator class implementation
  20. /***************************************************/
  21. @implementation SA_DiceEvaluator
  22. {
  23. NSInteger _maxDieCount;
  24. NSInteger _maxDieSize;
  25. }
  26. /************************/
  27. #pragma mark - Properties
  28. /************************/
  29. - (NSInteger)maxDieCount
  30. {
  31. return _maxDieCount;
  32. }
  33. - (void)setMaxDieCount:(NSInteger)maxDieCount
  34. {
  35. if(maxDieCount < 1)
  36. {
  37. _maxDieCount = 1;
  38. }
  39. else if(maxDieCount > NSIntegerMax / _maxDieSize)
  40. {
  41. _maxDieCount = NSIntegerMax / _maxDieSize;
  42. }
  43. else
  44. {
  45. _maxDieCount = maxDieCount;
  46. }
  47. }
  48. - (NSInteger)maxDieSize
  49. {
  50. return _maxDieSize;
  51. }
  52. - (void)setMaxDieSize:(NSInteger)maxDieSize
  53. {
  54. if(maxDieSize < 1)
  55. {
  56. _maxDieSize = 1;
  57. }
  58. else if(maxDieSize > [self.diceBag biggestPossibleDieSize] || maxDieSize > NSIntegerMax / _maxDieCount)
  59. {
  60. _maxDieSize = ([self.diceBag biggestPossibleDieSize] < (NSIntegerMax / _maxDieCount)) ? [_diceBag biggestPossibleDieSize] : NSIntegerMax / _maxDieCount;
  61. }
  62. else
  63. {
  64. _maxDieSize = maxDieSize;
  65. }
  66. }
  67. /**************************/
  68. #pragma mark - Initializers
  69. /**************************/
  70. - (instancetype)init
  71. {
  72. if(self = [super init])
  73. {
  74. _maxDieCount = DEFAULT_MAX_DIE_COUNT;
  75. _maxDieSize = DEFAULT_MAX_DIE_SIZE;
  76. self.diceBag = [[SA_DiceBag alloc] init];
  77. }
  78. return self;
  79. }
  80. /****************************/
  81. #pragma mark - Public methods
  82. /****************************/
  83. - (NSMutableDictionary *)resultOfExpression:(NSDictionary *)expression
  84. {
  85. // Check to see if the expression is erroneous (i.e. the parser has judged
  86. // that it is malformed, etc.). If so, decline to evaluate the expression;
  87. // return (a mutable copy of) it, unchanged.
  88. if(getErrorsForExpression(expression) != nil)
  89. {
  90. return [expression mutableCopy];
  91. }
  92. /*
  93. Even if an expression is not erroneous (i.e. if it has no syntax errors),
  94. it may still not be possible to evaluate it. For example, '5d0' is a
  95. perfectly well-formed die string, and will yield an expression tree as follows:
  96. @{SA_DB_TERM_TYPE : SA_DB_TERM_TYPE_ROLL_COMMAND,
  97. SA_DB_ROLL_COMMAND : SA_DB_ROLL_COMMAND_SUM,
  98. SA_DB_ROLL_DIE_COUNT : @{SA_DB_TERM_TYPE : SA_DB_TERM_TYPE_VALUE,
  99. SA_DB_VALUE : @(5)
  100. },
  101. SA_DB_ROLL_DIE_SIZE : @{SA_DB_TERM_TYPE : SA_DB_TERM_TYPE_VALUE,
  102. SA_DB_VALUE : @(0)
  103. }
  104. }
  105. This is, of course, an illegal expression; we can't roll a die of size 0
  106. (a die with zero sides?).
  107. If we encounter such an illegal expression, we add an appropriate error to
  108. the term. We are not required to set a value for the SA_DB_RESULT key in
  109. such a case.
  110. */
  111. // Check to see if the current term is an operation.
  112. if([expression[SA_DB_TERM_TYPE] isEqualToString:SA_DB_TERM_TYPE_OPERATION])
  113. {
  114. return [self resultOfExpressionDescribingOperation:expression];
  115. }
  116. // Check to see if the current term is a roll command.
  117. else if([expression[SA_DB_TERM_TYPE] isEqualToString:SA_DB_TERM_TYPE_ROLL_COMMAND])
  118. {
  119. return [self resultOfExpressionDescribingRollCommand:expression];
  120. }
  121. // If not an operation or a roll command, the current term can only be a
  122. // simple value expression (term type of SA_DB_TERM_TYPE_VALUE).
  123. else
  124. {
  125. return [self resultOfExpressionDescribingValue:expression];
  126. }
  127. }
  128. /****************************/
  129. #pragma mark - Helper methods
  130. /****************************/
  131. - (NSMutableDictionary *)resultOfExpressionDescribingValue:(NSDictionary *)expression
  132. {
  133. NSMutableDictionary *result = [expression mutableCopy];
  134. result[SA_DB_RESULT] = result[SA_DB_VALUE];
  135. return result;
  136. }
  137. - (NSMutableDictionary *)resultOfExpressionDescribingRollCommand:(NSDictionary *)expression
  138. {
  139. NSMutableDictionary *result = [expression mutableCopy];
  140. // For now, only a simple sum is supported. Other sorts of roll commands
  141. // may be added later.
  142. if([result[SA_DB_ROLL_COMMAND] isEqualToString:SA_DB_ROLL_COMMAND_SUM])
  143. {
  144. // First, recursively evaluate the expressions that represent the
  145. // die size and die count.
  146. result[SA_DB_ROLL_DIE_COUNT] = [self resultOfExpression:result[SA_DB_ROLL_DIE_COUNT]];
  147. result[SA_DB_ROLL_DIE_SIZE] = [self resultOfExpression:result[SA_DB_ROLL_DIE_SIZE]];
  148. // Evaluating those expressions may have generated an error(s);
  149. // propagate any errors up to the current term.
  150. addErrorsFromExpressionToExpression(result[SA_DB_ROLL_DIE_COUNT], result);
  151. addErrorsFromExpressionToExpression(result[SA_DB_ROLL_DIE_SIZE], result);
  152. // If indeed we've turned up errors, return.
  153. if(getErrorsForExpression(result) != nil)
  154. {
  155. return result;
  156. }
  157. // Evaluating the two sub-expressions didn't generate errors; this means
  158. // that we have successfully generated values for both the die count and
  159. // the die size...
  160. NSInteger dieCount = [result[SA_DB_ROLL_DIE_COUNT][SA_DB_RESULT] integerValue];
  161. NSInteger dieSize = [result[SA_DB_ROLL_DIE_SIZE][SA_DB_RESULT] integerValue];
  162. // ... but, the resulting values of the expressions may make it
  163. // impossible to evaluate the roll command. Check to see whether the die
  164. // count and die size have legal values.
  165. if(dieCount < 0)
  166. {
  167. addErrorToExpression(SA_DB_ERROR_DIE_COUNT_NEGATIVE, result);
  168. }
  169. else if(dieCount > _maxDieCount)
  170. {
  171. addErrorToExpression(SA_DB_ERROR_DIE_COUNT_EXCESSIVE, result);
  172. }
  173. if(dieSize < 1)
  174. {
  175. addErrorToExpression(SA_DB_ERROR_DIE_SIZE_INVALID, result);
  176. }
  177. else if(dieSize > _maxDieSize)
  178. {
  179. addErrorToExpression(SA_DB_ERROR_DIE_SIZE_EXCESSIVE, result);
  180. }
  181. // If indeed the die count or die size fall outside of their allowed
  182. // ranges, return.
  183. if(getErrorsForExpression(result) != nil)
  184. {
  185. return result;
  186. }
  187. // The die count and die size have legal values. We can safely roll the
  188. // requisite number of dice, and take the sum of the rolls (if needed).
  189. // NOTE: _maxDieSize is gauranteed to be no greater than the largest die
  190. // size that the SA_DiceBag can roll (this is enforced by the setter
  191. // method for the maxDieSize property), so we need not check to see
  192. // if the return value of rollDie: or rollNumber:ofDice: is valid.
  193. // We are also gauranteed that the product of _maxDieCount and
  194. // _maxDieSize is no greater than the largest unsigned value that can be
  195. // stored by whatever numeric type we specify simple value terms (terms
  196. // of type SA_DB_TERM_TYPE_VALUE) to contain (likewise enforced by the
  197. // setters for both maxDieSize and maxDieCount), therefore we need not
  198. // worry about overflow here.
  199. if(dieCount == 0)
  200. {
  201. result[SA_DB_RESULT] = @(0);
  202. result[SA_DB_ROLLS] = @[];
  203. }
  204. else if(dieCount == 1)
  205. {
  206. NSNumber *roll = @([self.diceBag rollDie:dieSize]);
  207. result[SA_DB_RESULT] = roll;
  208. result[SA_DB_ROLLS] = @[roll];
  209. }
  210. else
  211. {
  212. NSArray *rolls = [self.diceBag rollNumber:@(dieCount) ofDice:dieSize];
  213. result[SA_DB_RESULT] = [rolls valueForKeyPath:@"@sum.self"];
  214. result[SA_DB_ROLLS] = rolls;
  215. }
  216. // Return the successfully evaluated roll command expression.
  217. return result;
  218. }
  219. else
  220. {
  221. addErrorToExpression(SA_DB_ERROR_UNKNOWN_ROLL_COMMAND, result);
  222. return result;
  223. }
  224. }
  225. - (NSMutableDictionary *)resultOfExpressionDescribingOperation:(NSDictionary *)expression
  226. {
  227. NSMutableDictionary *result = [expression mutableCopy];
  228. // First, recursively evaluate the expressions that represent the
  229. // left-hand-side and right-hand-side operands.
  230. result[SA_DB_OPERAND_LEFT] = [self resultOfExpression:result[SA_DB_OPERAND_LEFT]];
  231. result[SA_DB_OPERAND_RIGHT] = [self resultOfExpression:result[SA_DB_OPERAND_RIGHT]];
  232. // Evaluating the operand may have generated an error(s); propagate any
  233. // errors up to the current term.
  234. addErrorsFromExpressionToExpression(result[SA_DB_OPERAND_LEFT], result);
  235. addErrorsFromExpressionToExpression(result[SA_DB_OPERAND_RIGHT], result);
  236. // If indeed we've turned up errors, return.
  237. if(getErrorsForExpression(result) != nil)
  238. {
  239. return result;
  240. }
  241. // Evaluating the operands didn't generate any errors. We have valid
  242. // operands.
  243. NSInteger leftOperand = [result[SA_DB_OPERAND_LEFT][SA_DB_RESULT] integerValue];
  244. NSInteger rightOperand = [result[SA_DB_OPERAND_RIGHT][SA_DB_RESULT] integerValue];
  245. // Check to see if the operation is subtraction.
  246. if([result[SA_DB_OPERATOR] isEqualToString:SA_DB_OPERATOR_MINUS])
  247. {
  248. // First, we check for possible overflow...
  249. if(leftOperand > 0 && rightOperand < 0 && NSIntegerMax + rightOperand < leftOperand)
  250. {
  251. addErrorToExpression(SA_DB_ERROR_INTEGER_OVERFLOW_SUBTRACTION, result);
  252. return result;
  253. }
  254. else if(leftOperand < 0 && rightOperand > 0 && NSIntegerMin + rightOperand > leftOperand)
  255. {
  256. addErrorToExpression(SA_DB_ERROR_INTEGER_UNDERFLOW_SUBTRACTION, result);
  257. return result;
  258. }
  259. // No overflow will occur. We can perform the subtraction operation.
  260. result[SA_DB_RESULT] = @(leftOperand - rightOperand);
  261. // Return the successfully evaluated subtraction expression.
  262. return result;
  263. }
  264. // Check to see if the operation is addition.
  265. else if([result[SA_DB_OPERATOR] isEqualToString:SA_DB_OPERATOR_PLUS])
  266. {
  267. // First, we check for possible overflow...
  268. if(rightOperand > 0 && leftOperand > 0 && NSIntegerMax - rightOperand < leftOperand)
  269. {
  270. addErrorToExpression(SA_DB_ERROR_INTEGER_OVERFLOW_ADDITION, result);
  271. return result;
  272. }
  273. else if(rightOperand < 0 && leftOperand < 0 && NSIntegerMin - rightOperand > leftOperand)
  274. {
  275. addErrorToExpression(SA_DB_ERROR_INTEGER_UNDERFLOW_ADDITION, result);
  276. return result;
  277. }
  278. // No overflow will occur. We can perform the addition operation.
  279. result[SA_DB_RESULT] = @(leftOperand + rightOperand);
  280. // Return the successfully evaluated addition expression.
  281. return result;
  282. }
  283. // Check to see if the operation is multiplication.
  284. else if([result[SA_DB_OPERATOR] isEqualToString:SA_DB_OPERATOR_TIMES])
  285. {
  286. // First, we check for possible overflow...
  287. if( ( leftOperand == NSIntegerMin && ( rightOperand != 0 || rightOperand != 1 ) ) ||
  288. ( rightOperand == NSIntegerMin && ( leftOperand != 0 || leftOperand != 1 ) ) ||
  289. ( leftOperand != 0 && ( (NSIntegerMax / ABS(leftOperand)) < rightOperand ) ) )
  290. {
  291. if((leftOperand > 0 && rightOperand > 0) || (leftOperand < 0 && rightOperand < 0))
  292. {
  293. addErrorToExpression(SA_DB_ERROR_INTEGER_OVERFLOW_MULTIPLICATION, result);
  294. }
  295. else
  296. {
  297. addErrorToExpression(SA_DB_ERROR_INTEGER_UNDERFLOW_MULTIPLICATION, result);
  298. }
  299. return result;
  300. }
  301. // No overflow will occur. We can perform the multiplication operation.
  302. result[SA_DB_RESULT] = @(leftOperand * rightOperand);
  303. // Return the successfully evaluated multiplication expression.
  304. return result;
  305. }
  306. // The operation is not one of the supported operators.
  307. else
  308. {
  309. // We add the appropriate error. We do not set a value for the
  310. // SA_DB_RESULT key.
  311. addErrorToExpression(SA_DB_ERROR_UNKNOWN_OPERATOR, result);
  312. return result;
  313. }
  314. }
  315. @end