Simon
1 概要
Cocoa の開発環境をデモンストレートするためのシンプルなゲ−ムです。このサンプルのアプリケーションがどんな機能を持っているのか、まずは実際に動かして確かめてみましょう。ダウンロードしたファイルの中には、ビルドされたアプリケーションが入っているので、ダブルクリックするだけで、すぐに動かすことが出来ます。
起動すると、上図のように4つの色の違うボタンがある四角いウインドウが表示されます。真ん中に現在の Count を表示する部分と、その下にメッセ−ジを表示する部分があります。下にはゲ−ムを開始する Start ボタンがあります。この Start を押すと、4つのボタンがランダムに暗く変化します。その順を記憶して、その通りにボタンを押します。このとき、Start ボタンはゲ−ムを中止する Stop ボタンにタイトルが変わっています。
注意:この内容は 2003 年 4 月に、個人的な覚え書きとして作成されました。その後一部修正しているものの、古い内容が残っている可能性があることに注意してください。
2 プロジェクトのファイル構成
では、プロジェクトを構成しているファイルを見ていきます。まず、Project Builder でプロジェクトファイル(Simon.pbproj)を開きます。
Classes のところに、「SimonController.h」と「SimonController.m」がありますが、これがアプリケ−ションをコントロ−ルするメインのクラスです(NSObject のサブクラス)。
Resources の「SimonController.nib」で、ユーザーインターフェイス等の定義がなされています。
3 SimonController.nibの構成
次に、SimonController.nib をダブルクリックして、Interface Builder でその中身を見てみましょう。
SimonController.nib のウィンドウを見ると、先程出てきた SimonController クラスのインスタンスがあります。この MySimonController には、水色で表示している 7 つのアウトレットがあります。これは、実行中にメッセ−ジを変えたり、ボタンの表示を変えたりするために用いられます。myButton1 〜 4 から ListenSequence: というメッセ−ジが送られます。また、myStartStop ボタンからは、startStopGame: というメッセ−ジが送られます。
4 SimonController クラス
まず、SimonController.h を見てみます。
SimonController.h
#import
#define MAX_SEQUENCE_LENGTH 100
@interface SimonController : NSObject
{
id myButton1;
id myButton2;
id myButton3;
id myButton4;
id myCounter;
id myMessage;
id myStartStop;
int listening;
int sequenceLength;
id sequence[MAX_SEQUENCE_LENGTH];
}
- (void)StartStopGame:(id)sender;
- (void)ListenSequence:(id)sender;
- (void)PlaySequence;
@end
Interface Builder で定義した 7 つのアウトレット以外に、listening、sequenceLength、sequence という3つのインスタンス変数が定義されています。また、定数 MAX_SEQUENCE_LENGTH という操作の最大回数を決める数を定義しています。
メソッドは3つですが、そのうち2つは、Interface Builder でボタンから接続されたアクションを実行します。PaySequence は、ゲ−ムの開始的に見本となるボタンの点滅を行います。
5 クラスの初期化
SimonController.m を見ていきます。まず最初にソ−スで使用する定数を宣言しています。
SimonController.m
#define HALF_A_SEC 30
#define ONE_SEC 60
#define TWO_SEC 120
#define NUM_SIMON_BUTTON 4
#define BUTTON_ONE 0
#define BUTTON_TWO 1
#define BUTTON_THREE 2
#define BUTTON_FOUR 3
#define START_STR "Start"
#define STOP_STR "Stop"
#define PRESS_START_STR "Press start to begin."
#define PLAYING_STR "Remember the sequence..."
#define LISTENING_STR "Repeat the sequence..."
#define GAME_OVER "Incorrect Sequence. Game Over"
そして初期化メソッド init の実装になります。
SimonController > init
- (id)init
{
self=[super init]; // スーパークラスの呼び出し
listening=FALSE;
sequenceLength=0;
return self;
}
スーパークラスの初期化メソッドを呼び出し、2つのインスタンス変数を初期化しているだけです。
6 ゲ−ムの開始
次に、StartStopGame: メソッドを見てみます。
SimonController > StartStopGame:
- (void)StartStopGame:(id)sender
{
static int playing = FALSE;
if(playing == TRUE) { // ゲ−ムが実行中なら
playing = FALSE; // 実行中だから停止する
listening = FALSE; // ボタンの押下をチェックしない
// Start/Stop ボタンのタイトルを変える
[myStartStop setTitle:[NSString stringWithCString:START_STR]];
[myStartStop display]; // タイトル表示をリフレッシュ
// メッセージを停止時のものに変える
[myMessage setStringValue:
[NSString stringWithCString:PRESS_START_STR]];
[myMessage display]; // メッセ−ジをリフレッシュ
sequenceLength = 0; // 手番を 0 にリセット
} else {
playing = TRUE; // 停止中だからゲームを開始
[myCounter setIntValue:0]; // カウンタのリセット
[myCounter display]; // スクリ−ン表示をリフレッシュ
[myStartStop setTitle:[NSString stringWithCString:STOP_STR]];
[myStartStop display]; // タイトル表示をリフレッシュ
// メッセージをプレイ中のものに変える
[myMessage setStringValue:
[NSString stringWithCString:PLAYING_STR]];
[myMessage display]; // メッセージをリフレッシュ
[self PlaySequence]; // 新しい手番を開始する
}
}
基本的に難しことはしていません。プレイ関係の変数をプレイ中か停止中に従って設定し、ボタン表示とメッセ−ジを変更しているだけです。プレイ中に新しい手番を開始するときは、メソッド PlaySequence を呼んで、それにまかせます。
7 各手番の実行
各手番は、メソッド PlaySequence によって行われます。
SimonController.m > PlaySequence
- (void)PlaySequence
{
int i = 0;
long junk;
listening = FALSE; // ボタンのチェックをしない
srand(time(NULL)); // C 言語ライブラリの乱数発生準備
switch (rand() % NUM_SIMON_BUTTON) {
// 乱数を生成してそれをボタンの数で割った余りで
// 0〜3 の間の乱数を得て、それによってケ−スを分ける
case BUTTON_ONE:
sequence[sequenceLength++] = myButton1;
// 手順配列に新しい手順を追加する
break;
case BUTTON_TWO:
sequence[sequenceLength++] = myButton2;
break;
case BUTTON_THREE:
sequence[sequenceLength++]=myButton3;
break;
case BUTTON_FOUR:
sequence[sequenceLength++]=myButton4;
break;
}
// 新しい手番が追加できたので見本の表示に移る
[myMessage setStringValue:
[NSString stringWithCString:PLAYING_STR]];
[myMessage display]; // メッセ−ジを表示
for (i=0; i < sequenceLength; i++) {
// 手番の数だけ表示を行う
Delay(HALF_A_SEC,&junk);
[myCounter setIntValue:i+1];
[myCounter display];
[sequence[i] performClick:self];
// ボタンクリックをシミュレートする
}
listening=TRUE; // ボタンチェックを開始する
[myMessage setStringValue:
[NSString stringWithCString:LISTENING_STR]];
// プレイヤ−の入力を待つメッセージに変える
[myMessage display]; // それを表示
}
これも難しくはない。単に乱数を作り、新しい手順の番号のボタンに対する参照を配列のなかに追加して、その配列で参照されるボタンにクリックをシミュレートさせ、そしてプレイヤーの入力を待つだけである。
ここで、見慣れないメソッドは、performClick: です。これはマウスがそのボタンで押された時と同じように、設定されたターゲットにアクションを送ることができます。
8 ボタンをクリックしたときの処理
ボタンをクリックしたときには、listenSequence メッセ−ジが送られます。このなかで、現在ユーザの手番であれば、押されたボタンが見本と同じかどうかをチェックしています。listening が FALSE であれば、見本のあとにユーザーが入力したわけではないので、それを無視します。
このように、ボタンなどのコントロ−ルに状態によって複数の処理をさせたい場合、コントロ−ラ−となるオブジェクトに状態変数を定義して、その値によって処理を変えると、呼ばれるメソッドが同じでも処理の内容を変えることができます。
では、listenSequence メソッドを見てみましょう。
SimonController.m > listenSequence
- (void)ListenSequence:(id)sender
{
static int index = 0;
// いまチェックしている手番のインデックス
long junk;
Delay(HALF_A_SEC,&junk); // すこし間をあける
if(listening == TRUE) { // チェックを行うべきなら
if(sequence[index] == sender) {
// プレイヤ−の選択があっていたなら
index++; // 次の手番に進む
[myCounter setIntValue:index]; // カウンタを更新する
[myCounter display];
if(index == sequenceLength) { // 手番の最後なら
[sender highlight:NO]; // ハイライトをリセットする
[sender display];
[self PlaySequence];
// 新しい手番を始める
index=0;
}
}else{ // 選択が間違っていたらゲームオーバー
[myMessage setStringValue:
[NSString stringWithCString:GAME_OVER]];
[myMessage display]; // メッセージを表示する
[sender highlight:NO]; // ハイライトをリセットする
[sender display];
Delay(TWO_SEC,&junk); // 少し時間をかせぐ
[self StartStopGame:self]; // ゲームを停止する
}
}else
index=0;
}
これも難しい処理はなにもない。単にボタンをチェックすべきならチェックして、正しいかどうかによって処理を変えているだけである。
管理人:神吉 秀典 E-mail: