サン・トウカできのみを貰う処理を例にします。処理は以下の通りです。
- 乱数を1個取得する
- 乱数を8で割った余りを計算する
- 以下のリストと照合し、対応するインデックスのきのみが貰える。
- `[ クラボ, カゴ, モモン, チーゴ, ナナシ, ヒメリ, オレン, キー ]`
これを、ライブラリをまったく使わずに書くと以下のようになります。
```csharp=
String PrettyPetal(uint seed) {
var berries = new string[] { "クラボ", "カゴ", "モモン", "チーゴ", "ナナシ", "ヒメリ", "オレン", "キー" };
seed = seed * 0x41C64E6Du + 0x6073u;
var rand = seed >> 16;
var berryIndex = rand % 8;
var berry = berries[berryIndex];
return berry;
}
```
このような書き方はいくつか問題があります。すぐに思いつくだけでも以下のようなものがあります。
- seedの更新処理に使われる定数がべた書きになっている
- 定数を間違う恐れがある(筆者は何度も書いたので暗記しています)
- 定数がマジックナンバーになっており、初見では読む人に意味が伝わらない
- seedの更新と乱数取得の順序を間違う恐れがある
- 古くに作られたツールでは「seedと乱数の対応関係を1つ分ずらす」という対応をしているものも多いが、これは本来存在しない『見かけ上の消費』を導入しなければならなくなる。「甘い香りで1消費」が有名な例。
- 「乱数の取得」という意味のあるひと塊の処理がどこからどこまでなのかがわかりづらい
PokemonPRNGを使うと以下のように書けます。
```csharp=
using PokemonPRNG.LCG32.StandardLCG;
String PrettyPetal(uint seed) {
var berries = new string[] { "クラボ", "カゴ", "モモン", "チーゴ", "ナナシ", "ヒメリ", "オレン", "キー" };
var berryIndex = seed.GetRand(8);
var berry = berries[berryIndex];
return berry;
}
```
(ここでもう力尽きた)
### 主な機能
LCGの操作として頻出する以下の処理が提供されています。
- `seed`を1つ進める : `Advance()`
- `seed`を一度に複数進める : `Advance(n)`
- `seed`を1つ戻す : `Back()`
- `seed`を一度に複数戻す : `Back(n)`
- 1つ進めた`seed`を得る(元の値は変更しない) : `NextSeed()`
- 複数進めた`seed`を得る(元の値は変更しない) : `NextSeed(n)`
- 1つ戻した`seed`を得る(元の値は変更しない) : `PrevSeed()`
- 複数戻した`seed`を得る(元の値は変更しない) : `PrevSeed(n)`
- 乱数を取得する(`seed`は1つ進む): `GetRand()`
- 乱数を`m`で割った余りを取得する(`seed`は1つ進む): `GetRand(m)`
- `0`からいくつ進めた`seed`かを取得する : `GetIndex()`
- `initialSeed`からいくつ進めた`seed`かを取得する : `GetIndex(initialSeed)`
`Advance(n)`や`GetIndex()`は効率的なアルゴリズムで実装されているため、処理速度の低下を気にすることなく使うことができます。
### 乱数処理のコンポーネント化
乱数処理を再利用可能にするために、部品(コンポーネント)化しましょう。
「乱数を使った生成処理をコンポーネント化したもの」を表すインタフェース `IGeneratable` が用意されています。
```csharp=
class PrettyPetalBerryGenerator : IGeneratable<string>
{
private static readonly string[] _berries = { "クラボ", "カゴ", "モモン", "チーゴ", "ナナシ", "ヒメリ", "オレン", "キー" };
public string Generate(uint seed)
=> _berries[seed.GetRand(8)];
}
```
これを利用する側は次のように書きます。
```csharp=
var seed = 0xBEEFCAFE;
var prettyPetal = new PrettyPetalBerryGenerator();
var berry = prettyPetal.Generate(seed);
```
また、`Advance`や`GetRand` などに合わせて、`seed` を主語にして書けるように、レシーバと引数を逆転させる拡張メソッドも用意されているため、以下のようにも書けます。
```csharp=
var seed = 0xBEEFCAFE;
var prettyPetal = new PrettyPetalBerryGenerator();
var berry = seed.Generate(prettyPetal);
```
ホウエン地方には他にもランダムにきのみをくれるNPCがいます。他のNPCのバリエーションも作成してみましょう。
```csharp=
class LilycoveCityBerryGenerator : IGeneratable<string>
{
private static readonly string[] _berries = { "クラボ", "カゴ", "モモン", "チーゴ", "ナナシ", "ヒメリ", "オレン", "キー", "ラム", "オボン" };
public string Generate(uint seed)
=> _berries[seed.GetRand(10)];
}
class BerryMasterBerryGenerator : IGeneratable<string>
{
private static readonly string[] _berries = { "ザロク", "ネコブ", "タポル", "ロメ", "ウブ", "マトマ", "モコシ", "ゴス", "ラブタ", "ノメル" };
public string Generate(uint seed)
=> _berries[seed.GetRand(10)];
}
```
同じような処理が繰り返し現れています。重複を無くしましょう。
```csharp=
abstract class BerryGenerator : IGeneratable<string>
{
private static readonly string[] _berries;
public BerryGenerator(string[] berries)
=> _berries = berries;
public string Generate(uint seed)
=> _berries[seed.GetRand((uint)_berries.Length)];
}
class LilycoveCityBerryGenerator : BerryGenerator
{
public LilycoveCityBerryGenerator() : base(new string[] { "クラボ", "カゴ", "モモン", "チーゴ", "ナナシ", "ヒメリ", "オレン", "キー", "ラム", "オボン" });
}
class BerryMasterBerryGenerator : BerryGenerator
{
public BerryMasterBerryGenerator() : base(new string[] { "ザロク", "ネコブ", "タポル", "ロメ", "ウブ", "マトマ", "モコシ", "ゴス", "ラブタ", "ノメル" });
}
```
「ランダムに貰えるきのみの処理はほぼ定型だけど、きのみのラインナップを差し替えてバリエーションを作りたい」「きのみのラインナップは既知のデータとして提供したい」を両立するために継承を使っています。この程度なら穏当な継承の使い方と言えるでしょう(ただし継承を使わないと両立ができないわけではありません)。
### 他のインタフェース
`IGeneratable`のほかに`IGeneratableEffectful`、`ILcgUser`、`ILcgUtilizer`が用意されています。
#### `IGeneratableEffectful`
`IGeneratable`は引数に受け取った`seed`を変更しないのに対し、`IGeneratableEffectful`は受け取った`seed`を書き換えます。生成結果のリストを計算したいときは`seed`を1つずつ進めながら生成結果を列挙したいため、`seed`を書き換えない`IGeneratable`を使うと良いでしょう。一方でコンポーネント化された乱数処理を内部的に使用するような場合は`seed`も一緒に進める必要があります。そのような場合は`IGeneratableEffectful`を使いましょう。
拡張メソッドは`IGeneratable`と同様に`seed.Generate(IGeneratableEffectful)`です。`generator`のクラスが両方のインタフェースを実装している場合、`seed.Generate(generator)`は`IGeneratable`と`IGeneratableEffectful`のどちらを対象とすれば確定できなくなりコンパイルが通らなくなるため、`var generator = new HogeGenerator() as IGeneratable`のように、型をどちらかのインタフェースとして指定しておきましょう。
#### `ILcgUser`
「乱数処理の生成結果には興味が無く、消費を発生させる用途で使いたい」ようなものには`ILcgUser`が使えます。こちらは`seed`を主語とする拡張メソッドは`Used`という名前になっています。
#### `ILcgUtilizer`
`ILcgUtilizer`は…`IGeneratableEffectful`と同じ定義になってますね…。何らかの結果を生成するのが主作用で「乱数の消費」を副作用と見なしたい場合は`IGeneratableEffectful`を、「乱数の消費」が主作用で、副作用として得られる生成結果も利用したい場合は`ILcgUtilizer`を使う…というような使い分けを想定していたような気がします。そのうちしれっと削除するかもしれません。。。
### LINQとの連携
```csharp=
var results = seed.NextSeed(1000)
.EnumerateSeed()
.EnumerateGeneration(generator)
.WithIndex(1000)
.Take(9000)
.Where((result) => result.Content.Nature == Nature.Adamant);
foreach(var (frame, individual) in results) {
Console.WriteLine($"{frame}F {individual.PID:X8} {individual.Nature}");
}
```
慣れると便利です。