ITベンダー管理はポンコツAIのデバッグと同じだった件

IT ベンダーというのは不思議な存在だ。私は発注側のユーザー企業にいて彼らの仕事を見ているものの、彼らの生態はなかなか理解しがたい。ここでいうベンダーとは SES 的なタイムチャージで稼働する人たちのことを言っています。私は開発をベンダーに委託している会社に所属しつつ、一応プログラマでもあり、内製の開発もできる。事業会社所属のプログラマの立場で見るとベンダーは「こいつら何なんだ?」と不思議に思うことが多い。

受発注という強固な立場の差が会社間にあって、立場の強いユーザー企業に所属している身分であるために思うことなのは否定できない。なので、彼らの立場も想像して考えるとともに、こういう考えは良くないという自省のために書きます。また私の主観的な観測範囲の中でのことだし、一般化して話していても、すべてに当てはまる話ではないことは断っておきます。

これから挙げるのは、彼らの立場では理屈は通るが、私と彼らとでは心情的に折り合えないことです。

Copy & Paste 開発

ベンダーを見ていてもっとも不思議に思うのは自分たちの仕事を改善しようとしないところだ。ここで仕事とは成果物だったりやり方のことです。あの人たちは成果物の品質を上げていこうという動きもしないし、仕事のやり方を改めて良い成果物が出るように工夫することもない。クラフトマンシップと呼ぶような職業人としての矜持はないのかと、イラつきを覚えることもありました。仕事など同じことの繰り返しであり、やるたびに腕を上げて前回より良い仕事をしようと一般に考えるべきではないでしょうか。

仕事の成果物の話だと、あの人たちは頑なに現行のものをまねて開発をしたがります。特に変える意味のない仕様を変えないのは分かるんですが、明らかに既存の実装に問題があっても、既存の通りにやってきます。既存のを参考にしてもいいのだが、インターネットにいくらでもリファレンスがある中で、なぜあえてその現場にある悪いものを踏襲するのか。会社としてやってきているのだから、蓄積され参照できるナレッジはおたくの会社にないんかと思うんですが、そんなものは全くない。

例えば――

例1)品質の低い独自フレームワークがその現場で使われていたら、絶対にそれで新規のも彼らは作りますね。それでいて変なものを使わされていると文句を言っていたり、品質の低さにハマって困っていたりするので、びっくりします。誰もそうしろなどと頼んでいなくて、自らそうやっているのに。

例2)Java 8 と 11 が使える環境でなぜか Java 8 を選んだり。最近に Java 7 で動くプログラムを作っていたのをみたこともあります。他のがサポート切れで放置されていたとしても、新規が新しいのを使ったほうがいいのは自明だと思うのですが、いいもの作ろうという動きがないのが不思議で仕方がない。

例3)発注側の会社名が変わったのに、ソースコードのヘッダの Copyright の表示に古い会社名を書いてくる。客の名前を間違えるとか仕事としてありえないだろう?

もちろん、ちゃんと指示していない発注側が悪いんですよ。ただ放っといたら悪い方を選びたがる意味がわからない。

仕事のやり方の話でも同様ですね。なお、私が見ているのは発注側が用意した環境で開発している人たちです。JIRA や CI、SonarQube などの環境を用意して渡したら、それ有効に使い始めるかというと、まずやってくれませんね。

例えば CI 環境や SonarQube を渡したとして、自発的にプロセスに組み込んで使ってはくれない。強制するためにオールグリーンのビルド結果を成果物として定義したら、リリースの直前の成果物の確認のときに、慌ててレポートを作って検出されたバグを直したりします。また SonarQube で出ている既存の警告を自ら直してくれることはない。

CI ってそういうものだっけ?あれって品質の状態のモニタリングによる継続的な品質向上を目的にしたもので、ダッシュボードを見て判断して随時にアクションを起こさないと効果は期待できない。世間のベストプラクティスを実践したいと思わないのだろうか。

もちろん、これもちゃんと指示していない発注側が悪いんですよ。

あいつらは ChatGPT みたいなもんだ

あの人たちは言われないとやらない。技術選定はやってあげなきゃならないし、ログの出し方も指定してあげなければならないし、git リポジトリに push して CI 通せと言わなきゃならないし、SonarQube はどの警告を直せと具体的に指示しなければいけない。そこまで言ってあげなきゃならないのかってものも、違うことをしているのを見つけたらフィードバックを与えねばならない。ただ指示すれば彼らは「承知しました」と言ってやってくれる。立場的に断れない。性能の悪い ChatGPT ぐらいに見なさなければだめだ。

