diff --git a/Plugin/Dice.php b/Plugin/Dice.php index b2d9b30..6428502 100755 --- a/Plugin/Dice.php +++ b/Plugin/Dice.php @@ -1,5 +1,7 @@ 20, - 'dice' => 100, - 'sides' => 100 - ); - - /** - * Holds the allowed characters and operators - * - * @var array - */ - protected $allowed = array - ( - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '+', '-', '/', '*', ' ', '<<', '>>', '%', '&', '^', '|', '~' - ); - - - /** - * Processes a request to perform a calculations on an expression. - * - * @param string $expr Expression to evaluate - * @return void - */ - protected function processExpression($expr) - { - // Clean up the expression - $expr = str_replace('\\', '/', preg_replace('/\s/', '', $expr)); - - // Parse equation - $out = ''; - $ptr = 1; - while (strlen($expr) > 0) { - $substr = substr($expr, 0, $ptr); - // Allowed string - if (array_search($substr, $this->allowed) !== false) { - $out .= $substr; - $expr = substr($expr, $ptr); - $ptr = 0; - // Parse error if we've consumed the entire equation without finding anything valid - } elseif ($ptr >= strlen($expr)) { - return null; - } else { - $ptr++; - } - } - $res = @eval('return ' . $out . ';'); - if ($res === false) { - return null; - } else { - return $res; - } - } - - /** - * Processes a request to perform dice rolls from a given message as well as - * prcessessing an optional expression that gets added to the final dice - * tota; - * Dice Syntax: [[#| ]]d[[+|-]] - * - * @param string $message Message to processes - * @return void - */ - protected function processDice($message, $recursive = false) - { - $message = trim($message); - if (!empty($message)) { - if (preg_match('{^(?:([0-9]+)[\#|:|\s])?(?:([0-9]+)[\s|d])?(?:[\s|d]?([0-9]+))(?:([+\*-])([^\s]*))?(.*)?$}ix', $message, $m)) { - $numDice = ($m[1] < 1 ? 1 : ($m[1] > $this->max['total'] ? $this->max['total'] : $m[1])); - $dice = ($m[2] < 1 ? 1 : ($m[2] > $this->max['dice'] ? $this->max['dice'] : $m[2])); - $sides = ($m[3] < 1 ? 1 : ($m[4] > $this->max['sides'] ? $this->max['sides'] : $m[3])); - $operator = trim($m[4]); - $expression = ((isset($m[5]) && !empty($m[5])) ? trim($m[5]) : '0'); - $description = rtrim(trim($m[6]), '+-/*<>%&^|~'); - $diceMessage = ($numDice > 1 ? $numDice . '#' : '') . $dice . 'd' . $sides . (!empty($operator) && !empty($expression) ? $operator . $expression : ''); - - $bonus = 0; - if (!empty($expression) && $this->allowExpressions) { - $expression = preg_replace('/((?:[0-9]+)?d[0-9]+(?:[+\*-][^\s]+)?)/e', '$this->processDice("\\1", true)', $expression); - $bonus = $this->processExpression($expression); - if (is_null($bonus)) { - return ($recursive ? null : 'Error while processing the dice expression.'); - } - } - - $output = array(); - for ($i = 0; $i < $numDice; $i++) { - $total = 0; - for ($d = 0; $d < $dice; $d++) { - $total += mt_rand(1, $sides); - switch ($operator) { - case '+': $total += $bonus; break; - case '-': $total -= $bonus; break; - case '*': $total *= $bonus; break; - } - } - $output[] = $total; - } - return ($recursive ? implode('+', $output) : 'Rolls a ' . $diceMessage . (!empty($description) ? ' ' . $description : '') . ' and gets ' . implode(', ', $output) . '.'); - } - } - return false; - } - /** * Forwards the dice/roll commands onto a central handler. * @@ -134,9 +19,8 @@ public function onDoRoll($message) $source = $this->event->getSource(); $target = $this->event->getNick(); - if ($result = $this->processDice($message)) { - $this->doPrivmsg($source, $target . ': ' . $result); - } + $calc = new Calc($message); + $this->doPrivmsg($source, $target . ': ' . $calc->infix() . ' => ' . $calc->calc()); } /** @@ -149,9 +33,8 @@ public function onDoDice($message) $source = $this->event->getSource(); $target = $this->event->getNick(); - if ($result = $this->processDice($message)) { - $this->doPrivmsg($source, $target . ': ' . $result); - } + $calc = new Calc($message); + $this->doPrivmsg($source, $target . ': ' . $calc->infix() . ' => ' . $calc->calc()); } /** @@ -166,8 +49,9 @@ public function onCtcp() $ctcp = strtoupper($this->event->getArgument(1)); list($ctcp, $message) = array_pad(explode(' ', $ctcp, 2), 2, null); - if (($ctcp == 'DICE' || $ctcp == 'ROLL') and $result = $this->processDice($message)) { - $this->doCtcpReply($source, $ctcp, $result); + $calc = new Calc($message); + if (($ctcp == 'DICE' || $ctcp == 'ROLL') and $result = $calc->calc()) { + $this->doCtcpReply($source, $ctcp, $calc->infix() . ' => ' . $result); } } } diff --git a/Plugin/Dice/calc.php b/Plugin/Dice/calc.php new file mode 100644 index 0000000..7f485d2 --- /dev/null +++ b/Plugin/Dice/calc.php @@ -0,0 +1,468 @@ +\d*)d(?P\d+|f|\%|\[[^\]]+\])((?Pk(?:eep)?(?P[<>])(?P\d+))|(?Pl(?:owest)?(?P\d+))|(?Ph(?:ighest)?(?P\d+))|(?Pr(?:eroll)?(?P[<>])(?P\d+))|(?Po(?:pen)?(?P[<>=])(?P\d+))|(?P[z]+))*'); + +class CalcSet +{ + protected $values = array(); + protected $label; + + function __construct($v) + { + if(is_array($v)) { + $this->values = $v; + $this->saved_values = $this->values; + $this->label = '[' . implode(', ', $v) . ']'; + } + else { + $this->label = $v; + preg_match('%^(?P\d*)\[(?P[^\]]+)\]$%i', $v, $matches); + $v = $matches['set']; + $values = explode(',', $v); + for($z = 0; $z < max(1,intval($matches['multiple'])); $z++) { + foreach($values as $v) { + $this->values[] = $v; + } + } + $this->saved_values = $this->values; + foreach($this->values as $k => $v) { + $calc = new Calc($v); + $this->values[$k] = $calc->calc(); + } + foreach($this->values as $k => $value) { + $calc = new Calc($value); + $this->values[$k] = $calc->calc(); + } + } + } + function __toString() + { + $out = array(); + foreach($this->saved_values as $key => $value) { + $vout = $this->values[$key]; + if(!isset($this->values[$key])) { + $vout = $this->saved_values[$key]; + } + if($vout === true) { + $vout = 'true'; + } + if($vout === false) { + $vout = 'false'; + } + if(isset($this->values[$key])) { + $out[] = $vout; + } + else { + $out[] = '' . $vout . ''; + } + } + $out = '[' . implode(', ', $out) . ']'; + return $out; + } + + function calc($operator, $operand) + { + $out = array(); + foreach($this->values as $value) { + $out[] = CalcOperation::calc($operator, $value, $operand); + } + return new CalcSet($out); + } + + function rcalc($operator, $operand) + { + $out = array(); + foreach($this->values as $value) { + $out[] = CalcOperation::calc($operator, $operand, $value); + } + return new CalcSet($out); + } + + function value() + { + $allnumeric = true; + foreach($this->values as $v) { + if(is_numeric($v)) { + } + else { + $allnumeric = false; + } + } + + if($allnumeric) { + return array_sum($this->values); + } + else { + return $this; + } + } +} + +class CalcDice extends CalcSet +{ + function __construct($v) + { + $this->values = array(); + $this->label = $v; + $usevalues = array(); + preg_match('/' . DICE_REGEX . '/i', $v, $matches); + if(intval($matches['multiple']) == 0 && $matches['multiple'] != '0') { + $matches['multiple'] = 1; + } + $matches += array('matches'=>'', 'openroll' => '', 'reroll' => '', 'keep' => ''); + for($z = 0; $z < $matches['multiple']; $z++) { + $keep = true; + + $newval = $this->rolltype($matches['dietype']); + + if($matches['reroll'] != '') { + $gtlt = $matches['rerolleval']; + $range = intval($matches['rerolllimit']); + if($gtlt == '<' && $newval < $range) { + $keep = false; + $z--; + } + if($gtlt == '>' && $newval > $range) { + $keep = false; + $z--; + } + } + + if($matches['openroll'] != '') { + $gtlt = $matches['openrolleval']; + $range = intval($matches['openrolllimit']); + $addvals = array($newval); + $addval = $newval; + + if($gtlt == '<') { + while($addval < $range) { + $addval = $this->rolltype($matches['dietype']); + $addvals[] = $addval; + } + } + if($gtlt == '>') { + while($addval > $range) { + $addval = $this->rolltype($matches['dietype']); + $addvals[] = $addval; + } + } + if($gtlt == '=') { + while($addval == $range) { + $addval = $this->rolltype($matches['dietype']); + $addvals[] = $addval; + } + } + $newval = new Calc(implode('+', $addvals)); + $newval = $newval->calc(); + } + + if($keep) { + $this->values['_' . count($this->values)] = $newval; + } + } + + $this->saved_values = $this->values; + + if($matches['keep'] != '') { + $gtlt = $matches['keepeval']; + $range = intval($matches['keeprange']); + foreach($this->values as $k => $v) { + $av = $v instanceof Calc ? $v->calc() : $v; + if($gtlt == '>' && $av <= $range) { + unset($this->values[$k]); + } + if($gtlt == '<' && $av >= $range) { + unset($this->values[$k]); + } + } + } + + asort($this->values); + if(isset($matches['highdice']) && $matches['highdice'] != '') { + $this->values = array_slice($this->values, -intval($matches['highdice']), null, true); + } + if(isset($matches['lowdice']) && $matches['lowdice'] != '') { + $this->values = array_slice($this->values, 0, intval($matches['lowdice']), true); + } + } + + function rolltype($dietype) { + if(is_numeric($dietype)) { + $newval = rand(1, $dietype); + } + elseif($dietype == 'f') { + $newval = rand(-1, 1); + } + elseif($dietype == '%') { + $newval = rand(1, 100); + } + elseif($dietype[0] == '[') { + $dietype = trim($dietype, '[]'); + $opts = explode(',', $dietype); + $newval = $opts[rand(0, count($opts)-1)]; + } + else { + var_dump($dietype); + } + return $newval; + } + + function __toString() + { + $out = array(); + foreach($this->saved_values as $key => $value) { + $vout = $this->saved_values[$key]; + if($vout === true) { + $vout = 'true'; + } + if($vout === false) { + $vout = 'false'; + } + if(isset($this->values[$key])) { + $out[] = $vout; + } + else { + if($vout instanceof Calc) { + $out[] = '' . $vout->calc() . ''; + } + else { + $out[] = '' . $vout . ''; + } + } + } + $_out = '[' . $this->label . ':'; + $comma = ''; + foreach($out as $o) { + if($o instanceof Calc) { + $_out .= $comma . $o->calc(); + } + else { + $_out .= $comma . $o; + } + $comma = ' + '; + } + $_out .= ']'; + return $_out; + } +} + +class CalcOperation +{ + static function calc($operator, $operand2, $operand1) + { + switch($operator) { + case '+': + return self::add($operand1, $operand2); + case '*': + return self::multiply($operand1, $operand2); + case '-': + return self::subtract($operand1, $operand2); + case '/': + return self::divide($operand1, $operand2); + case '^': + return self::exponent($operand1, $operand2); + case '>': + return self::greaterthan($operand1, $operand2); + case '<': + return self::lessthan($operand1, $operand2); + case '=': + return self::equalto($operand1, $operand2); + } + } + + static function reduce($r) { + if($r instanceof Calc) { + return $r->calc(); + } + if(is_numeric($r)) { + return $r; + } + throw Exception('This is not a number'); + } + + static function add($r1, $r2) + { + try{ + return self::reduce($r1) + self::reduce($r2); + } + catch(Exception $e) { + return $r1 . $r2; + } + } + + static function multiply($r1, $r2) + { + if(is_numeric($r1) && is_numeric($r2)) { + return $r1 * $r2; + } + } + + static function subtract($r1, $r2) + { + if(is_numeric($r1) && is_numeric($r2)) { + return $r1 - $r2; + } + } + + static function divide($r1, $r2) + { + if(is_numeric($r1) && is_numeric($r2)) { + return $r1 / $r2; + } + } + + static function exponent($r1, $r2) + { + if(is_numeric($r1) && is_numeric($r2)) { + return pow($r1, $r2); + } + } + + static function greaterthan($r1, $r2) + { + try{ + return self::reduce($r1) > self::reduce($r2); + } + catch(Exception $e) { + return $r1 > $r2; + } + } + + static function lessthan($r1, $r2) + { + if(is_numeric($r1) && is_numeric($r2)) { + return ($r1 < $r2); + } + } + + static function equalto($r1, $r2) + { + if(is_numeric($r1) && is_numeric($r2)) { + return ($r1 == $r2); + } + } +} + +class Calc +{ + private $ooo = array( + '>' => 0, + '<' => 0, + '=' => 0, + '-' => 10, + '+' => 10, + '*' => 20, + '/' => 20, + '^' => 30, + ); + + protected $expression; + protected $rpn = array(); + protected $infix = array(); + + function __construct($expression = '') + { + $this->expression = str_replace(' ', '', $expression); + + preg_match_all('%(?:(?P' . DICE_REGEX . ')|(?P\d*\[[^\]]+\])|(?P[\d\.]+)|(?P[+\-*^><=/])|(?P\$[a-z_]+)|(?P[()]))%i', $this->expression, $matches, PREG_SET_ORDER); + + $stack = array(); + + foreach($matches as $match) { + $match = array_filter($match); + + if(isset($match['numeral'])) { + $this->rpn[] = $match['numeral']; + $this->infix[] = $match['numeral']; + } + elseif(isset($match['dice'])) { + $dice = new CalcDice($match['dice']); + $this->rpn[] = $dice->value(); + $this->infix[] = $dice; + } + elseif(isset($match['set'])) { + $this->rpn[] = new CalcSet($match['set']); + $this->infix[] = end($this->rpn); + } + elseif(isset($match['operator'])) { + while(count($stack) > 0 && end($stack) != '(' && $this->ooo[$match['operator']] <= $this->ooo[end($stack)]) { + $this->rpn[] = array_pop($stack); + } + $stack[] = $match['operator']; + $this->infix[] = $match['operator']; + } + elseif(isset($match['variable'])) { + $this->rpn[] = $match['variable']; + $this->infix[] = end($this->rpn); + } + elseif(isset($match['parens'])) { + $this->infix[] = $match['parens']; + if($match['parens'] == '(') { + $stack[] = $match['parens']; + } + else { + while(count($stack) > 0 && end($stack) != '(') { + $this->rpn[] = array_pop($stack); + } + array_pop($stack); + } + } + else { + $stack = array('Invalid token:', $match); + break; + } + } + + while(count($stack) > 0) { + $this->rpn[] = array_pop($stack); + } + } + + function calc($vars = array() ) + { + + $stack = array(); + + foreach($this->rpn as $step) { + if(is_object($step) || !isset($this->ooo[$step])) { + $stack[] = $step; + } + else { + //echo "Operation: {$step}\n"; + //print_r($stack); + $r1 = array_pop($stack); + $r2 = array_pop($stack); + + if(is_numeric($r1) && is_numeric($r2)) { + $stack[] = CalcOperation::calc($step, $r1, $r2); + } + if($r1 instanceof CalcSet && is_numeric($r2)) { + $stack[] = $r1->calc($step, $r2); + } + if(is_numeric($r1) && $r2 instanceof CalcSet) { + $stack[] = $r2->rcalc($step, $r1); + } + } + } + + if(count($stack) > 1) { + return 'Missing operator near "' . $stack[1] . '".'; + } + else { + $out = reset($stack); + if(is_bool($out)) { + return $out ? 'true' : 'false'; + } + else { + return $out; + } + } + } + + function infix() + { + return implode(' ', $this->infix); + } +} + +?>