スポンサーリンク

【JavaScript】リバーシ(オセロ)の作り方を初学者向けに1行づつ解説!③【ゲーム】

javascript
勉強の為にJavaScriptでリバーシ(オセロ)を作りたい!
簡単なCPUを実装したい!

という方、初学者向けの解説記事です。

この記事を読んでできること

オセロの作り方を理解することができる
簡単なCPUを実装することができる

対象読者様

javascriptだけで簡単なゲームを作りたい方
コードの意味作り方を理解したい方

上記のような読者様を想定しております。

当記事では javascriptで初めて何かを作りたい方 を想定している為
4つのパートに分けて解説をしていきます。

今回は第3回(前回はこちら:リバーシの作り方を初学者向けに1行づつ解説!②)
クリックすると石が置けて、挟めたら石をひっくり返すまで解説していきます。

概要説明

今回はオセロを作るうえで必須の石をひっくり返す判定です。

正直、難しいですが要点に分けての解説に努めます。
一緒に頑張りましょうm(__)m

◆ポイント

①canReverce() → true/false を返す
②canReverceMasu() → ひっくり返せる可能性のあるマスの座標を返す
③canReverceHoukou()  → ②のマスがひっくり返せるか判定して配列を返す
①②③は関数。それぞれの役割を太文字で補足しています。

3つの関数は連動しており、中心となるのは②の関数です。
②⇒③⇒①の順に解説していきます。

canReverceMasu() を理解しよう!

function canReverceMasu(i, j, color) {
  if (data[i][j] === KURO || data[i][j] === SIRO) {
    return []; // タッチしたところに石があれば何もしない
  }

let touchedMawari = [
 [-1, -1],
 [0, -1],
 [1, -1],
 [-1, 0],
 [1, 0],
 [-1, 1],
 [0, 1],
 [1, 1]
];

let result = [];
  for (let p = 0; p < touchedMawari.length; p++) {
    let canReverseMasu = canReverceHoukou(
    i,
    j,
    touchedMawari[p][0],
    touchedMawari[p][1],
    color
    );
    result = result.concat(canReverseMasu);
  }
return result;
}

この関数でしていることは配列でひっくり返せる可能性のあるマスの座標を返すです。
座標…? 配列… ?  となると思います。部分毎に分割してみましょう。

2次元配列に慣れよう!

まず、配列についてです。こちらをご覧ください。

let touchedMawari = [
  [-1, -1],
  [0, -1],
  [1, -1],
  [-1, 0],
  [1, 0],
  [-1, 1],
  [0, 1],
  [1, 1]
 ];
『げぇ… なんかスゲー だるそうな 配列じゃん…』
お気持ちは大変よくわかります。
初学者の大きな壁ともいえる配列の中の配列、いわゆる2次元配列です。
私も親の仇のように嫌いでした。
しかし、一度理解するとその便利さに感動すると思います。
初学者の皆様にはその「わかった」を是非体感いただきたいのです。

配列の中身をピンポイントで指定しよう

2次元配列の最大の利点は情報量をたくさん入れることができるです。

変数を使う時、『letで宣言して…変数に3を入れて…ここで代入して…』としますが
いっぱい数字や文字を宣言するのは面倒ですし、ミスの元です。

たくさんの数字の配列に見えますが、下記の方法で簡単に数字を取り出すことができます。

是非ご参考下さい。

let touchedMawari = [ [●,●] [●,●] … ] をイメージしよう!

let touchedMawari = [
 [-1, -1],
 [0, -1],
 [1, -1],
 [-1, 0],
 [1, 0],
 [-1, 1],
 [0, 1],
 [1, 1]
];

本題です。

まず、この配列はクリックした座標に周りの8マスを探すことが目的です。
絵で示すとこのようになります。

クリックした座標の周りに相手の色があるかを調べてその座標を格納します。