ベンダーは機能的に、与えられた環境で言われたことをやるのが契約なので、言われたことだけやりますってスタンスは当然なんです。仕事の内容にも文句も言わずに実行するのが期待役割だ。逆に客が間違ったこと言っていても従わないといけない。適性がいる立場だとは思いますし、適応してしまうと当事者意識が薄れて、工夫して仕事を良くしていこうって発想がなくなるのも分かります。

SES の批判みたいになってしまうけど、別に事業会社でも同じような感じのところはある。環境は与えられ、やり方は指定され、判断はしてもらうものだと思っている当事者意識のない動きをする人はレベルの低い会社だと多い。辞めた会社の話です。社員が自発性を発揮できないような状態にするのは経営が悪いんだと思います。そういう会社は滅びたらいいと思います。

一方で、業務委託が快適に仕事ができるように、色々と用意してあげる発注側はかなり良心的でまともだと思っています。例えば新しいバージョンの Java で開発できる環境だったり、CI 環境だったりです。発注側が劣悪な環境に耐えて順応しているところに業務委託で入ったらひどいと思いますよ。たとえばこれとか。そういうの知ってるから、お前らのために色々整えてあげてるのになんで乗っかって来ないんだというのが、不思議だったんですが諦めました。

生成 AI をどうマネージするか

今のテーマは自発性を発揮できない立場の人たちに快適で効率のよいやり方を取り入れさせつつ、彼らの仕事をマネージするかです。自分の仕事を快適にするのはできたし、自発性が発揮できるのチームでの仕事を快適にするのもできたので、次は仕事を快適に効率よくしたいと思ってない人たちにどうやって品質の高い仕事をさせるかです。

言わないとやらないだけでなく、ベンダーは人の数も多い。直接雇用で人を集められないから、ベンダーに人を集めてもらっている。どこから来てるのか分からないスキルの怪しい人もいる。そこまで言わなきゃならないのってことまで言わないといけない一方で、数が多いから末端の作業員の仕事までつぶさに見るのも管理能力の限界を超えてしまう。マネージしきれないものをどうやってマネージしきるかが、今の興味だ。

生成AIで中身が追いきれないものが大量に生産される世界がもうすぐ訪れる。しかし、これは所属不明で顔も知らない大量の作業者がプロジェクトに入ってきている世界と同じではないでしょうか。性能の低い生成AIが並列で動いている世界です。人間をマネージできたら、AI の方が簡単でしょう。なぜなら AI の方が人間より賢いのだから。きたる近未来に向けて人間で練習中です。

変更管理プロセスと技術的負債の返済

ソフトウェア開発はリリースごとに差分を積み上げていくのが一般的なスタイルです。最初に動くものを組んで、リリースしたあとは触らずに放置されるべきものではない。周囲の環境の変化に合わせて要望があるので、ほとんどの場合は動くものをベースにして、新しい機能なりを追加してというのを繰り返します。

既存のコードベースに差分を積み上げる改良開発は、新規開発と比べて、難しいというか厳格なものです。最初はやっぱりワーっと統制のない状態で作られても完成までは何とかたどり着けます。超人的なエンジニアが独力で作り上げたって伝説が残ってたりしますね。完成までたどり着けない場合もありますが、それは今回議論したいことではありません。一方で、そのような新規開発と比べると、追加の開発は既存のものと整合性をとったり、既存のものを壊さないように気をつけたりする必要があるので、それなりに厳格な変更管理のプロセスが必要になります。新規開発と比べると改良開発はどうしても生産性が低いように見えてしまいます。でも、それはそういうものなので仕方のないことです。この事情を経営に説明できない開発組織ってのは良くないと思います。

厳格な変更管理のプロセスが今はなくても、まともな組織なら勝手に厳しくなります。プロセスは増える方向に重力が働いていて、基本的に増える一方です。どこかの変更の品質に問題があれば、その後の変更はプロセスが増えて統制が厳しくなります。これは自然の摂理でそういうものです。そうすると工数ってどんどん増えて行くので、傍目には生産性が低いように見えてしまいます。

ただ、私はプロセス増やして統制をきつくするのは必要だと認めるものの、それがどれほど効果的なのかは疑問に思っています。統制が緩かった過去の時代に積み上げられたものはそこにあるままだからです。それは一般には技術的負債と呼ばれるものですかね。地雷を踏んでしまった事故があったとして、地雷を避けるようなチェックを増やすのは直接的な回避策ですが、その分工数が増える。そしてチェックしきれるかというとしきれない。地雷原の中を歩かせるようなストレスフルな仕事の生産性が高いはずがないし、また地雷踏んでしまったら、さらにプロセスが厳しくなって、どんどん工数が増えてくる。

地雷を踏んだ事故があったら、普通に考えたら地雷をどけるべきでしょう。地雷は悪意あって置かれたもので、踏んだら確実に大きな傷を負うので、どけることに異論はだれもないと思いますが、踏まないようにうまく歩けてしまったり、踏んでもかならずしも爆発しないし、致命傷でもないリカバリ可能な傷で、うまくリカバリしてしまう人もいたりすると、どけようって判断にはなかなかならないのが困ったところです。

例としては、循環的複雑度がとても高い関数が挙げられるでしょうか。こういう複雑で危険なコードベースをいじったときに問題が起きたときに、再発防止策でチェックリストを作りますとか、影響範囲調査とリグレッションテストを強化しますってなるんですが、仮に運用されても効果はあっても工数がかかって開発がどんどん遅くなってくるでしょう。

じゃあ技術的負債は放置しないという方針ものに、循環的複雑度の基準値をつくって、それを超えるような変更は認めないというルールを作ったらうまくいくか。まあ、うまくいかないだろう。ここでうまくいっている状態とは、みながその基準に従って、新しく複雑な関数はつくらず、既存の複雑な関数は簡単にして、変更を重ねるうちに基準を超える複雑な関数がなくなっていく状態です。なぜなら、その統制を守るコストを負担するのが、変更を加える人になってしまうからだ。その差分だけなら気をつけて if 文を加える方が簡単だし、既存のコードが複雑であることに責任はないし、なぜわざわざ時間をかけたいと思うのでしょう。品質基準を押し付けるひとが、コストやスケジュールに責任をもっていないと、喧嘩になるだけだ。

先ほどの地雷原の話に戻ると、道通るのにいちいち地雷除去戦車を個人で用意しろって無理があるじゃない。道を早く行きたいって思いは強い。やっぱり地雷探知機で気をつけながら行こうってみんな思う。ルール無視しちゃう人はそうしちゃうでしょう。やっかいなのはルール無視しても早く着いたほうが褒められてしまうことだ。でもそういう風に統制効かなくなると地雷探知機もすっ飛ばした方が早くて、確率の問題なのでほとんどの場合はそれでうまくいくんだけど、たまに失敗して爆死する人が出る。

じゃあ、コストも出すし、スケジュールもゆるくするからやれってなるかというとそれもならない。プロセスを加えて仕事を増やすときはそんなのちょっとやろってコストが過小に見積もられて増やされるし、仕事が増えた分のアカウンタビリティは結局仕事をする側が持つ。簡単な仕事でもこれだけ手間がかかりますとか言うはかなりかなり手間である。統制を守るのはとてもコストがかかる。早く道を進みたいためにやっていたはずなのに、余計に道を進むのが遅くなる。それは一般に受け入れられない。

誰かがリーダーシップをもって地雷を除けって話になるのだろう。本当の地雷原だって、お前らの問題なのだから自分でやれよって思いますけど、やっぱり外から誰かが除去してあげないと問題は解決しない。差分の変更管理はプロセスで統制できるが、積まれてしまった差分はプロセスの問題じゃないってのが現在の考えです。対人地雷を禁止して地雷を増やさないのは変更管理プロセスのなかで制御できるが、変更管理プロセスはすでに積み上げられて問題は解決しない

コードレビューは無意味という話

主語がデカいというか、「すべてのコードレビューが無意味」はうそなんですが、「何も工夫のない原始的なコードレビューは非効率か逆効果」だとは思っています。プロセスとしてコードレビューを入れるなら、効果的に実践できるように指導するのがマネジメントの仕事でしょう。

何も工夫のない原始的なコードレビューと言っているのは、差分を見てレビューアが思いつきでコメントを入れるだけのものです。同じレビューアであっても言っていることが一貫しない。結果品質が安定しない。レビューアからしても毎回おなじことを言わなければならないと必要のない余計なストレスがたまる。

最悪なのはレビューアが品質にも納期にも責任を持っていない立場であるときだ。無責任に茶々入れられる結果に当然なるので、関係が悪くなる。レビューアもやれって言われて「見てやってる」って上からの気持ちだし、折り合わない。やらない方がマシだ。そのパッチの品質はちょっとは良くなるのかもしれないが、協力しあえない状態になる方が悪い。

安定した品質のものを余計なストレスなしにデリバリーできる状態にするというのは、マネージャーかアーキテクトと呼ばれる人がやるべきことなんですよ。プロセスを流れる成果物とプロセス自体があったとして、前者の品質には当然責任を彼らは負わなければならないが、後者を整えることにも彼らは責任がある。

コードレビューは、設計通りにコードが出来上がっているかと、動くかと、コードが規約通りになっているかどうかだけ見れば、それでいい。設計通りで動いて規約通りだったらどの変更は入れるべきです。レビューアの自分ルールなんて押し付けるべきではない。明文化されてもなければ聞いてもないことをあとから言われてやり直させられるのはストレスでしかない。

だから、コードレビューするなら前提してコーディングの規約がないといけない。例えばレベルの低いところだと、インデントがずれてるとか指摘するなら、ちゃんとインデントに関する規約を定めなければならない。さらに加えて Checkstyle の設定などを整備してスタイルチェックを自動でできる仕組みを整えなければならない。インデントがずれてると指摘だけして、コーディング規約を整えるわけでもなく、ビルドツールを整備することもなく、何度も同じことだけ言ってるのは、怠慢と主体性の欠如でしかない。

規約が十分でないこともあろう。規約がないところで指摘がしたくなったら、規約の方を育てる。規約が機械化されているなら設定をカスタマイズして、適用する。同じような箇所が他にもあるだろう。今の差分だけ直したって仕方ないともう直さないのか、今回の差分だけ直すのか、今回全部直すのが、バックログにいれるのか、入れるならどの優先度でやるのか、それは開発のマネジメントの判断である。人のコードに指摘するなら、自分の指摘がどれほどの影響があるか考えて指摘したい。

そのように育てられた規約とチェックツールは組織の財産です。規約で制限されたプログラミング言語は、不適切な機能や書き方を省いたプログラミング言語のサブセットであり、プログラミング言語を拡張して進化させたものと言って差し支えありません。ビルドが通るように作ったら勝手に適切な状態になる。そうしたらコードレビューなど要りませんよね。コードレビューはプログラミング言語の拡張の機会でしかない。開発組織をマネジメントするなら、こういう仕組みのほうにちゃんと投資したいし、投資してないところでは働きたくない。

ここまでやって、まだ規約では表現できない、問題のもっと良い解き方の議論などがレビューで行われることもあるだろう。これはコードレビューではないし、標準化もできないレビューアの能力が問われることだと思いますが、これこそ本質的でやるべきことだと思います。

バカラ必勝法の可能性

バカラの統計を分析して遊んでいますが、やっと身になる結果が発見されたかもしれません。バカラには必勝法がある可能性があります。

前回の結果として 0~4 の小さいカードが多いと player に有利で、5~9 の大きいカードが多いと banker に有利という結果を得ました。特定の一枚のカードではなくて、デッキに含まれていると好ましいカードに全体的に偏りがある場合の控除率の変化を調べてみました。

上のグラフはデッキに含まれる 5~9 のカードの割合と控除率の関係を示しています。偏りがない場合には rate = 5 / 13 = 0.38 です。驚くべきことに偏りが大きくなると控除率が 0 を下回る、つまり賭けの期待値が 1 を超える場合があることがわかります。5~9 のカードが 約 60 % を超えると banker に賭けたときの控除率が 0 を下回り、 約 18 % 以下になると player に賭けたときの控除率が 0 を下回ります。これはつまり、バカラでカウンティングが有効なことを示しています。ただ、控除率が負になる場面はかなり稀そうです。

一方で、実益がありそうな知見としては、player, banker の控除率が逆転するのは頻繁にありそうだという点が挙げられます。わずかな偏りで逆転が起きています。バカラは banker に賭けるのがわずかに有利だというニワカ知識だけで banker にだけ賭け続けてると、実際には不利な賭けをしている可能性があります。

さらに、偏りというのは一般に終盤のほうが大きくなります。つまり、デッキのカードが減ってくると最適な賭け方の控除率が減って遊びやすくなってきます。したがって、序盤は少なく賭けて、カードの偏りを見極めながら、終盤に大きく勝負に出るのが、効率的な遊び方だと言えるでしょう。

バカラでカウンティングは有効か

前回 バカラの勝率の計算 - 超ウィザード級ハッカーのたのしみ

バカラにハマったので、バカラを分析して遊んでいます。今回はカードの偏りがバカラの勝敗に与える影響について調べてみましょう。なぜこのようなことをするかというと、カウンティングが有効かどうかを調べるためです。ブラックジャックはカードの偏りの状態によっては期待値が 1 を超えことがあると知られています。カウンティングと呼ばれるテクニックで、デッキの中のカードの偏りの状態を推定するのが、有名なブラックジャックの必勝法です。バカラでも同様の必勝法が有効なのでしょうか?調べてみましょう。

