質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

87.79%

【再】オセロプログラムの添削をお願いします

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 2,040

score 27

前回の質問では、オセロプログラムを添削していただき本当にありがとうございました。
自身の無知も相まって、前回は非常に多数のご指摘を受けたため、
その点をなるべく修正したものを、もう一度投稿することに致しました。

しかし大変恐縮ながら、前回とは違い、今回全てのソースコードが完成しておりません。
主な理由として、「処理を分ける」内容が実現できなかったことが挙げられます。

Javaにある継承ができれば解決できそうなのですが、
そのような機能が備わっていないJavaScriptでうまくまとめる方法が分からなかったのです。
また、「グローバル変数を減らす」内容も実現できたとは言い難い状態です。

未完成なコードでお恥ずかしい限りですが、「自分だったらこう書く」といったような点があれば、
些細な内容でも全く構いませんので、ご指摘のほどよろしくお願いいたします。

ソースコード

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-language" content="ja">
<meta charset="UTF-8">
</head>

<body>
<div id="othello"></div>
<script type="text/javascript" src="othello.js"></script>
</body>
</html>
// 黒と白の画像を変数に格納
var STONE_BLACK = 'url("http://i.imgur.com/CLEpdaJ.png")';
var STONE_WHITE = 'url("http://i.imgur.com/VZgXNBM.png")';

var BLACK = 1;
var WHITE = 2;

var turn = BLACK;    // 先手は黒

/* 10×10のフィールドを作る */
var field = [
    [9, 9, 9, 9, 9, 9, 9, 9, 9, 9],
    [9, 0, 0, 0, 0, 0, 0, 0, 0, 9],
    [9, 0, 0, 0, 0, 0, 0, 0, 0, 9],
    [9, 0, 0, 0, 0, 0, 0, 0, 0, 9],
    [9, 0, 0, 0, 0, 0, 0, 0, 0, 9],
    [9, 0, 0, 0, 0, 0, 0, 0, 0, 9],
    [9, 0, 0, 0, 0, 0, 0, 0, 0, 9],
    [9, 0, 0, 0, 0, 0, 0, 0, 0, 9],
    [9, 0, 0, 0, 0, 0, 0, 0, 0, 9],
    [9, 9, 9, 9, 9, 9, 9, 9, 9, 9]
]; 
/* ------------------------------------------------------- */
// 黒と白の初期配置
function setStone() {
    field[4][5] = BLACK;
    field[5][4] = BLACK;
    field[4][4] = WHITE;
    field[5][5] = WHITE;
}

// 盤面を描写する処理
function showBoard() {
    var element = document.getElementById("othello");
    var df = document.createDocumentFragment();
    
    for(var y = 1; y <= 8; y++){
        for(var x = 1; x <= 8; x++) {
            var board = document.createElement("div");
            board.id = "p" + y + x;
            board.setAttribute("style", "border: 1px solid black; float: left; height: 60px;\
                                width: 60px; background: green; style: pointer");
            board.addEventListener("click", clickEvent, false);
            
            // 8ピース毎に改行する
            if((x % 8) == 1) board.style.clear = "both";
            
            // 初期配置の黒と白を描写する
            if(field[y][x] !== 0){
                var image = (field[y][x] == BLACK) ? STONE_BLACK : STONE_WHITE;
                board.style.backgroundImage = image;
            }
            df.appendChild(board);
        }
    }
    element.appendChild(df);
}
    
// 現在の手番が石を置けるかどうか判別する
function isReversible(y, x, player) {
    var opponent = (player == BLACK) ? WHITE : BLACK;
    var delta_y = [-1, -1, 0, 1, 1, 1, 0, -1];
    var delta_x = [0, 1, 1, 1, 0, -1, -1, -1];
    
    // 選択した箇所に既に石が置かれている場合、以降の処理を行わない
    if(field[y][x] !== 0) return false;
            
    // 選択した箇所の周囲8箇所に石が置けるかどうか判別する
    for(var pos = 0; pos < 8; pos++){
        var n = y + pos_y[pos];
        var m = x + pos_x[pos];
        
        // 周囲8箇所に相手の石がない場合、以降の処理を行わない
        if(field[n][m] !== opponent) continue;
        
        // 周囲8箇所の延長線上に相手の石がある間繰り返す
        while(field[n][m] === opponent){
            n += pos_y[pos];
            m += pos_x[pos];
            
            // 相手の石を挟んで自分の石がある場合、trueを返す
            if(field[n][m] === player) return true;
        }
    }
}
    
function stoneReverse() {
    /* TODO: 
     *    石をひっくり返す処理
     *     (isReversible()メソッドとソースコードはほぼ同じ)  */
}

function stoneNavigation() {
    /* TODO: 
     *    自分の石を置くことができるマス目の色を変更する
     *    (field[1][1]~field[8][8]まで全てのマス目に対して
     *    "isReversible();"を実行、trueが返れば背景色変更) */
}

function clickEvent() {
    var element = document.getElementById(this.id);

    // 設定したdividから配列の要素を得る
    var y = parseInt(element.id.substr(-2, 1));
    var x = parseInt(element.id.substr(-1, 1));
    
    var check = isReversible(y, x, turn);
    
    if(check){
        stoneReverse();
        turn = (turn == BLACK) ? WHITE : BLACK;
        stoneNavigation();
    }
}
    
onload = function() {
    setStone();
    showBoard();
}

未完成部分のコードについて

主にstoneReverse()について、具体的な解説をいただければ幸いです。
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

+2

こんな風に書き換えてみました。
↓は動作例です。
イメージ説明
// 黒と白の画像を変数に格納
var STONE_BLACK = 'url("http://i.imgur.com/CLEpdaJ.png")';
var STONE_WHITE = 'url("http://i.imgur.com/VZgXNBM.png")';

var BLACK = 1;
var WHITE = -1;

var turn = BLACK;    // 先手は黒

/* 10×10のフィールドを作る */
var field = [
  [9, 9, 9, 9, 9, 9, 9, 9, 9, 9],
  [9, 0, 0, 0, 0, 0, 0, 0, 0, 9],
  [9, 0, 0, 0, 0, 0, 0, 0, 0, 9],
  [9, 0, 0, 0, 0, 0, 0, 0, 0, 9],
  [9, 0, 0, 0, 0, 0, 0, 0, 0, 9],
  [9, 0, 0, 0, 0, 0, 0, 0, 0, 9],
  [9, 0, 0, 0, 0, 0, 0, 0, 0, 9],
  [9, 0, 0, 0, 0, 0, 0, 0, 0, 9],
  [9, 0, 0, 0, 0, 0, 0, 0, 0, 9],
  [9, 9, 9, 9, 9, 9, 9, 9, 9, 9]
];
var DELTAS = [[ 1, -1], [ 1, 0], [ 1, 1],
              [ 0, -1],          [ 0, 1],
              [-1, -1], [-1, 0], [-1, 1]
            ];

function getOpponent(player) {
  return player * (-1);
}
function getImage(player) {
  return (player === BLACK) ? STONE_BLACK : STONE_WHITE;
}
/* ------------------------------------------------------- */
// 黒と白の初期配置
function initField() {
  for (var y = 1; y <= 8; y++) {
    for (var x = 1; x <= 8; x++) {
      field[y][x] = 0;
    }
  }
  field[4][5] = BLACK;
  field[5][4] = BLACK;
  field[4][4] = WHITE;
  field[5][5] = WHITE;
}

// 盤面の初期化
function initBoard() {
  var element = document.getElementById("othello");
  var df = document.createDocumentFragment();
  var base_style = "border: 1px solid black; float: left; height: 60px; width: 60px; background: green; style: pointer";

  for (var y = 1; y <= 8; y++) {
    for (var x = 1; x <= 8; x++) {
      var cell = document.createElement("div");
      cell.id = "p" + y + x;
      cell.setAttribute("style", base_style);
      cell.addEventListener("click", clickEvent, false);
      df.appendChild(cell);

      // 8ピース毎に改行する
      if ((x % 8) === 1) {
        cell.style.clear = "both";
      }
    }
  }
  element.appendChild(df);
}

function redisplayBoard() {
  for (var y = 1; y <= 8; y++) {
    for (var x = 1; x <= 8; x++) {
      if (field[y][x] !== 0) {
        putStone(y, x, field[y][x]);
      }
    }
  }
}

// player が石をおける場所の一覧を得る。
function getValideHands(player) {
  var ans = [];
  var opponent = getOpponent(player);

  for (var x = 1; x <= 8; x++) {
    for (var y = 1; y <= 8; y++) {
      // 選択した箇所に既に石が置かれている場合、以降の処理を行わない
      if (field[y][x] !== 0) {
        continue;
      }

      // 選択した箇所からの8つの方向に石が置けるかどうか判別する
      for (var direction = 0; direction < 8; direction++) {
        var dy = DELTAS[direction][0];
        var dx = DELTAS[direction][1];
        var py = y + dy;
        var px = x + dx;

        // 周囲8箇所に相手の石がない場合、以降の処理を行わない
        if (field[py][px] !== opponent) {
          continue;
        }
        // 周囲8箇所の延長線上に相手の石がある間繰り返す
        while (field[py][px] === opponent) {
          py += dy;
          px += dx;
          // 相手の石を挟んで自分の石がある場合、ans に追加する。
          if (field[py][px] === player) {
            ans.push([y, x]);
          }
        }
      }
    }
  }
  return ans;
}

