もくじ へ

4. Objective-C らしいオセロに挑戦

 ちょっとしたプログラムと言うと、どうしてもオセロを作ってみたくなるのが私の性分なのである。その昔、Z80マシンのBASICで作って、2手先まで計算するだけでも遅くて、悲しい思いをした記憶が微かに残っている。DOSでC言語にはじめて出会ったときも、コンパイル言語の高速さと、再帰呼び出しの面白さに引かれて、作った記憶がある。(今となっては恨みの多い)N社のマシンだったので、ROMをコールしてグラフィックも使ったし、結構良い感じに出来上がったのだが、私自身がオセロに才能がないので、最終的に私程度にしか強くないソフトになったので、友人に勧められるものではなかった。と言うか、私はもともと、他人とオセロで対戦した経験などほとんど無くて、このソフトのデバッグで何百回とオセロをしたことが、現在の私のオセロの(そこそこの)技量となっているのである。
 さて、2年ちょっと前にMacユーザーになってから、OS9上でコードウォリアとか言う開発ソフトを借りて、オセロに挑戦したことがあった。何だか良く解らない難解な開発ソフトで、例題を参考にグチャグチャやっていたら、人間同士が2人で対戦するオセロ盤の段階までは大変な苦労をして辿り着いたのだが、ソフト開発に楽しみが感じられず、その先のコンピュータとの対戦モードを考えることもなく、見栄えも悪いままで頓挫し、それっきりMacでのソフト開発は止めていたのである。
 ろくな出来栄えのものではないのだが、小学生の娘と暇つぶしに対戦するには結構役に立っていたりして、OSXを起動ディスクにしてからは、クラシックからの起動が遅いと非難を浴びることになり、いっちょCocoaでかっこいいのを作ってやるかと思い立つきっかけになった訳である。

 さて、得意の長い前置きはともかくとして、言いたかったのは、今回のオセロソフトは、コンピューターとの対戦を目的としたものではなくて、あくまで人間2人が対戦するためのオセロ盤を目的としていると言うことです。

まずは機能確認バージョンから

 いっちょCocoaでかっこいいのを作ってやるかと思い立ったとは言え、見栄えの前に、まずは正しく動くオセロ盤としての機能を確認するバージョンから、無理せず作って行くことにします。

