解き味分析3

難易度の分類を変更し、バグで分類できてなかったものを整理し、定石を追加し、新アルゴリズムを追加した。BoundaryLUTの確定範囲の拡張も実装してみた。

結果、グラフの形は随分と変わってしまったが、職人さんの高難易度パズルの分析結果はこのようになった。いつもお世話になっております。

難易度4以下は通常の定石ルールで、分類は適当なのでスルー。5は早期閉路防止の簡単なやつとジョルダン曲線定理。6は仮想パス、7はブリッジ制約、8はLUT、9はGF2、10が先読みとなっている。この分類自体も適当ではある。

LUTはまとめて確定するのではなく、分析時は1手確定するごとに終了するように変更した。これにより必要なときにLUTで少しだけ確定して、その結果を使って定石ルールが次々に確定していく様子が見えるようになった。なのでGF2と難易度入れ替え。

このグラフより、LUTが必要な場所は実はほんの少しだったこと、定石ルールとアルゴリズムを足したらGF2と先読みが減ったこと、仮想パスで確定している場所が実際にあって効果が得られていること、早期閉路防止でxを打つことが意外に多いこと、そして、GF2と先読みが終盤に集中していることなどがわかる。

分析アルゴリズムは人間の手筋を再現することを目標にしていて、難易度の低い手から順番に使い、確定できなければ難易度を上げていって、1手確定できたらまた難易度の低い手から使う、ということをしている。従って難易度の高い手筋を使った場所は、それより低い難易度で確定できなかったことを意味している。

 

分析時には手筋をcsvで出力して、それを読み込んでグラフにしているのだが、このcsvの任意の位置の状態をPencilBox形式で出力して、Webアプリで読み込んで再現することができるようにしている。PencilBox形式は問題自体だけではなく、エッジの状態も保存できる形式である。

ルール適用ボタンもソルバーが確定させているが、アレとは適用順が違うし、一致していたとしても1000ステップ目が見たいからといって1000回ボタンを押すのもつらい。

で、先読みが適用されたあたりの状況を再現して眺めたりしていたのだが、職人が作るパズルは巧妙で確かに今のアルゴリズムでは逆立ちしても確定できないパターンになっていた。俺が見ると解き方はわかるのだが、それを処理する適切なアルゴリズムが思いつかない。

職人パズルが先読み無しで解けないということは、現状の問題生成ロジックでは職人パズルレベルの問題が生成できないということを意味する。

ふむ。まだまだ遊べそうだなこれ。

問題を自動生成するということ

ソルバーが問題を解くログを眺めていて、いくつか定石が漏れていることを発見したのでそれらの追加と、新アルゴリズムとして仮想パスを追加した。

仮想パスというのは小ループを防止する機能である。

「ここに線を入れたら小ループができるからxを入れよう」という処理は既にあって、でも、「ここに線を入れたらこっちも確定してループになるからここにはxを入れよう」ということができない。これを実現する。はずだ。

小ループ判定はUnion-Find(DSU)というアルゴリズムでやっているが、これを直近の将来繋がることが確定しているものについても仮想的に繋げて判定するという仕組みになる。

これらの追加で職人高難易度パズルを解くのにGF2と先読みの回数が減ったので、ソルバーは確実に賢くなり、同時に生成する問題の難易度も上がったはずだ。

問題生成時にはバックトラッキングによる先読みをしないのでソルバーの自力が問題の難易度に直結する。遅いというのもあるがなんか理不尽な感じがするのでできるだけ問題生成にはバックトラッキングは使いたくないと思っている。

さて、問題を生成する方法について、ここには書いていなかった気がするので、今回はその話。

問題を生成するときには以下のような処理をする。

1. ループを作って盤面を数字で埋める

2. 妥当性を計測してダメならやりなおし

3. ヒントを1つ削ってそれでも解けるか、複数回答が無いかを確認する

4. 解けなくなるまで3を繰り返す

まあ、シンプルである。

1のところが割と厄介で、何も考えないと隅っこの方に小さなループが1個あるだけの問題だとか、外周をぐるっと回るだけのループだとかができてしまう。ひたすらギザギザしてるだけとか、まっすぐと折り返しだけとか、そういうのもできかねない。

なので、それなりにそれっぽいループが作られるようにアルゴリズムを独自に工夫している。つまらないループがでていたら申し訳ない。

2はそれでも完璧ではないので検証して、例えば外周を這う割合だとか、直線率だとか、そういったものを判定してダメそうなら却下する。また、このパズルは面白いことにすべてのマスが数字で埋まっていても解けないことがあり得る。なのでそれもここで検証する。

