Flutterでバトルシップパズルを作成する2

## 概要
Flutterでバトルシップパズルを作成する
前回手動で生成していたセルデータを自動生成する

- [Flutterでバトルシップパズルを作成する1]
(https://qiita.com/iharakenji/items/abacc6bc49f7ce11fbbd)
- [Flutterでバトルシップパズルを作成する3]
(https://qiita.com/iharakenji/items/ff65749c4b6acd1b57d2)

###  実行環境
#### Mac
```sh
$ sw_vers
ProductName:    macOS
ProductVersion: 11.6
BuildVersion:   20G165
```

#### Flutter
```sh
$ flutter --version
Flutter 2.5.0 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 4cc385b4b8 (11 days ago) • 2021-09-07 23:01:49 -0700
Engine • revision f0826da7ef
Tools • Dart 2.14.0
```

## 手順
### 配置する項目を決める
船1(大きさ1, 数4)
船2(大きさ2, 数3)
船3(大きさ3, 数2)
船4(大きさ4, 数1)
波(大きさ1, 数2)

```dart
static const ship1 = Ship(1, 4);
static const ship2 = Ship(2, 3);
static const ship3 = Ship(3, 2);
static const ship4 = Ship(4, 1);
static const wave = 2;
```

### セルデータを初期化する
```dart
var cellTypes = List.generate(
        rows, (_) => List.generate(columns, (_) => CellType.None));
```

### 船を配置する
```dart
var ships = [ship4, ship3, ship2, ship1];
for (var ship in ships) {
  cellTypes = placeShip(cellTypes, ship, rows, columns);
}
```

### 波を配置する
```dart
cellTypes = placeWave(cellTypes, rows, columns);
```

## アプリの内容
### セルデータの作成
```dart
static List<List<CellType>> generate(int rows, int columns) {
  var cellTypes = List.generate(
      rows, (_) => List.generate(columns, (_) => CellType.None));

  var ships = [ship4, ship3, ship2, ship1];
  for (var ship in ships) {
    cellTypes = placeShip(cellTypes, ship, rows, columns);
  }
  cellTypes = placeWave(cellTypes, rows, columns);

  return cellTypes;
}
```

### 船を配置する
```dart
static List<List<CellType>> placeShip(List<List<CellType>> cellTypes, Ship ship, int rows, int columns) {
  for (var numIndex = 0; numIndex < ship.num; numIndex++) {
    var positions = buildPositions(cellTypes, ship, rows, columns);
    if (positions.isEmpty) {
      continue;
    }
    var position = positions[Random().nextInt(positions.length)];
    if (ship.size == 1) {
      cellTypes[position.item2][position.item3] = CellType.ShipCircle;
    } else if (position.item1 == Direction.Horizontal) {
      cellTypes[position.item2][position.item3] = CellType.ShipRoundLeft;
      for (var sizeIndex = 1; sizeIndex < ship.size; sizeIndex++) {
        cellTypes[position.item2][position.item3 + sizeIndex] =
            CellType.ShipSquare;
      }
      cellTypes[position.item2][position.item3 + ship.size - 1] =
          CellType.ShipRoundRight;
    } else {
      cellTypes[position.item2][position.item3] = CellType.ShipRoundTop;
      for (var sizeIndex = 1; sizeIndex < ship.size; sizeIndex++) {
        cellTypes[position.item2 + sizeIndex][position.item3] =
            CellType.ShipSquare;
      }
      cellTypes[position.item2 + ship.size - 1][position.item3] =
          CellType.ShipRoundBottom;
    }
  }
  return cellTypes;
}
```

### 波を配置する
```dart
static List<List<CellType>> placeWave(List<List<CellType>> cellTypes, int rows, int columns) {
  for (var i = 0; i < wave; i++) {
    List<Tuple2<int, int>> positions = [];
    for (var row = 0; row < rows; row++) {
      for (var column = 0; column < columns; column++) {
        if (cellTypes[row][column] == CellType.None) {
          positions.add(Tuple2(row, column));
        }
      }
    }
    var position = positions[Random().nextInt(positions.length)];
    cellTypes[position.item1][position.item2] = CellType.Wave;
  }
  return cellTypes;
}
```

### 波を配置する
```dart
static List<Tuple3<Direction, int, int>> buildPositions(List<List<CellType>> cellTypes, Ship ship, int rows, int columns) {
  List<Tuple3<Direction, int, int>> positions = [];
  for (var row = 0; row < rows; row++) {
    for (var column = 0; column < columns; column++) {
      if (canExistsHorizontal(
          cellTypes, ship, rows, row, columns, column)) {
        positions.add(Tuple3(Direction.Horizontal, row, column));
      }
      if (canExistsVertical(
          cellTypes, ship, rows, row, columns, column)) {
        positions.add(Tuple3(Direction.Vertical, row, column));
      }
    }
  }
  return positions;
}
```

### 船が配置できるかチェック(横方向)
```dart
static bool canExistsHorizontal(List<List<CellType>> cellTypes, Ship ship,
    int rows, int row, int columns, int column) {
  if (columns < column + ship.size) {
    return false;
  }
  // 左側チェック
  if (0 < column) {
    if (0 < row && cellTypes[row - 1][column - 1] != CellType.None) {
      return false;
    }
    if (cellTypes[row][column - 1] != CellType.None) {
      return false;
    }
    if (row + 1 < rows && cellTypes[row + 1][column - 1] != CellType.None) {
      return false;
    }
  }
  // 右側チェック
  if (column + ship.size < columns) {
    if (0 < row && cellTypes[row - 1][column + ship.size] != CellType.None) {
      return false;
    }
    if (cellTypes[row][column + ship.size] != CellType.None) {
      return false;
    }
    if (row + 1 < rows && cellTypes[row + 1][column + ship.size] != CellType.None) {
      return false;
    }
  }
  // 真ん中チェック
  for (var sizeIndex = 0; sizeIndex < ship.size; sizeIndex++) {
    if (0 < row && cellTypes[row - 1][column + sizeIndex] != CellType.None) {
      return false;
    }
    if (cellTypes[row][column + sizeIndex] != CellType.None) {
      return false;
    }
    if (row + 1 < rows &&
        cellTypes[row + 1][column + sizeIndex] != CellType.None) {
      return false;
    }
  }
  return true;
}
```

### 船が配置できるかチェック(縦方向)
```
static bool canExistsVertical(List<List<CellType>> cellTypes, Ship ship,
    int rows, int row, int columns, int column) {
  if (rows < row + ship.size) {
    return false;
  }
  // 上側チェック
  if (0 < row) {
    if (0 < column && cellTypes[row - 1][column - 1] != CellType.None) {
      return false;
    }
    if (cellTypes[row - 1][column] != CellType.None) {
      return false;
    }
    if (column + 1 < columns &&
        cellTypes[row - 1][column + 1] != CellType.None) {
      return false;
    }
  }
  // 下側チェック
  if (row + ship.size < rows) {
    if (0 < column && cellTypes[row + ship.size][column - 1] != CellType.None) {
      return false;
    }
    if (cellTypes[row + ship.size][column] != CellType.None) {
      return false;
    }
    if (column + 1 < columns &&
        cellTypes[row + ship.size][column + 1] != CellType.None) {
      return false;
    }
  }
  // 真ん中チェック
  for (var sizeIndex = 0; sizeIndex < ship.size; sizeIndex++) {
    if (0 < column &&
        cellTypes[row + sizeIndex][column - 1] != CellType.None) {
      return false;
    }
    if (cellTypes[row + sizeIndex][column] != CellType.None) {
      return false;
    }
    if (column + 1 < columns &&
        cellTypes[row + sizeIndex][column + 1] != CellType.None) {
      return false;
    }
  }
  return true;
}
```

### 船(数と大きさ)
```
class Ship {
  final int size, num;

  const Ship(this.size, this.num);
}
```

### 船の向き
```
enum Direction {
  Vertical,
  Horizontal,
}
```

## アプリの実行
### アプリを起動
```sh
flutter run
```

### 実行結果
![battleship2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109450/8d800268-75d8-0f14-6a56-eeae25456a05.gif)

## GitHub
- https://github.com/iharakenji/battleship

### Webアプリ
- https://battleship-74d4f.web.app/