Fandom

Scratchpad Wiki

Gumonji/Q

< Gumonji

322このwikiの
ページ数
新しいページをつくる
トーク0 シェアする

gumonji/Q(略称:gumoQ)とは、gumonjiの各ゾーンにスクリプトで動くロボットを生成したりすることのできるシステム、またはそのスクリプト自体を指すものです。

このページでは、そのgumonji/Qについて扱います。

gumonji/Qとは? 編集

gumonji/Qは、平たく言ってしまえば「自動bot」であり、その行動主体はエージェントと呼ばれます。このエージェントにあらかじめ用意しておいたシナリオと呼ばれるスクリプトを組み込むことで、様々な作業をさせることができます。

例としては、

  • 造山作業や水汲みなどの繰り返し作業
  • 建造物の維持、植物への水やりや動物への餌やりなどの手間のかかるメンテナンス
  • 各地点の水蒸気量や地面の高さ、雨量の計測などの気象観測
  • 果ては、ユーザが関わらなくてもエージェントだけが生活する空間の構築

などがあげられ、まさしく「何をやるのかはあなた次第」でしょう。

もう少し詳しく言うと、gumonji/Qは「シナリオ記述言語Q(KAWA版)」と呼ばれるものを用いています。これはSchemeというプログラミング言語で書かれているので、gumonji/Qは基本的にSchemeを使って書かれています。

また、そのシナリオは、「アクション」と呼ばれる実行と「キュー」と呼ばれる観測を組み合わせて書きます。例えば「こんにちは!」と言われて「こんにちはー」と返すシナリオならば

  1. 「こんにちは!」と言われるのを待つ(キュー)
  2. 言われたら自分も「こんにちは!」と言う(アクション)

といった感じになります。もっと複雑な分岐やループなどは先の述べたSchemeを使って書きます。

詳しくは公式サイト「gumonji/Qとは」をみてください。

簡単なチュートリアル 編集

ここまでgumonji/Qがどんなもので、どれほど素晴らしいか!について述べてきましたが、実際は普通の人にはとっつきにくく分かりづらいものになっています。

そこで、この節では

  1. 自分のゾーンでエージェントを呼び出し、シナリオを動かしてみる
  2. 簡単なシナリオを書いてみる
  3. さらにこの先、自分でシナリオを作っていく準備をする

というところまでの手順を書いておきます。

付属のシナリオを動かしてみる 編集

まずは自分のゾーンにエージェントを呼び出してどんな風になるのかをみてみます。 大まかな流れとしては

  1. gumonji/Qをダウンロードする
  2. 自分のゾーンを起動させて、Qの実行を許可する
  3. "runManager.bat"を起動し、Qウインドウを開く
  4. main.scmをロードする

という形になります。

1番目、2番目については多分説明は要らないと思うので省略します。 各自、公式サイトからgumonji/Qをダウンロードおよび適当なところに解凍して、自分のゾーンサーバーを起動&設定からQの実行を許可させてください。

Qウインドウを開く 編集

次に、解凍したファイルの中に"runManager.bat"というものがありるので、これを起動させます。 すると「Qウインドウ」と呼ばれる、gumonji/Qを操作するのに使う白いウインドウが開きます。開く方法は、

  • 素直にダブルクリックする
  • コマンドプロンプトから開く

の2種類がありますが、普通は前者の方法で開くはずです。ただし、場合によっては後者の方法でないと起動しない場合があるらしいので、その場合は

  1. コマンドプロンプトを開く("スタートメニュー>プログラム>アクセサリ"か、「ファイル名を指定して実行」でcmd.exeと打つ)
  2. 解凍したフォルダにカレントディレクトリを移動する(「cd "C:\~~"」と打つ)
  3. "runManager.bat"を起動する(runManager.batと打つ)

としてください。

エージェントを呼び出す 編集

無事にQウインドウが開いたら、今度はそのウインドウに(load "../main.scm")と打ってエンターを押してください(両端の括弧や途中のダブルクオーテーションマークを忘れずに!)。 そうすると、コマンドプロンプトの画面になにやらたくさん文字が出てくると思います。これでエージェントが無事にあなたのゾーンに呼び出されました!おめでとう! ちなみに、エージェント達はゾーンの座標20,30付近にいます。8体の無表情なロボットが走り回ってたり何かしゃべってたりとすごく不気味な状態ですが、何はともあれ、これでエージェントを呼び出すことはできました。

