ゲームのロジックを作成する

リスト3●ゲーム終了を判断するメソッド
リスト4●勝者を返すプロパティ
リスト5●プレーの順番を次にまわすメソッド
リスト6●カードのクリック・イベント・ハンドラ
図7●インデクサを記述するウィザードを起動する
図8●ウィザードの設定画面
リスト7●ウィザードが自動作成したインデクサの宣言
リスト8●Card オブジェクトを返すように完成 させたインデクサ
リスト9●配列と同様に使えるようにするため に実装したLengthプロパティ

 さてここまでで,プレーヤの人数と名前についての処理を実装できました。次に実際のプレーの様子を考えてみましょう。

 神経衰弱ゲームでは,まず最初のプレーヤがカードをクリックします*5。1枚目のカードなので,そのまま“表”になります。次のカードを同じプレーヤがクリックして“表”にし,1枚目と同じ“数”ならば,そのプレーヤが2枚のカードを取得します。そしてあと2回,カードをめくって同じように数が一致するかどうかを調べることができます。1枚目と2枚目が違う“数”の場合には,カードを2枚とも“裏”に戻して次のプレーヤの番になります。次のプレーヤは前のプレーヤと同じ操作をします。

 ここで注意してほしいのが,表にしたカードをもう一度クリックしても,裏にならないようにすることです。また,2枚のカードを,しばらく表示させておく仕組みも必要でしょう。カードめくりをプログラムで処理させると,2枚目は一瞬で裏に戻ってしまって,何のカードだったのか人間には確認できなくなってしまうからです。このため,メッセージボックスで「カードを取れた!」または「残念」と表示させ,メッセージボックスを閉じるまでの間,カードを表のままにしておき,数が同じだったのか違ったのかを確認できるようにします。

 ゲームが終了したかどうかの判定も必要になります。カードがすべてなくなったら終了です。これは,プレーヤがカードを取得した後のタイミングで行えばいいでしょう。プレーヤが取得したカードはすべて表になっているはずなので*6,裏になっているカードがなければゲーム終了と判断できます(リスト3[拡大表示])*7。誰が勝ったかは,持っているカードの枚数で判断できます。これはForm1のプロパティとして実装し,値の取得だけができるようにしました(リスト4[拡大表示])。

 ここではプレーヤの持ち札の枚数を順に比較して,前のプレーヤよりも持ち札の数が多いならば,勝者候補として名前が残るようにします。すべてのプレーヤについて比較を終えたときに名前が残っていたプレーヤが勝者になるわけです。札数が同じ場合は,順番の遅いプレーヤが勝者になるようにします。

 一つずつ順番に調べてループさせるという処理は,前回説明したforに似たステートメントを使います。リスト4のforeachキーワードがそれです(1)。これは,

foreach(オブジェクト in 複数のオブジェクトの配列など)

のようにして使います。オブジェクトの部分は,整数などの変数でも構いません。複数のオブジェクトを一つずつとりだして,すべてのオブジェクトについて調べるまで,処理を繰り返します。

 順番を次のプレーヤに渡すという処理は,単に現在のプレーヤのID番号を一つ増加させて,プレーヤ数より多くなってしまった場合に0に戻すという処理を行っています。また,表示してあるプレーヤの持ち札のフォームを隠して,次のプレーヤのフォームを表示させます(リスト5[拡大表示])。

 終了の判断,勝者の判定,順番を次に移す,こうした処理を使って,ゲームの流れをプログラムにするとリスト6[拡大表示]のようになります。プレーヤがフォーム上のいずれかのカードをクリックしたときに,ゲームは進行します。ゲームのロジックは,カードのクリック・イベント・ハンドラに記述しました。

