Flutter製のテキサスホールデムポーカーのiOS/Androidアプリをリリースしました

Flutter製のテキサスホールデムポーカーのiOS/Androidアプリをリリースしました

Flutterで作ったiOS/Androidアプリを初めてストアに出しました。テキサスホールデムというポーカーの計算機で、複数人のハンドやハンドレンジからそれぞれ誰がどれくらいの勝率があるかを計算できるものです。

UIインタラクション
UIインタラクション

ダウンロード

オープンソースにしています。IssueやStarなどでのフィードバックは励みになるので是非よろしくお願いします。

GitHub - axross/aqua: ♠️ Beautiful iOS/Android poker odds calculator app made of Flutter.
♠️ Beautiful iOS/Android poker odds calculator app made of Flutter. - axross/aqua
https://github.com/axross/aqua

実装について

マッチアップの計算

マッチアップは所謂モンテカルロシミュレーションで、一定回数の計算をランダムに試行して平均勝率を算出します。

ポーカーでは相手が持っているカードが何なのかをプレイ中に知ることはできず、複数の読みの候補をもとに判断を下していきます。このアプリはその判断を下す手助けをするツールなので、複数の読みを考慮した計算ができるようになっています。

たとえば相手の持っているハンドがAとKの組み合わせ、AとQの組み合わせ、AとJの同じスート (マーク) の組み合わせのどれかであるという読みの場合、AKが16通り、AQが16通り、AJが4通りで合計36通りあります。このうちAQとAJに対しては勝っていてAKには負けているのであれば勝率は16/36で約44.4%です。

この計算に次のファクターを加えます。

  • コミュニティカード (中央に開かれる全プレイヤー共通のカード) の枚数
  • 相手プレイヤーが持っていうるカードの組み合わせ数
  • 相手プレイヤーの人数

マッチアップを全通り計算する時間複雑度は次のようになり、全通り試行するのはおよそ現実的ではないことがわかります。マッチアップの選択をランダムにしているのはこのためです。

n = カードの枚数 (52枚) m = プレイヤーの人数 時間複雑度 = O((n^5)(n^2^m))

勝率計算

記事を書いた時点から何回か改善を加えているので大まかな方針だけですが、次のようになっています。

  1. コミュニティカードをランダムに選択する
  2. 各プレイヤーの持っているカードにコミュニティカードを加え、最強になる5枚の組み合わせをフォールスルーで探し、強さを数値化する
  3. プレイヤーの強さの数値の中から最強のものを探し、勝利フラグを立てる
  4. プレイヤーごとに勝利した回数を全試行回数で割り、勝率とする

勝率計算の1回はマイクロ秒単位で終わりますが、充分な精度で求めようとすると膨大な回数の試行が必要になります。数百回単位でチャンクとし、チャンクごとに別スレッドで計算するようにしてUIスレッドを止めないようにする必要があります。この手の並列処理にはIsolateを使うと便利です。

UIインタラクション

Flutter Tween Animation Example Flutter Tween Animation Example

Flutterは逐次的なシーケンスアニメーションと、値のグラデーションによるツウィーンアニメーションの2種類が扱えます。UIインタラクションに使うのは通常ツウィーンアニメーションの方で、これはFlutterの UI=F(S) モデルとうまく協調して動くAPIで作られています。

class SlidingButton extends StatefulWidget { _SlidingButtonState createState() => _SlidingButtonState(); } class _SlidingButtonState extends State<SlidingButton> with SingleTickerProviderStateMixin { Animation<double> animation; AnimationController controller; @override void initState() { super.initState(); controller = AnimationController(duration: const Duration(milliseconds: 300), vsync: this); animation = CurvedAnimation(parent: controller, curve: Curves.easeInOut); } @override Widget build(BuildContext context) => SlideTransition( position: Tween<Offset>( begin: const Offset(0, 0.125), end: const Offset(0, 0), ).animate(curvedAnimation), child: Button( child: Text("Tap to slide"), ), ); @override void dispose() { controller.dispose(); super.dispose(); } }

メディアクエリ

FlutterにはMediaQueryというAPIがあり、これによって端末やOSの色々な情報が取得できます。

  • ディスプレイのサイズや向き
  • 太いフォントを好んで利用するかどうか (iOS)
  • 24時間のタイムフォーマットを利用しているかどうか

これを利用することでダークモードもUIに簡単に反映できます。

Dark theme
Dark theme

実際に利用する際は、アプリケーションのできるだけ先祖に近い箇所でMediaQueryを取得するようにして InheritedWidget などを使って子孫ウィジェットに伝搬していく作りにするとよいと思います。 InheritedWidgetReactのContextに似た伝播モデルを司るウィジェットです。

リリース

さほど難しくないですが、やっぱりiOSとAndroidでの開発は経験しておいた方がいいです。コーディングに関する部分はFlutterによってうまく抽象化されているのでまったくiOS/Androidの知識がなくても扱えるようになっています。ですがリリースに際しての証明書での署名や難読化、デバイスのアーキテクチャごとのビルドなど、各プラットフォームの知識があった方がいい箇所もあります。