title: Apollo チュートリアル
Author: NISHIO Mizuho
mailto: mzh@portnet.ne.jp


1. 数当てゲーム

  今まで紹介したコントロールを使用して簡単な数当てゲームを作ります。

1.1. スクリプト

  %href: ./rb/number.rb number.rb%

--ruby
001  require 'phi'
002  require 'rgui/ui'
003  require 'dialogs'
004  
005  MAX = 5
006  $anwser = nil
007  srand
008  
009  # control
010  form = RGUI::Form.new(:form1, '数当てゲーム')
011  
012  form.width = 350
013  form.height = 90
014  
015  start_btn = Phi::Button.new(form, :start_btn1, 'ゲーム開始')
016  stop_btn = Phi::Button.new(form, :stop_btn1, 'ゲーム終了')
017  stop_btn.enabled = false
018  hbox1 = UI::Hbox.new([start_btn, stop_btn], 5)
019    
020  edit = Phi::Edit.new(form, :edit1, '')
021  answer_btn = Phi::Button.new(form, :answer_btn1, 'OK')
022  hbox2 = UI::Hbox.new([edit, answer_btn], 5)
023  
024  vbox = UI::Vbox.new([hbox1, hbox2], 5)
025  form.add(vbox)
026  
027  
028  # method
029  def ok_msg(msg)
030    Phi::message_dlg(msg, Phi::MT_INFORMATION, [Phi::MB_OK], 0)
031  end
032  
033  def error_msg(msg)
034    Phi::message_dlg(msg, Phi::MT_ERROR, [Phi::MB_OK], 0)
035  end
036  
037  def start_game
038    Phi::SCREEN.form1.start_btn1.enabled = false
039    Phi::SCREEN.form1.stop_btn1.enabled = true
040    $answer = rand(MAX)
041    ok_msg("答えは 0〜#{MAX - 1} の数字です。")
042  end
043  
044  def stop_game
045    Phi::SCREEN.form1.start_btn1.enabled = true
046    Phi::SCREEN.form1.stop_btn1.enabled = false
047    $answer = nil
048    Phi::SCREEN.form1.edit1.text = ''
049  end
050  
051  # event
052  answer_btn.on_click = proc do
053    unless $answer.nil?
054      if edit.text == $answer.to_s
055        ok_msg('正解です。')
056        stop_game
057      else
058        error_msg("正解は #{edit.text} ではありません。")
059      end
060    else
061      error_msg("ゲームを開始してください。")
062    end
063  end
064  
065  start_btn.on_click = proc do
066    if $answer.nil?
067      start_game
068    else
069      error_msg('BUG')
070    end
071  end
072  
073  stop_btn.on_click = proc do
074    if $answer
075      stop_game
076    else
077      error_msg('BUG')
078    end
079  end
080  
081  form.on_resize = proc do
082    form.layout
083  end
084  
085  form.show
086  Phi.mainloop
--

  このスクリプトを実行すると、%href: img/number.png このような% ウィンドウがあらわれます。

1.1.1. 解説
  このゲームでは「ゲーム開始」のボタンを押すと、「答え」に整数が設定されます。ゲームの目的はこの「答え」を当てることです。エディットに数字を入力した後「OK」ボタンを押すと、エディットの数字と「答え」が一致しているかどうかを調べます。答えが当たっている場合は「正解です。」というダイアログが表示され、答えが間違っている場合は間違っていることを示すダイアログが表示されます。

