1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2021-09-20

概要

Flutterでバトルシップパズルを作成する
GitHubに登録する
FirebaseでWebアプリを公開する

Flutterについて

英語: https://flutter.dev/docs
日本語: https://flutter.ctrnost.com/

バトルシップについて

実行環境

Mac

$ sw_vers
ProductName:    macOS
ProductVersion: 11.6
BuildVersion:   20G165

Flutter

$ 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

手順

Flutterのプロジェクトを作成

flutter create --org xxx.xxx.xxx battleship

作成したプロジェクトに移動

cd battleship

アプリの内容

メイン処理

lib/main.dart
import 'package:flutter/material.dart';

void main() => runApp(BattleshipApp());

class BattleshipApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) => MaterialApp(
        home: Scaffold(
          appBar: AppBar(
            title: const Text('BattleshipPuzzle'),
          ),
          body: _BattleshipHome(),
        ),
      );
}

状態管理(StatefulWidget)

class _BattleshipHome extends StatefulWidget {
  @override
  _BattleshipHomeState createState() => _BattleshipHomeState();
}

ステート

class _BattleshipHomeState extends State<_BattleshipHome> {
  // セルのサイズ
  final _size = 10;
  // セルデータの生成
  var _cellTypes = CellTypeGenerator.generate();
  // タップされたか?
  final _tapped = List.generate(10, (_) => List.generate(10, (_) => false));
  // タップ数
  var _tapCount = 0;

  @override
  Widget build(BuildContext context) {
    return _buildContainer();
  }

