Git Product home page Git Product logo

gobcdice's Introduction

GoBCDice

Build Status Build status codecov

GoBCDice is a Go implementation of BCDice, a dice bot for tabletop RPGs supporting many Japanese game systems. It will consist of the core dice roller (dice notation parser and evaluator) and many game-system-specific dice bots.

Usage

Prerequisite: Go ≥ 1.12

Currently, only the REPL of GoBCDice can be built and run.

cd cmd/GoBCDiceREPL

# Build REPL
go build

# Run REPL
./GoBCDiceREPL

To modify and build the dice notation parser (pkg/core/parser/parser.go), you also need to install github.com/mna/pigeon first:

GO111MODULE=off go get -u github.com/mna/pigeon

And then build it with the following commands:

cd pkg/core/parser
make

Core dice roller

The core dice roller of GoBCDice provides the common dice rolling feature.

BCDice supports the following dice notations (the detailed description can be found at bcdice/BCDice/docs/README.txt on GitHub). The notations currently supported by GoBCDice are checked.

  • Sum roll (加算ロール, D): xDn
    • With success check: xDn>=y etc.
  • Basic roll (バラバラロール, B): nBx
    • With success check: xBn>=y etc.
  • Exploding roll (個数振り足しロール, R): xRn>=y etc.
  • Compounding roll (上方無限ロール, U): xUn[t]
    • With success check: xUn[t]>=y etc.

x: number of dice, n: sides of die, y: target number, t: threshold for rerolling dice.

The optional syntaxes are as follows:

  • Embedding random number: [min...max]
  • Secret roll: SxDn etc.

The core dice roller also supports the following commands:

  • Calculation (arithmetic operation, C): C(1+2-3*4/5) etc.
  • Random sampling (choice): CHOICE[A,B,C] etc.

Operators

Operators available in dice rolling and calculation are listed.

Arithmetic operators

In arithmetic operations, a numerical value is treated as an integer.

  • Unary operators
    • Unary plus (noop) +
    • Unary minus (sign inversion) -
  • Binary operators
    • Add +
    • Subtract -
    • Multiply *
    • Divide
      • Divide with rounding down /
      • Divide with rounding /R
      • Divide with rounding up /U

Comparison operators

Comparison operators are used for a success check.

  • Equal to =
  • Not equal to <>
  • Less than <
  • Less than or equal to <=
  • Greater than >
  • Greater than or equal to >=

Author

raa0121

Original BCDice authors are Faceless and Taitai Takeru (たいたい竹流).

gobcdice's People

Contributors

blhsrwznrghfzpr avatar ochaochaocha3 avatar raa0121 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

gobcdice's Issues

ランダム選択コマンド(Choice)を実装する

https://github.com/torgtaitai/DodontoF/blob/v1.49.01/src_ruby/diceBotInfos.rb

choice[a,b,c]:列挙した要素から一つを選択表示。ランダム攻撃対象決定などに

このコマンドの場合、トークンの切り出し方が他のコマンドとまったく違う([] 内はカンマで区切られた任意の文字列)ため、字句解析器に状態を持たせて動作を分岐させる必要がある。

参考:青木峰郎『Rubyソースコード完全解説:第11章 状態付きスキャナ

抽象構文木の例

  • 入力:CHOICE[A,B,C]
    • 抽象構文木:(Choice "A" "B" "C")

ダイスロールで取り出されたダイスの面数が指定と異なる場合にエラーを発生させる

例えば (2*3-4)d6-1d4+1 (-> 2d6-1d4+1) という入力に対応するダイスロール結果を

6/6, 2/6, 3/6(最後のダイスの面数が4ではなく6になっている)

と指定した場合に、どどんとふのテストではエラーが発生するが、GoBCDiceではそのまま通っていた。テストケースの誤りに気づかない場合が想定されるため、どどんとふと同様に、指定した面数とダイスロール結果の面数が異なる場合にエラーが発生することが望ましい。