設 計

 どんな「ちょっとしたプログラム」でも、何をしたいかが明確で無ければ始められない。プログラミングとは自分の「思想」や「意志」を記号化する作業だからである。などと偉そうに言って、今までは完成予想図をここに見せていた訳ですが、何せ今回はオセロ盤ですから、絵を見なくても誰でも想像できるでしょう。
 8×8の格子で出来たオセロ盤があって、白と黒が選べるラジオボタンがあって、どっちの色が何個あるか表示するテキストボックスがあって、盤面を初期状態に戻すリセットボタンがあれば、これで十分でしょう。今回は、機能確認バージョンなので、白と黒のコマは、"W" と "B" の文字で代用することにします。
 余談ですが、前に作りかけで頓挫したOS9版は、白と黒のコマはグラフィックスだったのですが、コマの数の表示が無いので人間が数え、リセットボタンが無いので一旦終了して再度起動すると言う、何とも悲しいものでしたので、当たり前にそんな機能が付いているだけでも、Cocoaは素晴らしいと感動してしまいます。
 オセロ盤部分の実体は、Cocoaはヤッパリ!のONLINEの記事の「8. 簡易ランチャーを作る」を参考にして、NSButtonのサブクラスを作って、これを64個敷き詰めることにしました。64個の区画の中のある場所をクリックすると何かが起きる訳ですから、それぞれがボタンだったら良いかと考えた訳です。
 NSButtonのサブクラスを使わずに、単に64個のNSButtonを配置して、Model - View - Controller設計でオセロ盤を作ることはもちろん出来ると思いますし、その方が自然だと思います。しかし、ここではプログラミングの面白さを優先して、NSButtonのサブクラスを作って、各ボタンがまるで知能的に隣のボタンと話し合って盤面の変化を決めているかのような、そんなアルゴリズムを作って見ました。ボタン同士がメッセージのやり取りを通して自分たちの状態を変えて行くことが、私には「Objective-C らしいなあ」と感じられるので、このような表題にしました。こう言うアルゴリズムに興味の無い方から見ると、大変奇抜なアルゴリズムに見えるかも知れませんね。
 NSButtonのサブクラスをMyButtonと名付けています。これをウインドウに配置する際には、Cocoaはヤッパリ! に書いてあるように、CustomViewを配置してからインスペクターのクラスの設定のところでMyButtonを選ぶ必要があります。ボタンを配置してからインスペクターのクラスの設定のところでMyButtonを選ぶことも出来ますが、このやり方ではMyButtonの独自の初期設定(initWithFrameメソッド)がシステムにオーバーライドされてしまって使えなくなってしまうようです。
 MyButtonにはmyOutletと言う名前のアウトレットが一つだけあって、一番左上のMyButtonを起点として、一つ右へ、一つ右へと配線してあります。右端に来たら、一つ下の段の左端に配線してあります。すなわち、一番左上のMyButtonにあるメッセージを送った時に、そのメッセージを処理するそのメッセージ名のメソッドの中に、「myOutletに同名のメッセージを送れ」と書いてあったならば、そのメッセージは64個全てのMyButtonを通り抜けてから一番右下のMyButtonから出てくるようしておくのです。
 ボタンがボタン同士で話し合うためには、右側だけでなく周りの8方向のボタンにメッセージを送る必要があります。また、自分が何番のボタンであるのかを認識している必要もあります。そこで、64個全てのボタンを通り抜ける初期化メッセージを送って、64個のオブジェクトへのポインタを要素とするクラス定義の配列に、自分へのポインタを書き込ませることにしてあります。これによって、各ボタンはどの番号のボタンにでもメッセージを送ることができます。

 もう一つは、NSObjectのサブクラスにMyDelegateと言うのを定義してインスタンス化してあります。これはお決まりのFilesOwnerのデリゲートとして、初期化メソッドであるapplicationDidFinishLaunchingを実行するつもりでこう言う名前にしたのですが、それだけでなく、myOutletが一番左上のボタンに配線してあって、一番右下のボタンのmyOutletからはこのインスタンスに配線がしてあります。また、ラジオボタンやテキストフィールド、リセットボタンなども、このインスタンスと配線されています。
 例えば、盤面の白の数と黒の数を集計しろと、一番左上のボタンにメッセージを送ると、64個ぐるっと巡って、自分に集計結果が帰ってくる仕組みになっています。これをテキストボックスに書き込む訳です。

「MyButton部」のコーディング

クラス変数(定数)の解説
NSMutableArray *buttonArray
 64個のボタンに0〜63の連番を付けて、その番号のボタンオブジェクトへのポインタが初期化処理で格納される配列。

int dir[]={ 1,-7,-8,-9,-1,7,8,9 }
 自分から見て、右側を方向0として、反時計回りに8つの方向に対して0〜7の数字を割り振った場合に、方向0へ行くと言うことは連番を1増やすことであり、方向1へ行くと言うことは連番を7減らすことである。このような考えのもとに、方向を示す数字と、連番の増減値を対応表にした配列。

int userState
 プレーヤーが白ならば整数値'W'、黒ならば整数値'B'がセットされる。

インスタンス変数の解説
int myState
 自分が空白なら0、白ならば整数値'W'、黒ならば整数値'B'がセットされる。

int myNumber
 連番の自分の番号

メソッド(関数)の解説
BOOL isOkToGoto( int m, int i )
 自分の位置からある方向を見たとき、それがオセロ盤の外ではないことを返す関数。

- (id)initWithFrame:(NSRect)fRect
 ボタンの初期化。ここで重要なのは、自分がクリックされた時に、自分の中のmyActionメソッドが呼び出されるように定義していることです。

- (void)myInit
 64個のボタンを順番に全て巡って、自分のオブジェクトへのポインタをクラス定義の配列buttonArrayに登録し、自分の連番上の番号を知り、盤面を初期状態の表示にする、初期化処理。

- (void)setUserState:(int)bw
 プレーヤーの色をクラス変数に登録して、みんなで見れるようにする。全ボタンに実装される機能であるが、一番左上のボタン以外はこの機能は利用されない。

