【Unity】C#で「拡張ラングトンのアリ」を実装した件について

# はじめに

「ラングトンのアリ」って面白いですよね。単純な法則だけで美しい図形を描くことが出来るのはとても興味深いです。また、白と黒の2値の世界で行われる「ラングトンのアリ」を拡張し、アリの法則を自由に設定できる「ラングトンのアリの拡張」も不思議で面白いですよね。この、「ラングトンのアリの拡張」をUnityで実装してみたら、案外上手くいったので残しておきます。「ラングトンのアリ」を知らない方はまずはこちら↓を読んでください。きっと興味が出てくるはずです。

https://ja.wikipedia.org/wiki/%E3%83%A9%E3%83%B3%E3%82%B0%E3%83%88%E3%83%B3%E3%81%AE%E3%82%A2%E3%83%AA

http://www.iba.t.u-tokyo.ac.jp/~iba/Swarmlec/Langton-ant.pdf

https://bluebacks.kodansha.co.jp/books/9784062578301/appendix/brain-move10/

# 今回のラングトンのアリの仕様

※今回実装する「ラングトンのアリ」は、無限に実行し続けることを可能にするため、一般の「ラングトンのアリ」と異なる場合があります。

・アリは自身が持つ法則に従って行動する。（「色を反転する」というルールを「n+1番目の法則の色に変化させる」と解釈した）

・アリが持つ法則以外の色のマスを踏んだ時、自身が持つ法則の1番目を適応する。~~（ここをランダムに選ぶようにするか迷った。いや、ランダムにすると規則性を持つ意味がなくなるか...う～む）~~

・アリがフィールドの外側に出ようとしたとき、フィールドの反対側に移動する。上端と下端、右端と左端がつながっているフィールドである。（閉塞的な世界での「ラングトンのアリ」である）

# Unityで実装する

今回は、フィールドを作り出すスクリプトである「 GenerateField 」と、アリにあたるスクリプトである「 LangtonsAnt 」を実装します。

### GenerateField のスクリプト

・フィールドの大きさ（幅と高さ）を設定してください。

・マス目にあたるGameObject `Square` は `Hierarchy` -> `2D Object` -> `Sprites` -> `Square` で生成したものをプレハブ化して、参照してください。

・フィールドのマス目を格納する空のオブジェクト `SquareParent` は `Hierarchy` -> `Create Empty` で生成した空のオブジェクトを参照してください。このオブジェクトの子要素にフィールドが格納されます。

<details><summary> GenerateField のスクリプト</summary>

(上に空行が必要)

```C#:GenerateField.C#
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GenerateField : MonoBehaviour
{
    //フィールドの幅
    public int Width = 10;
    //フィールドの高さ
    public int Height = 10;

    //マス目
    public GameObject Square = null;

    //フィールドのマス目を格納する配列
    [SerializeField] private GameObject[,] field;

    //フィールドのマス目を格納する空のオブジェクト
    public Transform SquareParent = null;

    private void Awake()
    {
        field = new GameObject[Height, Width];

        for (int y = 0; y < Height; y++)
        {
            for (int x = 0; x < Width; x++)
            {
                field[y ,x] = Instantiate(Square, new Vector3(x, y, 0.0f), Quaternion.identity, SquareParent);
            }
        }
    }

    /// <summary>
    /// マス目の色を返す関数
    /// </summary>
    /// <param name="x">フィールドのｘ座標</param>
    /// <param name="y">フィールドのｙ座標</param>
    /// <returns>フィールドの[y,x]座標にあるマス目の色</returns>
    public Color getSquareColor(int x, int y)
    {
        return field[y, x].gameObject.GetComponent<SpriteRenderer>().color;
    }

    /// <summary>
    /// フィールドの幅を返す関数
    /// </summary>
    /// <returns>フィールドの幅</returns>
    public int getFieldWidth()
    {
        return this.Width;
    }

    /// <summary>
    /// フィールドの高さを返す関数
    /// </summary>
    /// <returns>フィールドの高さ</returns>
    public int getFieldHeight()
    {
        return this.Height;
    }
 
    /// <summary>
    /// 指定されたフィールドの座標の色を変更する関数
    /// </summary>
    /// <param name="x">フィールドのｘ座標</param>
    /// <param name="y">フィールドのｙ座標</param>
    /// <param name="color">変更した後の色</param>
    public void changeField(int x ,int y, Color color)
    {
        field[y, x].gameObject.GetComponent<SpriteRenderer>().color = color;
    }
}
```
</details>

### LangtonsAnt のスクリプト

・アリの画像（Sprite）にこのスクリプトをアタッチすると、アリの動きがわかりやすいです。

・現在のアリの位置を表す変数、`X` と `Y` を設定してください。初期位置も兼ねています。（XとYのどちらかに-1を入力すると、初期位置がフィールドの中心になります。）

・フィールドを生成するスクリプト `generateField` を参照してください。

<details><summary> LangtonsAnt のスクリプト</summary>