タッチした座標にすでにが入っている場合は空の配列を返すという処理になります。空の配列を返されるひっくり返せないマスをクリックしたことを意味します。

では、空ではない配列とはどのようなものでしょうか。

配列を返そう

空ではない、つまり配列が入ります。
例えばこんな感じです。

canReverseMasu = [
  [3, 3],
  [2, 2]
 ]

この配列はひっくり返せる可能性のあるマスの座標
まとめると、下記の部分は最後に配列をreturnで返すためのものです。

result return で配列を返そう

let result = [];
    for (let p = 0; p < touchedMawari.length; p++) {
     let canReverseMasu = canReverceHoukou(
     i,
     j,
     touchedMawari[p][0],
     touchedMawari[p][1],
     color
     );     
    result = result.concat(canReverseMasu);   
} return result;
順番としては
①変数resultを宣言
canReverseMasuに配列を格納  canReverceHoukouで特定しますが、それは後述します
result.concat(〇〇)で変数result〇〇を合体
return resultで配列を返す
となります。こちらのメモを見つつ①~④を読んでみてください。
■ i :タッチした座標のタテ / j:タッチした座標のヨコ /  color : 手番
■  p は touchedMawari の配列の数。つまり0~8です
■  touchedMawari[p][0] / touchedMawari[p][1]
・p = 0:  [0][0] → -1 / [0][1] → -1 
・p = 1:  [1][0] →  0 / [1][1] → -1
・p = 2:  [2][0] →  1 / [2][1] → -1

canReverceHoukou() を理解しよう!

function canReverceHoukou(i, j, Xhoukou, Yhoukou, color) {
  let checkedX = i + Xhoukou;
  let checkedY = j + Yhoukou;

  if (
    checkedX < 0 ||
    checkedY < 0 ||
    checkedX > 7 ||
    checkedY > 7 ||
    data[checkedX][checkedY] === color ||
    data[checkedX][checkedY] === 0
    ) {
  return []; // 盤外、同色、空ならfalse(挟めない)
  }

// 挟める候補の座標 = クリックした座標から見て違う色の石を2次元配列で格納
let canReverseMasu = [];
canReverseMasu.push([checkedX, checkedY]);

while (true) {
  checkedX += Xhoukou;
  checkedY += Yhoukou;
  if (
  checkedX < 0 ||
  checkedY < 0 ||
  checkedX > 7 ||
  checkedY > 7 ||
  data[checkedX][checkedY] === 0
  ) {
  return []; // 盤外・空・false ⇒ 挟むことができない
  }

if (data[checkedX][checkedY] === color) {
  return canReverseMasu; 
  } else {
  canReverseMasu.push([checkedX, checkedY]);
  }
 }
}

この関数の目的は canReverseMasuに渡した配列がtrueかfalseか判定するです。
ブロック毎に分けてますので、引数が何にあたるかだけを記載します。

■ i :タッチした座標のタテ / j:タッチした座標のヨコ /  color : 手番
■  XhoukouYhoukou i,j 周りの座標のセット

checkedX・checkedY とは?

let checkedX = i + Xhoukou
let checkedY = j + Yhoukou

まず、こちらの画像を思い出してください。

これはタッチした周辺の8マスを調べていますが、オセロのひっくり返す時のルールを思い出すと…そう、挟まないといけないのです。

つまり、ここでは周囲の8マスを調べるというより、8方向を調べると理解いただくとよいです。すると、この部分もすぐに意味が分かります。

checkedX += Xhoukou
checkedY += Yhoukou

『周囲8マスの座標checkedX/Y にもう一度X/Yhoukouを加える』

これで、隣接したマスの方向を調べることができるのです。

挟めない条件

「挟めない条件を全て満たさない時、石をひっくり返しなさい」

という条件文がここにはいくつかでてきます。

  if (
    checkedX < 0 ||
    checkedY < 0 ||
    checkedX > 7 ||
    checkedY > 7 ||
    data[checkedX][checkedY] === color ||
    data[checkedX][checkedY] === 0
    ) {
  return []; // 盤外、同色、空ならfalse(挟めない)
  }

  if (
  checkedX < 0 ||
  checkedY < 0 ||
  checkedX > 7 ||
  checkedY > 7 ||
  data[checkedX][checkedY] === 0
  ) {
  return []; // 盤外・空・false ⇒ 挟むことができない
  }

checkedX < 0はXが-1以下、つまり盤の外
checkedY > 7もXが8以上、も同じく盤の外
data[checkedX][checkedY] === colorは同じ色
data[checkedX][checkedY] === 0は配列が空っぽ、何もない

日本語におこすとこのようになるでしょうか。
これに該当する場合は return []で空っぽの配列が返る、ひっくり返す処理は行われないことなります。

canReverseMasu に仮の配列を格納しよう!

ーーーーーーーーーー
 data[checkedX][checkedY] === color
ーーーーーーーーーー
let canReverseMasu = [];
canReverseMasu.push([checkedX, checkedY]);
ーーーーーーーーーー
if (data[checkedX][checkedY] === color) {
  return canReverseMasu; 
  } else {
  canReverseMasu.push([checkedX, checkedY]);
  }
 }
ーーーーーーーーーー

↑は解説用にーーーーーーーーーーで一部のコードを省略しています。

緑の部分挟めない条件を満たさない = 挟める条件を満たした座標
canReverseMasuに仮の配列として格納します。

あくまで仮の配列を一度格納する都合上、仮の配列の方向に手番のプレイヤーの石の色があるか調べなければなりません。

data[checkedX][checkedY] === colorクリックした座標に隣接する石の色が同じか
data[checkedX][checkedY] === color仮の座標の先に手番のプレイヤーと同じ色があるかをそれぞれ調べています。

見た目は同じですが、処理の順番で意味が異なりますので要注意です。

この青の部分は満たす場合は挟める条件を満たすことになりますので
return canReverseMasuで格納した配列を返すことができます。

canReverceHoukou() を理解しよう!

function canReverce(color) {
  for (let x = 0; x < 8; x++) {
    for (let y = 0; y < 8; y++) {
    let canReverseMasu= canReverceMasu(x, y, color);
      if (canReverseMasu.length > 0) {
      return true;
      }
   }
 }
return false;
}

あとほんの少しですので頑張りましょう!

注目していただきたいのはcanReverseMasu
この中には下の流れでひっくり返してよい座標が入っていることをおさらしましょう。

関数 canReverceMasu でこの座標はひっくり返してよいですか?という座標を
canReverceHoukouに渡しました。

関数 canReverceHoukou では 渡された座標がひっくり返してよいか判定し、
ひっくり返してよい座標をcanReverceMasuに返しました。

返ってきた座標はどうなるの?

ひっくり返してよい座標とは[[0][1]]や[[3][3]]です。
配列があればreturn trueでひっくり返す処理を行います。
具体的には下記の赤文字の部分。これはtouch関数の一部を抜粋しています。
canReverseMasuの配列の数だけ[k][0]マスをSIROにするという処理が行われます。
let canReverseMasu = canReverceMasu(tate, yoko, SIRO);

if (canReverseMasu.length > 0) {
  for (let k = 0; k < canReverseMasu.length; k++) {
    put(canReverseMasu[k][0], canReverseMasu[k][1], SIRO);
  }
put(tate, yoko, SIRO);
kousin();

}

入っていない場合、ひっくり返せるものがない場合は空の配列となり
lengthは0、return falseでひっくり返す処理自体行われません。

まとめ

ここまでで白黒の石をはさむ処理編終了です!
大変お疲れさまでした!

このパート、とても難しかったと思いますが多次元配列への理解が深まれば今後がとても楽になります。

前:【JavaScript】リバーシ(オセロ)の作り方を初学者向けに1行づつ解説!②
次: