konanomono
@konanomono (伝説の 粉)

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

二次元配列操作とrandom操作

Q&A

Closed

解決したいこと

dart言語を使用して、マインスイーパーモドキを作成していますが、以下のことを実現したいです。

ですが、方法がわからず、質問いたします。

・ランダムで二次元配列上に爆弾を設置する(爆弾の値だとわかるように任意の数字で構いません)
・二次元配列上の爆弾に近いマスの周囲に、爆弾までの距離?を入れる。
・二次元配列上で空白マスを触った場合に一気に展開する?→表現があっているかはわかりません。

お忙しい中大変申し訳ありませんが、ご回答いただければ幸いです。

0

2Answer

基本的に考え方に関する質問だと思うので、Dart以外の言語に慣れているのであれば、その言語で書いてからDartに書き直すと理解しやすいと思います。

以下に例を挙げます。(数字は一般的なマインスイーパに合わせて周囲8マスにある地雷の数にしています。※地雷までの距離にすると空白マスの定義が不明です。)

(個人的に)わかりやすいようにクラスを定義しています。(各マスをMineCellクラス、全体をMineTableクラスとしています。)

import 'dart:math'; // 乱数用

// MineCellの2次元配列のエイリアス
typedef MineCellContainer = List<List<MineCell>>;

// MineTableクラス
class MineTable {
  final int cols, rows, numOfMines; // 列数, 行数, 地雷の数
  MineCellContainer table = [];
  
  // コンストラクタ(引数:列数, 行数, 地雷の数)
  MineTable(this.cols, this.rows, this.numOfMines) {
    // MineCellの2次元配列を生成
    table = List.generate(rows, (y) => List.generate(cols, (x) => MineCell(x, y)));
    
    // ここから地雷をランダムに置く処理
    Random rnd = Random(); // dart:mathライブラリにあるRandomクラス
    for (int i = 0; i < numOfMines; i++) {
      int x = rnd.nextInt(cols), y = rnd.nextInt(rows);
      MineCell target = table[y][x];
      if (target.isMine()) {
        i--; // すでにtargetが地雷の場合は地雷を置けないのでループを繰り返す
      } else {
        target.val = 9; // val=9が地雷(MineCellクラス参照)
      }
    }
    for (var row in table) {
      for (var cell in row) {
        cell.scanNeighbor(table); // 隣接マスをチェック(MineCellクラス参照)
      }
    }
  }
  
  // openメソッド(x列目y行目のマスを開ける)
  void open(x, y) {
    table[y][x].open(table);
  }
  // toStringメソッドを上書き(print用)
  String toString() {
    String result = '';
    for (var row in table) {
      result += row.join(' ') + '\n';
    }
    return result;
  }
}

// MineCellクラス
class MineCell {
  // 表示用リスト val=0: 空白, val=9: X(地雷)
  static List<String> label = [' ', '1', '2', '3', '4', '5', '6', '7', '8', 'X'];
  final int x, y; // x列目, y行目
  List<int> cols = [], rows = []; // 隣接列・隣接行格納用
  int val = 0; // val=0~8: 周囲にある地雷の数, val=9: 地雷
  bool opened = false; // すでに開けられているか
  String display = '@'; // 開けられていないときの表示
  
  // コンストラクタ(引数:x列目, y行目)
  MineCell(this.x, this.y);
  
  // isEmptyメソッド(空白マスか)
  bool isEmpty() {
    return val == 0;
  }
  // isMineメソッド(地雷マスか)
  bool isMine() {
    return val == 9;
  }
  // scanNeighborメソッド(隣接マスに地雷があるかチェック):地雷を置いたあとに実行する
  void scanNeighbor(MineCellContainer table) {
    // 0列(行)目, 最終列(行)は隣接マスが違う
    // 隣接列を定義
    cols = x == 0 ? [x, x+1] : x < table[0].length - 1 ? [x-1, x, x+1] : [x-1, x];
    // 隣接行を定義
    rows = y == 0 ? [y, y+1] : y < table.length - 1 ? [y-1, y, y+1] : [y-1, y];
    if (isEmpty()) { // 空白マスのみチェックする
      for (int c in cols) {
        for (int r in rows) {
          // 隣接マスが地雷ならvalを+1する
          if (c != x || r != y) val += table[r][c].isMine() ? 1 : 0;
        }
      }
    }
  }
  // openメソッド(マスを開ける)
  void open(MineCellContainer table) {
    if (!opened) { // まだ開けられていない場合のみ処理
      opened = true;
      display = label[val]; // 表示を@から変更
      // 空白のとき隣接マスを開ける
      if (isEmpty()) {
        for (int c in cols) {
          for (int r in rows) {
            table[r][c].open(table);
          }
        }
      }
    }
  }
  // toStringメソッドを上書き
  String toString() {
    return display;
  }
}