この1と2の部分は問題生成に関して重要なはずだがあまり話題にならない気がする。でも今のアルゴリズムだと例えば40x20なら問題無さげだが100x50とかのサイズになるとアホほどギザギザが並ぶので、そういう問題が作れるようになったらこの辺を再検討することになる。

初期盤面を探索中とか出ているタイミングがこの辺の処理をしている所だ。

で、3と4。これが世の中の問題生成や自動回答の話のメインになる部分で、ヒントを削って解けるかどうかを確認する。解けるかどうかを確認するには実際に解いてみるしかないので、ここでどのレベルの問題が解けるかが問題の難易度に直結することになる。

複数回答が無いことの確認もここでやるのでどうしても時間がかかる。

無制限ランダムのバックトラッキングを使うと凄まじい仮定法の嵐が必要な問題ができたりするわけで、うまいことちょうどいい問題を生成するためにはそれなりに人間が扱う定石を使うなどといった努力が必要になる。

俺自身はそこそこやれるほうなので、理不尽ではない範囲でひたすら難易度を上げるように努力してきたわけだが、最近になって解けないレベルになってきた感じだ。これ以上難易度を上げても解けないだけなので、解き味を調整するみたいなことはできないか、などと考えているのが今なわけだ。

まあ、そんな感じ。

解き味分析2

いろいろとバグっていたので修正。20x20Masterは解けるようになった。

このグラフの縦軸になっている難易度は使用した手筋=アルゴリズムで振り分けていて、以下のようになっている。

 

 

なお、このうち難易度9の3x3LUTは1回の適用で見つけた物を全部確定するので、幅が長くなるのと、2度目の実行をしない(する必要がない)から、LUT適用以後は難易度が下がったように見えるかもしれない。このあたりは分析としては課題か。

問題生成時にはバックトラックをしないので難易度10は出ない。分析時には制限付きで使用するようにしてある。対人間問題生成用ロジックなので無制限のバックトラックではなく、若干手加減気味の1手先読みロジックである。

各種アルゴリズムを駆使しないと解けないようになっている、というのがグラフからもわかる。

前回もお世話になった職人さんの高難易度パズルはこうなった。

LUTを適用したうえで1手先読みを駆使してなんとか解ききった状態である。グラフから各種アルゴリズムがそれぞれ使われていることがわかる。前半は簡単で、後半がとても難しくなっていることも読み取れる。

他に解けた難易度fもこんな感じだったので、そういう思想なのかもしれない。

Geminiさんに聞くと職人のパズルはどこかで引っかかって、そこを超えると一気に全部解ける、みたいなことを言うので、それとは違うなと。まあ人によるだろう。

ちなみに自分で解くと一部の手動バックトラック以外は大きく悩むことも無かったのでソルバーのアルゴリズムに実装できていない手筋がありそうだ。

自動生成問題のグラフと比べると、あっちは早い段階からGF2が必要な手筋が登場しているので、前半が簡単ということが無い。ジョルダン曲線とかブリッジの手筋は埋まってこないと使えないし、前半の難しい手筋はどうしてもバックトラックに近いものになってくることが想像できる。

たぶんそのあたりが解いてて楽しいパズルみたいな部分に影響してくるのではないか、などと考えてみている。問題生成時に解き味を分析してヒントを追加する、みたいなアルゴリズムもあったほうがよかったりするのかもしれない。

 

解き味分析

ソルバーがパズルを解くステップ数と、それぞれの手筋の難易度を保存して、その遷移をグラフにでもしてみたら、何かがわかるだろうか。それはパズルを作成した職人が仕掛けた起承転結山あり谷ありの大スペクタクルロマンを表現していたりするだろうか。

なんてことを思ったので、ログを保存してcsv化するようにGeminiさんにお願いして、試してみた。

いくつか問題があって、3x3LUTを最初に動かしてしまうと難易度の高い手筋も確定されてしまうので、ソルバーが「めっちゃ簡単やったわ」って報告してくることとか、ソルバーが使用する手筋が難易度順になっていないこととか、まあ、色々あったのだが、そういった事も解決した。

で、csvを出力してグラフにしたものが、例えばEasyの問題を生成して解かせたやつがこれ。

難易度はとても適当に分類してあるが、見るからに簡単な手筋しか使われていないことがわかる。

20x20Mediumがこれ。

難しい手筋が最後に現れた。クライマックスというやつだな。

20x20Masterがこれ。

GF2で解かないといけない場所がちょこちょこ出てくる。そういえば20x20ってGeminiさんにお願いすると作ってくるけど選択肢には無いな。

