ソースコード source code

下記アプリの主要なソースコードを公開しています。アプリ開発の参考になれば幸いです。

画像等が別途必要ですので下記情報のみでアプリが完成するものではありません。 アプリは少しずつ機能拡張していますのでストア公開されているアプリと内容が異なる場合があります。 コードはコピーして自由にお使いいただけます。ただし著作権は放棄しておりませんので全部の再掲載はご遠慮ください。部分的に再掲載したり、改変して再掲載するのは構いません。 自身のアプリ作成の参考として個人使用・商用問わず自由にお使いいただけます。 コード記述のお手本を示すものではありません。ミニアプリですので変数名などさほど気遣いしていない部分も有りますし間違いも有るかと思いますので参考程度にお考え下さい。 他の賢者の皆様が公開されているコードを参考にした箇所も含まれます。Flutter開発の熟練者が書いたコードではありません。 エンジニア向け技術情報共有サービスではありませんので説明は省いています。 GitHubなどへの公開は予定しておりません。

下記コードの最終ビルド日: 2022-12-09

pubspec.yaml

name: cardsfreecell
description: Cards Free Cell

# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev

# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.0.2+3

environment:
  sdk: ">=2.18.5 <3.0.0"

# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
  flutter:
    sdk: flutter
  shared_preferences: ^2.0.15
  just_audio: ^0.9.30
  flutter_svg: ^1.1.6
  google_mobile_ads: ^2.3.0
  flutter_localizations:    #多言語ライブラリの本体    # .arbファイルを更新したら flutter gen-l10n
    sdk: flutter
  intl: ^0.17.0   #多言語やフォーマッタなどの関連ライブラリ
  package_info_plus: ^3.0.2


  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^1.0.5

dev_dependencies:
  flutter_test:
    sdk: flutter

  flutter_launcher_icons: ^0.11.0    #flutter pub run flutter_launcher_icons
  flutter_native_splash: ^2.2.15     #flutter pub run flutter_native_splash:create

  # The "flutter_lints" package below contains a set of recommended lints to
  # encourage good coding practices. The lint set provided by the package is
  # activated in the `analysis_options.yaml` file located at the root of your
  # package. See that file for information about deactivating specific lint
  # rules and activating additional ones.
  flutter_lints: ^2.0.1

flutter_icons:
  android: "launcher_icon"
  ios: true
  image_path: "assets/icon/icon.png"
  adaptive_icon_background: "assets/icon/icon_back.png"
  adaptive_icon_foreground: "assets/icon/icon_fore.png"

flutter_native_splash:
  color: '#26f19a'
  image: 'assets/image/splash.png'
  color_dark: '#26f19a'
  image_dark: 'assets/image/splash.png'
  fullscreen: true
  android_12:
    icon_background_color: '#26f19a'
    image: 'assets/image/splash.png'
    icon_background_color_dark: '#26f19a'
    image_dark: 'assets/image/splash.png'


# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec

# The following section is specific to Flutter packages.
flutter:
  assets:
    - assets/sound/
    - assets/icon/
    - assets/image/
  generate: true    #自動生成フラグの有効化

  # The following line ensures that the Material Icons font is
  # included with your application, so that you can use the icons in
  # the material Icons class.
  uses-material-design: true

  # To add assets to your application, add an assets section, like this:
  # assets:
  #   - images/a_dot_burr.jpeg
  #   - images/a_dot_ham.jpeg

  # An image asset can refer to one or more resolution-specific "variants", see
  # https://flutter.dev/assets-and-images/#resolution-aware

  # For details regarding adding assets from package dependencies, see
  # https://flutter.dev/assets-and-images/#from-packages

  # To add custom fonts to your application, add a fonts section here,
  # in this "flutter" section. Each entry in this list should have a
  # "family" key with the font family name, and a "fonts" key with a
  # list giving the asset and other descriptors for the font. For
  # example:
  # fonts:
  #   - family: Schyler
  #     fonts:
  #       - asset: fonts/Schyler-Regular.ttf
  #       - asset: fonts/Schyler-Italic.ttf
  #         style: italic
  #   - family: Trajan Pro
  #     fonts:
  #       - asset: fonts/TrajanPro.ttf
  #       - asset: fonts/TrajanPro_Bold.ttf
  #         weight: 700
  #
  # For details regarding fonts from package dependencies,
  # see https://flutter.dev/custom-fonts/#from-packages

lib/ad_mob.dart

///
/// @author akira ohmachi
/// @copyright ao-system, Inc.
/// @date 2022-11-26
///

import 'package:flutter/material.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'dart:io';

class AdMob {
  late BannerAd _adMobBanner;
  bool _isAdMob = false;
  AdMob() { //constructor
    String adBannerUnitId = '';
    if (!kIsWeb && Platform.isAndroid) {
      adBannerUnitId = 'ca-app-pub-0000000000000000/0000000000';
      _isAdMob = true;
    } else if (!kIsWeb && Platform.isIOS) {
      adBannerUnitId = 'ca-app-pub-0000000000000000/0000000000';
      _isAdMob = true;
    }
    if (_isAdMob) {
      _adMobBanner = BannerAd(
        adUnitId: adBannerUnitId,
        size: AdSize.banner,
        request: const AdRequest(),
        listener: const BannerAdListener(),
      );
    }
  }
  void load() {
    if (_isAdMob) {
      _adMobBanner.load();
    }
  }
  Widget getAdBannerWidget() {
    if (_isAdMob) {
      return Container(
        alignment: Alignment.center,
        width: _adMobBanner.size.width.toDouble(),
        height: _adMobBanner.size.height.toDouble(),
        child: AdWidget(ad: _adMobBanner),
      );
    } else {
      return Container(height: 150);
    }
  }
  double getAdBannerHeight() {
    if (_isAdMob) {
      return _adMobBanner.size.height.toDouble();
    } else {
      return 150;  //for web
    }
  }
}

lib/app_condition.dart

///
/// @author akira ohmachi
/// @copyright ao-system, Inc.
/// @date 2022-11-26
///

class AppCondition {
  String appVersion = '';
  double canvasWidth = 0;
  bool canAutoComplete = false;   //ゲーム終盤で自動完了できる状態ならtrue
  bool doAutoComplete = false;    //ゲーム終盤で自動完了実行中はtrue
  //
  String getAppVersion() {
    return appVersion;
  }
  void setAppVersion(String str) {
    appVersion = str;
  }
  double getCanvasWidth() {
    return canvasWidth;
  }
  void setCanvasWidth(double w) {
    canvasWidth = w;
  }
  bool getCanAutoComplete() {
    return canAutoComplete;
  }
  void setCanAutoComplete(bool flag) {
    canAutoComplete = flag;
  }
  bool getDoAutoComplete() {
    return doAutoComplete;
  }
  void setDoAutoComplete(bool flag) {
    doAutoComplete = flag;
  }
}

lib/audio_play.dart

///
/// @author akira ohmachi
/// @copyright ao-system, Inc.
/// @date 2022-11-26
///

import 'package:just_audio/just_audio.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:cardsfreecell/const_value.dart';

