A set of classes for parsing, evaluating, and formatting die roll strings.
Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

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 negation 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