ともあれMasterの問題は作るときにはLUTを使うが分析用に解くときには使わないので途中で諦めている。3x3LUTで確定されるエッジはGF2で確定できないものがあるという意味だ。実はうちのソルバー内での最優秀アルゴリズムはGF2じゃなくて3x3LUTだったのかもしれない。

で、パズル職人の人のHP(スリザーリンク問題集)から問題を少々借りて、同様に試してみた。どの問題だったのかは忘れてしまったが、難易度fというやつの一つがこれ。

LUTが無いと途中で諦めてしまうが(あると解ける)、グラフ的には簡単な手筋が続いて所々難しい手筋が混ざるといった感じで、似たようなもんと言えなくもない?

手筋の難易度の起伏、という意味では自動生成したものも悪くないように見えるし、難易度的にも職人の高難易度パズルに近い雰囲気に見える。

なかなか面白い結果だ。でも自動生成のパズルが職人に匹敵するとは思えないから、何か大きな違いがあるんじゃないかと思っている。数字の配置とかは諦めているが、それだけじゃない何か。

そういうのが掴めるといいなーと思ってはいるのだが、分析に関しては素人なので、なかなかうまいこと思いつかないのだな。またなんか思いついたら試してみたいと思う。

リンクはっとこ。時々貼っておかないと記事を見た人が遊べないからな。

https://mirichi.github.io/loop_course_puzzle/

40x20Master再挑戦

久しぶりに40x20Masterをやってみたところ、難しすぎて全然歯が立たなくなっていた。

前にやってたときはそんなでもなかったはずだが、やってない間に何を変えたかなと考えてみたところ、おそらくだけど3x3LUTとGF2が、点対称を崩してのヒント削りと化学反応した結果じゃないかと。

もともとソルバーは俺が見てわかるパターンだけをコード化していて、それだと当然どれだけヒントを削っても盤面を大きくしても、俺なら見てわかる。当たり前である。

それをどうにかするために1手先読みとかを実装していたが、今はそれは無くて、代わりに3x3LUTとGF2が実装されている。

3x3LUTは数字だけから確定できるエッジを算出するが、これが適用されるパターンはとても多く、これを定石と呼ぶのであれば、俺も知らない定石が山のように発生する。そもそもループコースパズルは知らない定石に出くわしたときに悩みながら解法を探すパズルなわけだが、3x3LUTが出してくるパターンは極めてややこしい。1つ2つならよくても40x20だとこれがたくさん発生する。

GF2のほうは連立方程式なので具体的に何がどう確定しているのかはよくわからないが、1bitの計算であり偶数奇数しか判定できないから、そもそもとして精度は高くない。感覚としては仮定法で数手先を読むぐらいで、手数じゃなくて距離的な範囲での矛盾を読んで確定しているように見える。40x20だとこれがたくさん発生する。

そして、点対称を崩してヒントを極限まで削ると、難しい手法でギリギリ解けるような場所がたくさんある状態になる。3x3LUTが出すパターンを全て見つけたうえで、GF2による先読みが必要という話で、なるほどこれは難しいわけだ。

つまるところ、難易度という点では十分なレベルに達したということであり、俺がこれをサクサク解けるようになったらまた考えるが、しばらくは別方向のことを検討するのがよさげか。楽しさとかな。