偏りがないときの控除率

house edge
player 0.012350813
banker 0.010579058
banker (no commission) 0.014581045

偏りがないときの控除率は上記のようになります。控除率とは胴元の取り分のことです。ハウスエッジともいいます。1000 円賭けたら 11~15 円程度がカジノに持っていかれます。これを大きいと見るかどうかは価値観の問題ですが、バカラはハウスエッジが 1 程度に対してリターンが -100 か 100 と分散が大きく、ハウスエッジが気にならないぐらいお金が激しく上下します。このギャンブル性の高さが魅力なのでしょう。

no commission と書いているのは、Banker (以下 B とする) が勝ったときの手数料がないノーコミッションルールの場合です。普通のルールの場合は B に賭けるほうがやや有利ですが、ノーコミッションルールの場合は Player (以下 P とする) に賭けるほうが有利です。ノーコミッションルールのほうが遊ぶ側の手間は少ないのですが、控除率が低いので遊ぶなら普通ルールのほうが良いでしょうか。しかし、ルールが複雑な分、抜け道の必勝法があるかもしれませんので、ノーコミッションルールも含めて調べていきましょう。

1つの種類のカードの偏り

まずは1種類のカードの偏りが控除率に与える影響を調べます。今後カウンティングに使うことを目指しますので、デッキから特定のカードを抜いて、ゲームを継続したときの控除率の期待値がどうなるのかを調べます。

8 デッキの 416 枚のカードから X 点のカードを N 枚抜いたときの控除率を考えます。N が大きくなるにつれて、どのように控除率が増減するのかを調べます。0 から 9 まで 10 種類ありますが、一気に結果を貼ってしまいましょう。

0 カード (10, J, Q, K)

0 点のカード 10, J, Q, K は合計 8 x 4 x 4 = 128 枚あります。これらカードを抜いていくと P の控除率が上がり、B の控除率が下がります。一定の偏りがあったとき、ノーコミッションルールでの P と B のハウスエッジが逆転します。これはデッキの状態によっては最適な賭け方が変わることを示唆しています。

0 のカードがとても少ないと、極値を取った後に増加減少の向きが変わります。しかし、そこまで偏ることは珍しいので実用上は単調増加、単調減少として差し支えないでしょう。

1, 2, 3, 4 カード

1 から4のカードは概ね同じなのでまとめて紹介します。0 と同様に、1 から 4 のカードも引かれて、デッキから少なくなると B の控除率が下がり、P の控除率が上がります。

点数が多いカードのほうが、偏りの影響が大きくなるようです。値が大きい方が場に与える影響も大きいからと説明できるでしょう。

5 カード

0 - 4と比べて 5 は傾向が大きく変わります。5 のカード以降はこれまでとは逆に多くひくと、P に有利になっていきます。

6 カード

6 のカードはノーコミッションルールの下では特殊です。6 のカードが少ないと、B が 6 で勝つ場合が減り、B の控除率が減ります。しかし、6 が少ないと P の勝率も増えてしまうよう、6 が少ないからと B に賭けても P に賭ける以上の効果は期待できません。

7, 8, 9 カード

7, 8, 9 のカードも 5 と同様にデッキ内の数が少ないとき P に有利になります。

コミッションありとなしの場合の B の控除率を比べると、7, 8, 9 のカードが少なくなるにつれて、控除率の差が開いていきます。7, 8, 9 のカードが少ないと、7, 8, 9 の有利な点数で勝つ場合が少なくなるからでしょう。ノーコミッションルールの B が、特別有利になる条件は見つけれませんでしたが、逆にもっと不利になってしまう条件はあるようです。

カードごとの控除率の変化

カードの種類ごとの比較がしやすいように、控除率の変化をカードごとに並べました。1枚カードを抜いたときの控除率の変化を示しています。

B か P かどちらかが勝つゲームなので、基本的に B に有利なカードと P に有利なカードは分かれていて、全体的に良くなったり悪くなったりするカードはありません。0~4 の小さいカードを引くと B に有利になり、5~9 の大きいカードを引くと P が有利になります。ただし、ノーコミッションルールのときは 6 だけは引くと全体的に控除率が下がります。

またカードごとに場に与える影響に差があります。値が大きいほうが場に与える影響は大きいが、大きすぎると合計値が10を超えてしまうので、逆効果にもなるようで、中央が膨らんでいます。

