さくらんぼのlambda日記

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

Common Lispでゲーム用の状態遷移マシン 完成?

やっと、コード書く時間がとれたので、記録用に書きます。

singletonクラス作成用のパッケージ

とりあえず、singletonをつくるためのパッケージを作成しました。
http://cadr.g.hatena.ne.jp/g000001/20081202/1228199756
この記事が超参考になりました。

closer-mopを使うことで、特定の実装に依存しないでsingletonが実現できますね。
素晴らしいです。

(defpackage :singleton
  (:use :cl )
  (:export #:define-singleton-class
	   ))

(in-package :singleton)
(defclass singleton-meta (standard-class)
  ((%the-singleton-instance :initform () )))

(defmethod make-instance ((class singleton-meta) &key)
  (with-slots (%the-singleton-instance) class
    (if %the-singleton-instance
        %the-singleton-instance
        (let ((ins (call-next-method)))
          (setf %the-singleton-instance ins)
          ins))))

(defmethod c2mop:validate-superclass ((class singleton-meta)
                                      (super standard-class))
  'T)
(defmethod c2mop:validate-superclass ((class singleton-meta)
                                (superclass standard-class))
  ;; it's OK for a standard class to be a superclass of a singleton
  ;; class
  'T)

(defmethod c2mop:validate-superclass ((class singleton-meta)
                                (superclass singleton-meta))
  ;; it's OK for a singleton class to be a subclass of a singleton class
  'T)

(defmethod c2mop:validate-superclass ((class standard-class)
                                (superclass singleton-meta))
  ;; but it is not OK for a standard class which is not a singleton class
  ;; to be a subclass of a singleton class
  nil)

(defmacro define-singleton-class (name supers &rest args)
  (and (assoc :metaclass args)
       (error "Metaclass already specified."))
  `(defclass ,name ,supers ,@args
     (:metaclass singleton-meta)))


状態遷移マシン自体のパッケージ

状態遷移マシンは、こんな感じで実装してみました。
state-machine:stateを継承しているクラスを使うことを想定しています。

(in-package :cl)
(defpackage :state-machine
  (:use :cl :singleton)
  (:export #:state
  	   #:state-machine
	   #:set-current-state
	   #:set-prev-state
	   #:set-global-state
	   #:change-state
	   #:enter
	   #:execute
	   #:exit
	   #:update
	   #:current-state ) )
(in-package :state-machine)
(singleton:define-singleton-class state ()
  ())
(defmethod enter ((state-instance state) &rest arg) )
(defmethod execute ((state-instance state) &rest arg) )
(defmethod exit ((state-instance state) &rest arg) )
(defclass state-machine ()
  ((owner :initarg :owner)
   (current-state :initform nil  :accessor current-state)
   (prev-state :initform nil :accessor prev-state)
   (global-state :initform nil :accessor global-state)))

(defmethod set-current-state ((obj state-machine) s)
  (setf (current-state obj) s))
(defmethod set-prev-state ((obj state-machine) s)
  (setf (prev-state obj) s))
(defmethod set-global-state ((obj state-machine) s)
  (setf (global-state obj) s))  
    
(defmethod update ((obj state-machine))
  (when (not (null (global-state obj)))
    (execute (make-instance (global-state obj))))
  (when (not (null (current-state obj)))
    (execute (make-instance (current-state obj)))))

(defmethod change-state ((obj state-machine) new-state)
  (setf (prev-state obj) (current-state obj))
  (exit (make-instance (current-state obj)))
  (setf (current-state obj) new-state)
  (enter (make-instance (current-state obj))))

実際の例

こんな感じで、状態遷移マシンを構成してみます。

(singleton:define-singleton-class getup (state-machine:state)  ())
(singleton:define-singleton-class goodnight (state-machine:state) ())

(defmethod state-machine:enter ((state-instance getup) &rest arg) 
  (declare (ignore arg))
  (format t "enter getup~%"))
(defmethod state-machine:execute ((state-instance getup) &rest arg) 
  (declare (ignore arg))
  (format t "exec getup~%"))
(defmethod state-machine:exit ((state-instance getup) &rest arg) 
  (declare (ignore arg))
  (format t "exit getup~%"))

(defmethod state-machine:enter ((state-instance goodnight) &rest arg) 
  (declare (ignore arg))
  (format t "enter goodnight~%"))
(defmethod state-machine:execute ((state-instance goodnight) &rest arg) 
  (declare (ignore arg))
  (format t "exec  goodnight~%"))
(defmethod state-machine:exit ((state-instance goodnight) &rest arg) 
  (declare (ignore arg))
  (format t "exit  goodnight~%"))

(setf fsm (make-instance 'state-machine:state-machine))
(state-machine:set-current-state fsm  'getup)
(state-machine:update fsm)

さて、実際に実行してみると

CL-USER> (state-machine:update fsm)
exec getup
NIL
CL-USER> (state-machine:update fsm)
exec getup
NIL
CL-USER> (state-machine:update fsm)
exec getup
NIL
CL-USER> (state-machine:change-state fsm 'goodnight)
exit getup
enter goodnight
NIL
CL-USER> (state-machine:update fsm)
exec  goodnight
NIL

いい感じですねー。

課題

  • 状態遷移のためのstateを継承したクラスの定義が面倒くさい。パッケージをわざわざ指定するのは面倒くさい。

解決方法をちょっと考えてみます...。