楽しさとは(哲学

ループコースパズルはルールがシンプルなだけに、簡単にサクサク解けると作業感が出て、難しくて手に負えないと楽しくない。なんでもそうなんだろうけど。

解けるかどうかギリギリのラインを攻めているような難易度は手ごたえがあるだろうが、そのラインは人によるし、同じ人でも成長する。

パズル職人などはその人なりの哲学なり矜持なりがあって、解いてて楽しいパズルにこだわりがあるのだろうけど、自動生成する問題でどのようにこだわればいいのか。

3x3LUTとかGF2とかで地味に知っている定石では解けない問題が出るようにはなっているが、ここ最近これらの手筋は雰囲気が読み取れるようになり、定石として知識は無いけどこの辺りは配置で制約がかかっていそうだな、みたいな読みが当たるようになってきた。

すると15x15Masterでも詰まることが少なくなってきて、どうしたものかと考えているところ。

今日は解いてて楽しい問題の実験として、例えば実装されている各種アルゴリズムのすべてを駆使しないと解けないような問題を選別する、ということをやってみた。具体的には以下の大域定理である。GF2は普通に使われるだろうから条件には入ってるけどわりとどうでもいい。

1. Jordan Curve(内外判定)
2. ループの早期閉路禁止
3. 全体連結性(Bridge)

実際どうなるかはぜんぜんわからんかったから、終わらんかったらどうしようと思ったが、15x15Masterではだいたい1回か2回で生成できていたので、普通にいろいろな手法を使った問題が出ていたようだ。

つまりほぼ何も変わらない。

次はどうするかな。

 

難易度強化

いまは手軽さを重視して15x15のMasterをメインにしているのだが、もうちょい難易度が欲しいかな、などと思っていて、サクッと難しくする方法の一つに、複数回生成して一番ヒントが少ないものを選ぶ、というのがある。

まあ、ヒントが少なければ難しいのかと言うと厳密にイコールではないだろうが、そこはそれ、相関性があるのであればそれでよい。現状ヒント数以外の要素で難しくする手法が確立できていない。

複数回生成すると単純にn倍遅くなるので、まずは生成の高速化からか、いや、計測からだな、ということで、計測ロジックをGeminiさんに突っ込んでもらって計測。

[AC-3 Internal Time Breakdown]
  - 角制約: 2や3の角の処理                 : 8.92 ms
  - セル制約: 数字と線の数が合わない             : 22.94 ms
  - 角制約: 3の角から伸びる線                : 3.76 ms
  - 交点制約: 線は交差・分岐しない              : 26.62 ms
  - 交点制約: 角の処理(1の斜め等)             : 23.31 ms
  - 全体制約: 連立方程式(GF2)の更新           : 533.68 ms  <==== 圧倒的!
  - 全体制約: 内外判定(Jordan Curve)      : 4.06 ms
  - 全体制約: 高度な2の制約                 : 3.17 ms
  - 全体制約: 2の角のペア制約                : 1.43 ms
  - 全体制約: ループの早期閉路禁止              : 0.60 ms
  - 全体制約: 全体連結性(Bridge)           : 2.50 ms
  - (Internal queue handling overhead)    : 2.24 ms

どうやらGF2が圧倒的!に遅いらしい。連立方程式を解いてるからな(よくわかってない)。

GF2のコードを眺めてみると1手ずつUpdateGlobalGF2関数を呼んでいて、これがとても遅いのだろうと想像できたので、GF2実行時に溜まっているキューの中身をまとめて適用するようにしてもらった。

[AC-3 Internal Time Breakdown]
  - 角制約: 2や3の角の処理                 : 6.74 ms
  - セル制約: 数字と線の数が合わない             : 15.13 ms
  - 角制約: 3の角から伸びる線                : 3.88 ms
  - 交点制約: 線は交差・分岐しない              : 16.53 ms
  - 交点制約: 角の処理(1の斜め等)             : 18.33 ms
  - 全体制約: 連立方程式(GF2)の更新           : 308.63 ms
  - 全体制約: 内外判定(Jordan Curve)      : 3.55 ms
  - 全体制約: 高度な2の制約                 : 2.44 ms
  - 全体制約: 2の角のペア制約                : 0.94 ms
  - 全体制約: ループの早期閉路禁止              : 0.64 ms
  - 全体制約: 全体連結性(Bridge)           : 2.51 ms
  - (Internal queue handling overhead)    : 2.14 ms

随分速くなった。これが単純にバッチ実行の効果なのだが、AC-3の基本ループの中に呼び出しが混ざっていたので、ループから出して、一番最後に置いて、他の速いやつらで確定できなくなってから溜まっているキューをまとめて処理するように変えてもらった。

[AC-3 Internal Time Breakdown]
  - 角制約: 2や3の角の処理                 : 9.91 ms
  - セル制約: 数字と線の数が合わない             : 22.32 ms
  - 角制約: 3の角から伸びる線                : 4.65 ms
  - 交点制約: 線は交差・分岐しない              : 29.41 ms
  - 交点制約: 角の処理(1の斜め等)             : 26.13 ms
  - 全体制約: 連立方程式(GF2)の更新           : 218.77 ms
  - 全体制約: 内外判定(Jordan Curve)      : 9.12 ms
  - 全体制約: 高度な2の制約                 : 6.82 ms
  - 全体制約: 2の角のペア制約                : 3.06 ms
  - 全体制約: ループの早期閉路禁止              : 1.79 ms
  - 全体制約: 全体連結性(Bridge)           : 6.35 ms
  - (Internal queue handling overhead)    : 2.12 ms

GF2はかなり速くなった。他は全体的に遅くなっているが、それは他を優先して実行した結果で、増えた分と比べるとGF2が減った分のほうがはるかに多いから問題は無い。むしろ、GF2以外でどれだけ確定させることができるか、みたいな話になってくる。まだ伸びしろがあると言える。今回はここまでだが。

これで、現在メインの15x15Masterを含む、225マス以下のHard/Masterという条件で、5回生成して一番ヒントが少ない問題を採用するようにした。言うほど効果は無いかもしれないが。