個数振り足しロール(RRoll)を実装する

ダイスをバラバラにロールして、その成功数の個数分さらにロールして成功度を加算するロール(天羅万象など)にも対応しました。振り足し回数はゲーム設定で対応になっています。(デフォルトは無限) BのかわりにRを使うことで使用できます。条件式は必須で、ダイスの括弧内前処理も使えます。

例)

3r6>=4   3R6=6   3r6+2r6<=2   (3+2)r6>=5

個数振り足しの最大個数は10,000(Ruby版ではDiceBotクラスで定義)。

bcdice/BCDice#65 も参照。

再定義

READMEの記述と直感的に期待される挙動から、仕様を再定義する。

  • ダイスの指定はnRxのようにし、+で複数繋げられる
  • ダイス指定の直後に成功条件式を >=6<2 のように記述する
  • 指定された条件式を用いて各ダイスの成否判定が行われる
  • 成功したダイスは振り直される
  • 成功した回数をカウントし、成功数とする
  • []をつかった2R3+4R5[6]のような式は2R3+4R5>=6 の糖衣構文とする
  • 式中の数値は、0以上のみ受け付ける

抽象構文木の例

  • 入力:3r6>=4
    • 抽象構文木:(RRollComp (>= (RRollList nil (RRoll 3 6)) 4))
  • 入力:2R6+3R4>=4
    • 抽象構文木:(RRollComp (>= (RRollList nil (RRoll 2 6) (RRoll 3 4)) 4))
  • 入力:2r6[3]
    • 抽象構文木:(RRollList 3 (RRoll 2 6))
  • 入力:2r6[3]>=4
    • 抽象構文木:(RRollComp (>= (RRollList 3 (RRoll 2 6)) 4))

結果の型

成功数

object.Integer

結果のメッセージの例

  • 入力:3r6>=4
    • 出力:DiceBot : (3R6[4]>=4) > 1,2,5 + 4 + 1 > 成功数2
    • 境界値 [4] と条件の数値は等しい。
  • 入力:3r6>(1+3)
    • 出力:DiceBot : (3R6[4]>4) > 5,3,3 + 5 + 3 > 成功数2
    • 中置表記の右辺は評価後の数値。

文法をPEGで書き直す

以下の問題が発覚したため、構文解析を現在のLALR(1)構文解析器で行うことが不可能と分かった。原因は、トークンの先読みが1個しかできないことだった。

  • ランダム要素が含まれない数式部分と、ランダム要素が含まれ得るダイスロールの引数部分を見分けられない。
  • 端数指定除算の RU とダイスロールの xRnxUn が見分けられない。

そこで、これらの問題を解消できるPEG(Parsing Expression Grammar)構文解析器を作る。PEG構文解析器の特長は、トークンを無限回先読みできることと、解析の順番を明記できることである。一方、PEG構文解析器には左再帰(a <- a PLUS b など)を単純に書けないという弱点もある。これまでのLALR(1)構文解析器のテストコードをほぼそのまま使えるようにし、最終的に生成される抽象構文木が等しくなるように作る。

シークレットロール(S)を実装する

さらにこれらのダイスロールは他のプレイヤーに隠れてロールすることも可能です。
ダイスコマンドの先頭に「S」をつけてロールすると本人だけに返事が返ってきます。

例)

S3D6   S3B6   S6R6>=5   S3u6+5u6[6]>=7

全員のシークレットロールの結果を一斉に公開することが出来ます。
デフォルトの公開コマンドは「Open Dice!」です。

Quoridornを見ると、公開はオンセツール側で対応しているようなので、ダイスロールがシークレットロールかどうかを判断して結果にその情報を含めることのみ行う。

バラバラロール(BRoll)を実装する

https://github.com/bcdice/BCDice/blob/v2.03.01/docs/README.txt