```C#:LangtonsAnt.C#
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 現在のアリの向き（回転角）を表す定数
/// </summary>
public class Direction : MonoBehaviour
{
    public enum direction
    {
        Up = 0,
        Down = 180,
        Left = 90,
        Right = -90,
    }
}

/// <summary>
/// アリが回転する方向を表す定数
/// </summary>
public class Rotation : MonoBehaviour
{
    public enum rotation
    {
        Left,
        Right,
    }
}

/// <summary>
/// アリが従う法則を表す構造体
/// </summary>
[System.Serializable]
public struct LawData
{
    public Rotation.rotation rotation;
    public Color color;
}

public class LangtonsAnt : MonoBehaviour
{
    //現在のステップ数を表す変数
    [SerializeField] private int step = 0;

    //現在のアリの位置を表す変数
    [SerializeField] private int X = 0;
    [SerializeField] private int Y = 0;

    //フィールドを生成するスクリプト
    public GenerateField generateField = null;
    private int fieldWidth = 0;//フィールドの幅を格納する変数
    private int fieldHeight = 0;//フィールドの高さを格納する変数

    //現在のアリの向き（回転角）を表す定数
    [SerializeField] private Direction.direction direction = Direction.direction.Up;

    //アリが従う法則を格納する配列
    [SerializeField] private LawData[] lawDatas = null;

    private void Start()
    {
        fieldWidth = generateField.getFieldWidth();
        fieldHeight = generateField.getFieldHeight();

        //初期座標が負の値であれば、フィールドの中心に配置する
        if (this.X == -1 || this.Y == -1)
        {
            X = fieldWidth / 2;
            Y = fieldHeight / 2;
        }

        StartCoroutine(this.Action());
    }

    IEnumerator Action()
    {
        step++;

        Color color = generateField.getSquareColor(X, Y);

        LawData lawData = lawDatas[0];

        for (int i = 0; i < lawDatas.Length; i++)
        {
            if (color == lawDatas[i].color)
            {
                if (i + 1 >= lawDatas.Length)
                {
                    lawData = lawDatas[0];
                }
                else
                {
                    lawData = lawDatas[i + 1];
                }

                break;
            }
        }

        generateField.changeField(X, Y, lawData.color);

        if (lawData.rotation == Rotation.rotation.Left)
        {
            switch (this.direction)
            {
                case Direction.direction.Up: this.direction = Direction.direction.Left; X = (X - 1 < 0) ? fieldWidth - 1 : X - 1; break;
                case Direction.direction.Down: this.direction = Direction.direction.Right; X = (X + 1 >= fieldWidth) ? 0 : X + 1; break;
                case Direction.direction.Left: this.direction = Direction.direction.Down; Y = (Y - 1 < 0) ? fieldHeight - 1 : Y - 1; break;
                case Direction.direction.Right: this.direction = Direction.direction.Up; Y = (Y + 1 >= fieldHeight) ? 0 : Y + 1; break;
                default: break;
            }
        }
        else if (lawData.rotation == Rotation.rotation.Right)
        {
            switch (this.direction)
            {
                case Direction.direction.Up: this.direction = Direction.direction.Right; X = (X + 1 >= fieldWidth) ? 0 : X + 1; break;
                case Direction.direction.Down: this.direction = Direction.direction.Left; X = (X - 1 < 0) ? fieldWidth - 1 : X - 1; break;
                case Direction.direction.Left: this.direction = Direction.direction.Up; Y = (Y + 1 >= fieldHeight) ? 0 : Y + 1; break;
                case Direction.direction.Right: this.direction = Direction.direction.Down; Y = (Y - 1 < 0) ? fieldHeight - 1 : Y - 1; break;
                default: break;
            }
        }

        this.transform.position = new Vector3(X, Y, 0);
        this.transform.rotation = Quaternion.Euler(0, 0, (int)this.direction);

        yield return new WaitForSeconds(0.01f);

        StartCoroutine(this.Action());
    }
}

```
</details>

# 生成例

拡張ラングトンのアリは白と黒だけでなく、色と回転する方向を自由に設定できます。

生成した模様を例として掲載しておきます。

| ![202237_193145.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1522500/28451ecd-11d7-de31-1d84-752de85d65fb.png) | ![202238_32751.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1522500/e53cf93e-a2cc-6da1-2adc-f090dbb3788a.png) | ![202239_2544.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1522500/ac6b8206-9294-3fbf-fef8-369b8e8ceb15.png) | ![202239_13534.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1522500/d2a856f4-a26c-fe8a-e02b-6a0c48838fd3.png) | ![202239_16031.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1522500/b66f7e1a-2cb5-ea42-1e58-1e86f8637b47.png) | ![37_185152.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1522500/ca039c91-a31c-adfb-4e7b-9e74211e8e30.png) |
|:------------:|:------------:|:------------:|:------------:|:------------:|:------------:|

# おわりに

拡張ラングトンのアリ、いかがでしたか？

この記事を読んで「ラングトンのアリ」に対する興味が少しでも持ってくれたらうれしいです。

この実装方法はステップ数の増加が遅いように感じます。

使用するコンピュータの性能にもよりますが、もう少し良いアルゴリズムがあるかもしれません。

もし早いアルゴリズムを見つけたらぜひ教えてください！

それではまた今度～！