8方向の判定
- 2016.06.10
- 新入社員研修
シンメトリック新入社員の青木です。 これまでオセロを作り続けて2か月というところですが、なんとあと1か月もオセロを作りつづけてしまいます。何もわからないところからのスタートで最初は何から調べればいいのかもわからないぐらいでしたが、そろそろソースコードの書き方などもわかりはじめてきて面白味が高まってきました。これからの記事群でその面白味が伝えられることができたらなあと考えております。
今週の目標
まず私が作っている通信対戦オセロプログラムですが、オセロらしい機能はほぼすべてブラウザサイドのソースコードにて記述されています。サーバーに記述されているのはあるブラウザから座標のデータが送信されてきたときそれをwebsocket通信を用いて正しい相手に渡すという部分だけです。
まあその両方はできているので、もはやこれは通信対戦オセロプログラムでしかないのですが、現在の悩みとしてブラウザサイドに寄せて寄せて書いたので、そのソースコードがひたすらに長くて読みにくくなってしまいました。3500行あります。これからはDBに手を付けて勝率ランキングなどのmustな機能をつけなければならないのですが、この行数からさらに盛っていくのはかなりやんちゃかなと考えました。
ですので今週はこれをすっきりさせていきたいのですが、そもそもなぜ膨大な行数になってしまったかというと、オセロというゲームの流れの通りに関数を動かすように書いていたからです。その流れについて具体的に言語化してみます。 →サーバーから受け取った座標データに相手の色を置く処理 →受け取った座標から発せられる相手の色にひっくり返す処理 →全座標に対して自分が置ける座標の判別をする処理 →置ける座標にクリックなりで入力された場合にそこに自分の色を置く →入力した座標から発せられる自分の色にひっくり返す処理 →入力した座標をサーバーに送る処理 →サーバーにからの座標データを受け取る体制になる処理 →(ループ) 以上がこのオセロプログラムの根幹ですので、これらの機能をこの順番で書いていました。
これらの過程で、ふたつのひっくり返す処理と、自分が置ける座標がどこかを判別するための処理がとても冗長なコードになっていました。その理由を説明するために、黒の自分が選んだ座標の左側の石をひっくり返すための処理を以下に示します。 前提として、tableタグでオセロの盤面を表現し、その中に座標に紐づいたid属性を持つtdタグがあり、そのtdタグの中に黒・白・空白を表すspanタグが存在することにします。
var TX = ~; //置かれた座標のx
var TY = ~; //置かれた座標のy
var BLACK = ‘’; //自分の色(黒)のHTML
var WHITE = ‘’; //相手の色(白)のHTML
var dif =0; //最終的にひっくり返す数
var reversible = 0; //これが0のままであればひっくり返せないという判定
var NX = TX - 1; //置いた座標を(TX, TY)としたときの左隣のx座標
while(NX > -1){ //左端のマスはx=0なのでそれを超えない範囲でループ
var nextid = NX + ',' + TY; //参照したい座標の文字列
var nexttarget //それをidとした要素の中のHTMLの取得
= document.getElementById(nextid).innerHTML;
if(nexttarget == WHITE){ //参照先が相手の色のとき
dif++; //ひっくり返す数+1
}
else if(nexttarget == BLACK){ //参照先が自分の色のとき
if(dif > 0){ //dif=0のときは置いた隣に自分の色がある場合
reversible++;
}
break; //ループを抜ける
}
else{ //参照先空白のとき
break; //ループを抜ける
}
NX = NX--; //参照先を左に移動させてループへ
}
if(reversible == 1){ //ひっくり返すかどうかの判定
while(dif > 0){ //ひっくり返す回数ループ
var RX = -1 * dif + TX; //実際にひっくり返す座標のx
var reverseid = RX + ',' + TX; //ひっくり返したい座標の文字列
document.getElementById(reverseid).innerHTML //自分の色で上書き
= BLACK;
dif--; //実際にひっくり返す座標を右へ移動させてループへ
}
}
記事として載せるために改行やコメントの位置をある程度書き直しましたが、これが左側へ見た場合に限っての黒にひっくり返す処理です。
オセロのルール的には左だけではなく他に7方向同じように処理をする必要があり、8倍以上の長さになります。これを流れの通りに書いていたため、「相手にひっくり返される処理」「ある座標に対して自分がひっくり返せるかどうか判定する処理」「自分がひっくり返す処理」にその8方向への処理をそれぞれ書き下してしまいました。つまり合計24倍の長さの量になってしまい、これが3500行の中の80%を占めていました。今回はこの24倍を1倍から2倍程度にしたいという記事になります。
行ったアプローチ
このオセロにおいて冗長になった処理がすべて同じような部分だったのは幸いでした。まさしく同じような処理を使いまわせるメソッド化という概念があり、別個にその8方向参照するメソッドを用意すれば量が1/3になります。ただしまだ先ほどの8倍ほどの量があってたいがい冗長ですので、それを縮小したい気持ちが強いです。
ということで考えました。オセロが二次元である以上参照するべき方向は8を超えないので、8回の処理が行われるループ文の中に押し込めれば単純に1/8になってすっきりします。そのループのうちに参照先座標を8方向に移動できる処理が組み込めればいいということで、三角関数が適当であると気付きそのように書いてみました。
このように一つの変数で8方向を表現できるので、これを使って書き下してみます。
var TX = ~; //置かれた座標のx
var TY = ~; //置かれた座標のy
for(var n = 0 ; n < 8 ; n++){
var i = Math.round(Math.cos(n * Math.PI * 45 / 180)); //-1 or 0 or 1 になる
var j = Math.round(Math.sin(n * Math.PI * 45 / 180));
var NX = TX + i; //参照先の座標
var NY = TY + j;
var dif = 0;
var reversible = 0;
var ac; //動作時に置いたとされる方の色
var rc; //動作時にひっくり返される側の色
if( ){
//分岐 置くのが黒 or 置くのが白
}
while(NX > -1 && NX < 8 //範囲外に出るまで
&& NY > -1 && NY < 8){
var nextid = NX + ',' + NY;
var nexttarget = document.getElementById(nextid).innerHTML;
if(nexttarget == rc){
dif++;
}
else if(nexttarget == ac){
if(dif > 0){
reversible++;
}
break;
}
else{
break;
}
NX = NX + i;
NY = NY + j;
}
if(reversible == 1){
if( ){
//分岐 実際にひっくり返す処理 or ひっくり返せるかどうかの判別の処理
}
}
}
このように書いて組み込んでみました。3500行あったソースコードが600行になったので流石に読みやすいです。
作業結果
大きな問題はなく動きました。
課題
いよいよ残すはデータベースとの接続とその処理になります。データベースとの接続に関しては散発的に挑戦してはいるもののいまだ果たせていないので、今後本当にできるのかといったところですが、この峠を越せれば研修として出された課題であるところの要件を満たされるのでがんばりたいです。