[ buttonArray objectAtIndex: (myNumber+dir[dirToGo]) ]
 プログラム中に何回も出てくるので解説しておく。自分から見て、方向dirToGoにあるボタンと言う意味である。

- (void)myAction
 自分がクリックされた時の処理。自分が空白なら、自分の周り8方向を順に見回して、それがオセロ盤の外ではないなら、あなたを裏返して良いですか(canIReverceYou)と聞いて、もしOKだったら、さあ裏返しましょう(letsReverce)とメッセージを送る。一つでも裏返すことができたら自分をプレーヤーの色にして(iCanSet)、盤面に変化を与えたと言うメッセージ(didSetSome)を送る。

- (BOOL)canIReverceYou:(int)dirToGo
 裏返して良いですかと聞かれたので、自分が空白だったり、既にプレーヤーの色だったらNOと答える。そうではなくて、もう1個となりがまだオセロ盤の外ではないなら、もう1個となりに裏返して良いですか(canIReverceYouNext)と聞いて、オセロ盤の外ならNOと答える。

- (BOOL)canIReverceYouNext:(int)dirToGo
 自分が空白ならNOと答える。自分がプレーヤーの色ならYESと答える。そうではなくて、もう1個となりがまだオセロ盤の外ではないなら、もう1個となりに裏返して良いですか(canIReverceYouNext)と聞いて、オセロ盤の外ならNOと答える。

- (void)letsReverce:(int)dirToGo
 さあ裏返しましょうと言うことで、自分が相手の色であるなら、プレーヤーの色に変わり、お隣にも、さあ裏返しましょう(letsReverce)とメッセージを送る。

- (void)didSetSome:(int)us
 盤面に変化を与えたと言うメッセージを、一番右下のボタンまでリレーで伝える。

- (void)countWhite:(int)sumW black:(int)sumB
 自分が白なら白の、黒なら黒の合計値を1増やして、一番右下のボタンまでリレーで伝える。

「MyDelegate部」のコーディング

メソッドの解説

- (void)applicationDidFinishLaunching: (NSNotification *)aNote
 FilesOwnerのデリゲートとしての初期化メソッドです。ウィンドウ上のリセットボタンと同じ働きをする。

- (IBAction)myActionInit:(id)sender
 ウィンドウ上のリセットボタンのアクションで、ボタンの連番作成などをやったmyInitのメッセージの送り手。

- (void)myInit
 ボタンの全てを巡って、myInitのメッセージがここに帰ってきてしまいますので、形式的に受け取って何もしません。

- (IBAction)myActionBW:(id)sender
 プレーヤーの黒と白の切り替わりによって、ラジオボタンからくるアクション。

- (void)didSetSome:(int)us
 盤面に変化があったと言うメッセージを最終的に受け取って、プレーヤーの黒と白を切り替えるラジオボタンを自動で押してあげます。そして、盤面のコマの白と黒の数の集計をボタンにお願い(countWhite:black:)する。

- (void)countWhite:(int)sumW black:(int)sumB
 盤面のコマの白と黒の数の集計を最終的に受け取って、テキストボックスに表示。

サンプルソフトのダウンロード

 このページ、全く絵無しに作ってしまいました。気になるなら、どんなソフトかUPしておきますので煮るなり焼くなりして下さい。でも こんどは16KB! ソースのみですので、まずは「Project Builder」でコンパイルして下さい。
 あくまでも機能確認バージョンですので、実行して「なーんだ!」などと言わないで下さいね。素材と思って下さい。

今日はここまで、2002.2.3

 かっこいい出来栄えのバージョンは...、いつになることやら。


サンプルソフトのダウンロード(2)

 2週間もかけてこんなのかい、と思いっきり非難を受けそうですが、一応仕事で出張してたとか風邪引いてたとか言い訳はあるんですが..。とにかく、いつまでも「素材です」と言ってばかりもいられないので、数行直して(数行だけ?)、デフォルトのOn/Offボタンの色変化を利用して完成品にしてみました。Aquaインターフェースの素晴らしさで、何かこのままで良いかなと思ってしまったりします。
 同じく16KB! ソースのみですので、まずは「Project Builder」でコンパイルして下さい。

今日はここまで、2002.2.17

 本当にかっこいい出来栄えの、アニメとかサウンドとか使っちゃうバージョンは...、いつになることやら。