Compare commits
16 Commits
Author | SHA1 | Date | |
---|---|---|---|
84ef972fec | |||
050af10ac2 | |||
d85b7f5b6e | |||
9859028786 | |||
450c67004a | |||
766c1cb064 | |||
|
b13efe4adb | ||
98fb5f6683 | |||
4e61ef0657 | |||
098221f983 | |||
4b5c3e5ce8 | |||
958228ec9a | |||
a99f7eac83 | |||
e9d7cf9724 | |||
fb1dd360e5 | |||
379e48f86a |
0
.gitignore
vendored
Normal file → Executable file
0
.gitignore
vendored
Normal file → Executable file
0
.travis.yml
Normal file → Executable file
0
.travis.yml
Normal file → Executable file
|
@ -1,5 +1,8 @@
|
|||
# v0.1
|
||||
|
||||
## v0.1.3
|
||||
- Minor improvements
|
||||
|
||||
## v0.1.2
|
||||
- Block DOS attack in the parser
|
||||
|
||||
|
|
16
Makefile
Normal file
16
Makefile
Normal file
|
@ -0,0 +1,16 @@
|
|||
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
|
17
README.md
17
README.md
|
@ -1,16 +1,20 @@
|
|||
# rollable
|
||||
|
||||
TODO: Write a description here
|
||||
Roll and parse dices
|
||||
|
||||
## Installation [![travis](https://travis-ci.org/Nephos/crystal_rollable.svg)](https://travis-ci.org/Nephos/crystal_rollable)
|
||||
Works with crystal v1.0.0
|
||||
|
||||
## 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`:
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
rollable:
|
||||
github: Nephos/crystal_rollable
|
||||
git: https://git.sceptique.eu/Sceptique/rollable
|
||||
branch: master
|
||||
```
|
||||
|
||||
|
||||
|
@ -19,7 +23,8 @@ dependencies:
|
|||
|
||||
```crystal
|
||||
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
|
||||
```
|
||||
|
||||
|
||||
|
@ -29,7 +34,7 @@ TODO: Write development instructions here
|
|||
|
||||
## Contributing
|
||||
|
||||
1. Fork it ( https://github.com/Nephos/crystal_rollable/fork )
|
||||
1. Fork it ( https://git.sceptique.eu/Sceptique/rollable/fork )
|
||||
2. Create your feature branch (git checkout -b my-new-feature)
|
||||
3. Commit your changes (git commit -am 'Add some feature')
|
||||
4. Push to the branch (git push origin my-new-feature)
|
||||
|
@ -37,4 +42,4 @@ TODO: Write development instructions here
|
|||
|
||||
## Contributors
|
||||
|
||||
- [Nephos](https://github.com/Nephos) Arthur Poulet - creator, maintainer
|
||||
- [Sceptique](https://git.sceptique.eu/Sceptique) Arthur Poulet - creator, maintainer
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
name: rollable
|
||||
version: 0.1.2
|
||||
version: 1.0.0
|
||||
|
||||
crystal: 1.0.0
|
||||
|
||||
authors:
|
||||
- Arthur Poulet <arthur.poulet@mailoo.org>
|
||||
- Arthur Poulet <arthur.poulet@sceptique.eu>
|
||||
|
||||
license: MIT
|
||||
|
|
|
@ -5,7 +5,7 @@ describe Rollable::Dice do
|
|||
d.min.should eq 2
|
||||
d.max.should eq 40
|
||||
d.average.should eq 21
|
||||
expect_raises { Rollable::Dice.new 1001, 20 }
|
||||
expect_raises(Exception) { Rollable::Dice.new 1001, 20 }
|
||||
end
|
||||
|
||||
it "details" do
|
||||
|
@ -41,12 +41,15 @@ describe Rollable::Dice do
|
|||
Rollable::Dice.parse("-1d6").min.should eq -6
|
||||
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").min.should eq 1
|
||||
Rollable::Dice.parse("!1d6").max.should eq Rollable::Die.new(1..6, true).max
|
||||
end
|
||||
|
||||
it "parse (error)" do
|
||||
expect_raises { Rollable::Dice.parse("yolo") }
|
||||
expect_raises { Rollable::Dice.parse("1d6+1", true) }
|
||||
expect_raises { Rollable::Dice.parse("--1d4") }
|
||||
expect_raises(Exception) { Rollable::Dice.parse("yolo") }
|
||||
expect_raises(Exception) { Rollable::Dice.parse("1d6+1", true) }
|
||||
expect_raises(Exception) { Rollable::Dice.parse("--1d4") }
|
||||
end
|
||||
|
||||
it "consume" do
|
||||
|
|
|
@ -3,7 +3,8 @@ describe Rollable::Die do
|
|||
Rollable::Die.new(1..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)
|
||||
expect_raises { Rollable::Die.new(1001) }
|
||||
Rollable::Die.new(20, true).should be_a(Rollable::Die)
|
||||
expect_raises(Exception) { Rollable::Die.new(1001) }
|
||||
end
|
||||
|
||||
it "min, max, average" do
|
||||
|
@ -13,6 +14,9 @@ describe Rollable::Die do
|
|||
Rollable::Die.new(1..20).min.should eq 1
|
||||
Rollable::Die.new(1..20).max.should eq 20
|
||||
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
|
||||
|
||||
it "test" do
|
||||
|
@ -20,6 +24,7 @@ describe Rollable::Die do
|
|||
min = rand 1..10
|
||||
max = rand min..20
|
||||
((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
|
||||
|
||||
|
@ -27,18 +32,25 @@ 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(-20..-1)).should eq true
|
||||
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
|
||||
|
||||
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 true
|
||||
Rollable::Die.new(1..20, true).negative?.should eq false
|
||||
Rollable::Die.new(-1..-20, true).negative?.should eq true
|
||||
end
|
||||
|
||||
it "fixed?" do
|
||||
Rollable::Die.new(1..20).fixed?.should eq false
|
||||
Rollable::Die.new(1..1).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
|
||||
|
||||
it "reverse" do
|
||||
|
@ -46,12 +58,16 @@ describe Rollable::Die do
|
|||
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.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
|
||||
|
||||
it "to_s" do
|
||||
Rollable::Die.new(1..20).to_s.should eq "D20"
|
||||
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(1..20, true).to_s.should eq "!D20"
|
||||
Rollable::Die.new(2..4, true).to_s.should eq "!D(2,4)"
|
||||
end
|
||||
|
||||
it "cmp" do
|
||||
|
@ -84,5 +100,8 @@ describe Rollable::Die do
|
|||
((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..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
|
||||
|
|
|
@ -8,17 +8,31 @@ describe Rollable::Roll do
|
|||
r.min.should eq 5
|
||||
r.max.should eq 24
|
||||
r.average.should eq 14.5
|
||||
10.times do
|
||||
100.times do
|
||||
(5..24).includes?(r.test).should eq true
|
||||
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
|
||||
r = Rollable::Roll.new [Rollable::Dice.new(2, 6), Rollable::Dice.new(1, 4)]
|
||||
r.min_details.should eq([1, 1, 1])
|
||||
r.max_details.should eq([6, 6, 4])
|
||||
r.average_details.should eq([3.5, 3.5, 2.5])
|
||||
10.times do
|
||||
100.times do
|
||||
t = r.test_details
|
||||
(1..6).includes?(t[0]).should eq true
|
||||
(1..6).includes?(t[1]).should eq true
|
||||
|
@ -40,10 +54,12 @@ describe Rollable::Roll do
|
|||
r1.should be_a(Rollable::Roll)
|
||||
r1.min.should eq 6
|
||||
r1.max.should eq 16
|
||||
(Rollable::Roll.parse("!2d6+4").average > Rollable::Roll.parse("2d6+4").average).should be_true
|
||||
end
|
||||
|
||||
it "parse (error)" do
|
||||
expect_raises { Rollable::Roll.parse("yolo") }
|
||||
expect_raises(Exception) { Rollable::Roll.parse("yolo") }
|
||||
Rollable::Roll.parse("yolo") { |_| true } rescue fail("must be catch in block")
|
||||
end
|
||||
|
||||
it "parse (more)" do
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
require "./rollable/*"
|
||||
|
||||
module Rollable
|
||||
class ParsingError < Exception
|
||||
end
|
||||
end
|
||||
|
||||
require "./rollable/*"
|
||||
|
|
|
@ -3,7 +3,8 @@ require "./is_rollable"
|
|||
require "./die"
|
||||
require "./fixed_value"
|
||||
|
||||
module Rollable
|
||||
# Not a front class. It is used to represent a tuple of die type and die amount
|
||||
#
|
||||
# A `Dice` is a amount of `Die`.
|
||||
# It is rollable exactly like a classic `Die`
|
||||
#
|
||||
|
@ -19,7 +20,7 @@ module Rollable
|
|||
# d.min_details # => [1, 1]
|
||||
# d.test # => the sum of 2 random values between 1..6
|
||||
# ```
|
||||
class Dice < IsRollable
|
||||
class Rollable::Dice < Rollable::IsRollable
|
||||
MAX = 1000
|
||||
@count : Int32
|
||||
@die : Die
|
||||
|
@ -31,8 +32,8 @@ module Rollable
|
|||
end
|
||||
|
||||
# Create a `Dice` with "die_type" faces.
|
||||
def initialize(@count, die_type : Int32)
|
||||
@die = Die.new(1..die_type)
|
||||
def initialize(@count, die_type : Int32, exploding : Bool = false)
|
||||
@die = Die.new(1..die_type, exploding)
|
||||
check_count!
|
||||
end
|
||||
|
||||
|
@ -114,8 +115,13 @@ module Rollable
|
|||
end
|
||||
{% end %}
|
||||
|
||||
def <=>(right : Dice)
|
||||
average != right.average ? average - right.average <=> 0 : max != right.max ? max - right.max <=> 0 : min - right.min <=> 0
|
||||
def <=>(right : Dice) : Int32
|
||||
if average != right.average
|
||||
average - right.average > 0 ? 1 : -1
|
||||
elsif max != right.max
|
||||
max - right.max <=> 0
|
||||
else
|
||||
min - right.min <=> 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,34 +1,36 @@
|
|||
# coding: utf-8
|
||||
|
||||
module Rollable
|
||||
class Dice
|
||||
class Rollable::Dice
|
||||
# Returns the `Dice` and the string parsed from `str`, in a `NamedTuple`
|
||||
# with "str" and "dice" keys.
|
||||
#
|
||||
# - 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
|
||||
# the regexp.
|
||||
private def self.parse_string(str : String, strict = true) : NamedTuple(str: String, dice: Dice)
|
||||
match = str.match(/\A(?<sign>-|\+)? *(?<count>\d+)(?:(?:d)(?<die>\d+))?#{strict ? "\\Z" : ""}/i)
|
||||
match = str.match(/\A(?<sign>-|\+)? *(?<exploding>!)?(?<count>\d+)(?:(?:d)(?<die>\d+))?#{strict ? "\\Z" : ""}/i)
|
||||
raise ParsingError.new("Parsing Error: dice, near to '#{str}'") if match.nil?
|
||||
sign = (match["sign"]? || "+") == "+" ? 1 : -1
|
||||
count = match["count"]
|
||||
die = match["die"]?
|
||||
exploding = match["exploding"]? ? true : false
|
||||
if die.nil?
|
||||
return {str: match[0], dice: FixedValue.new_dice(sign * count.to_i)}
|
||||
{str: match[0], dice: FixedValue.new_dice(sign * count.to_i)}
|
||||
else
|
||||
return {str: match[0], dice: Dice.new(sign * count.to_i, die.to_i)}
|
||||
{str: match[0], dice: Dice.new(sign * count.to_i, die.to_i, exploding)}
|
||||
end
|
||||
end
|
||||
|
||||
# Return a valid string parsed from `str`. (see `#parse_string`)
|
||||
#
|
||||
# Yields the `Dice` parsed from `str`.
|
||||
#
|
||||
# Then, it returns the string read.
|
||||
# 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
|
||||
data = parse_string(str, strict)
|
||||
yield data[:dice]
|
||||
|
@ -72,4 +74,3 @@ module Rollable
|
|||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
require "./is_rollable"
|
||||
|
||||
module Rollable
|
||||
# Not a front class. It is used to represent a type of dice with faces
|
||||
#
|
||||
# A `Die` is a range of Integer values.
|
||||
# It is rollable.
|
||||
#
|
||||
|
@ -13,23 +14,32 @@ module Rollable
|
|||
# d.test # => a random value included in 1..6
|
||||
# ```
|
||||
# TODO: make it a Struct ?
|
||||
class Die < IsRollable
|
||||
class Rollable::Die < Rollable::IsRollable
|
||||
MAX = 1000
|
||||
EXPLODING_ITERATIONS = 4
|
||||
|
||||
@faces : Range(Int32, Int32)
|
||||
getter exploding : Bool
|
||||
|
||||
getter faces
|
||||
|
||||
def initialize(@faces)
|
||||
def initialize(@faces, @exploding = false)
|
||||
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
|
||||
|
||||
def clone
|
||||
Die.new(@faces)
|
||||
Die.new(@faces, @exploding)
|
||||
end
|
||||
|
||||
def initialize(nb_faces : Int32)
|
||||
def initialize(nb_faces : Int32, @exploding = false)
|
||||
raise ParsingError.new "Cannot die with more than #{MAX} faces (#{nb_faces})" if nb_faces > MAX
|
||||
@faces = 1..nb_faces
|
||||
if @faces.end < @faces.begin
|
||||
@faces = @faces.end..@faces.begin
|
||||
end
|
||||
end
|
||||
|
||||
# Number of faces of the `Die`
|
||||
|
@ -56,32 +66,56 @@ module Rollable
|
|||
# Die.new(1..6).reverse # => Die.new -6..-1
|
||||
# ```
|
||||
def reverse : Die
|
||||
Die.new -max..-min
|
||||
Die.new -@faces.end..-@faces.begin, @exploding
|
||||
end
|
||||
|
||||
def reverse!
|
||||
@faces = -max..-min
|
||||
@faces = -@faces.end..-@faces.begin
|
||||
self
|
||||
end
|
||||
|
||||
def max : Int32
|
||||
if @exploding
|
||||
@faces.end * EXPLODING_ITERATIONS
|
||||
else
|
||||
@faces.end
|
||||
end
|
||||
end
|
||||
|
||||
def min : Int32
|
||||
@faces.begin
|
||||
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
|
||||
def test : Int32
|
||||
if @exploding
|
||||
sum = 0
|
||||
explode { |value| sum += value }
|
||||
sum
|
||||
else
|
||||
@faces.to_a.sample
|
||||
end
|
||||
end
|
||||
|
||||
# Mathematical expectation.
|
||||
#
|
||||
# A d6 will have a expected value of 3.5
|
||||
def average : Float64
|
||||
@faces.reduce { |r, l| r + l }.to_f64 / @faces.size
|
||||
proba = @faces.size.to_f64
|
||||
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
|
||||
|
||||
# Return a string.
|
||||
|
@ -89,21 +123,24 @@ module Rollable
|
|||
# - It may be a dice ```(1..n) => "D#{n}"```
|
||||
# - Else, ```(a..b) => "D(#{a},#{b})"```
|
||||
def to_s : String
|
||||
if self.size == 1
|
||||
string = if self.size == 1
|
||||
min.to_s
|
||||
elsif self.min == 1
|
||||
"D#{self.max}"
|
||||
"D#{@faces.end}"
|
||||
else
|
||||
"D(#{self.min},#{self.max})"
|
||||
"D(#{@faces.begin},#{@faces.end})"
|
||||
end
|
||||
string = "!#{string}" if @exploding
|
||||
return string
|
||||
end
|
||||
|
||||
def ==(right : Die)
|
||||
@faces == right.faces
|
||||
@faces == right.faces && @exploding == right.exploding
|
||||
end
|
||||
|
||||
{% for op in [">", "<", ">=", "<="] %}
|
||||
def {{ op.id }}(right : Die)
|
||||
return false if @exploding != right.exploding
|
||||
average != right.average ?
|
||||
average {{ op.id }} right.average :
|
||||
max != right.max ?
|
||||
|
@ -112,8 +149,14 @@ module Rollable
|
|||
end
|
||||
{% end %}
|
||||
|
||||
def <=>(right : Die)
|
||||
average != right.average ? average - right.average <=> 0 : max != right.max ? max - right.max <=> 0 : min - right.min <=> 0
|
||||
def <=>(right : Die) : Int32
|
||||
if average != right.average
|
||||
average - right.average > 0 ? 1 : -1
|
||||
elsif max != right.max
|
||||
max - right.max <=> 0
|
||||
else
|
||||
min - right.min <=> 0
|
||||
end
|
||||
end
|
||||
end
|
||||
#
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
require "./die"
|
||||
|
||||
module Rollable
|
||||
# Allow to create a die with a fixed value.
|
||||
# Allows to create a die with a fixed value.
|
||||
# The die will only gives this value everytime.
|
||||
# (`.min`, `.max`, `.test`, `.average`)
|
||||
#
|
||||
|
@ -10,7 +9,7 @@ module Rollable
|
|||
# Die.new(n..n) # => FixedValue.new_die n
|
||||
# Dice.new(1, Die.new(n..n)) # => FixedValue.new_dice n
|
||||
# ```
|
||||
module FixedValue
|
||||
module Rollable::FixedValue
|
||||
# Return a `Die` with only one face.
|
||||
def self.new_die(value : Int32)
|
||||
Die.new value..value
|
||||
|
@ -21,4 +20,3 @@ module Rollable
|
|||
Dice.new 1, FixedValue.new_die(fixed)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
module Rollable
|
||||
abstract class IsRollable
|
||||
abstract class Rollable::IsRollable
|
||||
abstract def min : Int32
|
||||
abstract def max : Int32
|
||||
abstract def average : Int32
|
||||
abstract def average : Float64
|
||||
abstract def test : Int32
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,7 +3,6 @@ require "./die"
|
|||
require "./fixed_value"
|
||||
require "./dice"
|
||||
|
||||
module Rollable
|
||||
# `Roll` is a list of `Dice`.
|
||||
#
|
||||
# It is rollable, making the sum of each `Dice` values.
|
||||
|
@ -18,7 +17,7 @@ module Rollable
|
|||
# r.average # => 5.5
|
||||
# r.test # => the sum of a random value in 1..6 and 2
|
||||
# ```
|
||||
class Roll < IsRollable
|
||||
class Rollable::Roll < Rollable::IsRollable
|
||||
@dice : Array(Dice)
|
||||
|
||||
getter dice
|
||||
|
@ -81,7 +80,13 @@ module Rollable
|
|||
{% end %}
|
||||
|
||||
def <=>(right : Roll) : Int32
|
||||
average != right.average ? average - right.average <=> 0 : max != right.max ? max - right.max <=> 0 : min - right.min <=> 0
|
||||
if average != right.average
|
||||
average - right.average > 0 ? 1 : -1
|
||||
elsif max != right.max
|
||||
max - right.max <=> 0
|
||||
else
|
||||
min - right.min <=> 0
|
||||
end
|
||||
end
|
||||
|
||||
def order!
|
||||
|
@ -147,6 +152,5 @@ module Rollable
|
|||
clone.compact!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
require "./roll/*"
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
module Rollable
|
||||
class Roll
|
||||
class Rollable::Roll
|
||||
# Parse the string and return an array of `Dice`
|
||||
#
|
||||
# see `Dice.consume`
|
||||
|
@ -12,11 +11,11 @@ module Rollable
|
|||
# - sdice = [sign]?[dice]
|
||||
# - roll = [sign][dice][sdice]*
|
||||
# ```
|
||||
def self.parse_str(str : String?, list : Array(Dice) = Array(Dice).new) : Array(Dice)
|
||||
private def self.parse_str(str : String?, list : Array(Dice) = Array(Dice).new) : Array(Dice)
|
||||
return list if str.nil?
|
||||
str = str.strip
|
||||
sign = str[0]
|
||||
if sign != '+' && sign != '-' && !list.empty?
|
||||
if sign != '+' && sign != '-' && sign != '!' && !list.empty?
|
||||
raise ParsingError.new("Parsing Error: roll, near to '#{str}'")
|
||||
end
|
||||
str = str[1..-1] if sign == '-' || sign == '+'
|
||||
|
@ -28,11 +27,23 @@ module Rollable
|
|||
end
|
||||
|
||||
# Parse the string "str" and returns a new `Roll` object
|
||||
#
|
||||
# see `#parse_str`
|
||||
def self.parse(str : String) : Roll
|
||||
return Roll.new(parse_str(str))
|
||||
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
|
||||
@dice.reduce(nil) do |l, r|
|
||||
# puts "l:#{l.to_s}, r:#{r.to_s}"
|
||||
|
@ -48,4 +59,3 @@ module Rollable
|
|||
end.to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
module Rollable
|
||||
VERSION = "0.1.2"
|
||||
VERSION = "0.1.4"
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue
Block a user