さくらんぼのlambda日記

lambdaちっくなことからゲーム開発までいろいろ書きます。

コルーチンをCommon Lispで簡単に定義

また更新滞ってしまいました...。なかなか長続きしませんね。

久しぶりに日記のネタが浮かんだので、メモがてら書きます。

ゲームを作っているとコルーチンが欲しくなることが多々あります。例えば、敵の行動が「飛行状態から通常攻撃をして着地から必殺技を出す」という連携攻撃を考えると、普通に状態遷移でやると、

  • 飛行状態
  • 通常攻撃
  • 着地
  • 必殺技

こんな状態を考える必要があるのではないでしょうか?そしてStateパターンなどを使って実装している場合には、このすべての状態に対してクラス定義をしたり、関数定義をしたりする必要があります。

コルーチンを使うとこういう時に便利です。コルーチンは、「関数を途中まで実行して、次に同じ関数を呼んだ時には前回の続きから実行する」というものです。pythonのジェネレータとかで同様のことができますね。

で、common lispでそれを実現するのはcl-contでできるのですが、cl-contが表に出て欲しくないのと
手軽に定義したかったのでマクロを作りました。
こんなのです。

(defmacro def-coroutine (name args &body body)
  "generate croutine from body form"
  `(defun ,name ,args
     (cl-cont:with-call/cc
       (macrolet ((yield ()
		    (let ((cc (gensym)))
		      `(cl-cont:let/cc  ,cc ,cc))))
	 ,@body))))


使い方は簡単で

(def-coroutine test-func (input1 input2)
			(print input1)
			(yield)
			(print input2)
			(yield))

こんな風にコルーチンが定義できます。定義したコルーチンは

CL-USER> (setf hoge (test-func "1" "2"))

"1" 
#<COMPILED-LEXICAL-CLOSURE (:INTERNAL TEST-FUNC) #xCAD1AFE>
CL-USER> (funcall hoge)

"2" 
#<Compiled-function VALUES #x4071136>

こんな感じに使えます。