三すくみルールを取り入れたライフゲーム

# 概要

三すくみルールを取り入れたライフゲームです。
コンウェイのライフでは、各セルは生・死の２つの状態のみとりますが、新しいルールにおいては同一位置を占める R（Rock）/ P（Paper）/ S（Scissors）の３つの層それぞれの生死を考えます。

R / P / S それぞれの層は、基本的にコンウェイのライフとまったく同一のルールで動作します。

ただし、同一セルに R / P / S の生状態は共存できないものとします。次セル状態で共存を強いられそうになったときにはじゃんけんのルールに従って生き残りを決定することにしました（決定不能の場合にはいずれも死滅させます）。

また、隣接セルに異なる種類、特に自分よりじゃんけんのルール上弱い種類の生状態があった場合、本来のルールよりも生存あるいは生成の条件を緩和させます。

# はじめに

コンウェイのライフでは、各セルは生・死の２つの状態のみとります。
つまり、一種類の種しか存在していない世界です。

三種類の種が存在する世界にルールを拡張したいと思いました。
基本的な考え方は以下です。

- １つのセルは三種類の種のどれか１つのみが生状態を取ることができる。
- 一種類の種しか存在しない空間での振る舞いはコンウェイのライフと同一とする。
- 三すくみルール上で弱い（劣位の）種がセルに隣接している場合には、本来のルールでの生存条件・生成条件を若干緩和する（弱い種が隣接しているとより生存・生成しやすくなる）
- 三すくみルール上で強い（上位の）種がセルに隣接している場合には、本来のルールでの生存条件・生成条件を厳しくする（強い種が隣接しているとより生存・生成しにくくなる）
- ルールを適用した結果、同一セルに複数種が生成しそうになった場合、三すくみルールに従って勝ち抜く種を決定する。決定不能な場合（三種類全ての生成の場合）には、生成なしとする。

# ルールの具体化

これを以下のように実現することにしました。

まず、「グー」「チョキ」「パー」に対応する３種類の層を考えます。これらの層は１つのセル空間を共有します。

より具体的な実装イメージとしては、例えば、１つのセルを 3bit で表現するならば、「グー」は 0x01, 「チョキ」は 0x02、「パー」は 0x04 といった形で表現することができます。

コンウェイのライフにおけるルールは以下です。

<table>
<tr><th>現在のセルの状態</th><th>隣接セルにおける生状態の和</th><th>次のセルの状態</th></tr>
<tr><td>生</td><td>2</td><td>生</td></tr>
<tr><td>生</td><td>3</td><td>生</td></tr>
<tr><td>死</td><td>3</td><td>生</td></tr>
<tr><td colspan=2>上記以外すべて</td><td>死</td></tr>
</table>

これを以下のようにします。

<table>
<tr><th>現在のセルの状態</th>
<th>隣接セルにおける同一種の生状態の和</th>
<th>隣接セルにおける劣位種の生状態の和</th>
<th>隣接セルにおける上位種の生状態の和</th>
<th>次のセルの状態</th></tr>
<tr><td>生</td><td>?</td><td>?</td><td>?</td><td>生</td></tr>
<tr><td>生</td><td>2</td><td>?</td><td>?</td><td>生</td></tr>
<tr><td>生</td><td>3</td><td>?</td><td>?</td><td>生</td></tr>
<tr><td>死</td><td>?</td><td>?</td><td>?</td><td>生</td></tr>
<tr><td>死</td><td>3</td><td>?</td><td>?</td><td>生</td></tr>
<tr><td colspan=4>上記以外すべて</td><td>死</td></tr>
</table>

ここで、

- 「隣接セルにおける同一種の生状態の和」というのは、例えば「グー」のあるセルの場合では隣接する「グー」状態セルの和の和です。
- 「隣接セルにおける劣位種の生状態の和」というのは、例えば「グー」のあるセルの場合では隣接する「チョキ」状態セルの和の和です。
- 「隣接セルにおける上位種の生状態の和」というのは、例えば「グー」のあるセルの場合では隣接する「パー」状態セルの和の和です。
- 「?」としているのは、これから決定すべき部分です。

１つの種のみ存在する場合にはライフゲームの動きを再現する、という前提条件から、少なくともルールには以下が含まれなければなりません。

<table>
<tr><th>現在のセルの状態</th>
<th>隣接セルにおける同一種の生状態の和</th>
<th>隣接セルにおける劣位種の生状態の和</th>
<th>隣接セルにおける上位種の生状態の和</th>
<th>次のセルの状態</th></tr>
<tr><td>生</td><td>2</td><td>0</td><td>0</td><td>生</td></tr>
<tr><td>生</td><td>3</td><td>0</td><td>0</td><td>生</td></tr>
<tr><td>死</td><td>3</td><td>0</td><td>0</td><td>生</td></tr>
</table>

上記のテーブルで「?」にしている部分は、適当にルールを決めたプログラムを作って動かしてみてそれっぽい挙動を生み出すそれを選択する、という安直な方法で決めることにしました。

- 劣位種が隣接している場合にはより生存・生成しやすくなる
- 上位種が隣接している場合にはより生存・生成しにくくなる

という構想を元に実験してみて

- 劣位種が隣接している場合にはより生存・生成しやすくなる → あまり条件をゆるくしすぎると三種類が共存するパターンが空間全体を埋め尽くしていくつまらない結果になってしまった
- 上位種が隣接している場合にはより生存・生成しにくくなる → もともとのライフゲームのルールがそれほど生存・生成しやすいものではないので厳しくすることに意義が見いだせなかった

ということから、以下のようなルールに落ち着きました:

<table>
<tr><th>現在のセルの状態</th>
<th>隣接セルにおける同一種の生状態の和</th>
<th>隣接セルにおける劣位種の生状態の和</th>
<th>隣接セルにおける上位種の生状態の和</th>
<th>次のセルの状態</th></tr>
<tr><td>生</td><td>1</td><td>2以上</td><td>任意</td><td>生</td></tr>
<tr><td>生</td><td>2</td><td>任意</td><td>任意</td><td>生</td></tr>
<tr><td>生</td><td>3</td><td>任意</td><td>任意</td><td>生</td></tr>
<tr><td>死</td><td>1</td><td>3以上</td><td>任意</td><td>生</td></tr>
<tr><td>死</td><td>2</td><td>2以上</td><td>任意</td><td>生</td></tr>
<tr><td>死</td><td>3</td><td>任意</td><td>任意</td><td>生</td></tr>
<tr><td colspan=4>上記以外すべて</td><td>死</td></tr>
</table>

ここで「任意」としているのはどんな値であっても気にしないという意味です。

OpenCL と OpenGL で実装したのが以下です:

https://github.com/tadashi9e/game-of-life-opencl/tree/Rock-Paper-Scissors_Life_Game

実行例は以下です:

<https://youtu.be/2De6dKQT-TE?si=dZM5EAaHFM9RfPMd>

（赤は「グー」、緑は「チョキ」、青は「パー」として表示しています）

# まとめ

三すくみを取り入れたライフゲームのルールを考えてみました。

ルールを緩和しすぎると三種類のセルが共存するパターンがあまりに強くなりすぎるので、そこの加減が必要でした。

ライフゲームにこだわらなければもっと面白いパターンを作るルールがありそうです。

今回は OpenCL-OpenGL 間連携実装の勉強のために OpenCL と OpenGL を使ったのですが、ルールを手探りで決めていくにはむやみに実行性能を求める必要がない（むしろあまり速すぎるとなにが起きているのかわからなくなる）ので、Python を使って実装したほうがよかったかもしれないと思いました。