なお、このエージェントたちはQウインドウ(あるいはコマンドプロンプト)を消すと活動を停止します(消えはしない)。 もう一度動かしたかったら、またQウインドウを起動させて、main.scmをloadすれば動きます。

※注意:ゾーンの時間が止まっているとエージェントは動きません!

Hello, worldを作ってみる 編集

ここまでで「gumonji/Qがどんな風になるのか」というのは体験できたと思いますが、実際自分が何をやっているのかまったく分からないと思います。 なので、ここではプログラミング入門で必ずと言っていいほど使われる、かの有名なHello, worldプログラムを作ってみたいと思います。 Hello worldプログラムとは「ただ単に"Hello, world!"と表示させるだけ」のプログラムで、今回はエージェントに「Hello, world!」と1回発言してもらうことを目標にします。

今回の流れは

  1. シナリオを定義する
  2. Qウインドウを開く
  3. 基本的なライブラリをロードする
  4. エージェントを定義する
  5. エージェントにシナリオを割り当てる

となります。

シナリオを定義する 編集

最初に、シナリオを定義します。 シナリオとは、先に述べたように「エージェントがどのような行動をするのか」を表すものです。 つまり、今回で言えば「"Hello, world!"と言う」ことです。

まず、main.scmのある場所と同じところに新しく"HelloWorld.q"というファイルを作ります。 そして、このファイルを開いて、ここにシナリオを書いていきます。 新しくシナリオを定義するには「defscenario」を使います。これは

(defscenario シナリオ名 (引数、変数の宣言)
  (シーン名 (
    そのシーンでやること(アクション、キューetc)
  ))
)

という形で使います。引数とか変数とかシーンとかがいきなり出てきましたが、ここでは無視して構いません。 詳しくは『gumonji/Qの使い方』や他の人のシナリオをみてください。

さて、では実際にHelloWorldシナリオを定義してみます。

(defscenario HelloWorld ()
  (SayHelloWorld (
    (!speak :message "Hello, world!")
  ))
)

↑このようになります。1つ1つ説明すると、まず、1行目でシナリオ名は「HelloWorld」というシナリオを宣言します。 ただし、今回は変数も引数も使いませんので、シナリオ名のあとの()はからっぽです。 2行目では「SayHelloWorld(HelloWorldと言う)」というシーンを定義しています。 そして3行目で「"Hello, world"」と実際に発言させています。 この!speakが「何かを発言する」というアクションで、「:message ○○○」の○○○の部分を発言します。 4行目、5行目はそれぞれ「シーンの終わり」「シナリオの終わり」を示すカッコです。

以上の内容をHelloWorld.qに書き込んで保存してください。これでシナリオの定義は完了です。

ライブラリのロード 編集

次に、gumonji/Qを使う上で必要な様々なアクション(行動)、キュー(観測)、関数(手続き)を読み込まなければなりません。 実は、先に出てきた「!speak」や「defagent」というのは別の場所(具体的にはlibフォルダの中のファイル)で別に定義されていて、それを読み込まないとコンピュータは「!speakって何?defscenarioっておいしいの?」状態になります。

そのために、先ほどから出てきている「main.scm」というファイルを開いてみます。 このファイルは解凍したファイルの直下(runManager.batと同じところ)にありますが、Schemeの統合開発環境とかが入ってない限り普通は登録されているアプリケーションがないので単にダブルクリックだけでは開けません。 ですが、普通にメモ帳で開くことが出来るので、メモ帳で開きましょう。開いてみると

;; ○○○○の読み込み
(load "../○○○")

というのが続いていると思います。これが基本的なライブラリを読み込む作業の本体です。 既に気付いたかもしれませんが、(load ○○○)というのは「○○○というファイルをロードする」というものです。 先ほどこのmain.scmをロードしましたが、実際はさらにいくつかのファイルをロードしていただけ、ということになります。