賢い配列を使ってスマートなコードを記述する

 ところで,ゲームの終了を判定するリスト3を見て,“おや”と思った読者の方もいらっしゃるでしょう。(1)の部分です。確かCardsオブジェクトは,Cardクラスのオブジェクトをまとめたオブジェクト配列ではなくて,カードを混ぜる機能などを持った普通のクラスだったはずでは,と気が付いた方――スルドイです。

 通常のクラスからオブジェクトを作った場合,“[ ]”などを付けて配列のようには使えません。また,逆にオブジェクト配列として宣言したものは,“[ ]”を付けずにクラスに実装したメソッドを使うことができません。配列番号を指定しないと,LengthやCloneなど,利用できるメソッドやプロパティは限られ,独自のメソッドを使えないのです。

 では(1)は何なのでしょうか。実はこれ,C#の特徴的な機能で“インデクサ”と呼ぶものです*8。一言で説明すれば,番号によってアクセスできるプロパティというものになります。

 前回は,Cardsクラスの中に,playingCardsというオブジェクト配列を含めて,外部からアクセスできるようにしていました。特定のカードの情報を取得したり,設定する場合には,この配列を利用して,個々のオブジェクトを使うことになります。例えばn番目のカードの数字を取得するには,

(Cardsオブジェクト名).playingCards[n].Number

としていました。

 しかしインデクサを使えば,単純に

(Cardsオブジェクト名)[n].Number

と書くだけで済みます。プログラムを見ても数段わかりやすくなると思いませんか。

 それでは,インデクサをプログラムに組み込んで使い方を見てみましょう。まず,実装するクラスの中で,

(アクセス修飾子) 型 this [int idx]

などと宣言します。これに続く{ }内は通常のプロパティと同じように,getアクセサとsetアクセサを実装します。ただ,今までにない書き方なので「使いにくいな」と思われる方もいるでしょう。確かに思い出しにくい書き方です。でもご安心を。VS .NET 2003は,この部分を作成してくれるウィザードをちゃんと用意しています。

 クラス・ビューで,インデクサを追加したいクラスを右クリックして,ショートカット・メニューから[追加]-[インデクサの追加]を選び(図7[拡大表示]),インデクサの性質を決めます(図8[拡大表示])。

配列のようにしてインデクサを使う

 ここでは,Cardsクラスに複数のCardクラスを組み込んで,配列のようにアクセスしたいので,図8のように「インデクサの型」は選択肢の中から選ばずに「Card」と書きます。「パラメータの型」では「int」を選択し,パラメータ名は「idx」としました*9。「追加」ボタンをクリックすると,右の「パラメータの一覧」に設定したパラメータが移ります。複数のパラメータを追加して,多次元配列のようにインデクサを設計することも可能です。

 何のためのインデクサなのかわかるようにコメントを付けて「完了」ボタンをクリックすると,コードにインデクサの宣言が書き込まれます(リスト7[拡大表示])。今回は外部からCardオブジェクトそのものを変更することはないので,setアクセサの部分を削除して,getアクセサの戻り値をそれまで使っていたplayingCardとします(リスト8[拡大表示])。playingCardというフィールドは外部からアクセスできなくなるので,アクセス修飾子をprivate属性に変えておきます。

 また,カードの数を返すLengthというプロパティも追加しました(リスト9[拡大表示])。処理内容は,playingCardに格納してあるカードの枚数を返すものです。通常は52枚で一定ですが,オブジェクト配列のように使う場合と統一性を保つために実装しています。配列の要素数はLengthプロパティに格納されています。これと同じようにして,要素数を取得できるようにしたのです。

☆               ☆               ☆

 以上,前回作った“カード・ゲームの素”を拡張して神経衰弱ゲームを実装してきました。これで完成!としたいのですが,残念ながら誌面の関係ですべての拡張部分を説明し切れていません。今回の完成プログラムは,日経ソフトウエアのWebサイトからダウンロードできますので,ぜひこちらからダウンロードして前回との差を確認してください。なぜ変更したかを考えることが皆さんの学習になるはずです。

 今回作成したゲームは,できるだけ他のカード・ゲームに発展させられるように作ったつもりです。違うルールのカード・ゲームに改造したり,フォーム上でのトランプの並べ方を変更したり,ぜひ皆さんで独自の改造を加えて楽しんでプログラミングを学習してください。

 というところで,本連載は今回を最後にお休みさせていただきます。プログラミングの説明は,まだ入り口にさしかかったばかりです。これから一層険しくなるでしょうが,奥が深くて難しいからこそ楽しいのです。これからも楽しんで学習を続けてください。