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

続き。前回の追記部分で書いたように添付しておいた、OthelloオブジェクトのcheckPieceメソッドとPlayerオブジェクトのturnメソッドにバグがありました。申し訳ありません。なお前回のブログに直接書いたソースは修正してあります。今回はOthelloオブジェクトのcheckPieceとdoFlipを実装していきます。一応骨組みのようなものはあったのですが、肝心の駒に対する処理は入っていませんでした。実はここがオセロのかなり重要な部分だったりします。まずはOthelloオブジェクトの今できている部分だけ見ていきましょう。

/**
 * ゲームのルールや流れを管理するオブジェクト
 */
var Othello = {};
(function(){
	var board; //ボード
	var view; //ビュー
	var p1,p2; //プレイヤー
	var turn; //現在どちらのターンか
	var skip; //連続でパスした回数
	/**
	 * 初期化(外部公開)
	 */
	function init() {
		board = new Board();
		board.setPiece(Piece.WHITE, 3, 3);
		board.setPiece(Piece.WHITE, 4, 4);
		board.setPiece(Piece.BLACK, 3, 4);
		board.setPiece(Piece.BLACK, 4, 3);
		view = View;
		view.setOthello(this);
		view.setBoard(board);
		view.paint();
		p1 = new Player(Piece.BLACK,"御坂美琴");
		p1.setOthello(this);
		p2 = new Player(Piece.WHITE,"白井黒子");
		p1.setOthello(this);
		turn = p1; //先行
		skip = 0;
		alert("先行は"+turn+"("+turn.getPiece()+")です。"); //先行は御坂美琴(黒)です。
		turn = (turn == p1 ? p2 : p1); //三項演算子 changeTurnを呼ぶと番が交代するので、あえてターンを逆にしている
		changeTurn();
	}
	Othello.init = init; //外部に公開
	/**
	 * ターン交代(外部非公開)
	 * 置く場所があるかどうか調べ、あればプレイヤーのturn()メソッドを呼ぶ
	 * 無ければ、パスして、再びchangeTurn()
	 * 二回続けてパスされると、ゲーム終了
	 */
	function changeTurn() {
		//現在p1のターンならp2のターンに。そうでないならp1のターンにする。
		turn = (turn == p1 ? p2 : p1); //三項演算子
		//全セルから駒を置く場所があるか調べる
		for ( var y = 0; y < 8; y++) {
			for ( var x = 0; x < 8; x++) {
				var piece = turn.getPiece();
				if(0 < checkPiece(piece,x,y)){
					alert(turn+"("+piece+")の番");
					skip = 0;
					turn.turn(); //プレイヤーのturn()メソッドを呼ぶ
					return; //置く場所があればここで処理が終わる
				}	
			}
		}
		//置く場所が見つからない場合
		skip++;
		if(skip == 2){
			endGame();
		}else{
			alert(turn + "(" + piece + ")は置ける場所が無いのでパス");
			changeTurn();
		}
	}
	/**
	 * 指定した座標に駒を置いた場合、何枚取れるか返す(外部から公開)
	 */
	function checkPiece(piece,x,y) {
		if( board.getPiece(x,y) != Piece.EMPTY) //空のセル以外は禁止
			return 0;
		//この駒を中心に、8方向にチェックをかける
	}
	Othello.checkPiece = checkPiece; //外部に公開
	/**
	 * 指定した座標に駒を置いた場合、 駒が何枚取れたのか返す(外部公開)
	 * 異常な場合はnullが
	 * 異常ではないものの、駒を取れない場合は0が返ってくる
	 */
	function doFlip(player,x,y){
		if(player != turn) //引数のプレイヤーが、現在のターンのプレイヤーでない場合エラー
			return;
		var piece = player.getPiece();
		var flip = checkPiece(piece, x, y);
		if(flip == null || flip == undefined) //異常
			return;
		if(flip == 0) //駒を取れない
			return 0;
		//駒をひっくり返す処理を入れる
		changeTurn(); //ひっくり返せたのでターン交代
		return flip;
	}
	Othello.doFlip = doFlip; //外部に公開
	/**
	 * ゲームを終了(外部非公開)
	 * 勝ち負け判定を行う
	 */
	function endGame() {
		//board上にある駒の数を数える
		var piece;
		var black=0,white=0; //得点
		for ( var y = 0; y < 8; y++) {
			for ( var x = 0; x < 8; x++) {
				piece = board.getPiece(x,y);
				if(piece == Piece.BLACK)
					black++;
				else if(piece == Piece.WHITE)
					white++;
			}
		}
		alert("ゲームを終了します");
		//引き分け
		if(black == white){
			alert(black + " 対 " + white + "で引き分けです"); 			
		}else{ //どちらかが勝った
			var win = (black > white ? black : white);
			 //勝ったほうの色がp1の色ならp1の勝ち、そうでないならp2の勝ち 
			var winplayer = (win == p1.getPiece() ? p1 : p2);
			//例. 60 対 4で御坂美琴(黒)の勝ちです。
			alert(black + " 対 " + white + "で " + winplayer + "(" +win +")の勝ちです。"); 			
		}
	}
	/**
	 * Viewで発生したイベント(外部公開)
	 */
	 function selectEvent(x, y) {
		turn.selectEvent(x, y);
	}
	 Othello.selectEvent = selectEvent; //外部に公開
})();