// 現在の指手が [y][x] に石を置けるかどうか判別する。
function isReversible(y, x, player) {
  var validhands = getValideHands(player);
  for (var i = 0; i < validhands.length; i++) {
    if (validhands[i][0] === y && validhands[i][1] === x) {
      return true;
    }
  }
  return false;
}

// [y][x] に player が石を置く処理を行う。
// 挟んだ石があれば、それをヒックリ返す。
// (置く位置の妥当性はここではチェックしていない)
function playHand(y, x, player) {
  field[y][x] = player;
  putStone(y, x, player);
  reverseStones(y, x, player);
}

// 石をひっくり返す。
function reverseStones(y, x, player) {
  var opponent = getOpponent(player);
  // 選択した箇所からの8つの方向に石が置けるかどうか判別する
  for (var direction = 0; direction < 8; direction++) {
    var dy = DELTAS[direction][0];
    var dx = DELTAS[direction][1];
    var py = y + dy;
    var px = x + dx;

    // 周囲8箇所に相手の石がない場合、以降の処理を行わない
    if (field[py][px] !== opponent) {
      continue;
    }
    // 周囲8箇所の延長線上に相手の石がある間繰り返す
    while (field[py][px] === opponent) {
      py += dy;
      px += dx;
      // 相手の石を挟んで自分の石がある場合、石をひっくり返す。
      if (field[py][px] === player) {
        var yy = py - dy;
        var xx = px - dx;
        while(field[yy][xx] === opponent) {
          field[yy][xx] = player;
          changeStone(yy, xx , player);
          yy -= dy;
          xx -= dx;
        }
      }
    }
  }
}

// 自分の石を置くことができるマス目の色を変更する
// field[1][1]~field[8][8]まで全てのマス目に対して
function showNavigation(player) {
  var validhands = getValideHands(player);
  for (var i = 0; i < validhands.length; i++) {
    var f = getElement(validhands[i][0], validhands[i][1]);
    f.style.background = "gray";
  }
}

// 自分の石を置くことができるマス目表示を消す。
function clearNavigation() {
  for (var y = 1; y <= 8; y++) {
    for (var x = 1; x <= 8; x++) {
      if (field[y][x] === 0) {
        var f = getElement(y, x);
        f.style.background = "green";
      }
    }
  }
}

// [y, x] に相当する DOM の element を得る。
function getElement(y, x) {
  return document.getElementById("p" + y + x);
}

// 石を置く。
function putStone(y, x, player) {
  var f = getElement(y, x);
  f.style.backgroundImage = getImage(player);
}

// 石をひっくり返す。
function changeStone(y, x, player) {
  putStone(y, x, player);
  // TODO: 石をひっくり返すアニメーションをするともっと良い。
  //     jquery を使うとアニメーションが簡単にできる。
}

// マウスクリック時の処理。
// クリック位置が妥当なら、石をおいて、挟んだ石の反転を行う。
// クリック位置が不正なら、何もしない。
function clickEvent() {
  var element = document.getElementById(this.id);
  // 設定したdividからfiled の位置を得る。
  var y = parseInt(element.id.substr(-2, 1));
  var x = parseInt(element.id.substr(-1, 1));

  var check = isReversible(y, x, turn);
  if (check) {
    clearNavigation();
    playHand(y, x, turn);
    turn = getOpponent(turn);
    showNavigation(turn);
  }
  debug_show_fields();
}

// for debug: fiels[][] の内容を console に表示する。
function debug_show_fields() {
  for (var y = 1; y <= 8; y++) {
    var s = "" + y + ":";
    for (var x = 1; x <= 8; x++) {
      var c = ".";
      if (field[y][x] === BLACK) {
        c = "x";
      } else if (field[y][x] === WHITE) {
        c = "o";
      }
      s += c;
    }
    console.log(s);
  }
}

onload = function() {
  initBoard();
  initField();
  redisplayBoard();
};

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2015/10/19 15:00

    前回に引き続き、非常に丁寧な回答ありがとうございます。

    まだじっくり腰を据えてソースコードと向き合っていないため、
    また改めてコメントを追加する予定です。反応が遅くなってしまい申し訳ありません。


    キャンセル

+1

あくまでもアイデア出しということでご了解ください。

isReversibleは、置けるかチェックするメソッドではなく、
反転するマスを2次元配列のリストで返す内容にすると、
後続のstoneReverseの実装が容易になると思います。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 87.79%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る