Skip to content

Commit

Permalink
Initial commit.
Browse files Browse the repository at this point in the history
  • Loading branch information
christhekeele committed Mar 1, 2023
1 parent 3416d7e commit b86857e
Show file tree
Hide file tree
Showing 24 changed files with 686 additions and 2 deletions.
18 changes: 18 additions & 0 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
FROM elixir:1.14.3-slim

ARG USERNAME=vscode

RUN apt-get update && \
apt-get install -y postgresql-client && \
apt-get install -y inotify-tools && \
apt-get install -y vim && \
apt-get install -y git && \
apt-get install -y curl && \
apt-get install -y wget && \
apt-get install -y gnupg2

ENV MIX_HOME=/root/.mix
ENV HEX_HOME=/root/.hex

RUN mix local.hex --force && \
mix local.rebar --force
19 changes: 19 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/ubuntu
{
"name": "Ubuntu",
"build": {
// Path is relataive to the devcontainer.json file.
"dockerfile": "Dockerfile"
}
// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "uname -a",
// Configure tool-specific properties.
// "customizations": {},
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}
4 changes: 4 additions & 0 deletions .formatter.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Used by "mix format"
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]
26 changes: 26 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# The directory Mix will write compiled artifacts to.
/_build/

# If you run "mix test --cover", coverage assets end up here.
/cover/

# The directory Mix downloads your dependencies sources to.
/deps/

# Where third-party dependencies like ExDoc output generated docs.
/doc/

# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch

# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump

# Also ignore archive artifacts (built via "mix archive.build").
*.ez

# Ignore package tarball (built via "mix hex.build").
dice-*.tar

# Temporary files, for example, from tests.
/tmp/
1 change: 1 addition & 0 deletions .iex.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import Dice
22 changes: 20 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
# roll.online
# Dice

**TODO: Add description**

## Installation

