テラリウム徹底攻略ガイドDestinationを使ったコミュニケーション大石 晃裕+デジタルアドバンテージ |
さて、ActualDirectionを使ったコミュニケーションをここまでで説明してきた。次はより実用的なコミュニケーションを考えてみよう。
ActualDirectionを使ったコミュニケーションで最も大きな問題点は、何よりも「効率が悪い」ことだ。実際にデータを送るとして、10ターンも20ターンもそれだけに費やすのはいくら何でも現実的ではない。できれば1ターンで送ることができるようにしたい。
ではなぜ効率が悪いのだろうか。そもそも移動時に指定できるのは、ActualDirectionではなく、Destinationである。これを指定した場合、表せる「場合の数」は減少しているし、ActualDirectionが同じDestinationに対し一定ではないので、ActualDirectionの場合の数はさらに減ることとなる。場合の数は単純にデータ量と考えることができるので、この変換が効率を下げている原因と考えることができる。
では、データの受信側もDestinationを取得すればよい。これを取得するには、始めの方に説明したCurrentMoveToActionプロパティを利用する。これは本連載第5回でも利用しているので、併せてそちらも参考にしていただきたい。
Destinationが取得できるようになったら、あとはこれにどのようにしてデータを載せるかということである。それをこれから考えていこう。
まず、ActualDirectionの場合と同じように、最初に簡単なサンプルを用意した。前出のサンプル同様、このサンプルも、ひたすらコミュニケーションを続けるだけの生物であり、あくまで実験用である。TerrariumモードでこのサンプルをIntroduceしてもらえれば、コミュニケーションができていることを確認できるはずだ。ただし、これもActualDirectionを使ったコミュニケーションの場合と同じように、コミュニケーションの機能を別クラスに分離している。
→コミュニケーションを実装したクラス(communication2.cs)のダウンロード
→サンプル動物(animal2.cs)のダウンロード
まず、受信者と発信者の間では、データは直接System.Drawing.Point構造体でやりとりされる。このため、先ほどのActualDirectionを使ったコミュニケーションのように、移動において決まりごとをあまり設ける必要はない。具体的には、データとPoint構造体との変換手順を決めればデータはやりとりできる。具体的には、サンプルの実装を基に説明していこう。
データ、ID、とPoint構造体の相互変換
送受信するデータは、今回の場合も基本的には1byteとした。ただし、今度の場合はよりたくさんのデータを送る手段を作る意味でFastModeというデータ発信モードを用意し、この場合にはUInt16型のデータを発信できるようにんする。ただしFastModeの利用には注意すべき点がある。これについては後述する。
また、その生物のID(OrganismクラスのIDプロパティ)も利用するようにした。これはヘッダ情報を作成するために利用している。
まず始めに、表現できるビット数を求めなくてはならない。これは、テラリウムの世界の大きさが実行しているPCの性能によって変化し、固定ではないために必要となる。つまり、Destinationとして指定できる座標が世界の大きさによって限られてしまうからだ。
では、Destinationの場合の数を求めてみよう。X座標は0からWorldWidth-1までの間で指定できるためWorldWidth通り、Y座標は0からWorldHeight-1までの間で指定できるためWorldHeight通りとなる。X座標、Y座標の値は互いに無関係であるため、Destinationの場合の数は、全体としてWorldWidth×WorldHeight通りである。
ただ、これでは扱いにくいので、これによって最大何ビットを表現できるかを考える。この計算は実際に数式を見てもらった方が早いだろう。
表現できる桁数をDigitsとすると、次のように表せる。
Digits = (int)(log2(WorldWidth*WorldHeight))
ちなみに、このコミュニケーションでは、ある数値AとPoint構造体との変換式は以下のようにした。
■Point構造体→数値Aの変換
A = Point.X * WorldHeight + Point.Y
■数値A→Point構造体の変換
Point.X = A / WorldHeight
Point.Y = A % WorldHeight
ほかの表現法を作成することも可能だが、条件としてAとPoint構造体が1対1に対応していなくてはならない。
さて、次にデータ形式を考える。利用できるのは先ほど求めた桁数の2進数である。
これは以下のように規定した。
■NormalModeの場合
下位8bit:データ
残りの上位bit:ヘッダ(ただし最上位bitは0固定)
■FastModeの場合
下位16bit:データ
残りの上位bit:ヘッダ(ただし最上位bitは1固定)
それぞれについて簡単に説明する。
まずデータだが、これはユーザーが指定するものそのままである。これについてはあまり考える必要はない。
重要なのはヘッダである。ヘッダには、「それがデータであるかどうかの判別」と「モードの判別」という2つの役割がある。
ヘッダは、その情報を発信する生物のIDから生成し、同じIDから生成したヘッダは、最上位bitを除いて同じとなる。つまり、生物のIDから生成したものと、届いたヘッダのID部分を比較して一致すればデータであり、一致しなければデータではないということだ。ただし、偶然一致してしまうこともあるので、100%確実に判別できるとはいえない。この点は念頭に置いておく必要がある。
ちなみに、最上位bitを除いているのは、モードを2つ設けたためだ。これを判別するために、最上位bitをNormalModeならば0、FastModeならば1と規定している。簡単にいえば、モード指定に1bit割り当てた、ということである。
さて、ここでは以下の2つの変換を説明した。
- Point構造体と、ある桁数の2進数との相互変換
- ある桁数の2進数と、データとの相互変換
これらを使えば、データとPoint構造体の相互変換が可能になる。これは、
Point ⇔ A ⇔ Data
と書けば分かりやすいだろうか。あとはこれを利用してコミュニケーションを行えばよい。具体的には、発信時は、Data ⇒ Pointと変換して、
BeginMoving( MovementVector(Point, Speed) ); |
とすればよいし、受信する方は、
Point = AnimalState.CurrentMoveToAction.MovementVector.Destination; |
と取得して、Point ⇒ Dataと変換すればよい。また、この過程では、データかどうかの判定も行う。
それでは実装の説明はこれぐらいにして、こちらもCommunicationクラス群の使用法の説明に入ろう。
まずは先ほどと同様に、今回もSpeakクラスとListenクラスがあり、それを利用するようになっている。これら2つは、Communicationという抽象クラスから派生している。このクラスには、コミュニケーションに必要な基本的なメソッドを実装している。
Communicationクラス
このクラスは抽象クラスであり、直接使用することはできない。ただし、SpeakクラスやListenクラスはこれから派生している。
このクラスのパブリック・メンバは次のとおり。
int nPacketBits; //全体のビット数 |
これらは、前述したHeader、Dataそれぞれについてのビット数を返すプロパティ、またはメソッドである。これを利用して、パケットのサイズを取得できる。
また、このクラスには、以下の2つのvirtualメソッドが存在する。
protected virtual BitArray EncodePacket(BitArray bit);
protected virtual BitArray DecodePacket(BitArray bit);
いろいろな生物間でこのクラスを利用する場合、このクラスを実装しているすべての生物間でコミュニケーションが行えるということはお分かりいただけるると思う。しかしこれを望まない場合もあるはずだ。例えば、筆者はこのクラスを公開しているわけだが、このクラスをこのまま使用して自分が作成した生物かどうかを判別し、その生物を攻撃しないようにするとどうなるだろう。ほかの人もこれを利用すれば、偽装が可能である。
また、そこまでのことはなくとも、AとB、CとDはそれぞれコミュニケーションさせたいけれども、AとCとはコミュニケーションさせたくないといった場合もあるはずだ。そういう場合は、これらメソッドをオーバーライドすることによって、通信を暗号化をすることができる。
具体的にはこれは、先ほどの
Point ⇔ A ⇔ Data
の流れから、
Point ⇔ A' ⇔ A ⇔ Data
として実現している。この中央の変換にあたるのがこれらメソッドである。
簡単な例を挙げておこう。以下は、ビット列を反転するだけの暗号化を施したプログラム例である。
protected override BitArray EncodePacket(BitArray bit) |
送信を行うSpeakクラス
まず初期化である。初期化は下のように行う。
Speak Speaker = new Speak(this); |
ここまでは、ActualDirectionを使った場合のコミュニケーションのSpeakクラスと同じである。
しかし、次の、実際にデータを発信する方法が先ほどとは違っている。
Point dest = Speaker.GetPointNormalMode(Data); |
今度はこのように指定しなければならない。つまり、このクラスを利用して行うのは、「そのデータを送るときに指定する場所の取得」である。これは、動作を分離した方がよいと判断したからだ。分離することによって、自分で定義したBeginMovingメソッド相当の関数を利用することも可能になる。
ちなみに、もしFastModeで送信したい場合は、
Point dest = Speaker.GetPointFastMode(Data); |
と、GetPointFastModeの方のメソッドを利用すればよい。
ここで少しFastModeの注意点を書いておこう。
FastModeの最大の問題は、信頼性が低いということである。ここでいう信頼性とは、やり取りされるデータの信頼性ではなく、それが通信用のデータであるかどうかを判別する際の信頼性である。これは、ヘッダに十分なビット数が割り当てられないことによる。実際にどれくらい割り当てられるのかを計算してみよう。仮に1000×1000の広さのWorldであるとする。すると、利用できるビット数は、19bitである。つまりヘッダのサイズは3bitで、通信データかどうかの判別に使われるのは2bitである。すべて一様に分布すると考えれば、1/4の確率で誤認識が起こることになる。
つまり、FastModeはそのまま利用できるものではない。現実的には、NormalModeで一度発信したあとに、連続的にFastModeで送信を行うブロック転送などを想定したモードである。
受信を行うListenクラス
Listenクラスも、初期化は以下のとおりで同じである。
Listen Listener = new Listen(this); |
ただし、先ほどと同じように、実際に受信する方法が違っている。
これには、まず以下のようなメソッドを用意しなければならない。
void CatchDataCallBack(AnimalState sender, object data, bool FastMode) |
そしてこれを利用してデリゲートを作成し、以下のようにして、メソッドを呼び出す。
ArrayList arr = Scan(); |
ここでSearchPacketメソッドは、生物のArrayListの参照と、データを見付けた場合に呼び出すコールバック関数を引数に持つ。 SearchPacketメソッドの動作としては、与えられたArrayListの生物を1つずつ検査し、データを見付けた場合は与えられたコールバック関数を呼び出すようになっている。
このDestinationを使ったコミュニケーションでは、1ターンでの通信ができる。また、FastModeがあり、使い方に注意が必要ではあるが、16bitのデータ交換も可能だ。DestinationのPoint構造体を介したコミュニケーションで、1ターンに実際に送ることができるデータ総量は、データであることの判別などを行わなければ、16bit+アルファといったところだろう。ActualDirectionを使ったコミュニケーション(1byte送るだけでも数十ターンかかる)とは比べるまでもない。
結論としては、移動を使ったコミュニケーションにおいてはDestinationを使った方が効率的であるということである。
INDEX | ||
[連載]テラリウム徹底攻略ガイド | ||
第7回 ダンスによる動物間コミュニケーション | ||
コミュニケーションの基本 | ||
ActualDirectionを使ったコミュニケーション | ||
Destinationを使ったコミュニケーション | ||
コミュニケーションの利用 | ||
「連載 テラリウム徹底攻略ガイド」 |
- 第2回 簡潔なコーディングのために (2017/7/26)
ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている - 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう - 第1回 明瞭なコーディングのために (2017/7/19)
C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える - Presentation Translator (2017/7/18)
Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
|
|