オブジェクト指向っぽくオセロを作る

なんか作りたい

少し前にニコニコ動画で話題になっていた、「一時間でオセロを作ってみた」に即発されて、私もオセロを作ることにしました。この動画にあがっているオセロのソースは、作者さんのサイトで確認できます。今回オブジェクト指向JavaScriptの勉強のために、これと同じものをオブジェクト指向っぽく、一から作っていきたいと考えています。機能的にはほとんど変わりませんが、オブジェクト指向っぽく作るので、処理の流れが見通しよくなり、拡張性の高いものが出来ると思います。

登場人物を考える

ではさっそくはじめてみます。まずは登場人物、オブジェクトにしたいものを挙げていきます。

  • オセロの駒
    • 黒の駒
    • 白の駒
  • オセロの盤
  • プレイヤー
    • 人間
    • コンピューター
  • オセロのルール
  • オセロの表示

ざっと考えて、上記のようなものになりました。少ないような気がしますが、必要に応じて後々増やしていけばよいでしょう。「オセロの駒」、「オセロの盤」、「プレイヤー」は実際にオセロをする際、目に見えるものなので比較的すぐに思いつくオブジェクトです。これに対して「オセロのルール」は実際には目に見えないものですが、ゲームの流れを管理する重要な役割があります。
そして一番最後に、少し特殊ですが「オセロの表示」というオブジェクトを用意しました。これはグラフィカルな部分を担当させるオブジェクトです。上の方に「オセロの盤」というオブジェクトがあるので、こちらでグラフィカルな処理をしてもよいのですが、今回は「オセロの盤」をデータとして扱い、このデータを「オセロの表示」というオブジェクトを使って実際に画面に描写します。データとなるオブジェクトと、描写を担当するオブジェクトを分離する、いわゆるMVC(Model View Controller)パターンというやつになります。データ、つまりModelとなるのが「オセロの盤」オブジェクト。グラフィックの表示を行うのが「オセロの表示」オブジェクト。そしてModelとViewをつなぐ、Controllerの仕事は「オセロのルール」オブジェクトに担当させます。

それっぽい名前をつける

さすがに「オセロの駒」とか、「オセロのルール」のような名前のオブジェクトは使えませんので、それっぽい名前を考えておきます。また、これらのオブジェクトは代表的なものですので、文字の先頭を大文字にしておきます。

日本語名 名前 役割
オセロの駒 Piece 駒を表現する
オセロの盤 Board 盤上に何の駒が置いてあるかというデータ
プレイヤー Player プレイヤーの操作を表現
オセロのルール Othello ゲーム進行を担当
オセロの表示 View 実際に表示を行う

駒を作る

では作っていきましょう。まずは「オセロの駒」を表現するPieceオブジェクトです。オセロの駒について考えてみると、黒と白の2種類の駒があります。駒の種類を表現する方法としてよく、黒の駒を数字の1、白の駒を数字の2という風に表現する方法があります。しかしこれはオブジェクト指向的ではありません。この実装だと、黒の駒は数字の1に対応している、白の駒は2に対応しているという情報をプログラムを書く人が共通の認識として知っておく必要があります。利用例をあげます。