class AudioPlay {
  static const int soundOff = 1;
  static const int soundOn = 2;
  final String _audioStart = ConstValue.soundStart;
  final String _audioRetry = ConstValue.soundRetry;
  final String _audioBack = ConstValue.soundBack;
  final String _audioSlide = ConstValue.soundSlide;
  final String _audioComplete = ConstValue.soundComplete;
  static final List<AudioPlayer> _audioPlayerStart = [
    AudioPlayer(),
    AudioPlayer(),
    AudioPlayer(),
  ];
  static final List<AudioPlayer> _audioPlayerRetry = [
    AudioPlayer(),
    AudioPlayer(),
    AudioPlayer(),
  ];
  static final List<AudioPlayer> _audioPlayerBack = [
    AudioPlayer(),
    AudioPlayer(),
    AudioPlayer(),
    AudioPlayer(),
    AudioPlayer(),
    AudioPlayer(),
    AudioPlayer(),
    AudioPlayer(),
    AudioPlayer(),
    AudioPlayer(),
  ];
  static final List<AudioPlayer> _audioPlayerSlide = [
    AudioPlayer(),
    AudioPlayer(),
    AudioPlayer(),
    AudioPlayer(),
    AudioPlayer(),
    AudioPlayer(),
    AudioPlayer(),
    AudioPlayer(),
    AudioPlayer(),
    AudioPlayer(),
  ];
  static final List<AudioPlayer> _audioPlayerComplete = [
    AudioPlayer(),
    AudioPlayer(),
    AudioPlayer(),
  ];
  int _sound = soundOn;
  int _audioPlayerStartPtr = 0;
  int _audioPlayerRetryPtr = 0;
  int _audioPlayerBackPtr = 0;
  int _audioPlayerSlidePtr = 0;
  int _audioPlayerCompletePtr = 0;
  AudioPlay() { //constructor
    init();
  }
  void init() async {
    for (int i = 0; i < _audioPlayerStart.length; i++) {
      await _audioPlayerStart[i].setAsset(_audioStart);
    }
    for (int i = 0; i < _audioPlayerRetry.length; i++) {
      await _audioPlayerRetry[i].setAsset(_audioRetry);
    }
    for (int i = 0; i < _audioPlayerBack.length; i++) {
      await _audioPlayerBack[i].setAsset(_audioBack);
    }
    for (int i = 0; i < _audioPlayerSlide.length; i++) {
      await _audioPlayerSlide[i].setAsset(_audioSlide);
    }
    for (int i = 0; i < _audioPlayerComplete.length; i++) {
      await _audioPlayerComplete[i].setAsset(_audioComplete);
    }
  }
  void dispose() {
    for (int i = 0; i < _audioPlayerStart.length; i++) {
      _audioPlayerStart[i].dispose();
    }
    for (int i = 0; i < _audioPlayerRetry.length; i++) {
      _audioPlayerRetry[i].dispose();
    }
    for (int i = 0; i < _audioPlayerBack.length; i++) {
      _audioPlayerBack[i].dispose();
    }
    for (int i = 0; i < _audioPlayerSlide.length; i++) {
      _audioPlayerSlide[i].dispose();
    }
    for (int i = 0; i < _audioPlayerComplete.length; i++) {
      _audioPlayerComplete[i].dispose();
    }
  }
  void setSoundMode(int soundMode) {
    _sound = soundMode;
  }
  int getSoundMode() {
    return _sound;
  }
  String getSoundModeStr(dynamic context) {
    if (_sound == soundOff) {
      return AppLocalizations.of(context).off;
    } else if (_sound == soundOn) {
      return AppLocalizations.of(context).on;
    }
    return '';
  }
  void playSoundStart() async {
    if (_sound != soundOn) {
      return;
    }
    _audioPlayerStartPtr += 1;
    if (_audioPlayerStartPtr >= _audioPlayerStart.length) {
      _audioPlayerStartPtr = 0;
    }
    await _audioPlayerStart[_audioPlayerStartPtr].pause();
    await _audioPlayerStart[_audioPlayerStartPtr].seek(Duration.zero);
    await _audioPlayerStart[_audioPlayerStartPtr].play();
  }
  void playSoundRetry() async {
    if (_sound != soundOn) {
      return;
    }
    _audioPlayerRetryPtr += 1;
    if (_audioPlayerRetryPtr >= _audioPlayerRetry.length) {
      _audioPlayerRetryPtr = 0;
    }
    await _audioPlayerRetry[_audioPlayerRetryPtr].pause();
    await _audioPlayerRetry[_audioPlayerRetryPtr].seek(Duration.zero);
    await _audioPlayerRetry[_audioPlayerRetryPtr].play();
  }
  void playSoundBack() async {
    if (_sound != soundOn) {
      return;
    }
    _audioPlayerBackPtr += 1;
    if (_audioPlayerBackPtr >= _audioPlayerBack.length) {
      _audioPlayerBackPtr = 0;
    }
    await _audioPlayerBack[_audioPlayerBackPtr].pause();
    await _audioPlayerBack[_audioPlayerBackPtr].seek(Duration.zero);
    await _audioPlayerBack[_audioPlayerBackPtr].play();
  }
  void playSoundSlide() async {
    if (_sound != soundOn) {
      return;
    }
    _audioPlayerSlidePtr += 1;
    if (_audioPlayerSlidePtr >= _audioPlayerSlide.length) {
      _audioPlayerSlidePtr = 0;
    }
    await _audioPlayerSlide[_audioPlayerSlidePtr].pause();
    await _audioPlayerSlide[_audioPlayerSlidePtr].seek(Duration.zero);
    await _audioPlayerSlide[_audioPlayerSlidePtr].play();
  }
  void playSoundComplete() async {
    if (_sound != soundOn) {
      return;
    }
    _audioPlayerCompletePtr += 1;
    if (_audioPlayerCompletePtr >= _audioPlayerComplete.length) {
      _audioPlayerCompletePtr = 0;
    }
    await _audioPlayerComplete[_audioPlayerCompletePtr].pause();
    await _audioPlayerComplete[_audioPlayerCompletePtr].seek(Duration.zero);
    await _audioPlayerComplete[_audioPlayerCompletePtr].play();
  }
}

lib/buttons.dart

///
/// @author akira ohmachi
/// @copyright ao-system, Inc.
/// @date 2022-11-26
///

import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';        //flutter gen-l10n

import 'package:cardsfreecell/const_value.dart';

class SettingButton {
  IconButton button(BuildContext context, onPressFunc) {
    return IconButton(
      icon: const Icon(Icons.menu, color: ConstValue.colorAppHeaderText),
      tooltip: AppLocalizations.of(context).setting,
      onPressed: onPressFunc,
    );
  }
}

class StartButton {
  ElevatedButton button(BuildContext context, void Function() onPressFunc) {
    return ElevatedButton(
      onPressed: onPressFunc,
      style: ElevatedButton.styleFrom(
        elevation: 0,
        backgroundColor: ConstValue.colorAppHeader,
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(0.0)),
        side: const BorderSide(width: 0.5, color: ConstValue.colorAppBody),
        padding: const EdgeInsets.all(8.0),
      ),
      child: Text(AppLocalizations.of(context).start,
        style: const TextStyle(color: ConstValue.colorAppHeaderText)
      ),
    );
  }
}

