有限ステートマシンで遊ぶ(2)

MzSchemeの構造体を使って有限ステートマシン作りしてみました。らくちん過ぎる。

状態(state)

状態は4つあります。

ゲームステート
  V
戦闘ステート <--> 宿屋ステート
  V
死亡ステート

デザパタで言うと、Stateパターンと同じような感じです。


HPによって状態(ステート)を変えていきます。HPが少なくなってきたら宿屋へ移動。元気になったら戦闘へ。HPがゼロ以下になったら、死んじゃいます。悲しいです。


また、状態を切り替えるときにもメッセージを表示出来るようにしました。状態の中にstart、current、endという状態が入ってます。

へっぽこ AI RPG

動きはこんな感じ。

===== tanakaのへっぽこ AI RPG =====

さぁ冒険だ〜
いざ戦いへ〜
ゴブリンと戦います。
戦闘 : -7のダメージを受けました。    HP : 13
戦闘 : -11のダメージを受けました。    HP : 2
モウダメ。
ねむぃよぉ・・・
宿屋で休みます。
宿屋 : 9回復しました。              HP : 11
宿屋 : 5回復しました。              HP : 16
宿屋 : 6回復しました。              HP : 22
あぁ、よく寝た!
いざ戦いへ〜
ゴブリンと戦います。
戦闘 : -7のダメージを受けました。    HP : 15
戦闘 : -9のダメージを受けました。    HP : 6
モウダメ。
ねむぃよぉ・・・
宿屋で休みます。
宿屋 : 5回復しました。              HP : 11
宿屋 : 9回復しました。              HP : 20
宿屋 : 2回復しました。              HP : 22
あぁ、よく寝た!
いざ戦いへ〜
ゴブリンと戦います。
戦闘 : -11のダメージを受けました。    HP : 11
戦闘 : -13のダメージを受けました。    HP : -2
モウダメ。
死亡しました。
===== GAME OVER =====
bye!

あぁぁ死んじゃった・・・ゴブリンつえぇぇぇ。

コード

残念ながらMzScheme専用です。MzSchemeの構造体はヤバイ。

#!/usr/bin/mzscheme -f

;; player-struct
(define-struct player (name state hp))

(define (add-hp! player hp)
  (set-player-hp! player (+ hp (player-hp player))))

;; message
(define (message . args)
  (display (apply format args))
  (newline))

;; state-struct
(define-struct state (start current end))

(define (execute player state)
    (if (string? state)
        (message state)
        (state player)))

(define (change-state! player state)
  (execute player (state-end (player-state player)))
  (set-player-state! player state)
  (execute player (state-start (player-state player))))

;; state
(define (start-game player)
  (message "===== ~aのへっぽこ AI RPG =====\n" (player-name player))
  (change-state! player fight))

(define (fight-current player)
  (let ((damage (- (random 15))))
    (add-hp! player damage)
    (message "戦闘 : ~aのダメージを受けました。    HP : ~a" damage (player-hp player))
    (cond ((<= (player-hp player) 0)
           (change-state! player die))
          ((< (player-hp player) 10)
           (change-state! player sleep)))))

(define (sleep-current player)
  (let ((damage (random 10)))
    (add-hp! player damage)
    (message "宿屋 : ~a回復しました。              HP : ~a" damage (player-hp player))
    (cond ((> (player-hp player) 20)
           (change-state! player fight)))))

(define (bye!)
    (message "bye!"))

(define game (make-state 'empty start-game "さぁ冒険だ〜"))
(define fight (make-state "いざ戦いへ〜\nゴブリンと戦います。" fight-current "モウダメ。"))
(define sleep (make-state "ねむぃよぉ・・・\n宿屋で休みます。" sleep-current "あぁ、よく寝た!"))
(define die (make-state "死亡しました。\n===== GAME OVER =====" 'empty 'empty))

;; main
(define (main player n)
  (if (or (= n 0) (eq? die (player-state player)))
      (bye!)
      (begin
        (execute player (state-current (player-state player)))
        (main player (- n 1)))))

(main (make-player 'tanaka game 20) 20)
(exit)

たったこれだけで、AIプログラミング出来ちゃうSchemeに感動です。

AIエンジン自体はexecuteとchange-stateだけ。前回よりちょっと複雑です。

やばいAI楽しい。やっぱりSchemeだよ。

状態をツリーにしたら楽しそうだ。

戦闘の状態の中に、攻撃な状態があったり、魔法な状態があったりする。攻撃の中に剣で攻撃したり、矢で攻撃したり・・・うっほ。再帰だ。

しかし、再帰にも問題点があるな。攻撃中に死亡したとすると、戦闘状態を抜けなければならない。つまり、大域ジャンプが必要となってしまう。継続を使えばその点は解消出来るけど・・・継続か・・・考え中。

あぁ、違うな

状態ははツリーじゃない。「グラフ」。例えるならネットみたいなもの。検索欄に「ボクノス」って入れたら、ボクノスにたどり着ける。網の目のようになってるんだ。つまり、どの状況からでもゲームオーバーに飛べる。

ツリーじゃ表現出来ない。グラフだグラフ・・・まだリスト修行が必要だ・・・。