checkPieceの実装

では見ていきましょう。上にも書きましたが、最初はこうなっています。

/**
 * 指定した座標に駒を置いた場合、何枚取れるか返す(外部から公開)
 */
function checkPiece(piece,x,y) {
	if( board.getPiece(x,y) != Piece.EMPTY) //空のセル以外は禁止
		return 0;
	//この駒を中心に、8方向にチェックをかける
}

Boardでは駒を置いてない場所に駒のダミーとして、Piece.EMPTYを配置していました。駒を置くには必ずPiece.EMPTYの箇所でなければいけません。その処理を上で書いています。次に、駒を置けるのは相手の駒をひっくり返せる場所でなくてはいけません。例えば、自分が黒の駒だとします。駒をひっくり返せるということは、自分が黒の駒を置いた箇所の隣に白の駒がいくつか続き、そして最後に黒の駒があるということを意味しています。この部分を実装していきます。自分が駒を置いた場所を中心として、左上、上、右上、右、右下、下、左下、左という風に8方向に対してこのチェックを行い、その結果いずれかの方向でひっくり返せる駒があれば、そこに駒を置けるということが分かります。これを実装すると以下のようになります。

/**
 * 指定した座標に駒を置いた場合、何枚取れるか返す(外部から公開)
 */
function checkPiece(piece,x,y) {
	if( board.getPiece(x,y) != Piece.EMPTY) //空のセル以外は禁止
		return 0;
	if(piece != Piece.BLACK && piece != Piece.WHITE) //置けるのは黒か白のみ
		return 0;
	var oppositePiece = piece.getOpposite(); // 自分とは逆のコマ ex.黒なら白
	var pieceNum = 0; // このコマを置くことによって、とることができるコマの数
	/*
	 * 8方向をチェック
	 * 指定した方向に、自分とは逆のコマが続く限り進んでいく
	 * 例えば自分が黒なら、白のコマが続く限りすすむ
	 * 最後に、自分のコマを見つけたら、取れる駒の数をプラス
	 * これを8方向に対して行い、トータルで取れる駒の数を返す
	 */
	for ( var dy = -1; dy <= 1; dy++) { //dx,dyは次の方向
		for ( var dx = -1; dx <= 1; dx++) {
			if (dx == 0 && dy == 0) // このときは無視
				continue;
			var num = 0; // numは、特定の方向で取れるコマの数
			var nx = x + dx; // nx, nyは次の座標
			var ny = y + dy; 
			var nextPiece = board.getPiece(nx, ny); //次の座標のコマ
			while ( nextPiece == oppositePiece) { // 逆のコマが続く間、進んでいく
				num++;
				nx += dx;
				ny += dy;
				nextPiece = board.getPiece(nx, ny);
			}
			//相手の駒が1つ以上あり、最後に自分の駒があれば
			if (0 < num && nextPiece == piece) {
				pieceNum += num;
			}
		}
	}
	return pieceNum;		
}

詳しい説明は省略します。コメントいっぱい書いておいたので読んでください。使い方は以下のようになります。

board = new Board();
board.setPiece(Piece.WHITE, 3, 3);
board.setPiece(Piece.WHITE, 4, 4);
board.setPiece(Piece.BLACK, 3, 4);
board.setPiece(Piece.BLACK, 4, 3);
alert( Othello.checkPiece(Piece.BLACK, 0, 0) + "個置けます"); //0個置けます
alert( Othello.checkPiece(Piece.BLACK, 2, 3) + "個置けます"); //1個置けます

doFlipを実装する

先にdoFlipの現状を乗せておきます

/**
 * 指定した座標に駒を置いた場合、 駒が何枚取れたのか返す(外部公開)
 * 異常な場合はnullが
 * 異常ではないものの、駒を取れない場合は0が返ってくる
 */