1枚のカードの偏りと控除率への影響を調べて、有利なカード、不利なカードが明らかになりました。では 0~4 のカード、5~9 のカードが全体的に偏っている場合に控除率はどうなるのかが気になります。次回はそれを調べてみます。バカラでカウンティングは有効の答えは次回に回します。

バカラの勝率の計算

カジノでバカラを遊んでみて、これはハマる人が多いのも頷けるという感想を持った。ほとんど単純な丁半博打なのですが、徐々に状態が確定していく演出があって、当たったときのフワッと気が舞い上がる感覚がクセになります。次にやったときにもっと楽しめるように、少しバカラの統計を調べて遊んでみようと思う。

今日は1回のゲームでの確率を調べてみます。モンテカルロ法でやるのも芸がないので、頑張って手計算で調べていきましょう。ただバカラのルールは複雑だから、紙と鉛筆だと間違ってしまうので、Python でコードを書きながらやっていきます。

勝率の計算

まずはプレーヤー、バンカーそれぞれの勝率を求めます。プレーヤーが勝つ場合を場合分けして、それぞれの確率を求めて、足し合わせるというトップダウンのアプローチでも計算できますが、今回はいろいろな条件下での確率を計算してみたいので、ボトムアップなアプローチでやってみたいと思います。結局はいずれでも同じことですが。

まず、プレーヤーとバンカーにカードが全て配られて手札が確定した状態のすべての場合のそれぞれの確率を計算してしまいましょう。紙と鉛筆だとかなりつらいですが、計算機上でやっているのでこういうことができます。

手札の状態というか場の状態を表すデータ構造 Hand を定義します。

Hand = namedtuple('Hand', ["player", "banker"])

player フィールドがプレーヤーのカード、banker フィールドがバンカーのカードで、それぞれに配られたカードのタプルが入ります。例えば プレーヤーが A, J, 5 でバンカーが 2, 3, 4 のカードを順に引いたらなら、Hand(player=(1,0,5), banker=(2,3,4)) となります。3枚目を引かない場合は タプルの3つ目は None です。10, J, Q, K を区別する必要はないのでこれらのカードはすべて0で表しましょう。

この Hand 型の値 をキーとして、Hand が示す手札の状態になる確率を p_hand連想配列に入れていきます。

p_hand = dict()

すべてのカードの場合を for ループを回してそれぞれの確率を計算してきます。

# すべての最初2枚の場合について
for p1, p2, b1, b2 in product(list(range(10)), repeat=4):

    # 最初2枚の点数
    p = (p1 + p2) % 10
    b = (b1 + b2) % 10

    # プレーヤー、バンカーが両方とも2枚のとき。
    if p >= 8 or b >= 8 or (6 <= p <= 7 and 6 <= b <= 7):
        hand = Hand(player=(p1,p2,None), banker=(b1,b2,None))
        p_hand[hand] = p_cards(p1,p2,b1,b2)
        continue

    # プレーヤーが3枚目を引く。
    if p <= 5:
        for p3 in range(10):
            # テーブルに従って、バンカーが3枚目を引くかを判断する。
            if HIT_TABLE[b][p3]:
                for b3 in range(10):
                    hand = Hand(player=(p1,p2,p3), banker=(b1,b2,b3))
                    p_hand[hand] = p_cards(p1,p2,p3,b1,b2,b3)
                continue

            # バンカーは3枚目を引かない。
            hand = Hand(player=(p1,p2,p3), banker=(b1,b2,None))
            p_hand[hand] = p_hand[hand] = p_cards(p1,p2,p3,b1,b2)
        continue

    # バンカーのみ3枚目を引く
    for b3 in range(10):
        hand = Hand(player=(p1,p2,None), banker=(b1,b2,b3))
        p_hand[hand] = p_cards(p1,p2,b1,b2,b3)

さて、p_cards は引数のカードを引く確率となります。for ループで場の状態を全て数え上げて、それぞれの状態になる確率を求めていっています。p_cards は以下で計算しました。

def p_cards(*cards):
    """引数に与えられたカードを引く確率"""
    # 8 デッキと仮定する。
    N = 8
    # 0 から 9 のカードの枚数。
    deck = [4 * 13 * N] + [13 * N for i in range(1, 10)]

    p = 1.0
    for c in cards:
        # ほしいカードがなくなった場合は確率0
        if deck[c] <= 0:
            return 0.0
        p *= deck[c] / sum(deck)
        deck[c] -= 1
    return p

8デッキとして、カードの山の中のほしいカードの割当をかけあわせています。カードが尽きた場合は減らさずに0を返します。

