概要
Flutterでバトルシップパズルを作成する
GitHubに登録する
FirebaseでWebアプリを公開する
- [Flutterでバトルシップパズルを作成する2]
(https://qiita.com/iharakenji/items/137d02f3d045af3d68ff)
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 |
---|---|---|---|
ShipRoundTop | ShipRoundLeft | ShipRoundBottom | ShipRoundRight |
---|---|---|---|
アプリの実行
アプリを起動
flutter run
実行結果
GitHubに登録
Firebase設定
Firebaseプロジェクト作成
https://console.firebase.google.com/
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