Compare commits

..

No commits in common. "master" and "v0.1.2" have entirely different histories.

18 changed files with 480 additions and 604 deletions

0
.gitignore vendored Executable file → Normal file
View File

0
.travis.yml Executable file → Normal file
View File

View File

@ -1,8 +1,5 @@
# v0.1 # v0.1
## v0.1.3
- Minor improvements
## v0.1.2 ## v0.1.2
- Block DOS attack in the parser - Block DOS attack in the parser

View File

@ -1,16 +0,0 @@
NAME=`ls -d src/*/ | cut -f2 -d'/'`
all: deps_opt test
test:
crystal spec
deps:
shards install
deps_update:
shards update
deps_opt:
@[ -d lib/ ] || make deps
doc:
crystal docs
.PHONY: all run build release test deps deps_update doc

View File

@ -1,20 +1,16 @@
# rollable # rollable
Roll and parse dices TODO: Write a description here
Works with crystal v1.0.0 ## Installation [![travis](https://travis-ci.org/Nephos/crystal_rollable.svg)](https://travis-ci.org/Nephos/crystal_rollable)
## Installation
[![travis](https://travis-ci.org/Nephos/crystal_rollable.svg)](https://travis-ci.org/Nephos/crystal_rollable)
Add this to your application's `shard.yml`: Add this to your application's `shard.yml`:
```yaml ```yaml
dependencies: dependencies:
rollable: rollable:
git: https://git.sceptique.eu/Sceptique/rollable github: Nephos/crystal_rollable
branch: master
``` ```
@ -23,8 +19,7 @@ dependencies:
```crystal ```crystal
require "rollable" require "rollable"
Rollable::Roll.parse("2d6+4").test # => Roll 2 dices and add 4 to the sum Rollable::Roll.parse("2d6+4").test # => roll 2 dices and add 4 to the sum
Rollable::Roll.parse("!1d20 + !1d8").test # => Exploding dices
``` ```
@ -34,7 +29,7 @@ TODO: Write development instructions here
## Contributing ## Contributing
1. Fork it ( https://git.sceptique.eu/Sceptique/rollable/fork ) 1. Fork it ( https://github.com/Nephos/crystal_rollable/fork )
2. Create your feature branch (git checkout -b my-new-feature) 2. Create your feature branch (git checkout -b my-new-feature)
3. Commit your changes (git commit -am 'Add some feature') 3. Commit your changes (git commit -am 'Add some feature')
4. Push to the branch (git push origin my-new-feature) 4. Push to the branch (git push origin my-new-feature)
@ -42,4 +37,4 @@ TODO: Write development instructions here
## Contributors ## Contributors
- [Sceptique](https://git.sceptique.eu/Sceptique) Arthur Poulet - creator, maintainer - [Nephos](https://github.com/Nephos) Arthur Poulet - creator, maintainer

View File

@ -1,9 +1,7 @@
name: rollable name: rollable
version: 1.0.0 version: 0.1.2
crystal: 1.0.0
authors: authors:
- Arthur Poulet <arthur.poulet@sceptique.eu> - Arthur Poulet <arthur.poulet@mailoo.org>
license: MIT license: MIT

View File

@ -5,7 +5,7 @@ describe Rollable::Dice do
d.min.should eq 2 d.min.should eq 2
d.max.should eq 40 d.max.should eq 40
d.average.should eq 21 d.average.should eq 21
expect_raises(Exception) { Rollable::Dice.new 1001, 20 } expect_raises { Rollable::Dice.new 1001, 20 }
end end
it "details" do it "details" do
@ -41,15 +41,12 @@ describe Rollable::Dice do
Rollable::Dice.parse("-1d6").min.should eq -6 Rollable::Dice.parse("-1d6").min.should eq -6
Rollable::Dice.parse("-1d6").max.should eq -1 Rollable::Dice.parse("-1d6").max.should eq -1
Rollable::Dice.parse("-1d6").count.should eq 1 Rollable::Dice.parse("-1d6").count.should eq 1
Rollable::Dice.parse("!1d6").count.should eq 1
Rollable::Dice.parse("!1d6").min.should eq 1
Rollable::Dice.parse("!1d6").max.should eq Rollable::Die.new(1..6, true).max
end end
it "parse (error)" do it "parse (error)" do
expect_raises(Exception) { Rollable::Dice.parse("yolo") } expect_raises { Rollable::Dice.parse("yolo") }
expect_raises(Exception) { Rollable::Dice.parse("1d6+1", true) } expect_raises { Rollable::Dice.parse("1d6+1", true) }
expect_raises(Exception) { Rollable::Dice.parse("--1d4") } expect_raises { Rollable::Dice.parse("--1d4") }
end end
it "consume" do it "consume" do

View File

@ -3,8 +3,7 @@ describe Rollable::Die do
Rollable::Die.new(1..20).should be_a(Rollable::Die) Rollable::Die.new(1..20).should be_a(Rollable::Die)
Rollable::Die.new(10..20).should be_a(Rollable::Die) Rollable::Die.new(10..20).should be_a(Rollable::Die)
Rollable::Die.new(20).should be_a(Rollable::Die) Rollable::Die.new(20).should be_a(Rollable::Die)
Rollable::Die.new(20, true).should be_a(Rollable::Die) expect_raises { Rollable::Die.new(1001) }
expect_raises(Exception) { Rollable::Die.new(1001) }
end end
it "min, max, average" do it "min, max, average" do
@ -14,9 +13,6 @@ describe Rollable::Die do
Rollable::Die.new(1..20).min.should eq 1 Rollable::Die.new(1..20).min.should eq 1
Rollable::Die.new(1..20).max.should eq 20 Rollable::Die.new(1..20).max.should eq 20
Rollable::Die.new(1..20).average.should eq 10.5 Rollable::Die.new(1..20).average.should eq 10.5
Rollable::Die.new(1..20, true).min.should eq 1
Rollable::Die.new(1..20, true).max.should eq 80
Rollable::Die.new(1..20, true).average.should eq (10.5*0.05**0 + 10.5*0.05**1 + 10.5*0.05**2 + 10.5*0.05**3).round(3)
end end
it "test" do it "test" do
@ -24,7 +20,6 @@ describe Rollable::Die do
min = rand 1..10 min = rand 1..10
max = rand min..20 max = rand min..20
((min..max).includes? Rollable::Die.new(min..max).test).should eq(true) ((min..max).includes? Rollable::Die.new(min..max).test).should eq(true)
((min..max*Rollable::Die::EXPLODING_ITERATIONS).includes? Rollable::Die.new(min..max, true).test).should eq(true)
end end
end end
@ -32,25 +27,18 @@ describe Rollable::Die do
Rollable::Die.new(1..20).like?(Rollable::Die.new(1..20)).should eq true Rollable::Die.new(1..20).like?(Rollable::Die.new(1..20)).should eq true
Rollable::Die.new(1..20).like?(Rollable::Die.new(-20..-1)).should eq true Rollable::Die.new(1..20).like?(Rollable::Die.new(-20..-1)).should eq true
Rollable::Die.new(1..20).like?(Rollable::Die.new(1..10)).should eq false Rollable::Die.new(1..20).like?(Rollable::Die.new(1..10)).should eq false
Rollable::Die.new(1..20, true).like?(Rollable::Die.new(1..20, false)).should eq true
end end
it "negative?" do it "negative?" do
Rollable::Die.new(1..20).negative?.should eq false Rollable::Die.new(1..20).negative?.should eq false
Rollable::Die.new(-1..20).negative?.should eq false Rollable::Die.new(-1..20).negative?.should eq false
Rollable::Die.new(1..-20).negative?.should eq false
Rollable::Die.new(-1..-20).negative?.should eq true Rollable::Die.new(-1..-20).negative?.should eq true
Rollable::Die.new(1..20, true).negative?.should eq false
Rollable::Die.new(-1..-20, true).negative?.should eq true
end end
it "fixed?" do it "fixed?" do
Rollable::Die.new(1..20).fixed?.should eq false Rollable::Die.new(1..20).fixed?.should eq false
Rollable::Die.new(1..1).fixed?.should eq true Rollable::Die.new(1..1).fixed?.should eq true
Rollable::Die.new(20..20).fixed?.should eq true Rollable::Die.new(20..20).fixed?.should eq true
Rollable::Die.new(1).fixed?.should eq true
Rollable::Die.new(1..20, true).fixed?.should eq false
Rollable::Die.new(1, true).fixed?.should eq true
end end
it "reverse" do it "reverse" do
@ -58,16 +46,12 @@ describe Rollable::Die do
Rollable::Die.new(1..20).reverse.max.should eq -1 Rollable::Die.new(1..20).reverse.max.should eq -1
Rollable::Die.new(1..1).reverse.min.should eq -1 Rollable::Die.new(1..1).reverse.min.should eq -1
Rollable::Die.new(1..1).reverse.max.should eq -1 Rollable::Die.new(1..1).reverse.max.should eq -1
Rollable::Die.new(1..20, true).reverse.min.should eq -20
Rollable::Die.new(1..20, true).reverse.max.should eq -4
end end
it "to_s" do it "to_s" do
Rollable::Die.new(1..20).to_s.should eq "D20" Rollable::Die.new(1..20).to_s.should eq "D20"
Rollable::Die.new(2..2).to_s.should eq "2" Rollable::Die.new(2..2).to_s.should eq "2"
Rollable::Die.new(2..4).to_s.should eq "D(2,4)" Rollable::Die.new(2..4).to_s.should eq "D(2,4)"
Rollable::Die.new(1..20, true).to_s.should eq "!D20"
Rollable::Die.new(2..4, true).to_s.should eq "!D(2,4)"
end end
it "cmp" do it "cmp" do
@ -100,8 +84,5 @@ describe Rollable::Die do
((Rollable::Die.new(1..4) <=> Rollable::Die.new(1..4)) == 0).should eq true ((Rollable::Die.new(1..4) <=> Rollable::Die.new(1..4)) == 0).should eq true
((Rollable::Die.new(2..3) <=> Rollable::Die.new(1..4)) < 0).should eq true ((Rollable::Die.new(2..3) <=> Rollable::Die.new(1..4)) < 0).should eq true
(Rollable::Die.new(2..6) == Rollable::Die.new(4..4)).should eq false (Rollable::Die.new(2..6) == Rollable::Die.new(4..4)).should eq false
# TODO: (Rollable::Die.new(1..20, true) == Rollable::Die.new(1..20, false)).should eq false
# TODO: (Rollable::Die.new(1..20, true) > Rollable::Die.new(1..20, false)).should eq true
end end
end end

View File

@ -8,31 +8,17 @@ describe Rollable::Roll do
r.min.should eq 5 r.min.should eq 5
r.max.should eq 24 r.max.should eq 24
r.average.should eq 14.5 r.average.should eq 14.5
100.times do 10.times do
(5..24).includes?(r.test).should eq true (5..24).includes?(r.test).should eq true
end end
end end
it "initialize exploding" do
r = Rollable::Roll.new [
Rollable::Dice.new(1, 6, true),
]
r.should be_a Rollable::Roll
min = 1
max = 6*Rollable::Die::EXPLODING_ITERATIONS
r.min.should eq min
r.max.should eq max
100.times do
(min..max).includes?(r.test).should eq true
end
end
it "test (details)" do it "test (details)" do
r = Rollable::Roll.new [Rollable::Dice.new(2, 6), Rollable::Dice.new(1, 4)] r = Rollable::Roll.new [Rollable::Dice.new(2, 6), Rollable::Dice.new(1, 4)]
r.min_details.should eq([1, 1, 1]) r.min_details.should eq([1, 1, 1])
r.max_details.should eq([6, 6, 4]) r.max_details.should eq([6, 6, 4])
r.average_details.should eq([3.5, 3.5, 2.5]) r.average_details.should eq([3.5, 3.5, 2.5])
100.times do 10.times do
t = r.test_details t = r.test_details
(1..6).includes?(t[0]).should eq true (1..6).includes?(t[0]).should eq true
(1..6).includes?(t[1]).should eq true (1..6).includes?(t[1]).should eq true
@ -54,12 +40,10 @@ describe Rollable::Roll do
r1.should be_a(Rollable::Roll) r1.should be_a(Rollable::Roll)
r1.min.should eq 6 r1.min.should eq 6
r1.max.should eq 16 r1.max.should eq 16
(Rollable::Roll.parse("!2d6+4").average > Rollable::Roll.parse("2d6+4").average).should be_true
end end
it "parse (error)" do it "parse (error)" do
expect_raises(Exception) { Rollable::Roll.parse("yolo") } expect_raises { Rollable::Roll.parse("yolo") }
Rollable::Roll.parse("yolo") { |_| true } rescue fail("must be catch in block")
end end
it "parse (more)" do it "parse (more)" do

View File

@ -1,6 +1,6 @@
require "./rollable/*"
module Rollable module Rollable
class ParsingError < Exception class ParsingError < Exception
end end
end end
require "./rollable/*"

View File

@ -3,24 +3,23 @@ require "./is_rollable"
require "./die" require "./die"
require "./fixed_value" require "./fixed_value"
# Not a front class. It is used to represent a tuple of die type and die amount module Rollable
# # A `Dice` is a amount of `Die`.
# A `Dice` is a amount of `Die`. # It is rollable exactly like a classic `Die`
# It is rollable exactly like a classic `Die` #
# # It is also possible to get the details of a roll, using the methods
# It is also possible to get the details of a roll, using the methods # `.min_details`, `.max_details`, `.average_details`, `.test_details`
# `.min_details`, `.max_details`, `.average_details`, `.test_details` #
# # Example:
# Example: # ```
# ``` # d = Dice.parse "2d6"
# d = Dice.parse "2d6" # d.min # => 2
# d.min # => 2 # d.max # => 12
# d.max # => 12 # d.average # => 7
# d.average # => 7 # d.min_details # => [1, 1]
# d.min_details # => [1, 1] # d.test # => the sum of 2 random values between 1..6
# d.test # => the sum of 2 random values between 1..6 # ```
# ``` class Dice < IsRollable
class Rollable::Dice < Rollable::IsRollable
MAX = 1000 MAX = 1000
@count : Int32 @count : Int32
@die : Die @die : Die
@ -32,8 +31,8 @@ class Rollable::Dice < Rollable::IsRollable
end end
# Create a `Dice` with "die_type" faces. # Create a `Dice` with "die_type" faces.
def initialize(@count, die_type : Int32, exploding : Bool = false) def initialize(@count, die_type : Int32)
@die = Die.new(1..die_type, exploding) @die = Die.new(1..die_type)
check_count! check_count!
end end
@ -115,13 +114,8 @@ class Rollable::Dice < Rollable::IsRollable
end end
{% end %} {% end %}
def <=>(right : Dice) : Int32 def <=>(right : Dice)
if average != right.average average != right.average ? average - right.average <=> 0 : max != right.max ? max - right.max <=> 0 : min - right.min <=> 0
average - right.average > 0 ? 1 : -1
elsif max != right.max
max - right.max <=> 0
else
min - right.min <=> 0
end end
end end
end end

View File

@ -1,36 +1,34 @@
# coding: utf-8 # coding: utf-8
class Rollable::Dice module Rollable
class Dice
# Returns the `Dice` and the string parsed from `str`, in a `NamedTuple` # Returns the `Dice` and the string parsed from `str`, in a `NamedTuple`
# with "str" and "dice" keys. # with "str" and "dice" keys.
# #
# - If "strict" is true, then the string must end following the regex # - If "strict" is true, then the string must end following the regex
# `\A(!)?\d+(d\d+)?\Z/i` # `\A\d+(d\d+)?\Z/i`
# #
# - If "strict" is false, then the string doesn't have to finish following # - If "strict" is false, then the string doesn't have to finish following
# the regexp. # the regexp.
private def self.parse_string(str : String, strict = true) : NamedTuple(str: String, dice: Dice) private def self.parse_string(str : String, strict = true) : NamedTuple(str: String, dice: Dice)
match = str.match(/\A(?<sign>-|\+)? *(?<exploding>!)?(?<count>\d+)(?:(?:d)(?<die>\d+))?#{strict ? "\\Z" : ""}/i) match = str.match(/\A(?<sign>-|\+)? *(?<count>\d+)(?:(?:d)(?<die>\d+))?#{strict ? "\\Z" : ""}/i)
raise ParsingError.new("Parsing Error: dice, near to '#{str}'") if match.nil? raise ParsingError.new("Parsing Error: dice, near to '#{str}'") if match.nil?
sign = (match["sign"]? || "+") == "+" ? 1 : -1 sign = (match["sign"]? || "+") == "+" ? 1 : -1
count = match["count"] count = match["count"]
die = match["die"]? die = match["die"]?
exploding = match["exploding"]? ? true : false
if die.nil? if die.nil?
{str: match[0], dice: FixedValue.new_dice(sign * count.to_i)} return {str: match[0], dice: FixedValue.new_dice(sign * count.to_i)}
else else
{str: match[0], dice: Dice.new(sign * count.to_i, die.to_i, exploding)} return {str: match[0], dice: Dice.new(sign * count.to_i, die.to_i)}
end end
end end
# Return a valid string parsed from `str`. (see `#parse_string`) # Return a valid string parsed from `str`. (see `#parse_string`)
# #
# Yields the `Dice` parsed from `str`. # Yields the `Dice` parsed from `str`.
#
# Then, it returns the string read. # Then, it returns the string read.
# If strict is false, only the valid string is returned. # If strict is false, only the valid string is returned.
# ```
# Dice.parse("1d6") {|dice| dice.roll } => "1d6"
# ```
def self.parse(str : String, strict = true) : String def self.parse(str : String, strict = true) : String
data = parse_string(str, strict) data = parse_string(str, strict)
yield data[:dice] yield data[:dice]
@ -73,4 +71,5 @@ class Rollable::Dice
(negative? ? "-" : "") + "#{@count}" + (negative? ? @die.reverse : @die).to_s (negative? ? "-" : "") + "#{@count}" + (negative? ? @die.reverse : @die).to_s
end end
end end
end
end end

View File

@ -1,45 +1,35 @@
require "./is_rollable" require "./is_rollable"
# Not a front class. It is used to represent a type of dice with faces module Rollable
# # A `Die` is a range of Integer values.
# A `Die` is a range of Integer values. # It is rollable.
# It is rollable. #
# # Example:
# Example: # ```
# ``` # d = Die.new(1..6)
# d = Die.new(1..6) # d.min # => 1
# d.min # => 1 # d.max # => 6
# d.max # => 6 # d.average # => 3.5
# d.average # => 3.5 # d.test # => a random value included in 1..6
# d.test # => a random value included in 1..6 # ```
# ``` # TODO: make it a Struct ?
# TODO: make it a Struct ? class Die < IsRollable
class Rollable::Die < Rollable::IsRollable
MAX = 1000 MAX = 1000
EXPLODING_ITERATIONS = 4
@faces : Range(Int32, Int32) @faces : Range(Int32, Int32)
getter exploding : Bool
getter faces getter faces
def initialize(@faces, @exploding = false) def initialize(@faces)
raise ParsingError.new "Cannot die with more than #{MAX} faces (#{@faces})" if @faces.size > MAX raise ParsingError.new "Cannot die with more than #{MAX} faces (#{@faces})" if @faces.size > MAX
if @faces.end < @faces.begin
@faces = @faces.end..@faces.begin
end
end end
def clone def clone
Die.new(@faces, @exploding) Die.new(@faces)
end end
def initialize(nb_faces : Int32, @exploding = false) def initialize(nb_faces : Int32)
raise ParsingError.new "Cannot die with more than #{MAX} faces (#{nb_faces})" if nb_faces > MAX raise ParsingError.new "Cannot die with more than #{MAX} faces (#{nb_faces})" if nb_faces > MAX
@faces = 1..nb_faces @faces = 1..nb_faces
if @faces.end < @faces.begin
@faces = @faces.end..@faces.begin
end
end end
# Number of faces of the `Die` # Number of faces of the `Die`
@ -66,56 +56,32 @@ class Rollable::Die < Rollable::IsRollable
# Die.new(1..6).reverse # => Die.new -6..-1 # Die.new(1..6).reverse # => Die.new -6..-1
# ``` # ```
def reverse : Die def reverse : Die
Die.new -@faces.end..-@faces.begin, @exploding Die.new -max..-min
end end
def reverse! def reverse!
@faces = -@faces.end..-@faces.begin @faces = -max..-min
self self
end end
def max : Int32 def max : Int32
if @exploding
@faces.end * EXPLODING_ITERATIONS
else
@faces.end @faces.end
end end
end
def min : Int32 def min : Int32
@faces.begin @faces.begin
end end
private def explode(&block)
EXPLODING_ITERATIONS.times do |_|
value = @faces.to_a.sample
yield value
break if value != @faces.end
end
end
# Return a random value in the range of the dice # Return a random value in the range of the dice
def test : Int32 def test : Int32
if @exploding
sum = 0
explode { |value| sum += value }
sum
else
@faces.to_a.sample @faces.to_a.sample
end end
end
# Mathematical expectation. # Mathematical expectation.
# #
# A d6 will have a expected value of 3.5 # A d6 will have a expected value of 3.5
def average : Float64 def average : Float64
proba = @faces.size.to_f64 @faces.reduce { |r, l| r + l }.to_f64 / @faces.size
non_exploding_average = @faces.reduce { |r, l| r + l }.to_f64 / proba
if @exploding
EXPLODING_ITERATIONS.times.reduce(0.0) {|base, i| base + non_exploding_average / proba ** i }.round(3)
else
non_exploding_average
end
end end
# Return a string. # Return a string.
@ -123,24 +89,21 @@ class Rollable::Die < Rollable::IsRollable
# - It may be a dice ```(1..n) => "D#{n}"``` # - It may be a dice ```(1..n) => "D#{n}"```
# - Else, ```(a..b) => "D(#{a},#{b})"``` # - Else, ```(a..b) => "D(#{a},#{b})"```
def to_s : String def to_s : String
string = if self.size == 1 if self.size == 1
min.to_s min.to_s
elsif self.min == 1 elsif self.min == 1
"D#{@faces.end}" "D#{self.max}"
else else
"D(#{@faces.begin},#{@faces.end})" "D(#{self.min},#{self.max})"
end end
string = "!#{string}" if @exploding
return string
end end
def ==(right : Die) def ==(right : Die)
@faces == right.faces && @exploding == right.exploding @faces == right.faces
end end
{% for op in [">", "<", ">=", "<="] %} {% for op in [">", "<", ">=", "<="] %}
def {{ op.id }}(right : Die) def {{ op.id }}(right : Die)
return false if @exploding != right.exploding
average != right.average ? average != right.average ?
average {{ op.id }} right.average : average {{ op.id }} right.average :
max != right.max ? max != right.max ?
@ -149,14 +112,8 @@ class Rollable::Die < Rollable::IsRollable
end end
{% end %} {% end %}
def <=>(right : Die) : Int32 def <=>(right : Die)
if average != right.average average != right.average ? average - right.average <=> 0 : max != right.max ? max - right.max <=> 0 : min - right.min <=> 0
average - right.average > 0 ? 1 : -1
elsif max != right.max
max - right.max <=> 0
else
min - right.min <=> 0
end end
end end
end end
#

View File

@ -1,15 +1,16 @@
require "./die" require "./die"
# Allows to create a die with a fixed value. module Rollable
# The die will only gives this value everytime. # Allow to create a die with a fixed value.
# (`.min`, `.max`, `.test`, `.average`) # The die will only gives this value everytime.
# # (`.min`, `.max`, `.test`, `.average`)
# This is equivalent to #
# ``` # This is equivalent to
# Die.new(n..n) # => FixedValue.new_die n # ```
# Dice.new(1, Die.new(n..n)) # => FixedValue.new_dice n # Die.new(n..n) # => FixedValue.new_die n
# ``` # Dice.new(1, Die.new(n..n)) # => FixedValue.new_dice n
module Rollable::FixedValue # ```
module FixedValue
# Return a `Die` with only one face. # Return a `Die` with only one face.
def self.new_die(value : Int32) def self.new_die(value : Int32)
Die.new value..value Die.new value..value
@ -19,4 +20,5 @@ module Rollable::FixedValue
def self.new_dice(fixed : Int32) def self.new_dice(fixed : Int32)
Dice.new 1, FixedValue.new_die(fixed) Dice.new 1, FixedValue.new_die(fixed)
end end
end
end end

View File

@ -1,6 +1,8 @@
abstract class Rollable::IsRollable module Rollable
abstract class IsRollable
abstract def min : Int32 abstract def min : Int32
abstract def max : Int32 abstract def max : Int32
abstract def average : Float64 abstract def average : Int32
abstract def test : Int32 abstract def test : Int32
end
end end

View File

@ -3,21 +3,22 @@ require "./die"
require "./fixed_value" require "./fixed_value"
require "./dice" require "./dice"
# `Roll` is a list of `Dice`. module Rollable
# # `Roll` is a list of `Dice`.
# It is rollable, making the sum of each `Dice` values. #
# It is also possible to get the details of a roll, using the methods # It is rollable, making the sum of each `Dice` values.
# `.min_details`, `.max_details`, `.average_details`, `.test_details` # It is also possible to get the details of a roll, using the methods
# # `.min_details`, `.max_details`, `.average_details`, `.test_details`
# Example: #
# ``` # Example:
# r = Rollable.parse "1d6+2" # note: it also support "1d6 + 2" # ```
# r.min # => 3 # r = Rollable.parse "1d6+2" # note: it also support "1d6 + 2"
# r.max # => 9 # r.min # => 3
# r.average # => 5.5 # r.max # => 9
# r.test # => the sum of a random value in 1..6 and 2 # r.average # => 5.5
# ``` # r.test # => the sum of a random value in 1..6 and 2
class Rollable::Roll < Rollable::IsRollable # ```
class Roll < IsRollable
@dice : Array(Dice) @dice : Array(Dice)
getter dice getter dice
@ -80,13 +81,7 @@ class Rollable::Roll < Rollable::IsRollable
{% end %} {% end %}
def <=>(right : Roll) : Int32 def <=>(right : Roll) : Int32
if average != right.average average != right.average ? average - right.average <=> 0 : max != right.max ? max - right.max <=> 0 : min - right.min <=> 0
average - right.average > 0 ? 1 : -1
elsif max != right.max
max - right.max <=> 0
else
min - right.min <=> 0
end
end end
def order! def order!
@ -151,6 +146,7 @@ class Rollable::Roll < Rollable::IsRollable
def compact def compact
clone.compact! clone.compact!
end end
end
end end
require "./roll/*" require "./roll/*"

View File

@ -1,4 +1,5 @@
class Rollable::Roll module Rollable
class Roll
# Parse the string and return an array of `Dice` # Parse the string and return an array of `Dice`
# #
# see `Dice.consume` # see `Dice.consume`
@ -11,11 +12,11 @@ class Rollable::Roll
# - sdice = [sign]?[dice] # - sdice = [sign]?[dice]
# - roll = [sign][dice][sdice]* # - roll = [sign][dice][sdice]*
# ``` # ```
private def self.parse_str(str : String?, list : Array(Dice) = Array(Dice).new) : Array(Dice) def self.parse_str(str : String?, list : Array(Dice) = Array(Dice).new) : Array(Dice)
return list if str.nil? return list if str.nil?
str = str.strip str = str.strip
sign = str[0] sign = str[0]
if sign != '+' && sign != '-' && sign != '!' && !list.empty? if sign != '+' && sign != '-' && !list.empty?
raise ParsingError.new("Parsing Error: roll, near to '#{str}'") raise ParsingError.new("Parsing Error: roll, near to '#{str}'")
end end
str = str[1..-1] if sign == '-' || sign == '+' str = str[1..-1] if sign == '-' || sign == '+'
@ -27,23 +28,11 @@ class Rollable::Roll
end end
# Parse the string "str" and returns a new `Roll` object # Parse the string "str" and returns a new `Roll` object
#
# see `#parse_str` # see `#parse_str`
def self.parse(str : String) : Roll def self.parse(str : String) : Roll
return Roll.new(parse_str(str)) return Roll.new(parse_str(str))
end end
# Parse the string "str" and returns a new `Roll` object,
# and execute the "block" if an error occured
def self.parse(str : String) : Roll?
begin
return self.parse(str)
rescue err
yield err
return nil
end
end
def to_s : String def to_s : String
@dice.reduce(nil) do |l, r| @dice.reduce(nil) do |l, r|
# puts "l:#{l.to_s}, r:#{r.to_s}" # puts "l:#{l.to_s}, r:#{r.to_s}"
@ -58,4 +47,5 @@ class Rollable::Roll
end end
end.to_s end.to_s
end end
end
end end

View File

@ -1,3 +1,3 @@
module Rollable module Rollable
VERSION = "0.1.4" VERSION = "0.1.2"
end end