  Widget _buildContainer() => Center(
        child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
          Padding(
            padding: const EdgeInsets.fromLTRB(0, 20, 0, 20),
            child: Text('TapCount: $_tapCount'),
          ),
          // リセットボタン
          Padding(
            padding: const EdgeInsets.fromLTRB(0, 0, 0, 20),
            child: ElevatedButton(
              onPressed: () => _pushReset(),
              child: const Text('Reset'),
            ),
          ),
          Container(
            width: 440,
            height: 440,
            child: _buildColumn(),
          ),
        ]),
      );

  Widget _buildColumn() {
    final columns = Column(
      children: List.generate(
        _size,
        (index) => Container(child: _buildRow(index)),
      ),
    );

    columns.children.add(Row(
        children: List.generate(_size, (cellIndex) {
      final _cellTypesOfCell =
          List.generate(_size, (rowIndex) => _cellTypes[rowIndex][cellIndex]);
      return _buildShipCount(_cellTypesOfCell);
    })));

    return columns;
  }

  Widget _buildRow(int rowIndex) {
    final rows = Row(
      children: List.generate(_size, (index) => _buildCell(rowIndex, index)),
    );
    rows.children.add(_buildShipCount(_cellTypes[rowIndex]));
    return rows;
  }

  Widget _buildCell(int rowIndex, int columnIndex) {
    final cellType = _cellTypes[rowIndex][columnIndex];
    final alreadyTapped = _tapped[rowIndex][columnIndex];

    final cell = GestureDetector(
      onTap: () => setState(() {
        if (cellType == CellType.Wave) {
        } else if (alreadyTapped) {
        } else {
          _tapCount++;
          _tapped[rowIndex][columnIndex] = true;
        }
      }),
      child: Container(
        width: 40,
        height: 40,
        decoration: BoxDecoration(
          border: Border.all(color: Colors.blue),
        ),
        child: _buildCellCentent(cellType, alreadyTapped),
      ),
    );
    return cell;
  }

  // セルの状態によって表示を変更する
  Widget _buildCellCentent(CellType cellType, bool alreadyTapped) {
    if (alreadyTapped) {
      // タップされている場合
      switch (cellType) {
        case CellType.Wave:
          return Icon(Icons.waves);
        case CellType.None:
          return Icon(Icons.check);
        case CellType.ShipCircle:
          return Container(
            margin: EdgeInsets.all(5),
            decoration: const BoxDecoration(
              shape: BoxShape.circle,
              color: Colors.black,
            ),
          );
        case CellType.ShipSquare:
          return Container(
            margin: EdgeInsets.all(5),
            decoration: const BoxDecoration(
              shape: BoxShape.rectangle,
              color: Colors.black,
            ),
          );
        case CellType.ShipRoundTop:
          return Container(
            margin: EdgeInsets.all(5),
            decoration: const BoxDecoration(
              borderRadius: BorderRadius.only(
                topLeft: Radius.circular(20.0),
                topRight: Radius.circular(20.0),
              ),
              color: Colors.black,
            ),
          );
        case CellType.ShipRoundLeft:
          return Container(
            margin: EdgeInsets.all(5),
            decoration: const BoxDecoration(
              borderRadius: BorderRadius.only(
                topLeft: Radius.circular(20.0),
                bottomLeft: Radius.circular(20.0),
              ),
              color: Colors.black,
            ),
          );
        case CellType.ShipRoundBottom:
          return Container(
            margin: EdgeInsets.all(5),
            decoration: const BoxDecoration(
              borderRadius: BorderRadius.only(
                bottomLeft: Radius.circular(20.0),
                bottomRight: Radius.circular(20.0),
              ),
              color: Colors.black,
            ),
          );
        case CellType.ShipRoundRight:
          return Container(
            margin: EdgeInsets.all(5),
            decoration: const BoxDecoration(
              borderRadius: BorderRadius.only(
                topRight: Radius.circular(20.0),
                bottomRight: Radius.circular(20.0),
              ),
              color: Colors.black,
            ),
          );
        default:
          return Container();
      }
    } else {
      // タップされていない場合
      switch (cellType) {
        case CellType.Wave:
          return Icon(Icons.waves);
        default:
          return Container();
      }
    }
  }

  // 行(列)ごとの戦艦の数
  Widget _buildShipCount(List<CellType> cellTypes) {
    final shipCount = cellTypes.where((cellType) {
      switch (cellType) {
        case CellType.ShipCircle:
        case CellType.ShipSquare:
        case CellType.ShipRoundTop:
        case CellType.ShipRoundLeft:
        case CellType.ShipRoundBottom:
        case CellType.ShipRoundRight:
          return true;
        default:
          return false;
      }
    }).length;

    return Container(
      width: 40,
      height: 40,
      child: Center(
          child: Text(shipCount.toString(), style: const TextStyle(fontSize: 18))),
    );
  }

  // リセットが押された時
  void _pushReset() {
    setState(() {
      _tapCount = 0;
      _cellTypes = CellTypeGenerator.generate();
      for (var i = 0; i < _size; i++) {
        for (var j = 0; j < _size; j++) {
          _tapped[i][j] = false;
        }
      }
    });
  }
}

セルデータの作成

TODO: 自動生成できるようにする