function doFlip(player,x,y){
	if(player != turn) //引数のプレイヤーが、現在のターンのプレイヤーでない場合エラー
		return;
	var piece = player.getPiece();
	var flip = checkPiece(piece, x, y);
	if(flip == null || flip == undefined) //異常
		return;
	if(flip == 0) //駒を取れない
		return 0;
	//駒をひっくり返す処理を入れる
	changeTurn(); //ひっくり返せたのでターン交代
	return flip;
}

最初に少しだけ変更を加えておきます。エラー処理とか、最後にviewのpaintを呼んでいる部分が異なります。

/**
 * 指定した座標に駒を置いた場合、 駒が何枚取れたのか返す(外部公開)
 * 駒を取れない場合は0が返ってくる
 */
function doFlip(player,x,y){
	if(player != turn) //引数のプレイヤーが、現在のターンのプレイヤーでない場合エラー
		return 0;
	var piece = player.getPiece();
	var oppositePiece = piece.getOpposite();
	var flip = checkPiece(piece, x, y);
	if( !(0 < flip) )
		return 0;
	//駒をひっくり返す処理を入れる(未実装)
	
	alert(flip + "個取れました");
	view.paint();
	changeTurn(); //ひっくり返せたのでターン交代
	return flip;
}

駒をひっくり返す処理を実装すると以下のようになります。これはcheckPieceと似たような感じになっています。

/**
 * 指定した座標に駒を置いた場合、 駒が何枚取れたのか返す(外部公開)
 * 駒を取れない場合は0が返ってくる
 */
function doFlip(player,x,y){
	if(player != turn) //引数のプレイヤーが、現在のターンのプレイヤーでない場合エラー
		return 0;
	var piece = player.getPiece();
	var oppositePiece = piece.getOpposite();
	var flip = checkPiece(piece, x, y);
	if( !(0 < flip) )
		return 0;
	/*
	 * 駒をひっくり返す処理を入れる
	 * 上記でcheckPieceを呼んでいて、
	 * 駒が置けることはすでに分かっているので余計なエラーチェックはしない
	 * おおまかな処理はcheckPieceと似通っている
	 */
	board.setPiece(piece,x,y); //駒を置く
	for ( var dy = -1; dy <= 1; dy++) { //dx,dyは次の方向
		for ( var dx = -1; dx <= 1; dx++) {
			if (dx == 0 && dy == 0) // このときは無視
				continue;
			var num = 0; // numは、特定の方向で取れるコマの数
			var nx = x + dx; // nx, nyは次の座標
			var ny = y + dy; 
			var nextPiece = board.getPiece(nx, ny); //次の座標のコマ
			while ( nextPiece == oppositePiece) { // 逆のコマが続く間、進んでいく
				num++;
				nx += dx;
				ny += dy;
				nextPiece = board.getPiece(nx, ny);
			}
			/*
			 * 相手のコマが連続し、最後に自分のコマがあるなら
			 * もう一度この方向について、最初から処理していく
			 */
			if (0 < num && nextPiece == piece) {
				nx = x + dx;
				ny = y + dy;
				nextPiece = board.getPiece(nx, ny);
				while (nextPiece == oppositePiece) {
					board.setPiece(piece, nx, ny);
					nx += dx;
					ny += dy;
					nextPiece = board.getPiece(nx, ny);
				}
			}
		}
	}

	
	alert(flip + "個取れました");
	view.paint();
	changeTurn(); //ひっくり返せたのでターン交代
	return flip;
}

さて、何気にこれでオセロが完成しました。一応オセロがプレイできます。しかしメッセージ表示にalertでダイアログを使っているのでかなりうっとうしいです。これは次回直します。ここまでのソースとゲームのスクリーンショットを載せておきます。

oop-othello8-1.zip 直

doFlipとcheckPieceメソッドを改良

上のソースを見てもらうと分かりますがdoFlipとcheckPieceはかなり似た処理をやっています。というわけで、共通化できる部分を探し、新しいメソッドを作ります。これで少しは処理がすっきりするはずです。今回、checkPieceを分解し、ある方向について何枚コマを取れるかチェックを行う、lineCheckというメソッドを設けます。このメソッドは外部からはアクセスできないようにします。lineCheckの定義は以下のようにします。

function lineCheck(piece,x,y,dx,dy){}

piece、x、yは説明しなくても良いでしょう。dxとdyは-1から1の値をとり、x,yを中心としてどちらの方向にチェックを行うか決定します。少しややこしいメソッドですが、Othelloオブジェクト内からしか利用できませんので、Othelloオブジェクトの設計者だけが知っておけば良い仕様です。少し複雑でも問題ありません。以下のような実装になります。checkPieceを小さくしたものだと分かります。