2ゲーム目以降はカードが減っているので、以前のゲームの結果を反映すると確率は変わってきます。いわゆるカウンティングは呼ばれる必勝法はこの原理を利用します。しかし、終わったゲームの知識を反映しない立場に立つなら、何回目のゲームでも欲しいカードを引く確率は同じです。そのため、deck を毎回同じ値で初期化しても結果は変わりません。

HIT_TABLE は以下です。ディーラはこれを暗記して間違えないようにしなければならないので、大変ですね。人がやる仕事ではないが、人がやるから面白い。娯楽とはそういうものです。

# 縦が Banker の2枚目までの点数、横がPlayer の3枚目のカード
HIT_TABLE = [
    [1,1,1,1,1,1,1,1,1,1],
    [1,1,1,1,1,1,1,1,1,1],
    [1,1,1,1,1,1,1,1,1,1],
    [1,1,1,1,1,1,1,1,0,1],
    [0,0,1,1,1,1,1,1,0,0],
    [0,0,0,0,1,1,1,1,0,0],
    [0,0,0,0,0,0,1,1,0,0],
    [0,0,0,0,0,0,0,0,0,0],
]

これで1ゲームが終わったときのすべての状態とその確率を挙げることができました。全部で 339400 通りの状態がありました。

プレーヤ、バンカーそれぞれの勝率、タイとなる確率は、状態群から欲しい場合をフィルターして確率を足し合わせれば計算できます。

def point(hand):
    return sum(card for card in hand if card) % 10

player_win = sum(p for h, p in p_hand.items() if point(h.player) > point(h.banker))
banker_win = sum(p for h, p in p_hand.items() if point(h.player) < point(h.banker))
tie = sum(p for h, p in p_hand.items() if point(h.player) == point(h.banker))

結果以下のような値を得ました。

Winner Probability
Player 0.446177979
Banker 0.458480647
Tie 0.095341374

それっぽい値を得ることができました。サイトによって値が違うのはデッキの数など置いている仮定が違ったりしているのだと思われます。

次に色々な場合の確率を求めていきましょう。

カードを引く枚数

プレーヤの毎数 バンカーの毎数 確率
2 2 0.378750705
3 2 0.185579254
2 3 0.117666050
3 3 0.318003992

4割弱のゲームは最初の2枚で終わります。またバンカーだけ引く場合は少ないようです。感覚にあってますね。

最初2枚が分かったあとの勝率

バカラの醍醐味は、3枚目があとから配られたり、カードを絞ってめくったりして、状態が徐々に確定していくところですよね。最初2枚が確定している状態で、その後の勝率を見ていきましょう。

バンカー、プレーヤーのいずれかの最初2枚が明らかな状態での、もう片方の勝率は以下のとおりとなります。ただし、引き分けの場合は除いています。

バンカーの最初2枚の点数 プレーヤーの勝率 プレーヤーの最初2枚の点数 バンカーの勝率
0 0.699536232 0 0.701654826
1 0.680185869 1 0.695725285
2 0.662796859 2 0.685655013
3 0.645613308 3 0.669359519
4 0.613826427 4 0.642328585
5 0.556476320 5 0.600026889
6 0.497409035 6 0.498438328
7 0.332291914 7 0.332291914
8 0.104645082 8 0.104645082
9 0.000000000 9 0.000000000

基本的に点数が9 に違いほど有利です。相手が9だとすでに負けが確定していて、 8でもほぼ負けです。しかし、相手が 0 であっても、油断はできないことがわかります。絞るときの念じ方が重要だと言えるでしょう。

3枚目以降の逆転の可能性

双方の最初の2枚が確定して、その後に逆転する可能性を調べてみます。

最初2枚のプレーヤーとバンカーの点数差とプレーヤーの勝率の関係は以下のとおりです。2枚目で勝敗が確定している場合と引き分けの場合は除外しています。

点数差 プレーヤーの勝率
-7 0.166794800
-6 0.222154613
-5 0.256331981
-4 0.286572484
-3 0.307923668
-2 0.331206776
-1 0.387202181
0 0.496529573
1 0.595966939
2 0.640109980
3 0.662224521
4 0.689246802
5 0.720494534
6 0.782535047
7 0.833205200

基本的に点数差が大きい方が有利なようです。しかし、3枚目以降のカードで逆転の可能性も残されています。適度に逆転の可能性があって、ゲームバランスがよいと感じました。ただカードが引かれるのを眺めているだけなのですが、このあたりのハラハラドキドキがバカラの魅力だと思います。