class RetryButton {
  ElevatedButton button(BuildContext context, void Function() onPressFunc) {
    return ElevatedButton(
      onPressed: onPressFunc,
      style: ElevatedButton.styleFrom(
        elevation: 0,
        backgroundColor: ConstValue.colorAppHeader,
        shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(0.0)),
        side: const BorderSide(width: 0.5, color: ConstValue.colorAppBody),
        padding: const EdgeInsets.all(8.0),
      ),
      child: Text(AppLocalizations.of(context).retry,
        style: const TextStyle(color: ConstValue.colorAppHeaderText),
      ),
    );
  }
}

class UndoButton {
  ElevatedButton button(BuildContext context, void Function() onPressFunc) {
    return ElevatedButton(
      onPressed: onPressFunc,
      style: ElevatedButton.styleFrom(
        elevation: 0,
        backgroundColor: ConstValue.colorAppHeader,
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(0.0)),
        side: const BorderSide(width: 0.5, color: ConstValue.colorAppBody),
        padding: const EdgeInsets.all(8.0),
      ),
      child: Text(AppLocalizations.of(context).undo,
        style: const TextStyle(color: ConstValue.colorAppHeaderText),
      ),
    );
  }
}

lib/card_all.dart

///
/// @author akira ohmachi
/// @copyright ao-system, Inc.
/// @date 2022-11-26
///

import 'package:cardsfreecell/card_one.dart';

class CardAll {
  static final List<CardOne> cards = [
    CardOne(0,1,'assets/image/s1.svg'),
    CardOne(0,2,'assets/image/s2.svg'),
    CardOne(0,3,'assets/image/s3.svg'),
    CardOne(0,4,'assets/image/s4.svg'),
    CardOne(0,5,'assets/image/s5.svg'),
    CardOne(0,6,'assets/image/s6.svg'),
    CardOne(0,7,'assets/image/s7.svg'),
    CardOne(0,8,'assets/image/s8.svg'),
    CardOne(0,9,'assets/image/s9.svg'),
    CardOne(0,10,'assets/image/s10.svg'),
    CardOne(0,11,'assets/image/s11.svg'),
    CardOne(0,12,'assets/image/s12.svg'),
    CardOne(0,13,'assets/image/s13.svg'),
    CardOne(1,1,'assets/image/h1.svg'),
    CardOne(1,2,'assets/image/h2.svg'),
    CardOne(1,3,'assets/image/h3.svg'),
    CardOne(1,4,'assets/image/h4.svg'),
    CardOne(1,5,'assets/image/h5.svg'),
    CardOne(1,6,'assets/image/h6.svg'),
    CardOne(1,7,'assets/image/h7.svg'),
    CardOne(1,8,'assets/image/h8.svg'),
    CardOne(1,9,'assets/image/h9.svg'),
    CardOne(1,10,'assets/image/h10.svg'),
    CardOne(1,11,'assets/image/h11.svg'),
    CardOne(1,12,'assets/image/h12.svg'),
    CardOne(1,13,'assets/image/h13.svg'),
    CardOne(2,1,'assets/image/c1.svg'),
    CardOne(2,2,'assets/image/c2.svg'),
    CardOne(2,3,'assets/image/c3.svg'),
    CardOne(2,4,'assets/image/c4.svg'),
    CardOne(2,5,'assets/image/c5.svg'),
    CardOne(2,6,'assets/image/c6.svg'),
    CardOne(2,7,'assets/image/c7.svg'),
    CardOne(2,8,'assets/image/c8.svg'),
    CardOne(2,9,'assets/image/c9.svg'),
    CardOne(2,10,'assets/image/c10.svg'),
    CardOne(2,11,'assets/image/c11.svg'),
    CardOne(2,12,'assets/image/c12.svg'),
    CardOne(2,13,'assets/image/c13.svg'),
    CardOne(3,1,'assets/image/d1.svg'),
    CardOne(3,2,'assets/image/d2.svg'),
    CardOne(3,3,'assets/image/d3.svg'),
    CardOne(3,4,'assets/image/d4.svg'),
    CardOne(3,5,'assets/image/d5.svg'),
    CardOne(3,6,'assets/image/d6.svg'),
    CardOne(3,7,'assets/image/d7.svg'),
    CardOne(3,8,'assets/image/d8.svg'),
    CardOne(3,9,'assets/image/d9.svg'),
    CardOne(3,10,'assets/image/d10.svg'),
    CardOne(3,11,'assets/image/d11.svg'),
    CardOne(3,12,'assets/image/d12.svg'),
    CardOne(3,13,'assets/image/d13.svg'),
  ];
  void cardsShuffle() {
    cards.shuffle();
  }
  List getCards() {
    return cards;
  }
}

lib/card_one.dart

///
/// @author akira ohmachi
/// @copyright ao-system, Inc.
/// @date 2022-11-26
///

class CardOne {
  int _mark = 0;  //0|1|2|3 特別は-1
  int _num = 0;   //1..13 特別は0
  String _img = '';
  CardOne(int mark, int num, String img) { //constructor
    _mark = mark;
    _num = num;
    _img = img;
  }
  int getMark() {
    return _mark;
  }
  String getColor() {
    if (_mark == 0 || _mark == 2) {
      return 'BLACK';
    }
    if (_mark == 1 || _mark == 3) {
      return 'RED';
    }
    return '';
  }
  int getNum() {
    return _num;
  }
  String getImg() {
    return _img;
  }
  int getIndex() {
    int index = _mark * 13 + _num;
    if (index < 0) {
      return 0;
    }
    return index;  //1..52
  }
}

lib/const_value.dart

///
/// @author akira ohmachi
/// @copyright ao-system, Inc.
/// @date 2022-11-26
///

import 'package:flutter/material.dart';

class ConstValue {
  //const
  static const double canvasAspectRatio = 1 / 1.1;
  static const double menuBarHeight = 56.0;
  //Save.Load value to device
  static const String prefKeySound = 'keySound';
  static const String prefKeyLanguage = "keyLanguage";
  //area name
  static const int areaFree = 1;
  static const int areaAce = 2;
  static const int areaStage = 3;
  //color
  static const Color colorAppBackground = Color.fromARGB(255, 55,167,118);
  static const Color colorAppHeader = Color.fromARGB(255, 26,173,108);
  static const Color colorAppHeaderText = Color.fromARGB(255, 255, 255, 255);
  static const Color colorAppBody = Color.fromARGB(255, 38,241,154);
  static const Color colorHistoryLength = Color.fromARGB(180, 255,255,255);
  static const Color colorAutoCompleteButton = Color.fromARGB(50, 26,173,108);
  //image
  static const String imageIcon = 'assets/image/icon.svg';
  static const String imageFree = 'assets/image/free.svg';
  static const String imageAce = 'assets/image/ace.svg';
  static const String imageBlank = 'assets/image/blank.svg';
  //sound
  static const String soundStart = 'assets/sound/start.mp3';
  static const String soundRetry = 'assets/sound/retry.mp3';
  static const String soundBack = 'assets/sound/back.mp3';
  static const String soundSlide = 'assets/sound/slide.mp3';
  static const String soundComplete = 'assets/sound/complete.mp3';
}

lib/drag_animation.dart

///
/// @author akira ohmachi
/// @copyright ao-system, Inc.
/// @date 2022-11-26
///

class DragAnimation {
  List<double> dx = List.generate(53, (i)=>0);  //53個の配列要素数
  List<double> dy = List.generate(53, (i)=>0);
  List<int> speed = List.generate(53, (i)=>0);
  void setX(int i, double num) {
    dx[i] = num;
  }
  void setY(int i, double num) {
    dy[i] = num;
  }
  void addX(int i, double num) {
    dx[i] += num;
  }
  void addY(int i, double num) {
    dy[i] += num;
  }
  double getX(int i) {
    return dx[i];
  }
  double getY(int i) {
    return dy[i];
  }
  void setSpeedOff(int i) {
    speed[i] = 0;
  }
  void setSpeedOn(int i) {
    speed[i] = 200;
    Future.delayed(Duration(milliseconds: speed[i]), () {
      speed[i] = 0;
    });
  }
  int getSpeed(int i) {
    return speed[i];
  }
}

lib/game.dart

///
/// @author akira ohmachi
/// @copyright ao-system, Inc.
/// @date 2022-11-26
///

import 'package:cardsfreecell/card_one.dart';
import 'package:cardsfreecell/game_history.dart';
import 'package:cardsfreecell/const_value.dart';

class Game {
  final CardOne cardOneFree = CardOne(-1, 0, ConstValue.imageFree);
  final CardOne cardOneAce = CardOne(-1, 0, ConstValue.imageAce);
  final CardOne cardOneBlank = CardOne(-1, 0, ConstValue.imageBlank);
  List<List<CardOne>> cardStage = [
    [],[],[],[],[],[],[],[],
  ];
  List<List<CardOne>> cardFree = [[],[],[],[]];
  List<List<CardOne>> cardAce = [[],[],[],[]];
  final GameHistory gameHistory = GameHistory();
  Game() {  //constructor
    init();
  }
  void init() {
    cardStage = [
      [cardOneBlank],
      [cardOneBlank],
      [cardOneBlank],
      [cardOneBlank],
      [cardOneBlank],
      [cardOneBlank],
      [cardOneBlank],
      [cardOneBlank],
    ];
    cardFree = [
      [cardOneFree],
      [cardOneFree],
      [cardOneFree],
      [cardOneFree],
    ];
    cardAce = [
      [cardOneAce],
      [cardOneAce],
      [cardOneAce],
      [cardOneAce],
    ];
    gameHistory.init();
  }
  List<List<CardOne>> getCardStage() {
    return cardStage;
  }
  List<List<CardOne>> getCardFree() {
    return cardFree;
  }
  List<List<CardOne>> getCardAce() {
    return cardAce;
  }
  //free area へ移動
  void freeCardMove(int column, CardOne dragCard) {
    //move from free area to free area
    for (int x = 0; x < 4; x++) {
      if (cardFree[x].length == 2 && cardFree[x][1] == dragCard) {
        cardFree[x].removeLast();
        cardFree[column].add(dragCard);
        gameHistory.historyPush(dragCard, ConstValue.areaFree, x, ConstValue.areaFree, column);
        return;
      }
    }
    //move from stage area to free area
    for (int x = 0; x < 8; x++) {
      for (int y = 0; y < cardStage[x].length; y++) {
        if (cardStage[x][y] == dragCard) {
          cardStage[x].removeLast();
          cardFree[column].add(dragCard);
          gameHistory.historyPush(dragCard, ConstValue.areaStage, x, ConstValue.areaFree, column);
          return;
        }
      }
    }
  }
  //ace area へ移動
  void aceCardMove(int column, CardOne dragCard) {
    //move from free area to ace area
    for (int x = 0; x < 4; x++) {
      if (cardFree[x].length == 2 && cardFree[x][1] == dragCard) {
        cardFree[x].removeLast();
        cardAce[column].add(dragCard);
        gameHistory.historyPush(dragCard, ConstValue.areaFree, x, ConstValue.areaAce, column);
        return;
      }
    }
    //move from stage area to ace area
    for (int x = 0; x < 8; x++) {
      for (int y = 0; y < cardStage[x].length; y++) {
        if (cardStage[x][y] == dragCard) {
          cardStage[x].removeLast();
          cardAce[column].add(dragCard);
          gameHistory.historyPush(dragCard, ConstValue.areaStage, x, ConstValue.areaAce, column);
          return;
        }
      }
    }
  }
  //stage area へ移動
  void stageCardMove(int column, CardOne dragCard) {
    //move from free area to stage area
    for (int x = 0; x < 4; x++) {
      if (cardFree[x].length == 2 && cardFree[x][1] == dragCard) {
        cardFree[x].removeLast();
        cardStage[column].add(dragCard);
        gameHistory.historyPush(dragCard, ConstValue.areaFree, x, ConstValue.areaStage, column);
        return;
      }
    }
    //move from stage area to stage area
    for (int x = 0; x < 8; x++) {
      for (int y = 0; y < cardStage[x].length; y++) {
        if (cardStage[x][y] == dragCard) {
          cardStage[x].removeLast();
          cardStage[column].add(dragCard);
          gameHistory.historyPush(dragCard, ConstValue.areaStage, x, ConstValue.areaStage, column);
          return;
        }
      }
    }
  }
  bool isComplete() {
    return (cardAce[0].length == 14 && cardAce[1].length == 14 && cardAce[2].length == 14 && cardAce[3].length == 14) ? true : false;
  }
  int historyLength() {
    return gameHistory.getLength();
  }
  //undo
  void historyUndo() {
    CardMoveRecord? cardMoveRecord = gameHistory.historyPop();
    if (cardMoveRecord == null) {
      return;
    }
    final CardOne cardOne = cardMoveRecord.getCard();
    final int fromArea = cardMoveRecord.getFromArea();
    final int fromColumn = cardMoveRecord.getFromColumn();
    final int toArea = cardMoveRecord.getToArea();
    final int toColumn = cardMoveRecord.getToColumn();
    if (toArea == ConstValue.areaFree) {
      cardFree[toColumn].removeLast();
    } else if (toArea == ConstValue.areaAce) {
      cardAce[toColumn].removeLast();
    } else if (toArea == ConstValue.areaStage) {
      cardStage[toColumn].removeLast();
    }
    if (fromArea == ConstValue.areaFree) {
      cardFree[fromColumn].add(cardOne);
    } else if (fromArea == ConstValue.areaAce) {
      cardAce[fromColumn].add(cardOne);
    } else if (fromArea == ConstValue.areaStage) {
      cardStage[fromColumn].add(cardOne);
    }
  }

  //------------------------------------------

  //ゲーム終盤で自動完了できるか確認
  bool isAutoComplete() {
    List<List<CardOne>> aces = [
      [...cardAce[0]],
      [...cardAce[1]],
      [...cardAce[2]],
      [...cardAce[3]],
    ];
    List<List<CardOne>> frees = [
      [...cardFree[0]],
      [...cardFree[1]],
      [...cardFree[2]],
      [...cardFree[3]],
    ];
    List<List<CardOne>> stages = [
      [...cardStage[0]],
      [...cardStage[1]],
      [...cardStage[2]],
      [...cardStage[3]],
      [...cardStage[4]],
      [...cardStage[5]],
      [...cardStage[6]],
      [...cardStage[7]],
    ];
    void autoCompleteRecursion() {
      for (int acePos = 0; acePos < aces.length; acePos++) {
        for (int freePos = 0; freePos < frees.length; freePos++) {
          if (autoCompleteIsJoin(aces[acePos].last, frees[freePos].last)) {
            aces[acePos].add(frees[freePos].last);
            frees[freePos].removeLast();
            return autoCompleteRecursion();
          }
        }
        for (int stagePos = 0; stagePos < stages.length; stagePos++) {
          if (autoCompleteIsJoin(aces[acePos].last, stages[stagePos].last)) {
            aces[acePos].add(stages[stagePos].last);
            stages[stagePos].removeLast();
            return autoCompleteRecursion();
          }
        }
      }
    }
    autoCompleteRecursion();
    return (aces[0].length == 14 && aces[1].length == 14 && aces[2].length == 14 && aces[3].length == 14) ? true : false;
  }
  //エースの場所にカーdkが重ねられるか確認。isAutoCompleteとdoAutoCompleteで使用。
  bool autoCompleteIsJoin(CardOne ace, CardOne b) {
    if (ace.getMark() != b.getMark()) {
      return false;
    }
    if (ace.getNum() + 1 != b.getNum()) {
      return false;
    }
    return true;
  }
  //ゲーム終盤の自動完了を1個実行
  bool doAutoComplete() {
    for (int acePos = 0; acePos < cardAce.length; acePos++) {
      for (int freePos = 0; freePos < cardFree.length; freePos++) {
        if (autoCompleteIsJoin(cardAce[acePos].last, cardFree[freePos].last)) {
          final CardOne dragCard = cardFree[freePos].last;
          cardAce[acePos].add(dragCard);
          cardFree[freePos].removeLast();
          gameHistory.historyPush(dragCard, ConstValue.areaFree, freePos, ConstValue.areaAce, acePos);
          return true;
        }
      }
      for (int stagePos = 0; stagePos < cardStage.length; stagePos++) {
        if (autoCompleteIsJoin(cardAce[acePos].last, cardStage[stagePos].last)) {
          final CardOne dragCard = cardStage[stagePos].last;
          cardAce[acePos].add(dragCard);
          cardStage[stagePos].removeLast();
          gameHistory.historyPush(dragCard, ConstValue.areaStage, stagePos, ConstValue.areaAce, acePos);
          return true;
        }
      }
    }
    return false;
  }
}

lib/game_history.dart

///
/// @author akira ohmachi
/// @copyright ao-system, Inc.
/// @date 2022-11-26
///

import 'package:cardsfreecell/card_one.dart';

class GameHistory {
  List<CardMoveRecord> history = [];
  GameHistory() { //constructor
    init();
  }
  void init() {
    history = [];
  }
  void historyPush(CardOne moveCard, int moveFromArea, int moveFromColumn, int moveToArea, int moveToColumn) {
    history.add(CardMoveRecord(moveCard, moveFromArea, moveFromColumn, moveToArea, moveToColumn));
  }
  CardMoveRecord? historyPop() {
    if (history.isEmpty) {
      return null;
    }
    CardMoveRecord cardMoveRecord = history[history.length - 1];
    history.removeLast();
    return cardMoveRecord;
  }
  int getLength() {
    return history.length;
  }
}

class CardMoveRecord {
  late CardOne cardOne;
  late int fromArea;   //areaFree|areaAce|areaStage
  late int fromColumn;
  late int toArea;   //areaFree|areaAce|areaStage
  late int toColumn;
  CardMoveRecord(CardOne moveCard, int moveFromArea, int moveFromColumn, int moveToArea, int moveToColumn) {  //constructor
    cardOne = moveCard;
    fromArea = moveFromArea;
    fromColumn = moveFromColumn;
    toArea = moveToArea;
    toColumn = moveToColumn;
  }
  CardOne getCard() {
    return cardOne;
  }
  int getFromArea() {
    return fromArea;
  }
  int getFromColumn() {
    return fromColumn;
  }
  int getToArea() {
    return toArea;
  }
  int getToColumn() {
    return toColumn;
  }
}

lib/language_choice.dart

///
/// @author akira ohmachi
/// @copyright ao-system, Inc.
/// @date 2022-11-26
///

import 'package:flutter/material.dart';

class LanguageChoice {
  final Map<String,String> langMap = {
    'en': 'English',
    'bg': 'български език',
    'cs': 'Čeština',
    'da': 'dansk',
    'de': 'Deutsch',
    'el': 'Ελληνικά',
    'es': 'Español',
    'et': 'eesti keel',
    'fi': 'Suomen kieli',
    'fr': 'Français',
    'hu': 'magyar nyelv',
    'it': 'Italiano',
    'ja': '日本語',
    'lt': 'lietuvių kalba',
    'lv': 'Latviešu',
    'nl': 'Nederlands',
    'pl': 'Polski',
    'pt': 'Português',
    'ro': 'limba română',
    'ru': 'русский',
    'sk': 'Slovenčina',
    'sv': 'svenska',
    'th': 'ภาษาไทย',
    'zh': '中文',
  };
  List<String> languageList() {
    List<String> languages = [];
    langMap.forEach((key, value) {
      languages.add(key);
    });
    return languages;
  }
  String getLanguageName(String str) {
    if (langMap.containsKey(str)) {
      return langMap[str]!;
    }
    return '';
  }
  List<Locale> localeList() {
    List<Locale> locales = [];
    langMap.forEach((key, value) {
      locales.add(Locale(key));
    });
    return locales;
  }
}

lib/main.dart

///
/// @author akira ohmachi
/// @copyright ao-system, Inc.
/// @date 2022-11-26
///

import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_svg/svg.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';        //flutter gen-l10n
import 'package:package_info_plus/package_info_plus.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'dart:async';

import 'package:cardsfreecell/language_choice.dart';
import 'package:cardsfreecell/ad_mob.dart';
import 'package:cardsfreecell/audio_play.dart';
import 'package:cardsfreecell/card_all.dart';
import 'package:cardsfreecell/card_one.dart';
import 'package:cardsfreecell/drag_animation.dart';
import 'package:cardsfreecell/buttons.dart';
import 'package:cardsfreecell/game.dart';
import 'package:cardsfreecell/const_value.dart';
import 'package:cardsfreecell/app_condition.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  //SystemChrome.setPreferredOrientations([
  //  DeviceOrientation.portraitUp, //縦固定
  //]);
  MobileAds.instance.initialize();
  runApp(const MainApp());
}