If [available in Hex](https://hex.pm/docs/publish), the package can be installed
by adding `dice` to your list of dependencies in `mix.exs`:

```elixir
def deps do
[
{:dice, "~> 0.1.0"}
]
end
```

Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
be found at <https://hexdocs.pm/dice>.

An online dice roller
11 changes: 11 additions & 0 deletions lib/dice.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
defmodule Dice do

def expression(expression) do
expression |> Dice.Expression.new
end

def roll(expression) do
expression |> Dice.Expression.new |> Dice.Expression.evaluate
end

end
78 changes: 78 additions & 0 deletions lib/dice/expression.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
defmodule Dice.Expression do
defstruct [terms: []]

def new(expression) when is_binary(expression) do
expression |> parse
end

def generate do
{__MODULE__.Parser, :parse}
|> NimbleParsec.parsec
|> NimbleParsec.generate
|> parse
end

def evaluate(%__MODULE__{} = expression) do
expression.terms
|> Enum.map(&Dice.Expression.Term.evaluate(&1))
|> Enum.sum
end

def to_string(%__MODULE__{} = expression) do
expression.terms
|> Enum.map(&Dice.Expression.Term.to_string(&1))
|> Enum.join
|> String.trim_leading("+ ")
|> String.replace_leading("- ", "-")
end

def parse(input) when is_binary(input) do
{:ok, [expression: expression], _unparsed, _context, _line, _offset} = __MODULE__.Parser.parse(input)

expression
end

def combinator do
import NimbleParsec
import Dice.Expression.Literals

optional(whitespace_literal())
|> concat(unwrap_and_tag(Dice.Expression.Term.maybe_signed_term_combinator(), :term))
|> concat(repeat(concat(optional(whitespace_literal()), unwrap_and_tag(Dice.Expression.Term.signed_term_combinator(), :term))))
|> optional(whitespace_literal())
|> post_traverse({__MODULE__, :combinator_constructor, []})
end

def combinator_constructor(rest, args, context, _line, _offset) do
terms = :lists.reverse(Keyword.get_values(args, :term))

expression = %Dice.Expression{
terms: terms
}

{rest, [expression], context}
end

defimpl Inspect do
import Inspect.Algebra

def inspect(expression, _opts) do
[first_term | rest] = expression.terms

concat(List.flatten([
"Dice.Expression.new(\"",
break(""),
first_term
|> Dice.Expression.Term.to_string
|> String.trim_leading("+ ")
|> String.replace_leading("- ", "-"),
break(),
rest
|> Enum.map(&Dice.Expression.Term.to_string(&1))
|> Enum.intersperse(break()),
break(""),
"\")"
]))
end
end
end
29 changes: 29 additions & 0 deletions lib/dice/expression/constant.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
defmodule Dice.Expression.Constant do
defstruct [:value]

def evaluate(%__MODULE__{} = constant) do
constant.value
end

def to_string(%__MODULE__{} = constant) do
constant.value |> Integer.to_string
end

def combinator do
import NimbleParsec
import Dice.Expression.Literals

unwrap_and_tag(non_negative_integer_literal(), :value)
|> post_traverse({__MODULE__, :combinator_constructor, []})
end

def combinator_constructor(rest, args, context, _line, _offset) do
value = Keyword.fetch!(args, :value)

constant = %Dice.Expression.Constant{
value: value
}

{rest, [constant], context}
end
end
77 changes: 77 additions & 0 deletions lib/dice/expression/dice/numeric/dice.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
defmodule Dice.Expression.Dice.Numeric.Dice do
defstruct [:faces]

def roll(%__MODULE__{} = dice) do
case dice.faces do
number when is_integer(number) and number > 0 -> :rand.uniform(number)
%Range{} = range -> Enum.random(range)
faces when is_list(faces) -> Enum.random(faces)
end
end

def to_string(%__MODULE__{} = dice) do
case dice.faces do
number when is_integer(number) -> "d#{Integer.to_string(number)}"
%Range{} = range -> "d[#{range.first}..#{range.last}]"
faces when is_list(faces) -> "d{#{Enum.join(faces, ", ")}}"
end
end

def combinator do
import NimbleParsec
import Dice.Expression.Literals

dice_value_descriptor = non_negative_integer_literal()

dice_range_descriptor =
ignore(left_bracket_literal())
|> concat(unwrap_and_tag(integer_literal(), :first))
|> concat(ignore(range_literal()))
|> concat(unwrap_and_tag(integer_literal(), :last))
|> concat(right_bracket_literal())
|> post_traverse({__MODULE__, :dice_range_constructor, []})

dice_set_descriptor =
ignore(left_brace_literal())
|> concat(unwrap_and_tag(integer_literal(), :number))
|> repeat(concat(ignore(comma_literal()), unwrap_and_tag(integer_literal(), :number)))
|> concat(ignore(right_brace_literal()))
|> post_traverse({__MODULE__, :dice_set_constructor, []})

numeric_dice_descriptor = [
dice_value_descriptor,
dice_range_descriptor,
dice_set_descriptor
]

concat(ignore(dice_literal()), unwrap_and_tag(choice(numeric_dice_descriptor), :faces))
|> post_traverse({__MODULE__, :combinator_constructor, []})
end

def combinator_constructor(rest, args, context, _line, _offset) do
faces = Keyword.fetch!(args, :faces)

numeric_dice = %__MODULE__{faces: faces}

{rest, [numeric_dice], context}
end

def dice_range_constructor(rest, args, context, _line, _offset) do
first = Keyword.fetch!(args, :first)
last = Keyword.fetch!(args, :last)

{rest, [Range.new(first, last)], context}
end

def dice_set_constructor(rest, args, context, _line, _offset) do
numbers = Keyword.get_values(args, :number) |> :lists.reverse

{rest, [numbers], context}
end

end

# defmodule Dice.Expression.Dice.Percent do
# defstruct []
# percent_literal = string("%")
# end
58 changes: 58 additions & 0 deletions lib/dice/expression/dice/numeric/dice/roll.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
defmodule Dice.Expression.Dice.Numeric.Dice.Roll do
defstruct [:dice, number: 1, modifiers: []]

def evaluate(%__MODULE__{} = roll) do
rolls = for _ <- 1..roll.number do
Dice.Expression.Dice.Numeric.Dice.roll(roll.dice)
end

Enum.reduce(roll.modifiers, rolls, fn modifier, rolls ->
case modifier do
%Dice.Expression.Dice.Numeric.Dice.Roll.Modifier.Keep{} = modifier -> Dice.Expression.Dice.Numeric.Dice.Roll.Modifier.Keep.modify(modifier, rolls)
%Dice.Expression.Dice.Numeric.Dice.Roll.Modifier.Drop{} = modifier -> Dice.Expression.Dice.Numeric.Dice.Roll.Modifier.Drop.modify(modifier, rolls)
end
end)
|> Enum.sum
end

def to_string(%__MODULE__{} = roll) do
Enum.join([
(if roll.number == 1, do: "", else: Integer.to_string(roll.number)),
Dice.Expression.Dice.Numeric.Dice.to_string(roll.dice)
] ++ Enum.map(roll.modifiers, fn modifier ->
case modifier do
%Dice.Expression.Dice.Numeric.Dice.Roll.Modifier.Keep{} = modifier -> Dice.Expression.Dice.Numeric.Dice.Roll.Modifier.Keep.to_string(modifier)
%Dice.Expression.Dice.Numeric.Dice.Roll.Modifier.Drop{} = modifier -> Dice.Expression.Dice.Numeric.Dice.Roll.Modifier.Drop.to_string(modifier)
end
end))
end

def combinator do
import NimbleParsec
import Dice.Expression.Literals

numeric_dice_modifiers = [
Dice.Expression.Dice.Numeric.Dice.Roll.Modifier.Keep.combinator(),
Dice.Expression.Dice.Numeric.Dice.Roll.Modifier.Drop.combinator()
]

optional(unwrap_and_tag(non_negative_integer_literal(), :number))
|> concat(unwrap_and_tag(Dice.Expression.Dice.Numeric.Dice.combinator(), :dice))
|> concat(optional(repeat(unwrap_and_tag(choice(numeric_dice_modifiers), :modifier))))
|> post_traverse({__MODULE__, :combinator_constructor, []})
end

def combinator_constructor(rest, args, context, _line, _offset) do
number = Keyword.get(args, :number, 1)
dice = Keyword.fetch!(args, :dice)
modifiers = :lists.reverse(Keyword.get_values(args, :modifier))

roll = %__MODULE__{
dice: dice,
number: number,
modifiers: modifiers
}

{rest, [roll], context}
end
end
Loading

0 comments on commit b86857e

Please sign in to comment.