Skip to content

Commit

Permalink
Merge pull request #133 from L0neGamer/dice-variables
Browse files Browse the repository at this point in the history
Dice variables
  • Loading branch information
finnbar authored Jul 17, 2022
2 parents 29c184b + 47af85e commit 8a4072b
Show file tree
Hide file tree
Showing 9 changed files with 551 additions and 210 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ database*
*.cabal
stack.yaml.lock
.gitattributes
.vscode
26 changes: 22 additions & 4 deletions docs/Roll.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ As well as the arithmetic operators above, dice can be rolled, hence the name of

The basic format for this is `dX` where X is some number, meaning a single die of size X. Multiple dice can be rolled using `YdX`, meaning that Y dice are rolled of size X. Parentheses can be used for both Y and X in this case. If Y is greater than a number determined by the bot owner (150 by default), the roll will not be executed. This is the same number that governs the total amount of RNG calls allowed within a command's execution.

In addition to the above, there is syntax for rolling dice with arbitrary sides - `d{4,7,19,-5}`. This results in a die that is equally likely to result in four, seven, nineteen, or minus five. These numbers could be any expression instead.
In addition to the above, there is syntax for rolling dice with arbitrary sides - `d{4, 7, 19, -5}`. This results in a die that is equally likely to result in four, seven, nineteen, or minus five. These numbers could be any expression instead.

There is support for stacking dice. This means that if you write `2d4d5d6`, it will be parsed and executed as `((2d4)d5)d6`. Operations can be applied to the dice in this stack.