1.1.1.1. 初期化とコントロールの生成
  1〜3行はコントロールを使うためのおまじないです。すべてこれまでのチュートリアルで紹介したものですので、どういうものか分からない時は前のページに戻って調べてみてください。

  5〜7行は数当てゲームに使う変数や定数の設定です。 MAX には数当てゲームの「答え」に使われる最大の整数よりも一つ大きい値を定数として指定します。例えば、 MAX に3を代入すると、「答え」に使われる整数は0、1、2の三つになります。

  $answer はこのゲームの「答え」です。今までと違って先頭に「$」が付いていますが、これはRubyの大域変数を表わします。詳しくはRubyのマニュアルを見て欲しいのですが、簡単に言えばどこからでも参照することができる変数のことを大域変数と言います。逆に一定の範囲でしか使えない変数もあります。そういった変数にはいくつか種類がありますが、この章ではローカル変数についてのみ後ほど説明を行います。$answer には nil が代入されていますが、これは Ruby の NilClass のオブジェクトで、偽を表します。偽を表すといっても、 false とは多少意味合いが違います。詳しいことは Ruby のMLトピックスやマニュアルを参照してください。

  7行目の srand は乱数の初期化を行う Ruby のメソッドです。今回は「答え」を作るために乱数を使うので、ここで乱数を初期化しておきます。

  10〜25行はフォームやボタンを生成したり、配置や大きさを設定しています。以前紹介したパッキング行う UI::Hbox や UI::Vbox を使っているので、少々分かりにくいかもしれませんが、ウィンドウの外見とスクリプトを比較すれば理解できると思います。

  17行目では stop_btn.enabled( Phi::Button#enabled )の属性にfalseが代入されていることに注目してください。 Phi::Button#enabled はボタンを有効にするかどうか決める属性です。これにfalseが代入されると、ボタンの文字が淡色になってボタンを押すことが出来なくなります。

1.1.1.2. メソッド定義
  28〜49行はメソッド定義です。説明はそれぞれのメソッドが実際に実行される時に行います。

1.1.1.3. イベント処理(イベントハンドラ)
  51〜83行は何かイベントが起きた時のアクションを決定しています。

  最初に65〜71行の「ゲーム開始」のボタンが押された場合を説明します。「ゲーム開始」のボタンが押されると、$answer.nil?( Object#nil? Kernel#nil? )で $answer が nil でないかどうかを調べます。ゲームが開始されていると、$answer には数字が入りますので、67行が実行されます。ゲームが開始されていない場合は69行目が実行されます。

  67行目は37〜42行で定義された start_game のメソッド呼び出しになっています。 start_game が実行されると、「ゲーム開始」のボタンが押せなくなって、代わりに「ゲーム終了」のボタンが押せるようになります。次に「答え」が設定されて、どの範囲に「答え」があるのかメッセージダイアログを使って表示します。
  
  38と39行は見なれない形ですが、実行している事は17行目と同じようにボタンを有効にしたり、無効にしたりしているだけです。違う点はボタンのオブジェクトを参照するために start_btn や stop_btn を使っていないことです。どうしてこういう事をするのかというと、先ほど触れた Ruby のローカル変数の参照の問題があるからです。今まで触れませんでしたが、 start_btn や stop_btn は Ruby のローカル変数です。 Ruby のローカル変数は参照できる範囲が制限されていて、上のサンプルスクリプトだとメソッド定義の部分ではその外で使われている start_btn や stop_btn を参照することはできません(39行目くらいに p stop_btn を加えて確認してみてください )。そこで、以前説明した Phi::Button.new の第2引数や Phi::Form.new の第1引数を使って、Phi::SCREEN.form1.start_btn1 のようにしてメソッド定義の外にあるオブジェクトを参照しています。一部の定数や大域変数はメソッドの定義の部分でも自由に参照できるので、このような方法で「ゲーム開始」や「ゲーム終了」のボタンのオブジェクト( start_btn、stop_btn )を参照することが可能になります。

  40行目は Ruby のメソッドである rand を使って、乱数を発生させ、0 〜 MAX-1 までの整数を $answer に代入しています。

  41行目は29〜31行で定義されている ok_msg のメソッド呼び出しです。これはダイアログで説明した show_ok_msg と全く同じメソッドになっているので、説明はいらないでしょう。

  元に戻って66行目で $answer が nil でない場合、69行目が実行されて、33〜35行で定義されている error_msg のメソッドが呼び出されます。 error_msg はメソッドの第1引数を使ってエラーメッセージを表示するメソッドですので、 $answer が nil でない場合、「ゲーム開始」ボタンが押されると、'BUG'というメッセージが表示されるわけです。時間がある人はどうしてこれがバグになるのか考えてみてください(スクリプトを実行してみれば分かりますが)

  次に「ゲーム終了」のボタンが押された時のアクション(73〜79行で定義されています)を考えます。「ゲーム終了」のボタンが押されると、74行で $answer が nil でないかどうか判断します。「 if $answer」という形は馴れないと気持ち悪いかもしれませんが、この形では $answer が nil 以外のオブジェクトだと、 $answer は true を返します。 $answer が nil でない場合は75行が実行されて stop_game のメソッドが呼び出され、逆に $answer が nil の場合は error_msg のメソッドが呼び出されて、'BUG'というエラーメッセージが表示されます。

  44〜49行で定義されている stop_game のメソッドが呼び出されると、「ゲーム開始」のボタンが押せるようになり、代わりに「ゲーム終了」のボタンが押せなくなります。次に $answer に nil が代入されて、エディットの文字が空になります。これらの処理は次のゲームをするための準備になるわけです。

  52〜63行は「OK」ボタンが押された時のことがコードされています。53行ではゲームが開始されているかどうかを $answer の値で判断します。unless は if とは逆で「unless 〜」の〜の部分が false を返す時にその次の部分が実行されます。ゲームが開始されていれば、54行が実行され、「答え」の数字とエディットの数字を比較します。答えが正解であれば '正解です。' というメッセージを表示し、 stop_game のメソッドを呼び出します。答えが間違っていれば、不正解のメッセージを表示します。58行の %c:#{edit.text}% は edit.text( Phi::Edit#text )の値を文字列に変換して"〜"の部分に埋めこんでいます。変数の埋め込みの詳しいことは Ruby のマニュアルを読んでください。53行の条件分岐でゲームが開始されていなかった場合は61行が実行されて、"ゲームを開始してください。"というメッセージを表示します。

  最後の81〜83行ではコントロールの配置を変更する form.layout( RGUI::Form#layout )を呼び出しています。

  今回のスクリプトはあまり良い例ではないです。 Phi::SCREEN を使ってオブジェクトを参照するよりはクラスを定義してインスタンス変数を使う方が良いですし、 $answer のような大域変数は使用するべきではありません。自信のある方は数当てゲーム用のRubyのクラスを自分で定義して、上のスクリプトを書き直してみてください。

1.1.1.4. すいません
  今回はApolloの解説よりRubyの解説の方が多いです。Apolloのチュートリアルにふさわしくなるように上記の文章を修正してくださる方はいらっしゃらないでしょうか? 

1.2.1. クラスやメソッド
: Rubyの組み込みモジュール
* Kernel

: Rubyの組み込みクラス
* NilClass
* Object
* Fixnum

: Rubyの組み込みメソッド
* srand
* rand

: Phi::Button#
* Phi::Button#enabled ( Phi::Control#enabled )

: Phi::Edit#
* Phi::Edit#text