class MainApp extends StatefulWidget {
  const MainApp({super.key});
  @override
  State<MainApp> createState() => _MainAppState();
}

class _MainAppState extends State<MainApp> {
  Locale locale = AppLocalizations.supportedLocales.first;
  @override
  Widget build(BuildContext context) {
    final LanguageChoice languageChoice = LanguageChoice();
    final List<Locale> localeList = languageChoice.localeList();
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      localizationsDelegates: const [
        AppLocalizations.delegate,
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
      ],
      supportedLocales: localeList,
      locale: locale,
      home: const MainPage(),
    );
  }
}

class MainPage extends StatefulWidget {
  const MainPage({Key? key}) : super(key: key);
  @override
  State<MainPage> createState() => _MainPageState();
}

//-------------------------------------------------

class _MainPageState extends State<MainPage> {
  final GlobalKey<ScaffoldState> _key = GlobalKey<ScaffoldState>();
  final AppCondition appCondition = AppCondition();
  final AdMob adMob = AdMob();
  final AudioPlay audioPlay = AudioPlay();
  final CardAll cardAll = CardAll();
  final Game game = Game();
  final DragAnimation dragAnimation = DragAnimation();

  @override
  void initState() {
    super.initState();
    _getVersion();
    _loadSound();
    _loadLanguage();
    adMob.load();
  }
  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    _storeAppCanvasWidth(false);
    _onClickStartButton();
  }
  @override
  Widget build(BuildContext context) {
    _storeAppCanvasWidth(false);
    return Scaffold(
      key: _key,
      appBar: null,
      drawer: _hamburgerMenu(),
      backgroundColor: ConstValue.colorAppBackground,
      body: OrientationBuilder(builder: (context, orientation) {
        _storeAppCanvasWidth(true);
        return SafeArea(
          child: Center(
            child: Column(
              children: [
                _menuBar(),
                Expanded(
                  child: Container(
                    color: ConstValue.colorAppBody,
                    padding: const EdgeInsets.symmetric(horizontal: 8.0,vertical: 8.0),
                    child: Center(
                      child: AspectRatio(
                        aspectRatio: ConstValue.canvasAspectRatio,
                        child: Container(
                          width: double.infinity,
                          height: double.infinity,
                          color: ConstValue.colorAppBody,
                          child: _boxCanvas(),
                        ),
                      ),
                    ),
                  ),
                ),
                Container(
                  width: double.infinity,
                  color: ConstValue.colorAppBody,
                  child: adMob.getAdBannerWidget(),
                ),
              ]
            ),
          )
        );
      }),
    );
  }
  //@override
  //void didUpdateWidget(MainPage oldWidget) {
  //  super.didUpdateWidget(oldWidget);
  //}
  @override
  void dispose() {
    audioPlay.dispose();
    super.dispose();
  }

  //-----------------------------------------------

  Future<void> _getVersion() async {
    final PackageInfo packageInfo = await PackageInfo.fromPlatform();
    setState(() {
      appCondition.setAppVersion(packageInfo.version);
    });
  }
  void _storeAppCanvasWidth(bool reDraw) {
    SchedulerBinding.instance.addPostFrameCallback((_) {
      final double sizeWidth = _key.currentContext!.size!.width;
      final double margin = sizeWidth * 0.025;
      final double w = _key.currentContext!.size!.width - margin;
      final double h = _key.currentContext!.size!.height - margin - ConstValue.menuBarHeight - adMob.getAdBannerHeight();
      double appWidth = w;
      if (w / ConstValue.canvasAspectRatio > h) {
        appWidth = (h / (w / ConstValue.canvasAspectRatio)) * w;
      }
      appCondition.setCanvasWidth(appWidth.floorToDouble());
      if (reDraw) { //画面幅取得は非同期なので取得出来たら再描画
        setState(() {});
      }
    });
  }
  void _loadLanguage() async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    final String lang = prefs.getString(ConstValue.prefKeyLanguage) ?? 'en';
    if (!mounted) {
      return;
    }
    context.findAncestorStateOfType<_MainAppState>()!
      ..locale = Locale(lang)
      ..setState(() {});
  }
  void _saveLanguage(String lang) async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    prefs.setString(ConstValue.prefKeyLanguage, lang);
  }
  void _loadSound() async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    setState(() {
      audioPlay.setSoundMode(prefs.getInt(ConstValue.prefKeySound) ?? AudioPlay.soundOn);
    });
  }
  void _saveSound() async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    setState(() {
      prefs.setInt(ConstValue.prefKeySound, audioPlay.getSoundMode());
    });
  }
  void _onClickStartButton() {
    audioPlay.playSoundStart();
    cardAll.cardsShuffle();
    appCondition.setCanAutoComplete(false);
    appCondition.setDoAutoComplete(false);
    setState(() {
      _handOut();
    });
  }
  void _onClickRetryButton() {
    audioPlay.playSoundRetry();
    appCondition.setCanAutoComplete(false);
    appCondition.setDoAutoComplete(false);
    setState(() {
      _handOut();
    });
  }
  void _onClickUndoButton() {
    audioPlay.playSoundBack();
    appCondition.setCanAutoComplete(false);
    appCondition.setDoAutoComplete(false);
    setState(() {
      game.historyUndo();
    });
  }

  //カードを配る
  void _handOut() {
    final List cards = cardAll.getCards();
    game.init();
    for (int i = 0; i < 52; i++) {
      final CardOne card = cards[i];
      game.cardStage[i % 8].add(card);
    }
  }

  //-----------------------------------------------

  Widget _dialogSound() {
    return SimpleDialog(
      title: Text('${AppLocalizations.of(context).sound}: ${audioPlay.getSoundModeStr(context)}'),
      children: <Widget>[
        SimpleDialogOption(
            child: Text(AppLocalizations.of(context).off),
            onPressed: () {
              setState(() {
                audioPlay.setSoundMode(AudioPlay.soundOff);
              });
              _saveSound();
              Navigator.pop(context);
            }
        ),
        SimpleDialogOption(
            child: Text(AppLocalizations.of(context).on),
            onPressed: () {
              setState(() {
                audioPlay.setSoundMode(AudioPlay.soundOn);
              });
              _saveSound();
              Navigator.pop(context);
            }
        ),
      ],
    );
  }
  Widget _dialogLanguage() {
    final LanguageChoice languageChoice = LanguageChoice();
    final List<String> langList = languageChoice.languageList();
    return SimpleDialog(
      title: Text(AppLocalizations.of(context).language),
      children: List.generate(langList.length,(index) {
        return SimpleDialogOption(
            child: Row(
                children:[
                  SizedBox(
                      width: 25,
                      child: Text(langList[index])
                  ),
                  Text('- ${languageChoice.getLanguageName(langList[index])}')
                ]
            ),
            onPressed: () {
              _saveLanguage(langList[index]);
              context.findAncestorStateOfType<_MainAppState>()!
                ..locale = Locale(langList[index])
                ..setState((){});
              Navigator.pop(context);
            }
        );
      }),
    );
  }
  Widget _hamburgerMenu() {
    return Drawer(
      child: ListView(
        padding: EdgeInsets.zero,
        children: [
          SizedBox(
              height: 270,
              child: DrawerHeader(
                decoration: const BoxDecoration(color: ConstValue.colorAppHeader),
                child: Column(children:[
                  Row(children:[
                    SvgPicture.asset(ConstValue.imageIcon, width: 80, height: 80),
                    const SizedBox(width: 8),
                    Flexible(child: Text(AppLocalizations.of(context).appName,style: const TextStyle(color: Colors.white))),
                  ]),
                  Text(AppLocalizations.of(context).usage,style: const TextStyle(fontSize: 11,color: Colors.white)),
                ]),
              )
          ),
          ListTile(
            leading: const CircleAvatar(
              backgroundColor: ConstValue.colorAppHeader,
              child: Icon(
                Icons.audiotrack,
                color: ConstValue.colorAppHeaderText,
              ),
            ),
            title: Text('${AppLocalizations.of(context).sound}: ${audioPlay.getSoundModeStr(context)}'),
            trailing: const Icon(
              Icons.arrow_circle_right_outlined,
            ),
            onTap: () {
              showDialog(
                  context: context,
                  barrierDismissible: true,
                  builder: (_) {
                    return _dialogSound();
                  }
              );
            },
          ),
          Container(
            padding: const EdgeInsets.only(left: 15,right: 15,bottom: 20),
            child: Text(AppLocalizations.of(context).sound1,style: const TextStyle(fontSize: 11,color: Colors.black54)),
          ),
          ListTile(
            leading: const CircleAvatar(
              backgroundColor: ConstValue.colorAppHeader,
              child: Icon(
                Icons.language,
                color: ConstValue.colorAppHeaderText,
              ),
            ),
            title: Text(AppLocalizations.of(context).language),
            trailing: const Icon(
              Icons.arrow_circle_right_outlined,
            ),
            onTap: () {
              showDialog(
                  context: context,
                  barrierDismissible: true,
                  builder: (_) {
                    return _dialogLanguage();
                  }
              );
            },
          ),
          Container(
            padding: const EdgeInsets.only(left: 15,right: 15,top: 50),
            child: Text('v ${appCondition.getAppVersion()}',style: const TextStyle(fontSize: 11,color: Colors.black45)),
          ),
        ],
      ),
    );
  }
  Widget _menuBar() {
    return Container(
      height: ConstValue.menuBarHeight, // in logical pixels
      padding: const EdgeInsets.symmetric(horizontal: 8.0),
      decoration: const BoxDecoration(color: ConstValue.colorAppHeader),
      child: Row(
        children: <Widget> [
          StartButton().button(context,_onClickStartButton),
          const SizedBox(width: 8),
          RetryButton().button(context,_onClickRetryButton),
          const SizedBox(width: 8),
          UndoButton().button(context,_onClickUndoButton),
          const Expanded(
            child: SizedBox(width: 8),
          ),
          SettingButton().button(context,(){_key.currentState?.openDrawer();}),
        ],
      ),
    );
  }

  //-------------------------------------------------------------

  Widget _boxCanvas() {
    final double canvasWidth = appCondition.getCanvasWidth();
    final double cardGap = (canvasWidth * 0.05 / 7).floorToDouble();
    double cardWidth = ((canvasWidth - (cardGap * 7)) / 8).floorToDouble();
    if (cardWidth > 1) {
      cardWidth -= 2.0; //なぜかはみ出るから微調整
    }
    if (appCondition.getDoAutoComplete()) {
      if (game.doAutoComplete()) {
        appCondition.setDoAutoComplete(false);
        Future.delayed(const Duration(milliseconds: 150)).then((_) => {
          appCondition.setDoAutoComplete(true),
        });
      } else {
        appCondition.setDoAutoComplete(false);
        if (game.isComplete()) {
          audioPlay.playSoundComplete();
        }
      }
    }
    return Column(
      children: [
        Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            _transparentButton(),
            Container(width: cardGap * 3),
            Text(game.historyLength().toString(),
              style: const TextStyle(
                color: ConstValue.colorHistoryLength,
                fontSize: 30,
              ),
            ),
            Container(width: cardGap * 3),
            _autoCompleteButton(),
          ],
        ),
        Container(height: cardGap),
        Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Stack(children: _cardFreeImage(cardWidth,cardGap)),
            Container(width: cardGap),
            Stack(children: _cardAceImage(cardWidth,cardGap)),
          ],
        ),
        Container(height: cardGap * 3),
        Stack(children: _cardStageImage(cardWidth,cardGap)),
      ],
    );
  }
  Widget _autoCompleteButton() {
    if (appCondition.getCanAutoComplete()) {
      return ElevatedButton(
        onPressed: (){
          setState((){
            appCondition.setCanAutoComplete(false);
            appCondition.setDoAutoComplete(true);
          });
        },
        style: ElevatedButton.styleFrom(
          elevation: 0,
          backgroundColor: ConstValue.colorAutoCompleteButton,
          padding: const EdgeInsets.symmetric(horizontal: 8.0,vertical: 0),
        ),
        child: Text(AppLocalizations.of(context).autoComplete,
          style: const TextStyle(
            color: ConstValue.colorAppHeaderText,
            fontSize: 12,
          ),
        ),
      );
    } else {
      return _transparentButton();
    }
  }
  Widget _transparentButton() {
    //heightを合わせるためContainer()ではなく透明なボタン。
    return ElevatedButton(
      onPressed: null,
      style: ElevatedButton.styleFrom(
        elevation: 0,
        disabledForegroundColor: Colors.transparent, disabledBackgroundColor: Colors.transparent,
      ),
      child: const Text('',
        style: TextStyle(
          fontSize: 14,
        ),
      ),
    );
  }
  List<Widget> _cardFreeImage(double cardWidth, double cardGap) {
    final List<List<CardOne>> cardFree = game.getCardFree();
    final List<Widget> containerArray = [];
    for (int x = 0; x < 4; x++) {
      for (int y = 0; y < cardFree[x].length; y++) {
        final String cardImg = cardFree[x][y].getImg();
        final int cardIndex = cardFree[x][y].getIndex();
        final double marginLeft = (x * cardWidth) + (x * cardGap);
        if (y == 0) {
          final Container container = Container(
            margin: EdgeInsets.only(left: marginLeft),
            child: SvgPicture.asset(
              cardImg,
              width: cardWidth,
            ),
          );
          containerArray.add(container);
          final Container containerDragArea = Container(
            margin: EdgeInsets.only(left: marginLeft),
            child: DragTarget<CardOne>(
              builder: (context, accepted, rejected) {
                return SizedBox(
                  width: cardWidth,
                  height: cardWidth / 360 * 540,
                  //color: Colors.grey.withOpacity(0.5),
                );
              },
              onWillAccept: (data) {
                return (cardFree[x].length == 1) ? true : false;
              },
              onAccept: (data) {
                game.freeCardMove(x, data);
              },
            ),
          );
          containerArray.add(containerDragArea);
        } else {  //free cell内のカード
          final AnimatedPositioned container = AnimatedPositioned(
            left: dragAnimation.getX(cardIndex),
            top: dragAnimation.getY(cardIndex),
            duration: Duration(milliseconds: dragAnimation.getSpeed(cardIndex)),
            child: Container(
              margin: EdgeInsets.only(left: marginLeft),
              child: Draggable(
                data: cardFree[x][y],
                onDragStarted: () {
                  audioPlay.playSoundSlide();
                },
                feedback: SvgPicture.asset(
                  cardImg,
                  width: cardWidth,
                ),
                childWhenDragging: Container(),
                child: SvgPicture.asset(
                  cardImg,
                  width: cardWidth,
                ),
                onDragUpdate: (details) {
                  dragAnimation.setSpeedOff(cardIndex);
                  dragAnimation.addX(cardIndex,details.delta.dx);
                  dragAnimation.addY(cardIndex,details.delta.dy);
                },
                onDragEnd: (details) {
                  dragAnimation.setSpeedOn(cardIndex);
                  dragAnimation.setX(cardIndex,0);
                  dragAnimation.setY(cardIndex,0);
                },
              ),
            ),
          );
          containerArray.add(container);
        }
      }
    }
    return containerArray;
  }
  List<Widget> _cardAceImage(double cardWidth, double cardGap) {
    bool isAceCardJoin(CardOne aceLastCard, CardOne dragCard) {
      final int lastMark = aceLastCard.getMark();
      final int lastNum = aceLastCard.getNum();
      final int dragMark = dragCard.getMark();
      final int dragNum = dragCard.getNum();
      if (lastMark == -1 && dragNum == 1) {
        return true;
      }
      if (lastMark == dragMark && lastNum + 1 == dragNum) {
        return true;
      }
      return false;
    }
    final List<List<CardOne>> cardAce = game.getCardAce();
    final List<Container> containerArray = [];
    for (int x = 0; x < 4; x++) {
      for (int y = 0; y < cardAce[x].length; y++) {
        final String cardImg = cardAce[x][y].getImg();
        final double marginLeft = (x * cardWidth) + (x * cardGap);
        final Container container = Container(
          margin: EdgeInsets.only(left: marginLeft),
          child: SvgPicture.asset(
            cardImg,
            width: cardWidth,
          ),
        );
        containerArray.add(container);
      }
    }
    for (int x = 0; x < 4; x++) {
      final double marginLeft = (x * cardWidth) + (x * cardGap);
      final Container containerDragArea = Container(
        margin: EdgeInsets.only(left: marginLeft),
        child: DragTarget<CardOne>(
          builder: (context, accepted, rejected) {
            return SizedBox(
              width: cardWidth,
              height: cardWidth / 360 * 540,
              //color: Colors.grey.withOpacity(0.5),
            );
          },
          onWillAccept: (data) {
            final CardOne aceLastCard = cardAce[x][cardAce[x].length - 1];
            return isAceCardJoin(aceLastCard, data!);
          },
          onAccept: (data) {
            game.aceCardMove(x, data);
            if (game.isAutoComplete()) {
              setState((){
                appCondition.setCanAutoComplete(true);
              });
            }
            if (game.isComplete()) {
              appCondition.setCanAutoComplete(false);
              audioPlay.playSoundComplete();
            }
          },
        ),
      );
      containerArray.add(containerDragArea);
    }
    return containerArray;
  }
  List<Widget> _cardStageImage(double cardWidth, double cardGap) {
    bool isStageCardJoin(CardOne stageColumnLastCard, CardOne dragCard) {
      final String lastColor = stageColumnLastCard.getColor();
      final int lastNum = stageColumnLastCard.getNum();
      final String dragColor = dragCard.getColor();
      final int dragNum = dragCard.getNum();
      if (lastColor == '') {
        return true;
      }
      if (lastColor == dragColor) {
        return false;
      }
      if (lastNum - 1 != dragNum) {
        return false;
      }
      return true;
    }
    final List<List<CardOne>> cardStage = game.getCardStage();
    final List<Widget> containerArray = [];
    for (int x = 0; x < 8; x++) {
      for (int y = 0; y < cardStage[x].length; y++) {
        final String cardImg = cardStage[x][y].getImg();
        final int cardIndex = cardStage[x][y].getIndex();
        final double marginLeft = (x * cardWidth) + (x * cardGap);
        final double marginTop = (y <= 1) ? 0 : (y - 1) * (cardWidth * 0.35);
        if (y != 0 && y == cardStage[x].length - 1) { //列の最後のカード
          final AnimatedPositioned container = AnimatedPositioned(
            left: dragAnimation.getX(cardIndex),
            top: dragAnimation.getY(cardIndex),
            duration: Duration(milliseconds: dragAnimation.getSpeed(cardIndex)),
            child: Container(
              margin: EdgeInsets.only(left: marginLeft,top: marginTop),
              child: Draggable(
                data: cardStage[x][y],
                onDragStarted: () {
                  audioPlay.playSoundSlide();
                },
                feedback: SvgPicture.asset(
                  cardImg,
                  width: cardWidth,
                ),
                childWhenDragging: Container(),
                child: SvgPicture.asset(
                  cardImg,
                  width: cardWidth,
                ),
                onDragUpdate: (details) {
                  dragAnimation.setSpeedOff(cardIndex);
                  dragAnimation.addX(cardIndex,details.delta.dx);
                  dragAnimation.addY(cardIndex,details.delta.dy);
                },
                onDragEnd: (details) {
                  dragAnimation.setSpeedOn(cardIndex);
                  dragAnimation.setX(cardIndex,0);
                  dragAnimation.setY(cardIndex,0);
                },
              ),
            ),
          );
          containerArray.add(container);
        } else {
          final Container container = Container(
            margin: EdgeInsets.only(left: marginLeft,top: marginTop),
            child: SvgPicture.asset(
              cardImg,
              width: cardWidth,
            ),
          );
          containerArray.add(container);
        }
      }
    }
    for (int x = 0; x < 8; x++) {
      final double marginLeft = (x * cardWidth) + (x * cardGap);
      final double marginTop = (cardStage[x].length - 1) * (cardWidth * 0.35);
      final Container containerDragArea = Container(
        margin: EdgeInsets.only(left: marginLeft,top: marginTop),
        child: DragTarget<CardOne>(
          builder: (context, accepted, rejected) {
            return SizedBox(
              width: cardWidth,
              height: cardWidth / 360 * 540,
              //color: Colors.grey.withOpacity(0.5),
            );
          },
          onWillAccept: (data) {
            final CardOne stageColumnLastCard = cardStage[x][cardStage[x].length - 1];
            return isStageCardJoin(stageColumnLastCard, data!);
          },
          onAccept: (data) {
            game.stageCardMove(x, data);
          },
        ),
      );
      containerArray.add(containerDragArea);
    }
    return containerArray;
  }

}