├── .gitignore
├── .scrutinizer.yml
├── .travis.yml
├── README.rst
├── composer.json
├── demo.php
├── phpunit.xml.dist
├── src
└── DiceCalc
│ ├── Calc.php
│ ├── CalcDice.php
│ ├── CalcOperation.php
│ ├── CalcSet.php
│ └── Random.php
└── tests
├── DiceCalc
└── Tests
│ ├── Calc
│ ├── BasicRollTest.php
│ ├── BasicRollWithMathTest.php
│ ├── ConditionNumberTest.php
│ ├── CustomDieTest.php
│ ├── KeepPostfixTest.php
│ ├── LowestHighestTest.php
│ ├── MathTest.php
│ └── TargetNumberTest.php
│ └── CalcOperation
│ ├── AddTest.php
│ ├── CalcTest.php
│ ├── DevideTest.php
│ ├── EqualToTest.php
│ ├── ExponentTest.php
│ ├── GreaterThanTest.php
│ ├── LessThanTest.php
│ ├── MultiplyTest.php
│ ├── NaNTest.php
│ ├── ReduceTest.php
│ └── SubtractTest.php
└── bootstrap.php
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | composer.phar
3 | /vendor/
4 |
--------------------------------------------------------------------------------
/.scrutinizer.yml:
--------------------------------------------------------------------------------
1 | imports:
2 | - php
3 |
4 | tools:
5 | # PHP
6 | php_sim: true
7 | php_code_sniffer: true
8 | php_hhvm: true
9 | php_mess_detector: true
10 | php_pdepend: true
11 | php_loc: true
12 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 | php:
3 | - 5.4
4 | - 5.5
5 | - 5.6
6 | - 7.0
7 | - hhvm
8 |
9 | branches:
10 | only:
11 | - master
12 | - dev
13 |
14 | script: phpunit --coverage-text
15 |
16 | install: composer install
17 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | ========
2 | DiceCalc
3 | ========
4 |
5 | Project status
6 | --------------
7 |
8 | .. image:: https://travis-ci.org/ringmaster/dicecalc.svg?branch=master
9 | :target: https://travis-ci.org/ringmaster/dicecalc
10 |
11 | .. image:: https://scrutinizer-ci.com/g/ringmaster/dicecalc/badges/quality-score.png?b=master
12 | :target: https://scrutinizer-ci.com/g/ringmaster/dicecalc/?branch=master
13 |
14 | How to use
15 | ----------
16 | Create a new Calc class, and pass the dice expression to the constructor::
17 | $calc = new Calc($expression);
18 |
19 | Output the interpretation of the roll::
20 | echo $calc->infix();
21 |
22 | Output the result of the roll::
23 | echo $calc();
24 |
25 | Dice Expressions
26 | ----------------
27 | The basic expression is to use a "d" followed by the number of sides of the die to roll.
28 |
29 | *Examples:*
30 |
31 | d6
32 | roll a six-sided die
33 | d20
34 | roll a twenty-sided die
35 | d%
36 | roll a percentile (1-100) die
37 | df
38 | roll a fudge (-1, 0, 1) die
39 |
40 | To roll multiple of the same sided die, prefix the die type with a multiplier number.
41 |
42 | *Examples:*
43 |
44 | 3d6
45 | roll d6 three times, sum the result
46 | 2d20
47 | roll d20 twice, sum the result
48 | 6df
49 | roll six fudge dice, sum the result
50 |
51 | Basic math operations are possible as expressions.
52 |
53 | *Examples:*
54 |
55 | 4+4
56 | Add 4+4
57 | 4*5
58 | Multiply 4*5
59 | 4+4*5
60 | Multiply 4*5, then add 4 (order of operations is obeyed)
61 | (4+4)*5
62 | Add 4+4, then multiply by 5
63 |
64 | Math on sets of numbers.
65 |
66 | *Examples:*
67 |
68 | 2*[3,4]
69 | Multiply 2*3 and 2*4, resulting in a set [6,8]
70 | [1,2]*[3,4]
71 | Multiply these sets together, resulting in a nested set [[3,4],[6,8]]
72 |
73 | Any combination of dice or constants in an expressions.
74 |
75 | *Examples:*
76 |
77 | 2d6+4
78 | roll 2d6, sum the dice and add 4
79 | 3d8-5
80 | roll 3d8, sum the dice and subtract 5
81 | 2d4+d6
82 | roll 2d4 and d6, sum all the dice
83 |
84 | Evaluate whether a result meets a certain condition or target number.
85 |
86 | *Examples:*
87 |
88 | 4+4 > 7
89 | returns true, because 4+4 is greater than 7
90 | 5d6 > 5
91 | returns true, because 5d6 is always greater than 5
92 | d20+4 > 12
93 | simulates a d20 system skill value of 4 compared to a target number of 12
94 |
95 | Produce a set of rolls by surrounding the roll with brackets and prefixing with a multiplier.
96 |
97 | *Examples:*
98 |
99 | 6[3d6]
100 | produces a set of 6 numbers created by rolling 3d6
101 | 4[d%]
102 | produces a set of 4 numbers created by rolling percentile
103 |
104 | Roll a custom die by specifying the dice sides in brackets after the "d".
105 |
106 | *Examples:*
107 |
108 | d[1,2,2,3,3,4]
109 | Roll a six-sided die with custom numeric faces
110 | d[red,green,blue]
111 | Roll a three-sided die with colored faces
112 |
113 | Keep only the dice in an individual roll that meet specific conditions by applying the "k" postfix.
114 |
115 | *Examples:*
116 |
117 | 5d6 keep > 4
118 | Role five six-sided dice, keep only the dice that are greater than 4, sum the kept dice
119 | 5d6k>4
120 | same as above
121 | 6d8 keep < 4
122 | Roll six eight-sided dice, keep only the dice that are less than 4, sum the kept dice
123 | 6d8k<4
124 | same as above
125 |
126 | Keep only the dice that are the lowest or highest values rolled.
127 |
128 | *Examples:*
129 |
130 | 4d6 highest 3
131 | Roll 4d6, keep only the highest 3 dice
132 | 4d6h3
133 | same as above
134 | 2s20 lowest 1
135 | Roll 2d20, keep only the lowest die
136 | 2d20l1
137 | same as above
138 |
139 | Reroll dice that do not meet certain conditions.
140 |
141 | *Examples:*
142 |
143 | 3d6 reroll < 3
144 | Roll 3d6, reroll any die that is less than 3
145 | 3d6r<3
146 | same as above
147 | 2d% reroll < 40
148 | Roll 2d%, reroll any die that is less than 40
149 | 2d%r<40
150 | same as above
151 |
152 | Our stupid way of rolling D&D character stats.
153 |
154 | *Examples:*
155 |
156 | 4d6r<3h3
157 | Roll 4d6, reroll any die that is less than 3, keep the highest 3 dice, sum the kept dice
158 |
159 | Produce open-ended dice using the "o" prefix.
160 |
161 | *Examples:*
162 |
163 | 4d6o=6
164 | Roll 4d6. When any die lands on 6, roll that die again and add the result to that die. Sum all die totals.
165 |
166 | A stupid example nobody would ever use, I hope:
167 |
168 | 3d6r<4o=6k>6
169 | Roll 3d6. Reroll any die less than 4. When any die is a 6, reroll and add the new value to the original one. Sum the die totals of only those dice that are greater than 6.
170 |
171 | Future Enhancements
172 | -------------------
173 | * Better group handling
174 | * Better custom die handling
175 | * Variable replacements (To handle rolls like: d20 + $str_bonus > $target )
176 | * Range violation exceptions (d6k<0)
177 | * Non-text output method
178 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ringmaster/dicecalc",
3 | "description": "DiceCalc",
4 | "version": "1.0.0",
5 | "keywords": ["dice", "dice calc", "dice calculator", "dice roller"],
6 | "license": "MIT",
7 | "require-dev": {
8 | "phpunit/phpunit": "@stable",
9 | "friendsofphp/php-cs-fixer": "@stable",
10 | "phpmd/phpmd": "@stable",
11 | "phploc/phploc": "@stable",
12 | "pdepend/pdepend": "@stable",
13 | "sebastian/phpcpd": "@stable",
14 | "sebastian/phpdcd": "@stable",
15 | "squizlabs/php_codesniffer": "@stable",
16 | "jakub-onderka/php-parallel-lint": "^0.9.2"
17 | },
18 | "authors": [
19 | {
20 | "name": "Owen Winkler",
21 | "email": "a_github@midnightcircus.com"
22 | }
23 | ],
24 | "require": {
25 | "php": ">=5.4"
26 | },
27 | "autoload": {
28 | "psr-4": {
29 | "DiceCalc\\": "src/DiceCalc"
30 | }
31 | },
32 | "autoload-dev": {
33 | "psr-4": {"DiceCalc\\": "tests/DiceCalc"}
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/demo.php:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 | Calc Test
20 |
21 |
22 |
23 |
27 |
41 |
42 |
43 |
44 | infix();
48 | echo " => ";
49 | echo $calc() . "\n";
50 |
51 | ?>
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 | ./tests/DiceCalc/Tests
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/DiceCalc/Calc.php:
--------------------------------------------------------------------------------
1 |
10 | * @license MIT http://opensource.org/licenses/MIT
11 | */
12 | class Calc {
13 |
14 | const DICE_REGEX = '(?P\d*)d(?P\d+|f|\%|\[[^\]]+\])
15 | (
16 | (?Pk(?:eep)?(?P[<>])(?P\d+))
17 | |
18 | (?Pl(?:owest)?(?P\d+))
19 | |
20 | (?Ph(?:ighest)?(?P\d+))
21 | |
22 | (?Pr(?:eroll)?(?P[<>])(?P\d+))
23 | |
24 | (?Po(?:pen)?(?P[<>=])(?P\d+))
25 | |
26 | (?P[z]+)
27 | )*';
28 |
29 | /**
30 | * @var array $ooo A list of operators with comparative order of operations
31 | */
32 | private $ooo = [
33 | '>' => 0,
34 | '<' => 0,
35 | '=' => 0,
36 | '-' => 10,
37 | '+' => 10,
38 | '*' => 20,
39 | '/' => 20,
40 | '^' => 30,
41 | ];
42 |
43 | protected $expression;
44 | protected $rpn = [];
45 | protected $infix = [];
46 |
47 | protected $stack = [];
48 |
49 | /**
50 | * Create a dice calculation
51 | *
52 | * @param string $expression An expression to calculate
53 | */
54 | public function __construct($expression = '') {
55 | $this->expression = str_replace(' ', '', $expression);
56 |
57 | preg_match_all('%(?:
58 | (?P' . self::DICE_REGEX . ')
59 | |
60 | (?P\d*\[[^\]]+\])
61 | |
62 | (?P[\d\.]+)
63 | |
64 | (?P[+\-*^><=/])
65 | |
66 | (?P\$[a-z_]+)
67 | |
68 | (?P[()])
69 | )%ix', $this->expression, $matches, PREG_SET_ORDER);
70 |
71 | $this->stack = [];
72 |
73 | foreach ($matches as $match) {
74 | $match = array_filter($match, function ($value) {
75 | return $value !== false && $value !== '';
76 | });
77 |
78 | if (isset($match['numeral'])) {
79 | $this->match_numeral($match['numeral']);
80 | } elseif (isset($match['dice'])) {
81 | $this->match_dice($match['dice']);
82 | } elseif (isset($match['set'])) {
83 | $this->match_set($match['set']);
84 | } elseif (isset($match['operator'])) {
85 | $this->match_operator($match['operator']);
86 | } elseif (isset($match['variable'])) {
87 | $this->match_variable($match['variable']);
88 | } elseif (isset($match['parens'])) {
89 | $this->match_parens($match['parens']);
90 | } else {
91 | $this->stack = ['Invalid token:', $match];
92 | break;
93 | }
94 | }
95 |
96 | $this->clear_stack();
97 | }
98 |
99 | /**
100 | * @param int $numeral Numeral to add to the RPN stack
101 | */
102 | protected function match_numeral($numeral) {
103 | $this->rpn[] = $numeral;
104 | $this->infix[] = $numeral;
105 | }
106 |
107 | protected function match_dice($dice) {
108 | $dice = new CalcDice($dice);
109 | $this->rpn[] = $dice->value();
110 | $this->infix[] = $dice;
111 | }
112 |
113 | protected function match_set($set) {
114 | $this->rpn[] = new CalcSet($set);
115 | $this->infix[] = end($this->rpn);
116 | }
117 |
118 | /**
119 | * @param $variable
120 | */
121 | protected function match_variable($variable) {
122 | $this->rpn[] = $variable;
123 | $this->infix[] = end($this->rpn);
124 | }
125 |
126 | protected function match_parens($parenthesis) {
127 | $this->infix[] = $parenthesis;
128 | if ($parenthesis == '(') {
129 | $this->stack[] = $parenthesis;
130 | } else {
131 | while (count($this->stack) > 0 && end($this->stack) != '(') {
132 | $this->rpn[] = array_pop($this->stack);
133 | }
134 | array_pop($this->stack);
135 | }
136 | }
137 |
138 | /**
139 | * @param $operator
140 | */
141 | protected function match_operator($operator) {
142 | while (
143 | count($this->stack) > 0
144 | &&
145 | end($this->stack) != '('
146 | &&
147 | $this->ooo[$operator] <= $this->ooo[end($this->stack)]
148 | ) {
149 | $this->rpn[] = array_pop($this->stack);
150 | }
151 | $this->stack[] = $operator;
152 | $this->infix[] = $operator;
153 | }
154 |
155 | protected function clear_stack() {
156 | while (count($this->stack) > 0) {
157 | $this->rpn[] = array_pop($this->stack);
158 | }
159 | }
160 |
161 | /**
162 | * @return mixed|string
163 | * @throws \Exception
164 | */
165 | public function __invoke() {
166 |
167 | $stack = [];
168 |
169 | foreach ($this->rpn as $step) {
170 | if (is_object($step) || !isset($this->ooo[$step])) {
171 | $stack[] = $step;
172 | } else {
173 | $r1 = array_pop($stack);
174 | $r2 = array_pop($stack);
175 |
176 | if (is_numeric($r1) && is_numeric($r2)) {
177 | $stack[] = CalcOperation::calc($step, $r2, $r1);
178 | }
179 | if ($r1 instanceof CalcSet && is_numeric($r2)) {
180 | $stack[] = $r1->calc($step, $r2);
181 | }
182 | if (is_numeric($r1) && $r2 instanceof CalcSet) {
183 | $stack[] = $r2->rcalc($step, $r1);
184 | }
185 | if ($r1 instanceof CalcSet && $r2 instanceof CalcSet) {
186 | $stack[] = $r1->mcalc($step, $r2);
187 | }
188 | }
189 | }
190 |
191 | if (count($stack) > 1) {
192 | throw new \Exception('Missing operator near "' . $stack[1] . '".');
193 | } else {
194 | $out = reset($stack);
195 |
196 | return $out;
197 | }
198 | }
199 |
200 | public function infix() {
201 | return implode(' ', $this->infix);
202 | }
203 |
204 | }
205 |
--------------------------------------------------------------------------------
/src/DiceCalc/CalcDice.php:
--------------------------------------------------------------------------------
1 |
10 | * @license MIT http://opensource.org/licenses/MIT
11 | */
12 | class CalcDice extends CalcSet
13 | {
14 | protected $saved_values = [];
15 |
16 | public function __construct($set_value)
17 | {
18 | $this->values = [];
19 | $this->label = $set_value;
20 | preg_match('/' . Calc::DICE_REGEX . '/ix', $set_value, $matches);
21 | if (intval($matches['multiple']) == 0 && $matches['multiple'] != '0') {
22 | $matches['multiple'] = 1;
23 | }
24 | $matches += ['matches'=>'', 'openroll' => '', 'reroll' => '', 'keep' => ''];
25 | for ($z = 0; $z < $matches['multiple']; $z++) {
26 | $keep = true;
27 |
28 | $newval = $this->rolltype($matches['dietype']);
29 |
30 | if ($matches['reroll'] != '') {
31 | $gtlt = $matches['rerolleval'];
32 | $range = intval($matches['rerolllimit']);
33 | if ($gtlt == '<' && $newval < $range) {
34 | $keep = false;
35 | $z--;
36 | }
37 | if ($gtlt == '>' && $newval > $range) {
38 | $keep = false;
39 | $z--;
40 | }
41 | }
42 |
43 | if ($matches['openroll'] != '') {
44 | $gtlt = $matches['openrolleval'];
45 | $range = intval($matches['openrolllimit']);
46 | $addvals = [$newval];
47 | $addval = $newval;
48 |
49 | $evals = [
50 | '<' => function($addval, $range) { return $addval < $range; },
51 | '>' => function($addval, $range) { return $addval > $range; },
52 | '=' => function($addval, $range) { return $addval == $range; },
53 | ];
54 |
55 | while(isset($evals[$gtlt]) && $evals[$gtlt]($addval, $range)) {
56 | $addval = $this->rolltype($matches['dietype']);
57 | $addvals[] = $addval;
58 | }
59 |
60 | $newval = new Calc(implode('+', $addvals));
61 | $newval = $newval();
62 | }
63 |
64 | if ($keep) {
65 | $this->values['_' . count($this->values)] = $newval;
66 | }
67 | }
68 |
69 | $this->saved_values = $this->values;
70 |
71 | if ($matches['keep'] != '') {
72 | $gtlt = $matches['keepeval'];
73 | $range = intval($matches['keeprange']);
74 | foreach ($this->values as $k => $set_value) {
75 | $av = $set_value instanceof Calc ? $set_value() : $set_value;
76 | if ($gtlt == '>' && $av <= $range) {
77 | unset($this->values[$k]);
78 | }
79 | if ($gtlt == '<' && $av >= $range) {
80 | unset($this->values[$k]);
81 | }
82 | }
83 | }
84 |
85 | asort($this->values);
86 | if (isset($matches['highdice']) && $matches['highdice'] != '') {
87 | $this->values = array_slice($this->values, -intval($matches['highdice']), null, true);
88 | }
89 | if (isset($matches['lowdice']) && $matches['lowdice'] != '') {
90 | $this->values = array_slice($this->values, 0, intval($matches['lowdice']), true);
91 | }
92 | }
93 |
94 | public function rolltype($dietype)
95 | {
96 | if (is_numeric($dietype)) {
97 | $newval = Random::get(1, $dietype);
98 | } elseif ($dietype == 'f') {
99 | $newval = Random::get(-1, 1);
100 | } elseif ($dietype == '%') {
101 | $newval = Random::get(1, 100);
102 | } elseif ($dietype[0] == '[') {
103 | $dietype = trim($dietype, '[]');
104 | $opts = explode(',', $dietype);
105 | $newval = $opts[Random::get(0, count($opts)-1)];
106 | } else {
107 | var_dump($dietype);
108 | $newval = 'unknown';
109 | }
110 |
111 | return $newval;
112 | }
113 |
114 | public function __toString()
115 | {
116 | $out = [];
117 | foreach (array_keys($this->saved_values) as $key) {
118 | $vout = $this->saved_values[$key];
119 | if ($vout === true) {
120 | $vout = 'true';
121 | }
122 | if ($vout === false) {
123 | $vout = 'false';
124 | }
125 | if (isset($this->values[$key])) {
126 | $out[] = $vout;
127 | } else {
128 | if ($vout instanceof Calc) {
129 | $out[] = '' . $vout() . '';
130 | } else {
131 | $out[] = '' . $vout . '';
132 | }
133 | }
134 | }
135 |
136 | $result = '[' . $this->label . ':';
137 | $comma = '';
138 | foreach ($out as $o) {
139 | if ($o instanceof Calc) {
140 | $result .= $comma . $o();
141 | } else {
142 | $result .= $comma . $o;
143 | }
144 | $comma = ' + ';
145 | }
146 | $result .= ']';
147 |
148 | return $result;
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/src/DiceCalc/CalcOperation.php:
--------------------------------------------------------------------------------
1 |
10 | * @license MIT http://opensource.org/licenses/MIT
11 | */
12 | class CalcOperation
13 | {
14 |
15 | static protected $operators = [
16 | '+' => 'add',
17 | '*' => 'multiply',
18 | '=' => 'equalto',
19 | '<' => 'lessthan',
20 | '>' => 'greaterthan',
21 | '^' => 'exponent',
22 | '/' => 'divide',
23 | '-' => 'subtract',
24 | ];
25 |
26 | /**
27 | * @param string $operator
28 | * @param $operand2
29 | * @param $operand1
30 | *
31 | * @throws \Exception
32 | * @return bool|number
33 | */
34 | public static function calc($operator, $operand1, $operand2)
35 | {
36 | if(isset(static::$operators[$operator])) {
37 | return call_user_func(array('self', static::$operators[$operator]), self::reduce($operand1), self::reduce($operand2));
38 | }
39 | throw new \Exception('Unknown operator "' . $operator . '".');
40 | }
41 |
42 | /**
43 | * @param $operand
44 | *
45 | * @return number|string
46 | * @throws \Exception
47 | */
48 | public static function reduce($operand)
49 | {
50 | if(is_numeric($operand)) {
51 | return $operand;
52 | }
53 | elseif ($operand instanceof Calc) {
54 | return $operand();
55 | }
56 | throw new \Exception('This is not a number');
57 | }
58 |
59 | /**
60 | * @param number $operand1
61 | * @param number $operand2
62 | *
63 | * @return bool|number
64 | */
65 | protected static function add($operand1, $operand2)
66 | {
67 | return $operand1 + $operand2;
68 | }
69 |
70 | /**
71 | * @param number $operand1
72 | * @param number $operand2
73 | *
74 | * @return bool|number
75 | */
76 | protected static function multiply($operand1, $operand2)
77 | {
78 | return $operand1 * $operand2;
79 | }
80 |
81 | /**
82 | * @param number $operand1
83 | * @param number $operand2
84 | *
85 | * @return bool|number
86 | */
87 | protected static function subtract($operand1, $operand2)
88 | {
89 | return $operand1 - $operand2;
90 | }
91 |
92 | /**
93 | * @param number $operand1
94 | * @param number $operand2
95 | *
96 | * @return bool|number
97 | */
98 | protected static function divide($operand1, $operand2)
99 | {
100 | return $operand1 / $operand2;
101 | }
102 |
103 | /**
104 | * @param number $operand1
105 | * @param number $operand2
106 | *
107 | * @return bool|number
108 | */
109 | protected static function exponent($operand1, $operand2)
110 | {
111 | return pow($operand1, $operand2);
112 | }
113 |
114 | /**
115 | * @param number $operand1
116 | * @param number $operand2
117 | *
118 | * @return bool
119 | */
120 | protected static function greaterthan($operand1, $operand2)
121 | {
122 | return $operand1 > $operand2;
123 | }
124 |
125 | /**
126 | * @param number $operand1
127 | * @param number $operand2
128 | *
129 | * @return bool
130 | */
131 | protected static function lessthan($operand1, $operand2)
132 | {
133 | return ($operand1 < $operand2);
134 | }
135 |
136 | /**
137 | * @param number $operand1
138 | * @param number $operand2
139 | *
140 | * @return bool
141 | */
142 | protected static function equalto($operand1, $operand2)
143 | {
144 | return ($operand1 == $operand2);
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/src/DiceCalc/CalcSet.php:
--------------------------------------------------------------------------------
1 |
10 | * @license MIT http://opensource.org/licenses/MIT
11 | */
12 | class CalcSet {
13 | protected $values = [];
14 | protected $label;
15 |
16 | public function __construct($set_value) {
17 | if (is_array($set_value)) {
18 | $this->values = $set_value;
19 | $this->saved_values = $this->values;
20 | $this->label = '[' . implode(', ', $set_value) . ']';
21 | } else {
22 | $this->label = $set_value;
23 | preg_match('%^(?P\d*)\[(?P[^\]]+)\]$%i', $set_value, $matches);
24 | $set_value = $matches['set'];
25 | $values = explode(',', $set_value);
26 | for ($z = 0; $z < max(1, intval($matches['multiple'])); $z ++) {
27 | foreach ($values as $set_value) {
28 | $this->values[] = $set_value;
29 | }
30 | }
31 | $this->saved_values = $this->values;
32 | foreach ($this->values as $k => $set_value) {
33 | $calc = new Calc($set_value);
34 | $this->values[$k] = $calc();
35 | }
36 | foreach ($this->values as $k => $value) {
37 | $calc = new Calc($value);
38 | $this->values[$k] = $calc();
39 | }
40 | }
41 | }
42 |
43 | public function __toString() {
44 | $out = [];
45 | foreach (array_keys($this->saved_values) as $key) {
46 | $vout = $this->values[$key];
47 | if (!isset($this->values[$key])) {
48 | $vout = $this->saved_values[$key];
49 | }
50 | if ($vout === true) {
51 | $vout = 'true';
52 | }
53 | if ($vout === false) {
54 | $vout = 'false';
55 | }
56 | if (isset($this->values[$key])) {
57 | $out[] = $vout;
58 | } else {
59 | $out[] = '' . $vout . '';
60 | }
61 | }
62 | $out = '[' . implode(', ', $out) . ']';
63 |
64 | return $out;
65 | }
66 |
67 | public function calc($operator, $operand) {
68 | $out = [];
69 | foreach ($this->values as $value) {
70 | $out[] = CalcOperation::calc($operator, $operand, $value);
71 | }
72 |
73 | return new CalcSet($out);
74 | }
75 |
76 | public function rcalc($operator, $operand) {
77 | $out = [];
78 | foreach ($this->values as $value) {
79 | $out[] = CalcOperation::calc($operator, $value, $operand);
80 | }
81 |
82 | return new CalcSet($out);
83 | }
84 |
85 | public function mcalc($operator, $operand)
86 | {
87 | $out = [];
88 | foreach ($this->values as $value) {
89 | $out[] = $operand->rcalc($operator, $value);
90 | }
91 |
92 | return new CalcSet($out);
93 | }
94 |
95 | public function value()
96 | {
97 | $allnumeric = true;
98 | foreach ($this->values as $v) {
99 | if (is_numeric($v)) {
100 | } else {
101 | $allnumeric = false;
102 | }
103 | }
104 |
105 | if ($allnumeric) {
106 | return array_sum($this->values);
107 | } else {
108 | return $this;
109 | }
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/src/DiceCalc/Random.php:
--------------------------------------------------------------------------------
1 |
10 | * @license MIT http://opensource.org/licenses/MIT
11 | */
12 | class Random {
13 | public static $queue = null;
14 | public static $queue_list = [];
15 |
16 | public static function get($min, $max) {
17 | if(is_callable(self::$queue)) {
18 | $test_fn = self::$queue;
19 | $result = $test_fn($min, $max);
20 | }
21 | else {
22 | $result = mt_rand($min, $max);
23 | }
24 | return $result;
25 | }
26 |
27 | public static function set_queue($queue) {
28 | self::$queue = $queue;
29 | }
30 |
31 | public static function clear_queue() {
32 | self::$queue = null;
33 | }
34 |
35 | public static function queue_up() {
36 | $numbers = func_get_args();
37 | self::$queue_list = self::$queue_list + $numbers;
38 | self::set_queue(function($min, $max){
39 | return array_shift(self::$queue_list);
40 | });
41 | }
42 |
43 | public static function queue_min() {
44 | self::set_queue(function($min, $max) {
45 | return $min;
46 | });
47 | }
48 |
49 | public static function queue_max() {
50 | self::set_queue(function($min, $max) {
51 | return $max;
52 | });
53 | }
54 | }
--------------------------------------------------------------------------------
/tests/DiceCalc/Tests/Calc/BasicRollTest.php:
--------------------------------------------------------------------------------
1 | assertTrue(
20 | is_numeric($result),
21 | sprintf('Calc::calc(%s) result is not numeric: %s', $expression, $result)
22 | );
23 |
24 |
25 | Random::queue_min();
26 | $calc = new Calc($expression);
27 | $result = $calc();
28 | $this->assertTrue(
29 | ($result == $left),
30 | sprintf('Calc::calc(%s) result %s is not not bigger or equal %d', $expression, $result, $left)
31 | );
32 |
33 | Random::queue_max();
34 | $calc = new Calc($expression);
35 | $result = $calc();
36 | $this->assertTrue(
37 | ($result == $right),
38 | sprintf('Calc::calc(%s) result %s is not not less or equal %d', $expression, $result, $right)
39 | );
40 |
41 | Random::clear_queue();
42 | }
43 |
44 | public function basicRollProvider()
45 | {
46 | return [
47 | ['d6', 1, 6],
48 | ['1d6', 1, 6],
49 | ['2d6', 2, 12],
50 | ['3d6', 3, 18],
51 | ['100d6', 100, 600],
52 |
53 | ['d20', 1, 20],
54 | ['1d20', 1, 20],
55 | ['2d20', 2, 40],
56 |
57 | ['d%', 1, 100],
58 | ['1d%', 1, 100],
59 |
60 | ['df', -1, 1],
61 | ['1df', -1, 1],
62 | ['2df', -2, 2],
63 | ['6df', -6, 6],
64 | ['100df', -100, 100],
65 | ];
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/tests/DiceCalc/Tests/Calc/BasicRollWithMathTest.php:
--------------------------------------------------------------------------------
1 | assertTrue(
21 | is_numeric($result),
22 | sprintf('Calc::calc(%s) result is not numeric: %s', $expression, $result)
23 | );
24 | $this->assertTrue(
25 | ($result >= $left),
26 | sprintf('Calc::calc(%s) result %s is not not bigger or equal %d', $expression, $result, $left)
27 | );
28 | $this->assertTrue(
29 | ($result <= $right),
30 | sprintf('Calc::calc(%s) result %s is not not less or equal %d', $expression, $result, $right)
31 | );
32 | }
33 | }
34 |
35 | public function diceRollProvider()
36 | {
37 | return [
38 | ['d6+1', 2, 7],
39 | ['1d6+1', 2, 7],
40 | ['2d6+1', 3, 13],
41 | ['3d6+3', 6, 21],
42 | ['100d6+10', 110, 610],
43 |
44 | ['d20+5', 6, 26],
45 | ['1d20+3', 4, 23],
46 | ['2d20+4', 6, 44],
47 |
48 | ['d%+3', 4, 104],
49 | ['1d%+55', 56, 155],
50 |
51 | ['df+3', 2, 4],
52 | ['1df+5', 4, 6],
53 | ['2df+8', -6, 10],
54 | ['6df+1', -5, 7],
55 | ['100df+3', -97, 103],
56 |
57 | ['d6+d3', 2, 9],
58 | ['1d6+1d3', 2, 9],
59 | ['2d6+1d3', 3, 15],
60 | ['1d6+2d3', 3, 12],
61 | ['10d6+5d3', 15, 85],
62 | ];
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/tests/DiceCalc/Tests/Calc/ConditionNumberTest.php:
--------------------------------------------------------------------------------
1 | assertEquals($expected, $result);
19 | }
20 |
21 | public function diceMathConditionProvider()
22 | {
23 | return [
24 | ['1+1 = 2', true],
25 | ['3+8 = 11', true],
26 |
27 | ['1+1 > 1', true],
28 | ['1+1 > 3', false],
29 |
30 | ['1+1 < 1', false],
31 | ['1+1 < 3', true],
32 |
33 | ['4+4 > 7', true],
34 | ['4+4 > 8', false],
35 | ['4+4 > 9', false],
36 | ];
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/tests/DiceCalc/Tests/Calc/CustomDieTest.php:
--------------------------------------------------------------------------------
1 | assertTrue(is_numeric($result));
24 |
25 | $i++;
26 |
27 | $this->assertTrue(
28 | in_array($result, $availableValues),
29 | sprintf('Roll result %s not found in expected array', $result)
30 | );
31 |
32 | if (!array_key_exists($result, $foundElements)) {
33 | $foundElements[$result] = 0;
34 | }
35 |
36 | $foundElements[$result]++;
37 |
38 | if (count($foundElements) == count($availableValues)) {
39 | $allFound = true;
40 | }
41 |
42 | } while ($allFound === false && $i < 1000);
43 |
44 | $this->assertTrue($allFound, sprintf('Not all variants passed for %s', $expression));
45 |
46 | }
47 |
48 | public function customNumericDieProvider()
49 | {
50 | return [
51 | [
52 | 'd[1,2,2,3,3,4]',
53 | [1, 2, 3, 4],
54 | ],
55 | [
56 | 'd[2,4,6]',
57 | [2,4,6],
58 | ],
59 | ];
60 | }
61 |
62 | /**
63 | * @dataProvider customColorDieProvider
64 | */
65 | public function testCustomColorDie($expression, array $availableValues)
66 | {
67 |
68 | $allFound = false;
69 | $i = 0;
70 | $foundElements = [];
71 |
72 | $checkValues = [];
73 |
74 | foreach ($availableValues as $value) {
75 | $checkValues[] = '['.$expression.':'.$value.']';
76 | }
77 |
78 | do {
79 | $calc = new Calc($expression);
80 |
81 | $result = $calc();
82 |
83 | $this->assertInstanceOf('DiceCalc\CalcDice', $result);
84 |
85 | $resultString = (string) $result->value();
86 |
87 | $i++;
88 |
89 | $this->assertTrue(
90 | in_array($resultString, $checkValues),
91 | sprintf('Roll result %s not found in expected array', $result)
92 | );
93 |
94 | if (!array_key_exists($resultString, $foundElements)) {
95 | $foundElements[$resultString] = 0;
96 | }
97 |
98 | $foundElements[$resultString]++;
99 |
100 | if (count($foundElements) == count($availableValues)) {
101 | $allFound = true;
102 | }
103 |
104 | } while ($allFound === false && $i < 1000);
105 |
106 | $this->assertTrue($allFound, sprintf('Not all variants passed for %s', $expression));
107 |
108 | }
109 |
110 | public function customColorDieProvider()
111 | {
112 | return [
113 | [
114 | 'd[red,green,blue,yellow]',
115 | ['red', 'green', 'blue', 'yellow'],
116 | ],
117 | [
118 | 'd[red,green,blue]',
119 | ['red', 'green', 'blue'],
120 | ],
121 | ];
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/tests/DiceCalc/Tests/Calc/KeepPostfixTest.php:
--------------------------------------------------------------------------------
1 | assertTrue(
29 | is_numeric($result),
30 | sprintf('Calc::calc(%s) result is not numeric: %s', $expression, $result)
31 | );
32 |
33 | if ($result > 0) {
34 | $this->assertTrue(
35 | ($result >= $minResult),
36 | sprintf('Calc::calc(%s) result %s is not not bigger or equal %d', $expression, $result, $minResult)
37 | );
38 | $this->assertTrue(
39 | ($result <= $maxResult),
40 | sprintf('Calc::calc(%s) result %s is not not less or equal %d', $expression, $result, $maxResult)
41 | );
42 | }
43 | }
44 | }
45 |
46 | public function dataForKeepPostfixGreaterThan()
47 | {
48 | return [
49 | ['5d6 keep > 4', 5, 4, 6],
50 | ['5d6k>4', 5, 4, 6]
51 | ];
52 | }
53 |
54 | /**
55 | * @dataProvider dataForKeepPostfixLessThan
56 | *
57 | * @param $expression
58 | * @param $rollsNum
59 | * @param $minExpected
60 | * @param $maxExpected
61 | */
62 | public function testKeepPostfixLessThan($expression, $rollsNum, $minExpected, $maxExpected)
63 | {
64 | $minResult = $minExpected;
65 | $maxResult = $rollsNum * $maxExpected;
66 |
67 | for ($i = 0; $i < 100; $i++) {
68 |
69 | $calc = new Calc($expression);
70 |
71 | $result = $calc();
72 |
73 | $this->assertTrue(
74 | is_numeric($result),
75 | sprintf('Calc::calc(%s) result is not numeric: %s', $expression, $result)
76 | );
77 |
78 | if ($result > 0) {
79 | $this->assertTrue(
80 | ($result >= $minResult),
81 | sprintf('Calc::calc(%s) result %s is not not bigger or equal %d', $expression, $result, $minResult)
82 | );
83 | $this->assertTrue(
84 | ($result <= $maxResult),
85 | sprintf('Calc::calc(%s) result %s is not not less or equal %d', $expression, $result, $maxResult)
86 | );
87 | }
88 | }
89 | }
90 |
91 | public function dataForKeepPostfixLessThan()
92 | {
93 | return [
94 | ['5d6 keep < 4', 5, 1, 3],
95 | ['5d6k<4', 5, 1, 3]
96 | ];
97 | }
98 |
99 | }
100 |
--------------------------------------------------------------------------------
/tests/DiceCalc/Tests/Calc/LowestHighestTest.php:
--------------------------------------------------------------------------------
1 | assertTrue(
25 | is_numeric($result),
26 | sprintf('Calc::calc(%s) result is not numeric: %s', $expression, $result)
27 | );
28 | }
29 | }
30 |
31 | public function dataForLowestPostfix()
32 | {
33 | return [
34 | ['2d20 lowest 2', 2, 2],
35 | ['2d20l2', 2, 2]
36 | ];
37 | }
38 |
39 | /**
40 | * @dataProvider dataForHighestPostfix
41 | *
42 | * @param $expression
43 | * @param $rollsNum
44 | * @param $maxResults
45 | */
46 | public function testHighestPostfix($expression, $rollsNum, $maxResults)
47 | {
48 |
49 | for ($i = 0; $i < 100; $i++) {
50 |
51 | $calc = new Calc($expression);
52 |
53 | $result = $calc();
54 |
55 | $this->assertTrue(
56 | is_numeric($result),
57 | sprintf('Calc::calc(%s) result is not numeric: %s', $expression, $result)
58 | );
59 | }
60 | }
61 |
62 | public function dataForHighestPostfix()
63 | {
64 | return [
65 | ['4d6 highest 3', 4, 1, 3],
66 | ['4d6h3', 4, 1, 3]
67 | ];
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/tests/DiceCalc/Tests/Calc/MathTest.php:
--------------------------------------------------------------------------------
1 | assertEquals($expected, $result);
18 | }
19 |
20 | public function diceMathProvider()
21 | {
22 | return [
23 | ['0+0', 0],
24 | ['1+1', 2],
25 | ['1+0', 1],
26 | ['0+1', 1],
27 | ['10+1', 11],
28 | ['1*1', 1],
29 | ['1*2', 2],
30 | ['2*1', 2],
31 | ['4*5', 20],
32 | ['4+4*5', 24],
33 | ['(4+4)*5', 40],
34 | ['(100+9)*40', 4360],
35 |
36 | ['1-1', 0],
37 | ['1-10', -9],
38 | ['10-1', 9],
39 | ['4-4*5', -16],
40 | ['(4-4)*5', 0],
41 | ];
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/tests/DiceCalc/Tests/Calc/TargetNumberTest.php:
--------------------------------------------------------------------------------
1 | assertTrue($result, sprintf('We never reached true from %s', $expression));
26 | }
27 |
28 | public function diceRollTargetProvider()
29 | {
30 | return [
31 | ['d6 > 1'],
32 | ['d6 > 3'],
33 | ['d6 > 5'],
34 |
35 | ['d6 < 2'],
36 | ['d6 < 4'],
37 | ['d6 < 6'],
38 |
39 | ['2d6 > 2'],
40 | ['2d6 < 12'],
41 |
42 | ['2d6+5 > 7'],
43 | ['2d6+5 < 17'],
44 |
45 | ['2d6-1 > 1'],
46 | ['2d6-5 < 6'],
47 | ];
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/tests/DiceCalc/Tests/CalcOperation/AddTest.php:
--------------------------------------------------------------------------------
1 | assertSame($expected, $actual);
21 | }
22 |
23 | public function dataForAdd()
24 | {
25 | return [
26 | [5, 2, 7],
27 | ['5', 2, 7],
28 | [5, '2', 7],
29 | ['5', '2', 7],
30 | ];
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/tests/DiceCalc/Tests/CalcOperation/CalcTest.php:
--------------------------------------------------------------------------------
1 | assertEquals($expected, $actual);
22 | }
23 |
24 | /**
25 | * @expectedException \Exception
26 | */
27 | public function testCalcUnknownOperator() {
28 | CalcOperation::calc('?', 1, 2);
29 | }
30 |
31 | public function dataForCalc()
32 | {
33 | return [
34 | ['+', 5, 2, 7],
35 | ['*', 5, 2, 10],
36 | ['-', 5, 2, 3],
37 | ['/', 5, 2, 2.5],
38 | ['^', 5, 2, 25],
39 | ['>', 5, 2, true],
40 | ['<', 5, 2, false],
41 | ['<', 2, 5, true],
42 | ['>', 2, 5, false],
43 | ['=', 2, 2, true],
44 | ['=', 2, 3, false],
45 | ];
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/tests/DiceCalc/Tests/CalcOperation/DevideTest.php:
--------------------------------------------------------------------------------
1 | assertSame($expected, $actual);
21 | }
22 |
23 | public function dataForDivide()
24 | {
25 | return [
26 | [5, 2, 2.5],
27 | ['5', 2, 2.5],
28 | [5, '2', 2.5],
29 | ['5', '2', 2.5],
30 | ];
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/tests/DiceCalc/Tests/CalcOperation/EqualToTest.php:
--------------------------------------------------------------------------------
1 | assertSame($expected, $actual);
21 | }
22 |
23 | public function dataForEqualTo()
24 | {
25 | return [
26 | [1, 1, true],
27 | ['1', 1, true],
28 | [1, '1', true],
29 | ['1', '1', true],
30 | ];
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/tests/DiceCalc/Tests/CalcOperation/ExponentTest.php:
--------------------------------------------------------------------------------
1 | assertSame($expected, $actual);
21 | }
22 |
23 | public function dataForExponent()
24 | {
25 | return [
26 | [2, 8, 256],
27 | ['2', 8, 256],
28 | [2, '8', 256],
29 | ['2', '8', 256],
30 | ];
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/tests/DiceCalc/Tests/CalcOperation/GreaterThanTest.php:
--------------------------------------------------------------------------------
1 | ', $num1, $num2);
19 |
20 | $this->assertSame($expected, $actual);
21 | }
22 |
23 | public function dataForGreaterThan()
24 | {
25 | return [
26 | [2, 1, true],
27 | ['2', 1, true],
28 | [2, '1', true],
29 | ['2', '1', true],
30 | ];
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/tests/DiceCalc/Tests/CalcOperation/LessThanTest.php:
--------------------------------------------------------------------------------
1 | assertSame($expected, $actual);
21 | }
22 |
23 | public function dataForLessThan()
24 | {
25 | return [
26 | [1, 2, true],
27 | ['1', 2, true],
28 | [1, '2', true],
29 | ['1', '2', true],
30 | ];
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/tests/DiceCalc/Tests/CalcOperation/MultiplyTest.php:
--------------------------------------------------------------------------------
1 | assertSame($expected, $actual);
21 | }
22 |
23 | public function dataForMultiply()
24 | {
25 | return [
26 | [5, 2, 10],
27 | ['5', 2, 10],
28 | [5, '2', 10],
29 | ['5', '2', 10],
30 | ];
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/tests/DiceCalc/Tests/CalcOperation/NaNTest.php:
--------------------------------------------------------------------------------
1 | assertEquals(3, $actual);
15 | }
16 |
17 | /**
18 | * @throws \Exception
19 | * @expectedException \Exception
20 | */
21 | public function testReduceOnThrowingException()
22 | {
23 | $param = new \stdClass();
24 |
25 | CalcOperation::reduce($param);
26 | }
27 |
28 | public function testReduceOnCalc()
29 | {
30 | $param = new Calc('1d6');
31 |
32 | $actual = CalcOperation::reduce($param);
33 |
34 | $this->assertTrue(($actual >= 1));
35 | $this->assertTrue(($actual <= 6));
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/tests/DiceCalc/Tests/CalcOperation/SubtractTest.php:
--------------------------------------------------------------------------------
1 | assertSame($expected, $actual);
21 | }
22 |
23 | public function dataForSubtract()
24 | {
25 | return [
26 | [5, 2, 3],
27 | ['5', 2, 3],
28 | [5, '2', 3],
29 | ['5', '2', 3],
30 | ];
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/tests/bootstrap.php:
--------------------------------------------------------------------------------
1 | add('DiceCalc\\Tests\\', __DIR__ . '/../../');
19 | unset($classLoader);
20 |
--------------------------------------------------------------------------------