class CellTypeGenerator  {
  static List<List<CellType>> generate() =>
      [
        [
          CellType.Wave,
          CellType.None,
          CellType.ShipCircle,
          CellType.None,
          CellType.None,
          CellType.None,
          CellType.None,
          CellType.None,
          CellType.ShipRoundTop,
          CellType.None
        ],
        [
          CellType.ShipRoundTop,
          CellType.None,
          CellType.None,
          CellType.None,
          CellType.None,
          CellType.ShipRoundTop,
          CellType.None,
          CellType.None,
          CellType.ShipSquare,
          CellType.None
        ],
        [
          CellType.ShipRoundBottom,
          CellType.None,
          CellType.ShipRoundLeft,
          CellType.ShipRoundRight,
          CellType.None,
          CellType.ShipSquare,
          CellType.None,
          CellType.None,
          CellType.ShipRoundBottom,
          CellType.None
        ],
        [
          CellType.None,
          CellType.None,
          CellType.None,
          CellType.None,
          CellType.None,
          CellType.ShipRoundBottom,
          CellType.None,
          CellType.None,
          CellType.None,
          CellType.None
        ],
        [
          CellType.ShipCircle,
          CellType.None,
          CellType.None,
          CellType.None,
          CellType.None,
          CellType.None,
          CellType.None,
          CellType.None,
          CellType.None,
          CellType.None
        ],
        [
          CellType.None,
          CellType.None,
          CellType.None,
          CellType.ShipRoundTop,
          CellType.None,
          CellType.None,
          CellType.None,
          CellType.None,
          CellType.None,
          CellType.None
        ],
        [
          CellType.None,
          CellType.None,
          CellType.None,
          CellType.ShipRoundBottom,
          CellType.None,
          CellType.None,
          CellType.None,
          CellType.None,
          CellType.None,
          CellType.None
        ],
        [
          CellType.None,
          CellType.None,
          CellType.None,
          CellType.None,
          CellType.None,
          CellType.None,
          CellType.None,
          CellType.None,
          CellType.None,
          CellType.None
        ],
        [
          CellType.ShipCircle,
          CellType.None,
          CellType.Wave,
          CellType.None,
          CellType.None,
          CellType.None,
          CellType.None,
          CellType.None,
          CellType.None,
          CellType.None
        ],
        [
          CellType.None,
          CellType.None,
          CellType.ShipCircle,
          CellType.None,
          CellType.None,
          CellType.None,
          CellType.ShipRoundLeft,
          CellType.ShipSquare,
          CellType.ShipSquare,
          CellType.ShipRoundRight
        ],
      ];
}

セルの状態

enum CellType {
  Wave,
  None,
  ShipCircle,
  ShipSquare,
  ShipRoundTop,
  ShipRoundLeft,
  ShipRoundBottom,
  ShipRoundRight,
}
Wave None ShipCircle ShipSquare
Wave None ShipCircle ShipSquare
ShipRoundTop ShipRoundLeft ShipRoundBottom ShipRoundRight
ShipRoundTop ShipRoundLeft ShipRoundBottom ShipRoundRight

アプリの実行

アプリを起動

flutter run

実行結果

battleship.gif

GitHubに登録

Firebase設定

Firebaseプロジェクト作成

https://console.firebase.google.com/
image.png
image.png
image.png

Firebaseプロジェクトを初期化

firebase init hosting

GitHubでデプロイする設定

.github/workflows/firebase-hosting-merge.yml
# This file was auto-generated by the Firebase CLI
# https://github.com/firebase/firebase-tools

name: Deploy to Firebase Hosting on merge
'on':
  push:
    branches:
      - main
jobs:
  build_and_deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - run: git clone https://github.com/flutter/flutter.git
      - run: echo "$(pwd)/flutter/bin" >> $GITHUB_PATH
      - run: flutter pub get
      - run: flutter build web
      - uses: FirebaseExtended/action-hosting-deploy@v0
        with:
          repoToken: '${{ secrets.GITHUB_TOKEN }}'
          firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_BATTLESHIP_74D4F }}'
          channelId: live
          projectId: battleship-74d4f
.github/workflows/firebase-hosting-pull-request.yml
# This file was auto-generated by the Firebase CLI
# https://github.com/firebase/firebase-tools

name: Deploy to Firebase Hosting on PR
'on': pull_request
jobs:
  build_and_preview:
    if: '${{ github.event.pull_request.head.repo.full_name == github.repository }}'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - run: git clone https://github.com/flutter/flutter.git
      - run: echo "$(pwd)/flutter/bin" >> $GITHUB_PATH
      - run: flutter pub get
      - run: flutter build web
      - uses: FirebaseExtended/action-hosting-deploy@v0
        with:
          repoToken: '${{ secrets.GITHUB_TOKEN }}'
          firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_BATTLESHIP_74D4F }}'
          projectId: battleship-74d4f

Webアプリ公開

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?