OCamlでオブジェクト指向を使ったゲームのアクター
この記事はML Advent Calendar 2014の3日目の記事です。
昨日は代数データ型を使ってみましたが、今日はオブジェクト指向を使ってみます。 オブジェクト指向自体の説明はもっと良い記事があると思うので、複数のキャラクターを同じリストに入れて動かしたり、親子関係を持たせて、動かすオブジェクトを書いてみます。
open Printf class actor = object(this) val mutable move = fun()->() method move = move() end
まずは、actorクラスを書いてみます。moveというmutableな関数を持つメンバ変数とmoveメソッドを定義します。OCamlのオブジェクト指向はRubyのオブジェクト指向に近いので、メンバ変数は使えないんですよね。そんな事は知ってるって気もしますけど、、、。
type actor = <move: unit>
で、型をかいてみると、こんな感じになります。moveメソッドだけがあります。<>でオブジェクトは括るようです。
class enemy_a = object(this) val mutable move = fun()->() method move = move() method private move1() = printf("a1\n"); move <- this#move2 method private move2() = printf("a2\n"); move <- this#move1 method init = move <- this#move1; this end
敵を定義してみます。initメソッドで初期化して、move1を入れるというように書きました。 コンストラクタのような物の書き方があるのかないのか、わからなかったので、initメソッドを作りました。 これ、ヘーって思うかもしれないんですが、メソッドを関数として取り出す方法で1時間とか結構悩んだんです。 ちょっとググっても、見つからないし、、、。という事で、これを見ればもう悩まないはずです。
class enemy_b = object(this) inherit actor method private move1() = printf("b1\n"); move <- this#move2 method private move2() = printf("b2\n"); move <- this#move1 method init = move <- this#move1;this end
inheritで継承を使う事も出来るんですね。 enemy_aより短く書けました。
type e_parent = < move : unit; init : e_parent; k :unit > type e_child = < move : unit; init: e_child >
次にe_parentとe_childの型を作っておきます。 このオブジェクトの型も見慣れないので辛かった。 色んなメソッドだけを<>で括ったものが型になります。
class enemy_child(parent:e_parent) = object(this) inherit actor method private move1() = printf("c2\n"); parent#k method init = move <- this#move1; (this :> e_child) end
enemy_childクラスを定義します。enemy_childは親を受け取る形になります。initはe_childと明示する必要があって苦労しました。何処をどう、型合わせたら良いんじゃ!っていう。
class enemy_c = object(this) inherit actor val mutable childs: e_child list = [] method private move1() = printf("c1\n"); List.iter(fun e->e#move) childs method k = printf("c1 k\n") method init = move <- this#move1; childs <- [ (new enemy_child(this :> e_parent))#init ; (new enemy_child(this :> e_parent))#init ]; (this :> e_parent) end
enemy_cという親のクラスを定義して、childs(childrenって書けよ)リストに子供を入れてみました。 この辺型合わせるのが、最初大変でした。相互参照させるの難しくありません?って感じでした。 何回も大変大変って書くなよw
let _ = let f = let a = new enemy_a in (fun()-> a#init) in (f())#move; let ls = [ (new enemy_a#init :> actor); (new enemy_b#init :> actor); (new enemy_c#init :> actor)] in for i = 0 to 3-1 do List.iter(fun l-> l#move) ls; printf"--\n" done
で、リストに入れて動かせます。 とりあえず、OCamlはJavaのインターフェイスを書く感じで型を定義しておいて、その型でリストを作れば様々な型のオブジェクトを1つのリストに登録して使う事が出来ました。
でも、<>でオブジェクトの型を書かなくても、出来ないのでしょうか?と思って書いてみたのが次のプログラムです。できるんですね。
open Printf (* 動くだけ *) class actor = object(this) method move = () end
動くだけのクラスを定義します。
class actor_a = object(this) method move = printf("move a\n") end
同じ型のクラスを書きます。型が同じ形なので1つにまとめられます。
(* 状態を持ったアクター *) class actor_m = object(this) val mutable move = fun()->() method move = move() method private move1() = () method init = move<-this#move1;(this :> actor_m) end
次に初期か関数があるオブジェクトを作ってみます。
(* メッセージを受け取るアクター *) class actor2 = object(this) val mutable move = fun()->() method move = move() method private move1() = () method msg = () method init = (this :> actor2) end
actor2はmsgメソッドがあるアクターとします。微妙に違うクラス並んでるだけですけど。
(* 子供 *) class actor2_c(parent:actor2) = object(this) val mutable move = fun()->() method move = move() method init = move <- this#move1; this method private move1() = printf("child move1\n"); parent#msg; move <- this#move2 method private move2() = printf("child move2\n"); parent#msg method msg = printf("child msg ok\n") end (* 親 *) class actor2_p = object(this) val mutable move = fun()->() method move = move() val mutable childs: actor2 list = [] method private move1() = printf("parent move\n"); List.iter(fun e->e#move; e#msg) childs method msg = printf("parent msg ok\n") method init = move <- this#move1; childs <- [ (new actor2_c(this:>actor2))#init ; (new actor2_c(this:>actor2))#init ]; (this :> actor2) end
親子関係のあるオブジェクトはこんな感じで書けます。
let _ = let actors = [ new actor_a; (((new actor2_p)#init) :> actor); new actor_a; ] in List.iter(fun a->a#move) actors
で、アクターを1つのリストにまとめて書く事が出来ました。特に型は書かなくても出来ましたね。 型は書いてないけど、インターフェイスになるようなclassを作れば型を作ったのと同じというような気持で書けます。 こんな風に、OCamlのオブジェクトは使えるんですねぇ。っという話でした。
明日は@master_qさんの「禅問答的に #ATS2 の型理論を説明してみたよ」です。