さらに、よくみてみるとこのmain.scmで読み込んでいるファイルの中で、一番下のファイルだけ他とちょっと違う感じになっています。 後で分かりますが、このファイルがサンプルシナリオの本体でこのファイルを読み込むことで、あの8体の不気味君たちが作られ動くようになります。 が、逆に言えばこれを読みこんでしまうと、毎回あの不気味君たちに会わなければならなくなるので、削除するか先頭に;;(セミコロン2つ)を付けて無効化しましょう。

;; シナリオの割り当て
;; (load "../scenario/scenario.q")

そうした後、ファイルを上書き保存して、Qウインドウを開いて

(load "../main.scm")

と打ってエンターを押すと、必要なファイルだけが読み込まれます(何も起きません!)。 コマンドプロンプトに変化もなく、load文の下にあたらしく>が出てきていればOKです。

エージェントを定義して動かす 編集

さぁ、これで下準備は整いました。あとはエージェントをゾーンに呼び出して、そのエージェントに「HelloWorldシナリオをやってくれ」と言うだけです。

まずは、先に定義したシナリオを読み込んでおきましょう。基本ライブラリ(main.scm)を読み込んだ時と同様に

(load "../HelloWorld.q")

とQウインドウに打ち込んでHelloWorld.qを読み込ませます。

次に、エージェントをゾーンに呼び出します。これにはdefagentを使います。使い方は

(defagent エージェント名 :category エージェントの種類 :x x座標 :y y座標)

となっています。今回は次のようにしましょう。

(defagent HelloWorld-kun :category "robot" :x 50 :y 50)

これは「HelloWorld-kun」という名前のrobotタイプのエージェントを、座標(50, 50)に召喚!することを意味します。 実際に50, 50に行ってみて、そこにロボットエージェントがいたら成功です。

さて、そして最後にこの呼び出したエージェントHelloWorld-kunにHelloWorldシナリオを割り当てます。 エージェントへのシナリオの割り当てはfutureを使って次のようになります。

(future (シナリオ名 エージェント名))

なので、今回は

(future (HelloWorld HelloWorld-kun))

となります。

ここまでをきちんとQウインドウに入力して実行できれば、futureした瞬間にロボットが「Hello, world!」と発言するはずです。 これで、「gumonji/Q版Hello world」の完成です!お疲れ様でした。 もしうまく行かなかった場合は、もう一度最初から見直してやり直してみましょう。

開発環境を整える 編集

未執筆。内容予定は

  • エージェントを消す方法
  • シナリオの再割り当ての方法
  • もう少し扱いやすくする方法

もっと詳しく 編集

Schemeについて 編集

Schemeについては、既に各種参考書や、ウェブ上にも資料がいくつかるので解説はそちらにゆずります。

以下、参考までにウェブ上の資料をリンクしておきます。

gumonji/Qについて 編集

gumonji/Q自体についてもっと詳しく知りたい場合は、以下にあげる『使い方』と『リファレンス』をみてください。

また、scenarioフォルダやlibフォルダ内のファイルも参考になるでしょう。

特に『アクション・キューリファレンス」はgumonji/Qで使える基本的なアクション・キューが載っているので是非ダウンロードしておくのをオススメします。

また、libフォルダ内のファイルにはgumonji/Q独自のアクション・キュー・関数などの情報が書かれているので、リファレンスだけではよく分からない場合はそちらを読むといいでしょう。

シナリオ記述言語Qについて 編集

シナリオ記述言語Qは京都大学の石田研で開発されたもので、詳しくは専用のウェブサイトで見ることが出来ます。

Q自体については、以下の文書が参考になるでしょう。

特に2つ目の『Qコネクタ仕様書』は、なぜかgumonjiの公式サイトからリンクがないのですが、しかしアクションやキューを定義したり、エージェントの属性についてなど、「本格的に何かをさせようと思ったら必要な情報」が満載ですので、読んでおいて損はないと思います。

シナリオなどの投稿 編集

セルロミンを取得するアクション 編集

  • アクション用になってなかったので修正。--エギー会話/履歴 2008年8月13日 (水) 14:05 (UTC)
;; ある座標付近に落ちているセルロミンを拾って缶に補充するアクション
;;    <引数>
;;     x, y : セルロミンを探す場所の中心座標
;;     SeeRange : セルロミンを探す範囲で、中心座標からの距離(?seeキューのdistパラメータ)
 