void main() {
  MineTable table = MineTable(9, 9, 9); // 9x9、地雷が9個のテーブルを作る
  table.open(0, 0); // x=0, y=0を開ける
  print(table);
  table.open(8, 8); // x=8, y=8を開ける
  print(table);
}

print関数を使っていますが、実際に実装するならば標準出力にしてマスを開けるごとにコンソールをクリアしたほうがいいかもしれません。

1Like

Comments

  1. @konanomono

    Questioner

    す、すごい。。。!!
    ご回答ありがとうございます。
    コードを見ながらどんな処理をしているかを確認します。

    ありがとうございます。
  2. @konanomono

    Questioner

    先日は回答ありがとうございます。

    なんとなく、クラスを使用して実現可能なのは分かったのですが、
    こちらを関数ごとに分けた場合はどのようになるのか気になった(確実に面倒)のですが
    関数ごとに分けることができず、少々モヤモヤしています。

    大変お手数をおかけして申し訳ありません。。。
    クラスを使用せず、関数ごとに分けた場合を教えていただけないでしょうか?

(関数に分けていませんが)クラスを使わない例を載せます。簡単に言えば

  • クラスを使う……2次元リストは1つで、中に様々な情報(数字、表示する文字列、開けられているかどうかなど)を持つ構造体がある
  • クラスを使わない……2次元リストが持つ情報はそれぞれ1つだけなので、複数の2次元リストが必要(変数が増えがち)

のような感じでしょうか。拡張性(さらに機能を増やしたときの見通しのよさ)はクラスに分があるかなと思います。

import 'dart:math';

void main() {
  int cols = 9, rows = 9, numOfMine = 9;
  List<List<int>> vals = List.generate(rows, (_) => List.generate(cols, (_) => 0));
  List<List<String>> disp = List.generate(rows, (_) => List.generate(cols, (_) => '@'));
  List<List<bool>> opened = List.generate(rows, (_) => List.generate(cols, (_) => false));
  
  // ランダムに地雷を置く
  Random rnd = Random();
  for (int i = 0; i < numOfMine; i++) {
    int x = rnd.nextInt(cols), y = rnd.nextInt(rows);
    if (vals[y][x] < 9) {
      vals[y][x] = 9;
    } else {
      i--;
    }
  }
  
  // 周囲の地雷をチェックして数字に反映
  for (int x = 0; x < cols; x++) {
    for (int y = 0; y < rows; y++) {
      List<int> neighborX = x == 0 ? [x, x+1] : x < cols-1 ? [x-1, x, x+1] : [x-1, x];
      List<int> neighborY = y == 0 ? [y, y+1] : y < rows-1 ? [y-1, y, y+1] : [y-1, y];
      if (vals[y][x] < 9) {
        for (int c in neighborX) {
          for (int r in neighborY) {
            if (c != x || r != y) vals[y][x] += vals[r][c] == 9 ? 1 : 0;
          }
        }
      }
    }
  }
  
  // マスを開ける用の関数
  void open(int x, int y) {
    if (!opened[y][x]) {
      opened[y][x] = true;
      if (vals[y][x] == 0) {
        List<int> neighborX = x == 0 ? [x, x+1] : x < cols-1 ? [x-1, x, x+1] : [x-1, x];
        List<int> neighborY = y == 0 ? [y, y+1] : y < rows-1 ? [y-1, y, y+1] : [y-1, y];
        for (int c in neighborX) {
          for (int r in neighborY) {
            if (c != x || r != y) open(c, r);
          }
        }
      }
    }
  }
  
  // print用の関数
  void printField(List<List<int>> vals) {
    // dispを更新
    List<String> label = [' ', '1', '2', '3', '4', '5', '6', '7', '8', 'X'];
    for (int x = 0; x < cols; x++) {
      for (int y = 0; y < rows; y++) {
        disp[y][x] = opened[y][x] ? label[vals[y][x]] : '@';
      }
    }
    // dispをprint
    String rslt = '';
    for (List<String> row in disp) {
      rslt += row.join(' ') + '\n';
    }
    print(rslt);
  }
  
  open(0, 0); // x=0, y=0を開ける
  printField(vals);
  open(8, 8); // x=8, y=8を開ける
  printField(vals);
}
1Like

Your answer might help someone💌