ダイスの値の和を使うのではなく「ある値以上のダイスの個数」を使うゲーム(シャドウランなど)にも対応しました。Dの代わりにBを使うことで、合計ではなくバラバラの値が表示されます。ボーナスを記入することは出来ません。

例)

3b6   10b6   8B10

最後に条件を指定すると、条件にあうダイスがいくつあるのか数えます。使える条件は、以上(>=)、以下(<=)、未満(<)、より大(>)、以外(<>)、同じ(=)です。ダイス同士の加算式のみ使えます。ただしダイスの面数は揃えてください。条件の数値に計算式は使えません。ただし括弧内前処理は可能です。

構文解析するため、条件指定時に条件部に計算式を使うことも可能。説明ではダイスの面数を揃えることが要求されているが、どどんとふ上では異なる面数でも結合できた。

抽象構文木の例

  • 入力:2b6+4b10(条件なし)
    • 抽象構文木:(BRollList (BRoll 2 6) (BRoll 4 10))
    • BRollList は複数個の子を持つ。
  • 入力:2b6+4b10>3(条件あり)
    • 抽象構文木:(BRollComp (> (BRollList (BRoll 2 6) (BRoll 4 10)) 3)
    • ここで出てくる比較演算子は、BRollの各結果に対して、リスト操作のmapで効くようなイメージ。

結果の型

  • 条件なし:object.Array<*object.Integer>
  • 条件あり:object.Array<*object.Integer>, object.Integer

結果のメッセージの例

  • 入力:2b6+4b10(条件なし)
    • 出力:DiceBot : (2B6+4B10) > 1,3,1,10,3,1
  • 入力:2b6+4b10>3(条件あり)
    • 出力:DiceBot : (2B6+4B10>3) > 4,2,4,2,10,4 > 成功数4

ランダム数値取り出しの評価を実装する

ランダム数値取り出し [m...n] の評価を実装する。

ランダム数値取り出しは、ダイスロールの一種として扱われることが分かっている。これは、どどんとふ上でランダム数値取り出しを入力すると、[1...n]n が4、8、10、12、20、100だった場合に対応するダイスが表示されたためである。そこで、既存のダイスロールとうまく共存させることを意識して実装する。

追記:

[m...n] のランダム数値取り出しは、実際にダイスロールに変換されていた。このランダム数値取り出しは、r = n - m + 1(mからnまでの整数の数)とおくと、1Dr に変換される。このダイスロールの結果を d と書くと、ランダム数値取り出しの結果は (m - 1) + d = m + d - 1 となる。

現在のBCDiceの実装では、m < n の場合のみ受け付け、そうでない場合はエラーになる。

参考:https://github.com/bcdice/BCDice/blob/f8881ea551565592580bb1dd9d8348f2d2cdfb58/src/bcdiceCore.rb#L1595-L1617

中置表記:括弧をつけるべきかの判断

中置記法表現を生成する際、演算子の優先順位や可換性を考慮して、括弧をつけるかどうかを決定する。

Rubyによるアルゴリズム実装例:

module Ast
  module Feature
    # 可換な2項演算子
    module Commutative
      def commutative?
        true
      end
    end

    # 非可換な2項演算子
    module Noncommutative
      def commutative?
        false
      end
    end

    module Parenthesization
      # 優先度を利用して括弧で囲む
      # @param [Object] element 対象要素
      # @return [Array] 括弧で囲んだかと変換後の結果
      def parenthesize_by_precedence(element)
        if element.precedence < self.precedence
          [true, "(#{element})"]
        else
          [false, element.to_s]
        end
      end
    end

    # 左結合の2項演算子
    module LeftAssociativeBinaryOperator
      include Parenthesization

      # 演算子を間に入れる
      # @return [String]
      def inject_operator(operator, inject_spaces = true)
        is_left_parenthesized, left_str = parenthesize_by_precedence(left)
        is_right_parenthesized, right_str_1 = parenthesize_by_precedence(right)
        right_str =
          if right.precedence == self.precedence && !right.commutative?
            "(#{right_str_1})"
          else
            right_str_1
          end

        [left_str, operator, right_str].join
      end
    end
  end

  # 加算
  class Add < Struct.new(:left, :right)
    include Feature::LeftAssociativeBinaryOperator
    include Feature::Commutative

    def precedence
      100
    end

    def to_s
      inject_operator('+')
    end
  end

  # 減算
  class Subtract < Struct.new(:left, :right)
    include Feature::LeftAssociativeBinaryOperator
    include Feature::Noncommutative

    def precedence
      100
    end

    def to_s
      inject_operator('-')
    end
  end

  # 乗算
  class Multiply < Struct.new(:left, :right)
    include Feature::LeftAssociativeBinaryOperator
    include Feature::Commutative

    def precedence
      200
    end

    def to_s
      inject_operator('*')
    end
  end

  # 除算
  class Divide < Struct.new(:left, :right)
    include Feature::LeftAssociativeBinaryOperator
    include Feature::Noncommutative

    def precedence
      200
    end

    def to_s
      inject_operator('/')
    end
  end
end

上方無限ロール(URoll)を実装する

ダイスをバラバラにロールして、その結果が上方無限境界値以上になると再度振り、その値をダイス目に加えることができるロール(ロールマスターやシャドウランなど)にも対応しました。これは上方無限が成立し続ける限り振り続けます。BのかわりにUを使うことで使用できます。境界値は"[境界値]"で指定するほかにマスターコマンドで自動設定もできます。条件式がない場合は、合計値が出るようにしました。条件式をつけると目標値にいくつ達したかも表示します。

例)