(defactionadapter !GetCellulomin
  ;; 引数宣言
  (self
   &key (request_id 0)
        (SeeRange 10)
        (x 0)
        (y 0)
  ;; パターン変数宣言
   &pattern ($CellulominID 0)   ;; 見つけたセルロミンのID
            ($CellulominAL '()) ;; 見つけたセルロミンのアソシエーションリスト
            ($CanID 0)          ;; セルロミン缶のID
            ($CanAL '())        ;; まとめたセルロミンのアソシエーションリスト
  )
  ;; replyする
  (wait-reply request_id)
 
  ;; セルロミンを探す場所まで移動
  (!walk :x x :y y)
 
  ;; ?seeキューでセルロミンを探す
  (?see :type "セルロミン" :dist SeeRange :id $CellulominID)
 
  ;; 見つけたセルロミンの位置するx, y座標を取得
  (!look :id (refval $CellulominID) :keys '("x" "y") :values $CellulominAL)
 
  ;; 見つけたセルロミンのところまで移動
  (!walk :x (get-value-str "x" $CellulominAL) :y (get-value-str "y" $CellulominAL))
 
  ;; どこの座標で見つけたのか発言
  (!speak :message (list "座標:("
                         (get-value-str "x" $CellulominAL)
                         ", "
                         (get-value-str "y" $CellulominAL)
                         ")でセルロミンをみつけたよ!"
                   )
  )
 
  ;; 見つけたセルロミンのmol量を取得
  (!look :id (refval $CellulominID) :keys '("mol") :values $CellulominAL)
 
  ;; セルロミンを補充する
  (!store :id (refval $CellulominID))
 
  ;; 拾ったセルロミンのmol量を発言
  (!speak :message (list (get-value-str "mol" $CellulominAL)
                         "[mol]のセルロミンを補充したよ!"
                   )
  )
 
  ;; 持っているセルロミン缶のIDを取得
  (!get-item-id :type "缶" :id $CanID)
 
  ;; 缶内のセルロミンのmol量を取得する
  (!look :id (refval $CanID) :keys '("mol") :values $CanAL)
 
  ;; 拾ったら何molになったか発言する
  (!speak :message (list "補充したら、セルロミン量が"
                         (get-value-str "mol" $CanAL)
                         "[mol]になったよ!"
                   )
  )
 
  ;; アクション終了
)
 
;; アクション定義
(defaction !GetCellulomin (:SeeRange in) (:x in) (:y in))

アイテム関連のアクション 編集

使い方サンプルシナリオ 編集

;; アイテム関連アクションののサンプルシナリオ
(defscenario TestScenario-Items ()
  (test (
    ;; 座標25, 70に移動して、その周辺に32番から48番のアイテム(完成品)を置く
    (!PutItems :X 25 :Y 70 :From 32 :To 48)
 
    ;; 座標20, 70に移動して、その周囲に3マスにあるアイテム全てを拾う
    (!PickupItems :X 20 :Y 70 :Range 3)
 
    ;; 持っているアイテムのうち、1番から16番のアイテム(素材)を調べて言う
    (!SayWhatIHave :From 1 :To 16)
 
    (!speak :message "テストシナリオ終了!")
  ))
)

自分の持っているアイテムを言うアクション 編集

;; 自分の持っているアイテムを言うアクション
;;   <引数>
;;     From, To : 調べるアイテムの開始番号と終了番号(指定しなければ全て)
 
(defactionadapter !SayWhatIHave
  (self
   &key (request_id 0)
        (From 1)
        (To 48)
   &pattern ($itemID 0) ;; 調べるアイテムのID
            ($itemAL '()) ;; 調べるアイテムのアソシエーションリスト
   &aux (i From)   ;; アイテムのインデックス(ループ変数)
        (existN 0) ;; 持っていたアイテムの数
  )
  (wait-reply request_id) ;; リクエストに返答
 
  ;; 発言開始を告げる
  (!speak :message "私が持ってるアイテムを言うよ!")
 
  ;; FromからToまでのインデックスのアイテムを調べる
  (let ItemLoop ((i From)) (when (<= i To)
 
    ;; アイテムのIDを取得
    (!get-item-id :index i :id $itemID)
 
    ;; IDがゼロでなければ(アイテムが存在していれば)
    (unless (= (refval $itemID) 0)
      ;; 持っていたアイテムの数をインクリメント
      (set! existN (+ existN 1))
 
      ;; アイテムのタイプとmolを取得
      (!look :id (refval $itemID) :keys '("type" "mol") :values $itemAL)
 
      ;; 持っているアイテムの情報を発言
      (!speak :message (list i "番目"
                             "のアイテムは"
                             (get-value-str "mol" $itemAL)
                             "[mol]の"
                             (get-value-str "type" $itemAL)
                             "だね。"
                       )
      )
    )
 
    (?wait) ;; ループ待機
    (ItemLoop (+ i 1)) ;; indexのインクリメント
  ))
 
  ;; 最終的に持っていたアイテムの数を発言
  (!speak :message (list "以上、全部でアイテムは" existN "コ持ってたよ!"))
 
  ;; アクション終了
)
 
 
;; アクション定義
(defaction !SayWhatIHave (:From in) (:To in))

周囲のアイテムを全て拾うアクション 編集

;; 周囲のアイテムを全て拾うアクション
;;   <引数>
;;     X, Y  : アイテムを拾う中心座標(指定しなければ現在位置)
;;     Range : 拾うアイテムを探す範囲
 
(defactionadapter !PickupItems
  (self
   &key (X -1)
        (Y -1)
        (Range 1)
   &pattern ($groundAL 0) ;; 地面の状態を表すアソシエーションリスト
            ($itemAL 0)   ;; 見つけたアイテムのアソシエーションリスト
   &aux (dx 0)      ;; アイテムを探す場所の相対x座標
        (dy 0)      ;; アイテムを探す場所の相対y座標
        (pickedN 0) ;; 拾ったアイテムの数
        (itemID 0)  ;; 地面にあるアイテムのID
  )
 
  ;; XもYも-1じゃなかったら、現在位置を指定された座標に移動
  (if(and (not (= X -1)) (not (= Y -1)))
    (!walk :x X :y Y)
  )
 
  (!speak :message "今からアイテムを拾うよ!")
 
  ;; 自分の周囲のアイテムの探索(dx, dyともに-RangeからRangeの範囲)
  (let XLoop ((dx (- 0 Range))) (when (<= dx Range)
    (let YLoop ((dy (- 0 Range))) (when (<= dy Range)
 
      ;; 指定されれた座標の地面に置いてあるアイテムIDを取得
      (!observe :dx dx :dy dy :keys '("item") :values $groundAL)
      (set! itemID (get-value-int "item" $groundAL))
 
      ;; 取得したアイテムIDが0でなければ(アイテムが存在したら)
      (unless (= itemID 0)
        ;; 拾ったアイテムの数をインクリメント
        (set! pickedN (+ pickedN 1))
 
        ;; 見つけたアイテムのタイプを取得
        (!look :id itemID :keys '("type") :values $itemAL)
 
        ;; アイテムを見つけた座標とそのタイプ、IDを発言
        (!speak :message (list "相対座標:(" dx "," dy ")から"
                               "アイテム「" (get-value-str "type" $itemAL) "」"
                               "(ID:" itemID ")を拾ったよ!"
                         )
        )
 
 
        ;; 見つけたアイテムを拾う
        (!pickup :id itemID)
      )
 
      (?wait) ;; ループ待機
      (YLoop (+ dy 1)) ;; dyのインクリメント
    ))
    (XLoop (+ dx 1)) ;; dxのインクリメント
  ))
 
  ;; 最終的に拾ったアイテムの数を発言
  (!speak :message (list "全部で" pickedN "コのアイテムを拾ったよ!"))
 
  ;; アクション終了
)
 
 
;; アクション定義
(defaction !PickupItems (:X in) (:Y in) (:Range in))

自分の持っているアイテムを置くアクション 編集

;; 自分の持っているアイテムを置くアクション
;;   <引数>
;;     X, Y  : アイテムを置く中心座標(指定しなければ現在位置)
;;     From, To : 置くアイテムの開始番号と終了番号(指定しなければ全て)
;;   
;;   ※自分の周囲7マス(-3 - 3マス)に順番にアイテムを置いていくので、
;;     既にそこにアイテムがある場合は置けません
 
(defactionadapter !PutItems
  (self
   &key (request_id 0)
        (X -1)
        (Y -1)
        (From 1)
        (To 48)
   &pattern ($puttingItemID 0)   ;; 置こうとしているアイテムのID
            ($puttingItemAL '()) ;; 置こうとしているアイテムのアソシエーションリスト
            ($groundAL '())      ;; 置こうとしている地面の状態を表すアソシエーションリスト
            ($groundItemAL '())  ;; 地面にあるアイテムのアソシエーションリスト
   &aux (i (- From 1))          ;; アイテムのインデックス(ループ変数)
        (dx 0)            ;; アイテムを置く場所の相対x座標
        (dy 0)            ;; アイテムを置く場所の相対y座標
        (putN 0)          ;; 置いたアイテムの数
        (unputtableN 0)   ;; 置けなかったアイテムの数
        (groundItemID 0)  ;; 地面にあるアイテムのID
  )
  (wait-reply request_id) ;; リクエストに返答
 
  (!speak :message "今からアイテムを置くよ!")
 
  ;; XもYも-1じゃなかったら、現在位置を指定された座標に移動
  (if(and (not (= X -1)) (not (= Y -1)))
    (!walk :x X :y Y)
  )
 
  ;; 自分の周囲のアイテムの探索(dx, dyともに-RangeからRangeの範囲)
  ;;  かつ、アイテム番号がToを越えたらループから抜ける
  (let XLoop ((dx -3)) (when (and (<= dx 3) (<= i To))
    (let YLoop ((dy -3)) (when (and (<= dy 3) (<= i To))
 
      ;; アイテム番号をインクリメント
      (set! i (+ i 1))
 
      ;; 持っているアイテムのIDを取得
      (!get-item-id :index i :id $puttingItemID)
 
      ;; 指定されれた座標の地面に置いてあるアイテムIDを取得
      (!observe :dx dx :dy dy :keys '("item") :values $groundAL)
      (set! groundItemID (get-value-int "item" $groundAL))
 
      ;; 座標にアイテムが置いてあって(IDがゼロでなくて)
      ;; かつ、その座標に置くアイテムを持っている場合
      (if (and (not (= groundItemID 0)) (not (= (refval $puttingItemID) 0)))
        (begin
          ;; 置けなかったアイテムの数をインクリメント
          (set! unputtableN (+ unputtableN 1))
 
          ;; 見つけたアイテムのタイプを取得
          (!look :id groundItemID :keys '("type") :values $groundItemAL)
 
          ;; アイテムがおけなかった旨を発言
          (!speak :message (list "相対座標:(" dx "," dy ")の"
                                 "アイテム「" (get-value-str "type" $groundItemAL) "」が邪魔で"
                                 i "番目のアイテムが置けなかったよ!"
                           )
          )
        )
        ;; そうでなくて、さらに置くべきアイテムがある場合
        (unless (= (refval $puttingItemID) 0)
 
          ;; 置いたアイテムの数をインクリメント
          (set! putN (+ putN 1))
 
          ;; アイテムを置く(アイテムを持ってなければ置かないこともある)
          (!put :id (refval $puttingItemID) :dx dx :dy dy)
 
          ;; 音を鳴らす(空発言で代用)
          (!speak)
        )
      )
 
      (?wait) ;; ループ待機
      (YLoop (+ dy 1)) ;; dyのインクリメント
    ))
    (XLoop (+ dx 1)) ;; dxのインクリメント
  ))
 
  ;; 置けなかったアイテムの数を発言
  (!speak :message (list "結局、" putN "コのアイテムを置いて、"
                         unputtableN "コのアイテムが置けなかったよ!"))
 
  ;; アクション終了
)
 
 
;; アクション定義
(defaction !PutItems (:X in) (:Y in) (:From in) (:To in))

広告ブロッカーが検出されました。


広告収入で運営されている無料サイトWikiaでは、このたび広告ブロッカーをご利用の方向けの変更が加わりました。

広告ブロッカーが改変されている場合、Wikiaにアクセスしていただくことができなくなっています。カスタム広告ブロッカーを解除してご利用ください。

Fandomでも見てみる

おまかせWiki