答えのない問題に挑む心構え

「答えのない問題」と呼ばれる種類の問題があります。この種類の問題に取り組む仕事は、過重労働のイメージが強い。その理由が問題の構造にあることに気づいたので記録しておく。

まず答えのない問題について説明します。答えのない問題とは、明確な解決策や結論が存在しなかったり、主観性の違いにより一意の答えが存在しない問題のことです。明らかな答えはなくて、人によって言うことが変わるような問題です。例えば広告のクリエイティブだったり、ビジネス上の戦略立案や意思決定に関する問題、投資判断の問題は、答えがない問題です。どんな広告を出したらいいかに唯一の正解はないし、企業の戦略も唯一の正解があるわけではありませんね。

『左利きのエレン』で描かれている広告会社のクリエイティブの人たちとか、コンサルタントの人たちの SNS とか眺めていると、彼らの仕事はハードワークの印象が強い。やりがいが強く夢中になれる仕事だから、ハードワークなのかとも思えるが、それだけではなさそうだ。取り組む問題の種類が、ハードワークを産んでいる気がする。

対比として答えのある問題を考えてみましょう。それは、予め答えが用意されている問題ではなくて、解けたときに解が正解かどうか分かる問題のことをいいます。例えば、PC の使い方が分からなかったから、Google で調べて解決したなどはよくあると思います。簡単な問題ですが、確かに明らかに問題を解決している。そして問題が解決されたので、仕事を終えて家に帰ることができる。もっと難しい例として、作っているプログラムが何かしらの理由で動かないのを挙げましょうか。問題を切り分けていって、原因を突き止めます。そして、解決すれば、家に帰れます。

こういう問題を解決するのは私は好きです。解けたときに快感は代え難いものがあります。もちろん全く手がかりが見つからずに、それで帰れないとなるとしんどいですが、ほとんどの場合解があることはわかってるので、気が楽です。また、専門でずっとやっていることであれば、出会う問題は過去に見たことのある問題の類型が多いので、解決の目処がたたずに途方にくれるなんてことも少ないです。専門家が一定の時間かけて解決できない問題なら、もうそれは解けない問題だと見切ってしまうこともできます。

数学の未解決問題のような解ける見込みがあるかも分からない問題に挑むのは精神的にすり減りますが、普通の仕事だとそんなものには取り組む機会もないので、置いておきましょう。デバッグが下手なジュニアがバグが解決せずに苦しそうにしているのは、その問題が解ける見込みがあると見えてないからで、そのしんどさは未解決問題に挑むのと似ているかもしれないが、それも今回は置いておきましょう。

一方で、答えのない問題は、そもそも問題が解決されたという状態がない。どっちがエラいかとかはないが、異なるルールで評価される問題なのは間違いないです。戦略立案を例にすれば、未来のことなんて分かず、何が正解かなんて知りようがありませんので、それが正しいかどうかって誰も言えないです。明確な答えがないので、どんなものでも答えになりえます。どんな愚策であっても戦略は戦略です。無数にある案を、多角的に検討して最良の案を選ぶのが求められていることです。

正解かどうかが明らかに分からないため、問題への解答の妥当性は解答にいたるプロセスで判断されます。正解かどうかはその時点では誰にも分からないのだから、正しさについては誰も責任をとれない。こんだけやったんだから結果が誤っていても仕方ないよねっていう努力の跡が要求される。それは積み上げられたファクトだったりするかもしれないし、長年鍛えた思考技術かもしれない。

論理的には考えるのでしょうけど、十分に考え抜いたのかどうかは論理ではない。そのため、答えのない問題は、精神論と親和性が高い。答えのない問題は、明確に解けたという状態がないから、どこまでもコネコネやろうと思えばできる。時間と体力が許す限りコネコネと練った案が求められる。高い手数料を取っているアクティブファンドの運用成績が、サルに運用させるよりも劣っていたとしても、誰もサルには運用を任せない。マッキンゼーが予測する未来が合っているなんてだれも思ってないですが、私が予測する未来なんて誰も聞いてくれない。優秀な人が苦しそうに死にそうにやっているのが求められる。

専門家が知識と経験をもとにサクサクっと問題を解決するやり方とは、だいぶ違う。問題が解けたから帰りますってわけにはいかない。答えのない問題は全く異なるルールで行われているゲームです。割に合わないのでようやらないと思いますが、取り組む必要が出てきたら、問題を解くのとはちょっと違うものだと理解して挑むのが実用上は重要ですかね。知らずにやると疲弊すると思います。