3u6[6]   1U100[96]+3   3u6+5u6[6]>=7   (5+6)u10[10]+5>=8

(マスターコマンドで境界値登録してある場合は 3u6>=6 と記述できます。)

マスターコマンドがどどんとふやBCDice-APIで使えるかは不明なので、まずは境界値を指定した場合のみを実装する。

抽象構文木の例

  • 入力:3u6[6]
    • 抽象構文木:(URollExpr (URollList 6 (URoll 3 6)))
  • 入力:1U100[96]+3
    • 抽象構文木:(URollExpr (+ (URollList 96 (URoll 1 100)) 3))
  • 入力:3u6+5u6[6]>=7
    • 抽象構文木:(URollComp (>= (URollExpr (URollList 6 (URoll 3 6) (URoll 5 6))) 7))
  • 入力:(5+6)u10[10]+5>=8
    • 抽象構文木:(URollComp (>= (URollExpr (+ (URollList 10 (URoll (+ 5 6) 10)) 5)) 8))

評価の対象とする型、結果の型

結果のメッセージの例

  • 入力:3u6[6]
    • 出力:DiceBot : (3U6[6]) > 11[6,5],7[6,1],7[6,1] > 11/25 (最大/合計)
  • 入力:1U100[96]+3
    • 出力:DiceBot : (1U100[96]+3) > 155[98,57]+3 > 158/158(最大/合計)
  • 入力:3u6+5u6[6]>=7
    • 出力:(3U6+5U6[6]>=7) > 3,5,3,10[6,4],1,15[6,6,3],5,1 > 成功数2
  • 入力:(5+6)u10[10]+5>=8
    • 出力:DiceBot : (11U10[10]+5>=8) > 3,2,7,4,7,5,4,4,7,16[10,6],1+5 > 成功数9

AST: 構造体の整理

ASTノードの構造体を整理して少なくする。メンバ変数、関数がほとんど同じ構造体が多いため。

方針

現在実装した評価器および中置表記処理における、型による分岐で必要なものを残す。

残すもの、マージ元