if(p == 1){//黒のときの処理}else if(p == 2) //白のときの処理}else{ //その他のときの処理}

この例では、pに黒か白どちらかの駒を示す数字が入っています。コメントの部分がついていなければ、黒なのか白なのか直観的には、どちらに対する処理なのか分りませんよね。

オブジェクト指向はこういった場合、黒の駒は数字の1ではなく「黒の駒」というオブジェクト、白の駒も同様に数字の2ではなく「白の駒」というオブジェクトとして表現します。するとこんな風に改善することができます。

if(p == Piece.BLACK){//黒のときの処理}else if(p == Piece.WHITE) //白のときの処理}else{ //その他のときの処理}

Piece.BLACKというのは「黒の駒を示すオブジェクト」です。同様にPiece.WHITEは「白の駒を示すオブジェクト」になります。数字と違い、名前を見ればなんとなく「黒の駒に対する処理なんだな」ということが分ります。ちなみにBLACKとWHITEは定数というのが分りやすいように大文字で表記しています。また、この例をみただけでは分りませんが、BLACKとWHITEの中にはそれぞれ、数字ではなく、「駒を示すオブジェクト」が入ります。

JavaScriptenumっぽいの

さて、上記で

  • 黒の駒を示すオブジェクト
  • 白の駒を示すオブジェクト
  • 駒を示すオブジェクト

が登場しました。これを実装していきます。

ここでは、progress.from.tvで紹介されていた、JavaScriptenumっぽいのを表現するテクニックによって駒を表現してみました。面白いテクニックです。

var Piece = function() {};
Piece = {
	BLACK : new Piece(), //インスタンス生成
	WHITE : new Piece()  //インスタンス生成
};

まずは上記の一行目をご覧ください。ここでは空のPieceという関数を作っています。ちなみにJavaScriptでは、関数もオブジェクトの一種になります。次に二行目以降のPiece={}の内側をご覧ください。少し変わった書式をしていますが、これもオブジェクトの宣言方法の一種として認められています。{}が一つのオブジェクトを示し、そのオブジェクトに対して、プロパティBLACKを付け加え、new Piece()という値を代入しています。また、同様にプロパティWHITEを宣言し、new Piece()という値を代入しています。ここで、BLACKとWHITEの間はコロン「,」で区切られています。ついついセミコロン「;」を使いたくなるかもしれませんが、この場合はコロン「,」が正解です。

さて、このnewというキーワードは既存の関数から、新しいオブジェクトを生成するための処理になります。JavaScriptにはクラスという概念はありませんが、Pieceオブジェクトをクラス、要するにオブジェクトを生成するための設計図とみなし、そこから新しいオブジェクトを作っています。そして新しく「駒を示すオブジェクト」を二つ生成し、それぞれ{}オブジェクトのBLACKとWHITEというプロパティに代入しています。BLACKに代入したほうが今後「黒の駒を示すオブジェクト」として扱われ、同様にWHITEに代入したほうが「白の駒を示すオブジェクト」として扱われます。

最後にここで作った{}というオブジェクトを再びPieceという入れ物に代入しています。これによって、3、4行目のようにPieceから新規にオブジェクトを作ることができなくなりました。しかしその一方で、Pieceは{}というオブジェクトを指すようになったので、今後は先ほど定義したBLACKプロパティとWHITEプロパティにPiece.BLACK、Piece.WHITEという風にしてアクセスできるようになったというわけです。これによって、先ほどもあげましたが、次のような使い方が可能になります。

if(p == Piece.BLACK){//黒のときの処理}else if(p == Piece.WHITE) //白のときの処理}else{ //その他のときの処理}
enumっぽいのを発展させる

上で作ったEnumっぽいやつに、少し機能を追加します。

var Piece = function() {
	arguments.callee.prototype.toString = function() { // 文字列にする
		switch (this) {
		case Piece.BLACK:
			return "黒";
		case Piece.WHITE:
			return "白";
		}
		return "";
	};
	
	arguments.callee.prototype.getOpposite = function() { // 逆の色を返す
		switch (this) {
		case Piece.BLACK:
			return Piece.WHITE;
		case Piece.WHITE:
			return Piece.BLACK;
		}
		return null;
	};
};
Piece = {
	BLACK : new Piece(), //インスタンス生成
	WHITE : new Piece(), //インスタンス生成
	EMPTY : new Piece()  //インスタンス生成
};

「駒を示すオブジェクト」にメソッドを二つ追加し、新しい駒の種類として「空の駒を示すオブジェクト」を追加しました。二行目から見ていきます。arguments.calleeという記述がありますが、これはこの関数を定義しているオブジェクトを指しています。つまり一行目のPieceオブジェクトを指しています。よって、arguments.callee.prototype.toStringの部分は、Piece.prototype.toStringと書いても良いでしょう。ただし、一行目でこの関数名を変更した場合、Piece.prototype.toStringもそれに合わせて名前を変更する必要があります。これを防ぐために、Pieceとすべきところを、arguments.calleeという遠まわしな表記をしています。

次に二行目のarguments.callee.prototype.toString = function(){}に注目してください。

arguments.callee.prototype.toString = function() { // 文字列にする

現在「駒を示すオブジェクト」を作っている最中でした。「駒を示すオブジェクト」は、「黒の駒を示すオブジェクト」と「白の駒を示すオブジェクト」のクラス(設計図)となるものでした。そのためここで、「黒の駒を示すオブジェクト」と「白の駒を示すオブジェクト」に持たせるプロパティをあらかじめ定義しておくことができます。ここではtoStringというプロパティを定義し、その中にfunction()を代入しています。
ちなみにこれと似たようなものとして、thisによるプロパティの宣言方法があります。thisを使うと以下のようになります。

var Piece = function() {
	this.toString = function() {};
}

しかし、thisを使った場合と、arguments.callee.prototypeを使った場合では、厳密には異なります。arguments.callee.prototypeを使った場合、「黒の駒を示すオブジェクト」と「白の駒を示すオブジェクト」でtoStringプロパティの参照先が同一になります。これに対して、thisを使った場合は異なる参照先になります。ここら辺はJavaScriptのプロトタイプチェーンの仕組みが大きく関係していますが、それの説明は別の機会にしましょう。とりあえずarguments.callee.prototypeを使うと、参照先が同一になります。これによって、重複している部分を削減できるというわけです。とりあえず、プロパティの値を後ほど変更するならば、thisを使う。プロパティの値が固定ならば、arguments.callee.prototypeを使うと理解しておけばよいと思います。今回toStringの値は変更しませんのでarguments.callee.prototypeを使いました。

さて話を戻しましょう。次にtoStringの機能について説明します。これはオブジェクトを文字にするときの処理です。この関数は次のようにして利用します。

alert( Piece.BLACK.toString() );

これを実行すると、「黒」という文字がアラートとして表示されます。また次のように記述しても同じ結果を得られます。

alert( Piece.BLACK );

もともと全てのオブジェクトはtoString()というメソッドを持っており、上記のalertの引数のように文字列が必要なタイミングになると自動的にtoString()が呼ばれるようになっています。今回もともと持っているtoString()をオーバーライドしたわけです。

次にこの部分についてみてみます。

arguments.callee.prototype.getOpposite = function() { // 逆の色を返す

ここもtoStringと同じく、getOppositeというメソッドを定義しています。これは現在の色と反対の色を返すものです。例えば黒を選択しているときは白を返します。オセロのプログラムを書く上で、今選択しているのと反対の色を知りたいということがよくあります。そんなときに使います。

最後に

とりあえず今回はここまで。JavaScriptの説明をきちんと書いていたら、肝心のオセロは大して進捗がないのに長い文章になってしまいました。読者にどこまで前提知識があるかがポイントになるんですが、なるべく誰でも読めるようにと説明を書いていくと、いつも長くなってしまいます……。文章を書くのって難しいですね。次回はもう少しサクサク進めていきます。さて次回は「オセロの盤」を表すBoardオブジェクトを作っていきます。