/**
 *  (x,y)を中心としてdx,dy方向に駒をひっくり返せるかチェックする(外部非公開)
 * @param piece		駒の種類
 * @param x	0~7
 * @param y	0~7
 * @param dx		x方向(-1 ~ 1)
 * @param dy		y方向(-1 ~ 1)
 * @return	ひっくり返せる駒の枚数
 */
function lineCheck(piece,x,y,dx,dy){
	if( board.getPiece(x,y) != Piece.EMPTY) //空のセル以外は禁止
		return 0;
	if(piece != Piece.BLACK && piece != Piece.WHITE) //置けるのは黒か白のみ
		return 0;
	if(!(-1 <= dx && dx <= 1 && -1 <= dy && dy <= 1 ) || (dx == 0 && dy == 0))
		return 0;
	var oppositePiece = piece.getOpposite(); // 自分とは逆のコマ ex.黒なら白
	var num = 0; // numは、取れるコマの数
	var nx = x + dx; // nx, nyは次の座標
	var ny = y + dy; 
	var nextPiece = board.getPiece(nx, ny); //次の座標のコマ
	while ( nextPiece == oppositePiece) { // 逆のコマが続く間、進んでいく
		num++;
		nx += dx;
		ny += dy;
		nextPiece = board.getPiece(nx, ny);
	}
	//相手の駒が1つ以上あり、最後に自分の駒があれば
	if (0 < num && nextPiece == piece) {
		return num;
	}
	return 0;
}

これをつかうとcheckPieceとdoFlipがだいぶすっきりします。以下のようになりました。

/**
 * 指定した座標に駒を置いた場合、何枚取れるか返す(外部から公開)
 * @param piece 駒の種類
 * @param x
 * @param y
 * @return
 */
function checkPiece(piece,x,y) {
	var pieceNum = 0; // このコマを置くことによって、とることができるコマの数
	/*
	 * 8方向をチェック
	 * 指定した方向に対して、lineCheckメソッドを呼ぶ
	 * そのトータルの個数を最後に返す
	 */
	for ( var dy = -1; dy <= 1; dy++) { //dx,dyは次の方向
		for ( var dx = -1; dx <= 1; dx++) {
			var num = lineCheck(piece,x,y,dx,dy);
			if(0 < num)
				pieceNum += num;
		}
	}
	return pieceNum;
}
Othello.checkPiece = checkPiece; //外部に公開

/**
 * 指定した座標に駒を置いた場合、 駒が何枚取れたのか返す(外部公開)
 * 駒を取れない場合は0が返ってくる
 * @param player 駒を設置するプレイヤー
 * @param x
 * @param y
 * @return		駒が何枚取れたのか
 */
function doFlip(player,x,y){
	if(player != turn) //引数のプレイヤーが、現在のターンのプレイヤーでない場合エラー
		return 0;
	var piece = player.getPiece();
	var oppositePiece = piece.getOpposite();
	var pieceNum = 0; //totalで取れる駒の枚数
	/*
	 * 駒をひっくり返す処理を入れる
	 * 最初にlineCheckを呼び、これによって取れる駒の数をチェック
	 * 1枚以上とれるなら、その方向で、相手の駒をひっくり返していく
	 */
	for ( var dy = -1; dy <= 1; dy++) { //dx,dyは次の方向
		for ( var dx = -1; dx <= 1; dx++) {
			var num = lineCheck(piece,x,y,dx,dy);// numは、特定の方向で取れるコマの数
			if(0 < num){
				pieceNum += num; //totalの個数をプラス
				nx = x + dx;
				ny = y + dy;
				nextPiece = board.getPiece(nx, ny);
				while (nextPiece == oppositePiece) {
					board.setPiece(piece, nx, ny); //駒をひっくり返す
					nx += dx;
					ny += dy;
					nextPiece = board.getPiece(nx, ny);
				}					
			}
		}
	}
	if(0 < pieceNum){ //もし駒がひっくり返せたら
		board.setPiece(piece,x,y); //最後に中央に駒を置く
		alert(pieceNum + "個取れました");
		view.paint();
		changeTurn(); //ひっくり返せたのでターン交代
	}
	return pieceNum;
}
Othello.doFlip = doFlip; //外部に公開

まだ完成じゃないよ!

上の修正を加えたソースコードを添付しておきます。一通りゲームはできるようになりましたが、ここからまだたくさん修正を加えるところがあります。まず、メッセージ表示の部分です。アラートを使うのをやめて、Viewを使ってDOMを操作することでメッセージを表示できるようにします。次にプレイヤークラスですが、人間だけでなくコンピューターを実装します。

oop-othello8-2.zip 直