がるの健忘録

エンジニアでゲーマーで講師で占い師なおいちゃんのブログです。

TRPGの成功判定で学ぶオブジェクトの切り方への一案

とりあえずきれいごとな建前として「クラスをどう設計していくのかの一例を示してみたい」っていうあたりの要望がある、ってことにしといて。


ネタ的にものすごくニッチな気もするのだが。
書きたくなったんだからしゃ〜ないw
# っていうか「実装する気満々だったりする」ので単純に「ちょ〜どいいから記事にして備忘録にしちゃえ〜」って感じです B-p


まず、大雑把に「TRPGにおける成功判定(ダイスを使うもの)」について「最アバウトレイヤー」で考える。
・ダイスを振る
・成功したり失敗したりする:結果とか呼んでみる


うんこんなもんだ。
プログラムっぽく書くとこんな感じ。

結果 = ダイスを振る();

素晴らしい。
見もふたもない、とも言う。


さて。結果とダイスを振る、のどっちから考察してもいいんだけど…とりあえず結果から。
基本的には「成功」か「失敗」の2択なので。2択ならbooleanでいいんぢゃね? ってことで考えてみる。
この辺の考えの甘さとそれに伴う修正の仕方とかは後でやるから、気づいてたら一端失念してw

boolean = ダイスを振る();


さて、処理への考察。
一般的に「さいころを振って」「ある数値基準に、それより大きかったり小さかったりすると、成功したり失敗したりする」もんです。

boolean = ダイスを振る(振るダイスのタイプと数, 目標値);

そうだなぁとりあえず「D&D 4thのST判定」とか局所なところから。
1d20ふって(20面ダイスを1回振って)10以上なら成功、9以下なら失敗。
この場合要素としては「1d20」「10(以上なら成功)」なので。その辺をてけとうにパラメタっぽくすると…こう?

boolean = ダイスを振る("1d20", 10);


ここいらあたりまではぶっちゃけオブジェクトとかいらない。別にぺたんとしたfunctionで十分。
問題はここから。
「ダイスによる判定」は、実際のところそれはそれはバラエティに富んでたりする。


まずは「上方判定」と「下方判定」。さいころの目が「小さいほうがいい感じ」と「大きいほうがいい感じ」ってのがある。D&Dとかは上方だし(昔は上下ばらんばらんだったんだけどねぇ)。GURPSなんてのは典型的な下方。
さらに。さいころの目を足すのではなくて「個々のさいころと目標値を比較して、目標値を上回ったダイスの数で判定(シャドウラン/WoD)」とかっていうのもあります。
さらにさらに、ごく一部のシステム(具体的には央華封神)には「裏成功」というびみょ〜な成功もあります。
ついでに。結果も「成功と失敗」だけではなくて、一般的には「クリティカルとファンブル」ってのがあります。改心の一撃&痛恨の一撃とかまぁ色々表現がありますが。ないものもあります。


まずとりあえず「結果、ってのは2種類とは限らない」らしいことが骨身にしみてわかったので、一端boolean説を取り下げます。
毎度のごとく「結果、というよくわからんモヤモヤしたもの」に戻します。
この辺のフットワークのよさとかケツの軽さとかは重要なのでチェック。試験に出るよ。

結果 = ダイスを振る();


先に結果のほうから再考察。
色々なシステムを大まかに統合して、「結果」というカテゴリに入る状態や情報は
・成功
・失敗
・クリティカル
ファンブル
・裏成功かどうか
・成功数
・失敗数(ボッチ数)
あたり。数を聞くものを除いて、基本的には「個々にis関数を作って問い合わせる」のが楽なので。
極めて大まかに、この子をクラスにします。

class 結果
{
  public abstract boolean is成功();
  public abstract boolean is失敗();
  public abstract boolean isクリティカル();
  public abstract boolean isファンブル();
  public abstract boolean is裏成功かどうか();
  public abstract int get成功数();
  public abstract int get失敗数();
}

となるわけですな。結果の状態が増えたら適当に増やしといて、っていう気軽さがオブジェクトの身上。
なので、一端、戻り値である「結果」については考察終了。


次に「ダイスを振る」ほう。
結局のところ「ある目標値に対してダイスを振ってどうだったか」を聞くので、本質的な部分である

結果 = ダイスを振る(振るダイスのタイプと数, 目標値);

の部分には差異がない。ここ重要。


差異がないからんじゃってんで、割とすぐに思いつくのはこーゆーインタフェース。

結果 = ダイスを振る(判定種別, 振るダイスのタイプと数, 目標値);

い〜んだけどっていうかぜんぜんよくないんだけど。
上述の実装は、やらせてみると、大体こんな感じ。

結果 function ダイスを振る(判定種別, 振るダイスのタイプと数, 目標値)
{
  switch(判定種別)
  {
  case 'D&DのST':
   処理;
   処理;
   処理;
   break;

  case 'ソードワールド':
   処理;
   処理;
   処理;
   break;

  case 'シャドウラン':
   処理;
   処理;
   処理;
   break;

  }

 - 以下略 -

で、保守性の観点などから、はっきりいって「ンベ B-p」な感じです。
これをみて「きしょい」って思える程度の感性は、もっていて損ないと思う。
こゆ時に、GoFの「工場(Factory)」をつかうであるざんす。


んと…イメージを大まかに書くと、こう。

ダイス処理インスタンス = 工場::作成依頼(システム名);
結果 = ダイス処理インスタンス->処理よろ〜(振るダイスのタイプと数、目標値);

ずぼらな人的には、こう。

結果 = 工場::作成依頼(システム名)->処理よろ〜(振るダイスのタイプと数、目標値);


これをやると「システムが増えたとき」に有利なほかに「とあるシステム(用のダイス処理)への修正」が「ほかのシステム(用のダイス処理)に影響しない」ので、その辺がなんていうか「気軽」です。


まぁ、こうやっておいちゃんはクラスを切っていくわけですね。
「上から下に」落とすんじゃなくて、もっとこう…なんていうか…あぢゃいるでいんたらくてぶな感じ?(いってみたかっただけ)
こまめに作って、ちょこちょこと修正しながら、時々は大本に立ち戻りながら、ゆっくりと「荒削りから仕上げに向けて」進めていく感じ。


このほうが楽なんじゃないかなぁ、って思う、おいちゃんからの一案でした。