以下の箇条書きについて、残すものは第1レベル、マージ元は第2レベル。

  • ast.Command
    • ast.DRollExpr
    • ast.DRollComp
    • ast.BRollComp
    • ast.RRollComp
    • ast.URollComp
    • ast.Calc
  • ast.BRollList
  • ast.RRollList
  • ast.URollExpr
  • ast.Choice
  • ast.PrefixExpression
    • ast.UnaryMinus
  • ast.BasicInfixExpression
    • ast.Compare
    • ast.Add
    • ast.Subtract
    • ast.Multiply
  • ast.Divide
    • ast.DivideWithRoundingUp
    • ast.DivideWithRounding
    • ast.DivideWithRoundingDown
  • ast.VariableInfixExpression
    • ast.DRoll
    • ast.BRoll
    • ast.RRoll
    • ast.URoll
    • ast.RandomNumber
  • ast.Int
  • ast.String
  • ast.Nil
  • ast.SumRollResult

加算ロールの成功判定を実装する

https://github.com/bcdice/BCDice/blob/v2.03.01/docs/README.txt

加算ロールの結果に条件式で目標値を指定して、ゲーム固有の成功度判定を自動表示することができます。現在は"<="と">="のみ対応。判定のためにはゲーム設定を行う必要があります。成功度を必要としない成功か失敗のみの判定は、ゲーム設定無しでも出来ます。

基本的には比較演算子の実装なので、バラバラロールと同様に以下の演算子を実装すればよい。

  • 以下(less than or equal to)<=
  • 以上(greater than or equal to)>=
  • 未満(less than)<
  • 超過(greater than)>
  • 等価(equal to)=
  • 非等価(not equal to)<>

抽象構文木の例

  • 入力:2d6>7
    • 抽象構文木:(DRollComp (> (DRoll 2 6) 7))

評価の対象とする型、結果の型

単純な成功判定ならば以下でよさそう。とりあえず今は論理型を返すのみにしたい。

object.Integer 演算子 object.Integer -> object.Boolean

TODO:クリティカルやファンブルはどうするか?(必要なゲームシステムのダイスボットを実装するときに考えたい)

結果のメッセージの例

  • 入力:2d6+1>7
    • 出力:
      • DiceBot : (2D6+1>7) > 7[4,3]+1 > 8 > 成功
      • DiceBot : (2D6+1>7) > 2[1,1]+1 > 3 > 失敗
  • 入力:2d6+1>(2*4)
    • 出力:
      • DiceBot : (2D6+1>8) > 9[6,3]+1 → 10 > 成功
      • DiceBot : (2D6+1>8) > 4[3,1]+1 → 5 > 失敗
      • 中置表記の右辺は評価後の数値。

評価結果の文字列化

抽象構文木の項の書換えおよび評価によって結果のメッセージを生成する。以下では nDx[m...n] といったランダムに変化する部分を「可変ノード」と呼ぶ。

  1. 可変ノードの引数を評価して整数に変換し、その結果の中置表記を生成する。
  2. 可変ノードの値を確定させる段階で止める評価を行い、その結果の中置表記を生成する。
  3. 最後まで評価を行い、最終的な評価結果を文字列に変換する。

ゲームタイプと1〜3の文字列を " > " で結合させた結果を並べれば、結果のメッセージができる。

例:

  • 入力:(2+3)d6-1+3d6+2
  • 結果のメッセージ:DiceBot : (5D6-1+3D6+2) > 17[2,3,1,5,6]-1+13[5,4,4]+2 > 31

ダイスロール結果のノードを作る

ダイスロール結果を記録し、中置表記でそれらを表示できるノードを作る。

最終的に出るメッセージは以下のようなもの。

DiceBot : (3D6/2+1D6) > 3[1,2,4]+5[5] > 8

これは

DiceBot : (入力された式の中置表記) > 値決定後の中置表記 > 評価結果

と表せる。真ん中の段階で、振られたダイスの出目と合計を含むノードが必要となるので、これを実装する。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.