Expand Down Expand Up @@ -60,15 +60,30 @@ With the introduction of this notation, it is worth noting that the normal (with

## Lists

As well as simple expressions, basic list expressions can be formed. You can form a basic list using `{e,f,g}`, where `e`, `f`, and `g` are expressions as seen before. Additionally, by using `N#YdX` syntax, you can roll `N` amount of dice following `YdX`.
As well as simple expressions, basic list expressions can be formed. You can form a basic list using `{e, f, g}`, where `e`, `f`, and `g` are expressions as seen before. Additionally, by using `N#YdX` syntax, you can roll `N` amount of dice following `YdX`.

As an addendum to custom dice, if a list value is bracketed then it can be used in custom dice. For example, `5d(4#4d6)` rolls five dice, whose sides are determined by rolling 4d6 4 times. Do note that laziness still applies here, meaning that the RNG cap can be very quickly reached.

Lists are limited to 50 items long currently (which is configurable).

## Complex Operations

There are two operators that are more complex and have specific organisational requirements, that allow for a great deal of control in the program. With them comes more complex structures for expressions as a whole.

If statements take an expression, and then two either integer values or list values. If the expression is non-zero, the first value is returned. If the expression is zero, the second value is returned. The syntax for it is `if expression then t else f`, where `expression` is an integer value, and `t` and `f` are both either integer values or list values. Only one of `t` or `f` is ever evaluated.

Var statements take a name and either an integer value or a list, and set a variable with that name to that value. If the var statement is lazy (with an exclamation mark before the variable name) the value is recalculated every time the variable is used. A var statement returns the value on the left side. To create and use list variables, they must be prepended with `l_`. The syntax can be something like `var name = value`, `var !name = value`, or `var l_name = value`, or so on. These bound values can then be used in other calculations. Variable names consist only have lower case letters and underscores.

To fully utilise these expression types, statements have been made, which, when constructed together with a value, creates a program. A statement is an integer value or list value followed by a semicolon. Below are a couple example programs (which are multiple statements followed by a single value). One quality of life feature is that a lazy var expression won't be evaluated until the variable is first used.

- `var l_list = (2d6)#3d6; {length(l_list), minimum(l_list), maximum(l_list), sum(l_list)/length(l_list)}`
- Get the length, minimum, maximum, and average value of a random list.
- `var !k = 1d20; var t = k; var !t_iseven = if mod(t, 2) then 0 else 1; if t_iseven then k * t + 20 else t`
- Create a lazy variable `k`. Evaluate it into a variable `t`. Check whether `t` is even, and place in a variable. Depending on whether `t` is even or not, either output another random number times by `t` (and add 20 to distinguish it), or just output `t`.

## Functions

Here are all the functions, what they take, and what they return.
Here are all the functions, what they take, and what they return. They are called with `name(arg1, arg2)`.

### Returns an Integer
- abs (integer) - the absolute value of an integer
Expand All @@ -91,12 +106,15 @@ Here are all the functions, what they take, and what they return.
- take (integer, list) - take the first `n` values from a list, where `n` is the integer given
- between (integer, integer) - generate a list between the two given integers (inclusive)
- concat (list, list) - concatenate two lists together
- replicate (integer, integer) - create a list of length the first integer, consisting of elements of only the second element
- set (integer, integer, list) - set the item at the index of the first integer to the value of the second integer in the given list
- insert (integer, integer, list) - insert the item at the index of the first integer to the value of the second integer in the given list

# Statistics

As well as generating values, statistics based off of expressions can be found. There is a total time limit of 10 seconds for this command, with 5 seconds given to calculations and 5 seconds given to generating the bar chart.

To get these statistics, calling the `roll` command with the `stats` subcommand will generate the requested statistics. The expression given has to return an integer.
To get these statistics, calling the `roll` command with the `stats` subcommand will generate the requested statistics. The expression given has to return an integer. Stats can only be generated on single expressions and not programs.

The bot will give the mean, the standard deviation, and the top ten most common values of the distribution, as well as graphing the entire distribution.

Expand Down
48 changes: 36 additions & 12 deletions src/Tablebot/Plugins/Roll/Dice.hs
Original file line number Diff line number Diff line change
Expand Up @@ -9,39 +9,63 @@
-- This plugin contains the neccessary parsers and stucture to get the AST for an
-- expression that contains dice, as well as evaluate that expression.
--
-- The behind the scenes for the dice is split into four files.
-- The behind the scenes for the dice is split into six files, two of which
-- are for generating dice statistics.
-- - DiceData - the data structures for the AST for dice
-- - DiceFunctions - functionality for dealing with functions and processing
-- them
-- - DiceParsing - parsers for getting all the DiceData items
-- - DiceEval - methods for evaluating elements from DiceData
-- - DiceStats - filling the type classes and function needed to generate
-- statistics on dice
-- - DiceStatsBase - functions to process dice value distributions
--
-- Below is the regex representing the parsing for the expressions, and
-- explanations for each component
-- explanations for each component. It's not 100% accurate to the actual data
-- representation, but it's close enough that you can start reading `DiceData`,
-- which is the canonical representation of the AST, and then DiceParsing.
--
-- If there is a gap between terms, any number of spaces (including none) is valid, barring in lstv, dice, die, dopr, ords; spaces are added manually in those.
-- If there is a gap between terms, any number of spaces (including none) is
-- valid, barring in lstv, dice, die, dopr, ords, funcBasics, misc; spaces are
-- added manually in those.
--
-- lstv - nbse "#" base | funcBasics | lstb
-- TODO: it's usually safer to put these kinds of grammars in Backus-Naur form
-- rather than regex due to the sheer number of regex standards and possible
-- overloading of ?.
--
-- prog - stat* (lstv | expr)
-- stat - (lstv | expr) ";"
-- misc - ifst | vars
-- ifst - "if" spc1 expr spc1 "then" spc1 (lstv | expr) spc1 "else" spc1 (lstv | expr)
-- vars - "var" spc1 "!"? ("l_" name spcs "=" spcs lstv | name spcs "=" spcs expr)
-- lstv - nbse "#" base | funcBasics | lstb | name | misc
-- lstb - "{" expr ("," expr)* "}" | "(" lstv ")"
-- expr - term ([+-] expr)?
-- expr - term ([+-] expr)? | misc
-- term - nega ([*/] term)?
-- nega - "-" expo | expo
-- expo - func "^" expo | func
-- func - funcBasics | base
-- base - dice | nbse
-- base - dice | nbse | name
-- nbse - "(" expr ")" | [0-9]+
-- dice - base die dopr?
-- die - "d" "!"? (bse | lstb)
-- die - "d" "!"? (base | lstb)
-- dopr - dopo+
-- dopo - "!"? (("rr" | "ro") ords | ("k"|"d") (("l" | "h") nbse | "w" ords))
-- ords - ("/=" | "<=" | ">=" | "<" | "=" | ">") nbase
-- spcs - " "*
-- spc1 - " "+
-- argv - lstv | expr
-- funcBasics - {some string identifier} "(" (argv ("," argv)*)? ")"
-- funcBasics - {some string identifier} "(" spcs (argv (spcs "," spcs argv)*)? spcs ")"
-- name - [a-z_]*
--
-- lstv (ListValues) - representing all possible list values (basic list values, functions that return lists, and values which are lists of length N that consist of `Base`s)
-- prog (Program) - representing a complete program - a series of statements and a value to output at the end.
-- stat (Statement) - representing a single statement - an expression or list value
-- misc (MiscData) - either an if or a var
-- ifst (If) - representing one of two values depending on the outcome of an expression
-- vars (Var) - setting a variable to a certain value
-- lstv (ListValues) - representing all possible list values (basic list values, functions that return lists, and values which are lists of length N that consist of `Base`s, as well as a MiscData value)
-- lstb (ListValuesBase) - representing some basic list values (those that can be used in dice expressions, such as manually created lists and bracketed `ListValues`)
-- expr (Expr) - representing addition, subtraction, or a single `Term` value
-- expr (Expr) - representing addition, subtraction, or a single `Term` value, or a MiscData value
-- term (Term) - representing multiplication, division, or a single `Negation` value
-- nega (Negation) - representing a negation, or a single `Expo` value
-- expo (Expo) - representing exponentiation or a single `Func` value
Expand All @@ -55,7 +79,7 @@
-- ords (AdvancedOrdering and NumBase) - representing a more complex ordering operation than a basic `Ordering`, when compared to a `NumBase`
-- argv (ArgValue) - representing an argument to a function
-- funcBasics - a generic regex representation for a general function parser
module Tablebot.Plugins.Roll.Dice (evalInteger, evalList, ListValues (..), defaultRoll, PrettyShow (prettyShow), integerFunctionsList, listFunctionsList, Converter (promote)) where
module Tablebot.Plugins.Roll.Dice (evalProgram, evalInteger, evalList, ListValues (..), defaultRoll, PrettyShow (prettyShow), integerFunctionsList, listFunctionsList, maximumListLength, maximumRNG, Converter (promote)) where

import Tablebot.Plugins.Roll.Dice.DiceData
( Converter (promote),
Expand All @@ -64,7 +88,7 @@ import Tablebot.Plugins.Roll.Dice.DiceData
ListValues (..),
NumBase (Value),
)
import Tablebot.Plugins.Roll.Dice.DiceEval (PrettyShow (prettyShow), evalInteger, evalList)
import Tablebot.Plugins.Roll.Dice.DiceEval (PrettyShow (prettyShow), evalInteger, evalList, evalProgram, maximumListLength, maximumRNG)
import Tablebot.Plugins.Roll.Dice.DiceFunctions (integerFunctionsList, listFunctionsList)
import Tablebot.Plugins.Roll.Dice.DiceParsing ()

Expand Down
43 changes: 40 additions & 3 deletions src/Tablebot/Plugins/Roll/Dice/DiceData.hs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,37 @@ import Data.Text (Text)
import Data.Tuple (swap)
import Tablebot.Plugins.Roll.Dice.DiceFunctions (FuncInfo, FuncInfoBase)

-- | Set the variable `varName` to the value `varValue`. This also returns the
-- evaluated `varValue`.
--
-- List variables have to be prefixed with `l_`. This really helps with parsing.
data Var a = Var {varName :: Text, varValue :: a} | VarLazy {varName :: Text, varValue :: a} deriving (Show)

-- | If the first value is truthy (non-zero or a non-empty list) then return
-- the `thenValue`, else return the `elseValue`.
data If b = If {ifCond :: Expr, thenValue :: b, elseValue :: b} deriving (Show)

-- | Either an If or a Var that returns a `b`.
data MiscData b = MiscIf (If b) | MiscVar (Var b) deriving (Show)

-- | An expression is just an Expr or a ListValues with a semicolon on the end.
--
-- When evaluating, VarLazy expressions are handled with a special case - they
-- are not evaluated until the value is first referenced. Otherwise, the value
-- is evaluated as the statement is encountered
data Statement = StatementExpr Expr | StatementListValues ListValues deriving (Show)

-- | A program is a series of `Statement`s followed by either a `ListValues` or
-- an Expr.
data Program = Program [Statement] (Either ListValues Expr) deriving (Show)

-- | The value of an argument given to a function.
data ArgValue = AVExpr Expr | AVListValues ListValues
deriving (Show)

-- | Alias for `MiscData` that returns a `ListValues`.
type ListValuesMisc = MiscData ListValues

-- | The type for list values.
data ListValues
= -- | Represents `N#B`, where N is a NumBase (numbers, parentheses) and B is a Base (numbase or dice value)
Expand All @@ -29,6 +56,10 @@ data ListValues
LVFunc (FuncInfoBase [Integer]) [ArgValue]
| -- | A base ListValues value - parentheses or a list of expressions
LVBase ListValuesBase
| -- | A variable that has been defined elsewhere.
LVVar Text
| -- | A misc list values expression.
ListValuesMisc ListValuesMisc
deriving (Show)

-- | The type for basic list values (that can be used as is for custom dice).
Expand All @@ -40,9 +71,12 @@ data ListValues
data ListValuesBase = LVBParen (Paren ListValues) | LVBList [Expr]
deriving (Show)

-- | Alias for `MiscData` that returns an `Expr`.
type ExprMisc = MiscData Expr

-- | The type of the top level expression. Represents one of addition,
-- subtraction, or a single term.
data Expr = Add Term Expr | Sub Term Expr | NoExpr Term
-- subtraction, or a single term; or some misc expression statement.
data Expr = ExprMisc ExprMisc | Add Term Expr | Sub Term Expr | NoExpr Term
deriving (Show)

-- | The type representing multiplication, division, or a single negated term.
Expand Down Expand Up @@ -70,7 +104,7 @@ newtype Paren a = Paren a
deriving (Show)

-- | The type representing a numeric base value value or a dice value.
data Base = NBase NumBase | DiceBase Dice
data Base = NBase NumBase | DiceBase Dice | NumVar Text
deriving (Show)

-- Dice Operations after this point
Expand Down Expand Up @@ -176,3 +210,6 @@ instance Converter Dice Base where

instance Converter Die Base where
promote d = promote $ Dice (promote (1 :: Integer)) d Nothing

instance Converter [Integer] ListValues where
promote = LVBase . LVBList . (promote <$>)
Loading

0 comments on commit 8a4072b

Please sign in to comment.