思考ゲームとして、オセロの裏返し判定を書いてみる。
const fields = [...Array(8*8)].map((o,i)=>({ x:i%8, y:parseInt(i/8), c:null }));
const [ w, b ] = ["white","black"];
とりあえず8×8の盤面がないとお話にならないので準備。
2D配列でもいいんだろうけど、処理がめんどくさそうなので1D配列にしておく。
代わりに各マスには座標を持ってもらう。
白黒はよく使うはずなので定数化。
const find = o=> (e,i,arr)=>e.x==o.x&&e.y==o.y ;
目的のマスを見つける処理も多用しそうなので先に定義。
最初に真ん中の4マスにコマを置く。
[ { x:3, y:3, c:w },{ x:4, y:3, c:b },
{ x:3, y:4, c:b },{ x:4, y:4, c:w } ]
.forEach(o=>fields.find(find(o)).c=o.c);
オセロの裏返し判定条件は、
(1)対象のマスを含む縦横斜めの1直線上で
(2)隣接した連続するマスに対象以外の色が配置されていて
(3)その終端に対象と同じ色がある
ってことなので、まずは(2)(3)の判定を作ってみる。
const reversibles = (v, line)=>{
const partial = line.filter((e,i)=>i>line.findIndex(find(v)));
if(partial.length==0) return [];
let str = partial.map(e=>(!e.c)?" ":e.c.charAt(0)).reduce((a,n)=>a+n);
const c = v.c.charAt(0);
str=str.replace(new RegExp("^([^ "+ c +"]+" + c + ")?.*$"), "$1");
return partial.filter((e,i)=>i<str.length-1);
};
こんな感じ。
1直線から自分より後ろの部分を切り出して判定。
正規表現使うと楽だった。
ここでは1直線上で裏返し可能なマスを配列で返すように実装。
後は(1)の条件をクリアできれば目的達成。
const reversi = o=>{
const pos = fields.find(find(o));
if (pos.c) return -1;
const horizontal = fields.filter(e=>e.y==o.y);
const vertical = fields.filter(e=>e.x==o.x);
const leftup = fields.filter(e=>e.x-e.y==o.x-o.y);
const leftdown = fields.filter(e=>e.x+e.y==o.x+o.y);
const result = []
.concat(reversibles(o, horizontal))
.concat(reversibles(o, horizontal.reverse()))
.concat(reversibles(o, vertical))
.concat(reversibles(o, vertical.reverse()))
.concat(reversibles(o, leftup))
.concat(reversibles(o, leftup.reverse()))
.concat(reversibles(o, leftdown))
.concat(reversibles(o, leftdown.reverse()))
;
if(result.length) {
pos.c=o.c;
result.forEach(e=>fields.find(find(e)).c=o.c);
}
return result.length;
};
で、こうなる。
-1が返ってきたらそのマスは使用中。
0だったら裏返せるマスがないのでそのマスは使用不可。
配列に「reverse()」かけると配列自体が変更されるけど、使い捨てるので良しとする。
const horizontal = fields.filter(e=>e.y==o.y);
const vertical = fields.filter(e=>e.x==o.x);
縦横の直線はすぐにわかるが、
const leftup = fields.filter(e=>e.x-e.y==o.x-o.y);
const leftdown = fields.filter(e=>e.x+e.y==o.x+o.y);
x、yの差が同一のマスを選択すると左肩上がり、右肩下がりの直線が、
x、yの和が同一のマスを選択すると左肩下がり、右肩上がりの直線が、
それぞれ取れるのは常識だよね?
最後に出来上がった物をテストしてみる。
/* ===== test ===== */
// (3,3):white (4,3):black
// (3,4):black (4,4):white
// -- 1st:white(3,5)
console.log(reversi({x:3,y:5,c:w}));
// (3,3):white (4,3):black
// (3,4):white (4,4):white
// (3,5):white
// -- 2nd:black(2,5)
console.log(reversi({x:2,y:5,c:b}));
// (3,3):white (4,3):black
// (3,4):black (4,4):white
// (2,5):black (3,5):white
// -- 3rd(1):white(2,5) -! already occupied
console.log(0>reversi({x:2,y:5,c:w})?"occupied":"not occupied");
// -- 3rd(2):white(0,0) -! not available
console.log(0==reversi({x:0,y:0,c:w})?"not yet":"go on");
const arr2string = arr=>{
let str = "[\n", sep = "";
arr.forEach((e,i)=>{
str += sep + "(" + i + "){ x:" + e.x + ", y:" + e.y + ", c:" + e.c + " }";
sep = ", " + (i%8==7?"\n":"");
});
return str + "\n]";
};
console.log(arr2string(fields));
以上。