ニコリ148号が書店に並んでいくつかの夜と朝を迎えましたね。(叙情的に迫ってみる)
まいなすよん作はふくめん算とカックロの2問。オモパ総没かうむー。軽くコメント。
カックロ7
入り口がちょっと細いかなと思いつつ、作者の思惑に乗っていただければ、展開と収束はなかなかいいテンポで楽しんでいただけるんではないかなーと。(曖昧にぼかしてみる)
ふくめん算2
漢字を多用しつつ、そこここで漢字の読みを変える技巧が冴えているのではないかなと。
ということで軽いコメントでしたまる
このところ、金曜の2130からニコ生で配信されている「ドキドキうまガール」をよく見ています。
http://dokidoki-umagirl.jp/
某ニコリさんと仲良くさせてもらっていながら競馬にはまったく詳しくない私なのですが、いやあ、画面が華やかでとてもよろしいと思います。
ということで唐突にスケルトンパズルです。
テキスト版
盤面] ■___■____ _■_■__■_■ _____■■__ __■_■__■_ _■___■___ ■■_■___■_ ____■■___ __■_■__■_ _■____■__ [リスト] [2文字] あん かお 一同 引手 画餅 呼子 賛同 賛美 進み 進物 中手 田子 毎日 [3文字] つくし 衣料品 岡惚れ 顔写真 似顔絵 深呼吸 人物画 人類愛 奈良物 不確か 餅ばな [4文字] 一心不乱 我田引水 商品作物 ???? [5文字] ????? ????? [7文字] おぺれーしよん
ニコリ147号オモパコーナーの感想です。
今回は新作2作におさらい5作。夏の暑さに負けずに、いってみましょう。
かくれミノ
形優先の考え方は楽しいですね。別解なしに仕上げるには、それなりにきつい制約の構造にしなければならないかなーと思っていましたが、割と調整が利くルールになっています。裏返しOKはちょっと作成段階で見逃しが発生しやすくなるかもしれません。解きやすくなるのは現行なのでこっちがいいと私は思います。
流れるループ
向き付けループに対して、方向に意味があるというのは意外に新しい感覚でいいですね。向きの逆回り方向について考慮する必要があると、ちょっと難易度が上がり気味でしょうか。ループを狭い範囲で処理しようとしてもできないこともあるので、盤面は大きいほうが楽しそうです。
黒どっち
ひとつの記号の影響範囲が大きくなりがちで、その組み合わせで盤面を埋めていきますので、技巧の冴えが必要になりますね。掲載されている問題はさすがクリアしていると思います。問題ごとの特色を出すのがちょっと難しいかも知れないですね。
四足問題
気持ちよく決まる部分と、考えさせる部分を混在させることがしやすく、起伏を設計しやすいルールになっています。特に、難所をうまく他から切り離すことができるので、作者の意図する道に導きやすいのが利点ではなかろうかと。
ざくざくだんご
掲載が続いていますが、まだまだどんなパズルか共通認識ができてない(んじゃないでしょうか)なところが魅力ですね。普段、作成してない人が作りやすいってのはとてもいいことです。
縦横無陣
細かい議論をつなげていくことができて良いですね。この見た目だと、線を伸ばす方向にルールを構成したくなると思うのですが、そうしなかったことが功を奏していると思います。
ぬりめいず
もう作者さんたちがルールを手の内に入れた感がありますな。といって限界が見えているというわけでもなく期待大です。もうひとつふたつ新しい光景が見られそうですね。
ということで今回はこんなで。
ニコリ147号が発売になってます。
まいなすよん作は2問。ちょっと少なめ。まーここ数回が調子良すぎました。
以下軽くコメント。
ざくざくだんご4
以前に触れた、黒丸で壁を作るノリの問題です。私は丸が立て込んでいる感のある盤面が好みなのですが、探すのが面倒かなあという気もしなくはないです。
セレクトワーズ2
初めて作ったかな? よく覚えてないや。入り口をどう設定していいか迷いますね。
以上。
追記ですけど、前号に掲載いただいた問題に別解を出してしまいました。申し訳ない。
危なそうな雰囲気あったから修正入れたはずだったんだけどなあ……
フィルマットというパズルがあります。ありますが皆さんご存知です?
まいなすよんは永遠の若手ですので、当然ちゃ当然なのですが、私もニコリ本誌で現役の頃を知らないくらいです。とはいうもののニコリのページの紹介がありますので、ご興味の向きはそちらをどうぞ。
そして例題はこちら。小さいですが。
今回は、フィルマットをSugar制約ソルバーで解くことを考えたいと思います。
以前、ぼんさんやカーブデータを解いた時は、盤面に番号を振ってそれを変数として探す、という手法をとりましたが、今回はトラディッショナル(?)に盤面の各マスに変数を割り振ります。
以下のような感じ。左側が変数、右側が最終的に答えとなる数字です。
で、天下り的ですが、補助的な変数を導入します。左側が変数、右側が最終的に答えとなる数字なのは同じですが、数字5と6が設定されています。各数字の役割は以下のようになります。
数字3→面積3のブロックに含まれ、その両端になる。
数字4→面積4のブロックに含まれ、その両端になる。
数字5→面積3のブロックに含まれ、その両端にならない。
数字6→面積4のブロックに含まれ、その両端にならない。
本質的には要らないっちゃ要らないんですが、たぶん変数b_xx_yyを導入したほうが見やすいと思うのですよね。Sugar慣れした方には、もうこれで伝わるものは伝わったかと思うのですが、説明を続けます。
まずは変数の定義。
( int a_00_00 1 4 ) ; 中略 ( int a_03_03 1 4 ) ( int b_00_00 1 6 ) ; 中略 ( int b_03_03 1 6 )
次に、この2種類の変数の基本的な関係を記述します。x_00_00の位置の関係のみ列挙します。
( => ( = b_00_00 1 ) ( = a_00_00 1 ) ) ( => ( = b_00_00 2 ) ( = a_00_00 2 ) ) ( => ( = b_00_00 3 ) ( = a_00_00 3 ) ) ( => ( = b_00_00 4 ) ( = a_00_00 4 ) ) ( => ( = b_00_00 5 ) ( = a_00_00 3 ) ) ( => ( = b_00_00 6 ) ( = a_00_00 4 ) )
表出の記述。例題では4つだけなので全部書いてみます。
( = a_00_00 1 ) ( = a_00_01 3 ) ( = a_02_02 2 ) ( = a_00_03 3 )
各マスについて、そのマスに1から4の数字が入った場合について場合分けして、そのマスの上下左右のマスが満たすべき条件を記述します。さらに、3が入る場合と4が入る場合は、それぞれについて端かそうでないかで場合分けします。説明をすっ飛ばして感覚的に書くと、そのマスに1から6の数字が入った場合について場合分けします。
辺や隅の処理は面倒なので、以下では例としてx_01_01のマスについて記述します。辺や隅は条件をとりうる条件を絞るだけですので、適宜読み替えてください。
まずそのマスに1が入る場合。これは簡単です。隣接するマスには1が入らないということをSugarに教えます。
( => ( = b_01_01 1 ) ( and ( != a_01_00 1 ) ( != a_01_02 1 ) ( != a_00_01 1 ) ( != a_02_01 1 ) ) )
次にそのマスに2が入る場合。これも簡単。隣接するマスには2がただひとつだけ入るということをSugarに教えます。
( => ( = b_01_01 2 ) ( or ( and ( = a_01_00 2 ) ( != a_01_02 2 ) ( != a_00_01 2 ) ( != a_02_01 2 ) ) ( and ( = a_01_02 2 ) ( != a_01_00 2 ) ( != a_00_01 2 ) ( != a_02_01 2 ) ) ( and ( = a_00_01 2 ) ( != a_01_00 2 ) ( != a_01_02 2 ) ( != a_02_01 2 ) ) ( and ( = a_02_01 2 ) ( != a_01_00 2 ) ( != a_01_02 2 ) ( != a_00_01 2 ) ) ) )
次にそのマスに3が入る場合。隣接するマスには5がただひとつだけ入り、その5が入るマス以外には3も5も入りません。
( => ( = b_01_01 3 ) ( or ( and ( = b_01_00 5 ) ( != a_01_02 3 ) ( != a_00_01 3 ) ( != a_02_01 3 ) ) ( and ( = b_01_02 5 ) ( != a_01_00 3 ) ( != a_00_01 3 ) ( != a_02_01 3 ) ) ( and ( = b_00_01 5 ) ( != a_01_00 3 ) ( != a_01_02 3 ) ( != a_02_01 3 ) ) ( and ( = b_02_01 5 ) ( != a_01_00 3 ) ( != a_01_02 3 ) ( != a_00_01 3 ) ) ) )
変数a_xx_yyと変数b_xx_yyが混在して見にくいと思いますけどすみません。もっと説明うまい人がSugar使いになってくれるといいんですけど。
とはいうものの、泣いてばかりいたってなんとかはなんとかなので、説明を続けます。次はひとつ飛ばしてそのマスに5が入る場合。この時は、「上下にはいずれも3が入り、左右いずれにも3も5も入らない」か、「左右にはいずれも3が入り、上下いずれにも3も5も入らない」が制約となります。以下のとおり。
( => ( = b_01_01 5 ) ( or ( and ( = 3 b_01_00 ) ( = 3 b_01_02 ) ( != 3 a_00_01 ) ( != 3 a_02_01 ) ) ( and ( = 3 b_00_01 ) ( = 3 b_02_01 ) ( != 3 a_01_00 ) ( != 3 a_01_02 ) ) ) )
4と6の場合は3と5の場合と同様ですね。6に関しては片方が4、片方が6になるので、逆転する場合も記述しなくてはいけません。以下のとおり。
( => ( = b_01_01 4 ) ( or ( and ( = b_01_00 6 ) ( != a_01_02 4 ) ( != a_00_01 4 ) ( != a_02_01 4 ) ) ( and ( = b_01_02 6 ) ( != a_01_00 4 ) ( != a_00_01 4 ) ( != a_02_01 4 ) ) ( and ( = b_00_01 6 ) ( != a_01_00 4 ) ( != a_01_02 4 ) ( != a_02_01 4 ) ) ( and ( = b_02_01 6 ) ( != a_01_00 4 ) ( != a_01_02 4 ) ( != a_00_01 4 ) ) ) )
( => ( = b_01_01 6 ) ( or ( and ( = 4 b_01_00 ) ( = 6 b_01_02 ) ( != 4 a_00_01 ) ( != 4 a_02_01 ) ) ( and ( = 6 b_01_00 ) ( = 4 b_01_02 ) ( != 4 a_00_01 ) ( != 4 a_02_01 ) ) ( and ( = 4 b_00_01 ) ( = 6 b_02_01 ) ( != 4 a_01_00 ) ( != 4 a_01_02 ) ) ( and ( = 6 b_00_01 ) ( = 4 b_02_01 ) ( != 4 a_01_00 ) ( != 4 a_01_02 ) ) ) )
以上で、同じ面積が隣り合わない1xnのブロックを配置する、という制約が記述できました。ここでちょっとした注意として、四隅には5も6も入らないという制約が必要になります。ブロックの端が盤面外に出てしまうような状況を許すからです。こういうの忘れがち。
「境界線が十字にならない」は割と簡単で、2x2のマスを取り出した時、十字いずれかが境界線にならなければいいですね。左上4マスを取り出します。
( or ( = a_00_00 a_01_00 ) ( = a_00_01 a_01_01 ) ( = a_00_00 a_00_01 ) ( = a_01_00 a_01_01 ) )
これはルールをすべて記述できていないからで、「数字が二つ以上入るブロックはない」を記述すれば良いです。数字が二つ以上入るブロックが生じるためには、パズルがきちんと成立しているという仮定を置くなら、
・表出で、3にひとマス空いて3が入っている
・表出で、4にひとマス空いて4が入っている
・表出で、4にふたマス空いて4が入っている
という状況ですね。この状況に対し、「数字が二つ以上入るブロックはない」を実現するためには、その「?マス空いて」のところに別の数字が入ればよいということになります。
例題だと、「a_02_00のマスに3が入らない」ということを記述すればいいですね。
( != a_02_00 3)
以上で、全部の制約は記述できたはずなのですが、最後のところが、個人的には見通し悪いなーと思ってしまうわけです。
なんかうまい処理はないかなっと思っていますの。
ぼんさんというパズルがあります。
詳しくはニコリのぼんさんの紹介のページをどうぞ。
今回は、ぼんさんをsugar制約ソルバーを使用して解いてみたいと思います。
盤面に以下のように座標を設定します。
各○に対して番号を付けます。各○の移動後の座標を変数として、sugar先生にこれを探してもらうことにします。
手順を先に書いておくと、
1. 変数を定義する。
2. 縦横にまっすぐ移動することを制約に落とす。
3. ある○の移動後の座標に対し、点対称の位置にどれか別の移動後の○が存在する。
4. 各○の移動の軌道上に、他の○の移動前、移動後それぞれの位置が存在しない。
5. 任意の2つの○に対し、その移動の軌道が交差しない。
となります。
4と5を分割したのはその方が見通しがいいかなーと思ったからなのですが、効果的かどうかはちょっと自信がありません。
それではいつものように1.の変数定義です。サンプル問題を変換すると以下のようになります。
( int x00 0 63 ) ( int x01 0 63 ) ( int x02 0 63 ) ( int x03 0 63 ) ( int x04 0 63 ) ( int x05 0 63 ) ( int x06 0 63 ) ( int x07 0 63 ) ( alldifferent x00 x01 x02 x03 x04 x05 x06 x07 )
番号は座標の若い順に付けています。以下に示すスクリプト内には登場しませんが、「移動前の各○の座標」を示しておきます。
x00の移動前の座標 → y00=3
x01の移動前の座標 → y01=10
x02の移動前の座標 → y02=13
x03の移動前の座標 → y03=32
x04の移動前の座標 → y04=38
x05の移動前の座標 → y05=48
x06の移動前の座標 → y06=56
x07の移動前の座標 → y07=60
次に、2.縦横にまっすぐ進むということを制約に落とします。
( or ( = 3 ( % x00 8 ) ) ( = 0 ( / x00 8 ) ) ) ( or ( = 2 ( % x01 8 ) ) ( = 1 ( / x01 8 ) ) ) ( or ( = 5 ( % x02 8 ) ) ( = 1 ( / x02 8 ) ) ) ( or ( = 0 ( % x03 8 ) ) ( = 4 ( / x03 8 ) ) ) ( or ( = 6 ( % x04 8 ) ) ( = 4 ( / x04 8 ) ) ) ( or ( = 0 ( % x05 8 ) ) ( = 6 ( / x05 8 ) ) ) ( or ( = 0 ( % x06 8 ) ) ( = 7 ( / x06 8 ) ) ) ( or ( = 4 ( % x07 8 ) ) ( = 7 ( / x07 8 ) ) )
面倒なので全部書いてしまいましたが、移動距離が明示されている○に関しては移動場所は最大でも上下左右いずれかの4つですね。○00に関してこの制約を記述してみます。盤外に出てしまうような移動はこの時点で削っています。
( or ( = x00 6 ) ( = x00 27 ) ( = x00 0 ) )
移動距離が明示されている○に関して、すべて書き並べてください
さて次は、3.ある○の移動後の座標に対し、点対称の位置にどれか別の移動後の○が存在する、を記述します。座標の付け方からこれは簡単です。たとえば○00の場合は以下のようになります。
( or ( = x00 ( - 63 x01 ) ) ( = x01 ( - 63 x00 ) ) ( = x00 ( - 63 x02 ) ) ( = x02 ( - 63 x00 ) ) ( = x00 ( - 63 x03 ) ) ( = x03 ( - 63 x00 ) ) ( = x00 ( - 63 x04 ) ) ( = x04 ( - 63 x00 ) ) ( = x00 ( - 63 x05 ) ) ( = x05 ( - 63 x00 ) ) ( = x00 ( - 63 x06 ) ) ( = x06 ( - 63 x00 ) ) ( = x00 ( - 63 x07 ) ) ( = x07 ( - 63 x00 ) ) )
考慮している○の移動後の座標が、盤面サイズの半分より大きいか小さいかで場合分けされるのですが、面倒なので全部書き並べてます。
すべての○に対してこの記述を行います。まともにやると、たぶん半分は無駄な記述ですけどそのへんは気にしない方針で。
続いて4. 各○の移動の軌道上に、他の○の移動前、移動後それぞれの位置が存在しない、です。
ある○が、上下左右いずれに移動するかで場合分けします。
左右方向に移動する場合は、軌道上のマスの座標は区間で表現できます。下の図を参考にしてください。
35の位置にある○が左に移動して33の位置に来たとすると、この軌道上のマスは33以上35以下になりますね。スクリプトではこれを逆に表現して、「他の○の始点と終点は33より小さくなるか、35より大きくなる」として記述しています。
x00が右側に移動するとき、その軌道上に他の○の始点および終点が存在しない、を、以下のように書きました。
; move r ( => ( and ( < 3 x00 ) ( > 8 ( - x00 3 ) ) ) ( and ( or ( < x01 3 ) ( > x01 x00 ) ) ( or ( < 10 3 ) ( > 10 x00 ) ) ( or ( < x02 3 ) ( > x02 x00 ) ) ( or ( < 13 3 ) ( > 13 x00 ) ) ( or ( < x03 3 ) ( > x03 x00 ) ) ( or ( < 32 3 ) ( > 32 x00 ) ) ( or ( < x04 3 ) ( > x04 x00 ) ) ( or ( < 38 3 ) ( > 38 x00 ) ) ( or ( < x05 3 ) ( > x05 x00 ) ) ( or ( < 48 3 ) ( > 48 x00 ) ) ( or ( < x06 3 ) ( > x06 x00 ) ) ( or ( < 56 3 ) ( > 56 x00 ) ) ( or ( < x07 3 ) ( > x07 x00 ) ) ( or ( < 60 3 ) ( > 60 x00 ) ) ) )
他の○の始点に関しては固定されているので、あってもなくてもよろしい条件が残っていますが、ま特に気にしない方針で。(再び)
上下方向の移動は、区間から外れるという条件だけでは表現できませんが、区間から外れてしかも縦方向の軌道からも外れる、という制約を加えればOKです。下の図を参照してください。
35の位置から上に移動して11まで動くとします。単純に11と35を両端にして区間を作ると、深緑色を付けた区間になります。そこから、35を含むタテ列を除外するような条件をつければよろしい。
; move u ( => ( and ( > 3 x00 ) ( < 7 ( - 3 x00 ) ) ) ( and ( or ( > x01 3 ) ( < x01 x00 ) ( != ( % x01 8 ) ( % x00 8 ) ) ) ( or ( > 10 3 ) ( < 10 x00 ) ( != 2 ( % x00 8 ) ) ) ( or ( > x02 3 ) ( < x02 x00 ) ( != ( % x02 8 ) ( % x00 8 ) ) ) ( or ( > 13 3 ) ( < 13 x00 ) ( != 5 ( % x00 8 ) ) ) ( or ( > x03 3 ) ( < x03 x00 ) ( != ( % x03 8 ) ( % x00 8 ) ) ) ( or ( > 32 3 ) ( < 32 x00 ) ( != 0 ( % x00 8 ) ) ) ( or ( > x04 3 ) ( < x04 x00 ) ( != ( % x04 8 ) ( % x00 8 ) ) ) ( or ( > 38 3 ) ( < 38 x00 ) ( != 6 ( % x00 8 ) ) ) ( or ( > x05 3 ) ( < x05 x00 ) ( != ( % x05 8 ) ( % x00 8 ) ) ) ( or ( > 48 3 ) ( < 48 x00 ) ( != 0 ( % x00 8 ) ) ) ( or ( > x06 3 ) ( < x06 x00 ) ( != ( % x06 8 ) ( % x00 8 ) ) ) ( or ( > 56 3 ) ( < 56 x00 ) ( != 0 ( % x00 8 ) ) ) ( or ( > x07 3 ) ( < x07 x00 ) ( != ( % x07 8 ) ( % x00 8 ) ) ) ( or ( > 60 3 ) ( < 60 x00 ) ( != 4 ( % x00 8 ) ) ) ) )
さて最後に、5. 任意の2つの○に対し、その移動の軌道が交差しない。を記述します。始点と終点を除いた軌道が重なる、という条件を付けると、その2つの○の位置関係は、次の2パターンに限定されます。
前者を右下がりの関係、後者を左下がりの関係と呼びます。
右下がりの関係について考えます。図のようにAからDと名前を付けます。AとBに関しては、○とマスにともにAとBという名前がついていると思ってください。
右下がりの関係で軌道が交差するとしたら、「Aが右に移動し、かつ、Bが上に移動する」か、あるいは、「Bが左に移動し、Aが下に移動する」という状態しか起きません。前者に関してはAとBがともにCのマスを越えて移動した場合、後者に関してはAとBがともにDのマスを越えて移動した場合に交差が発生します。
前者と後者で場合分けします。都合により、「Aが右に移動する場合」と「Bが左に移動する場合」で場合分けを行います。
「Aが右に移動する」という仮定のもと、下図をご覧ください。
35がA、53がB、37がCに対応します。Aが右に移動する場合、AがCを越えないようにするためには、Aの終点の座標が37以下ならば良いです。これはわかりやすいですね。で、Aが右に移動するという条件なので、53が左か右か下に移動したなら、これらは自動的に交差しないことが確定します。左か右か下に移動するなら、いずれも37以上の座標になります。Bが上に移動したとき37を越えないようにするためには、Bの終点の座標が37以上ならばよいです。
まとめると、右下がりの関係でAが右に移動する場合には、Aの座標がC以下、あるいはBの座標がC以上であるという条件をつければ、交差することはなくなります。(以上以下をわりとラフに使ったので、端の議論がいい加減ですが、適切に汲んでください)
もとの問題図で右下がりの関係にあるx00とx02について、この制約を書き下してみます。以下のようになるでしょう。
( => ( and ( < 3 x00 ) ( > 8 ( - x00 3) ) ) ( or ( < x00 5 ) ( > x02 5 ) ) ) ( => ( and ( > 13 x02 ) ( > 8 ( - 13 x02 ) ) ) ( or ( < x00 11 ) ( > x02 11 ) ) )
左下がりの関係も同様です。x00とx01の記述だけ書きます。
( => ( and ( > 3 x00 ) ( > 8 ( - 3 x00 ) ) ) ( or ( > x00 2 ) ( > x01 2 ) ) ) ( => ( and ( < 10 x01 ) ( > 8 ( - x01 10) ) ) ( or ( < x00 11 ) ( > x01 11 ) ) )
以上で、sugar先生に頑張ってもらうお膳立ては完了したはず。サンプルは私の環境で数秒で解答が返ってきましたが、大きくしたらどうなるんでしょうねえ……
ということで今回はここまで。
ニコリ146号オモパコーナーの感想です。
今回はおさらい5作に新作2作。さくっとさらっと行ってみましょう。
ざくざくだんご
ぽやっとした進行が魅力だと思うのですが、構成要素の種類が少ないので、ここをこうしたい、という作者の意図が見えずらいかもしれないですね。まー受け取られようはコントロール次第でしょう。個人的には、黒丸で壁を作る方向を試しているのですが、なかなか採用されません。
四足問題
調節が利きやすくていいですね。具体的には、先読みの回数を制御することが現実的なので、難易度がうまいこと振り分けられそうです。まあそこまで考えて作らなくともという感はありますが。
ペイントイコジ
大きなブロックを作りやすくなるので、大きい盤面の方が楽しそうです。探すのが面倒な可能性もありますが、サーチの範囲を少なくする工夫はできるはずなので、可能性をつぶす方向で作るってものでしょう多分。
四角スライダー
比較的四角が少ない盤面にしたほうが、動きが出ていいかなーと思っていましたが、2番の問題を見るとそうとばかりも言えなさそうですね。あと、やはり初期配置の四角と移動後の四角が紛らわしいことは困ったもんだと思います。原稿では、初期配置を丸印(さとがえりの形式)で送ったのですが、その表記は採用されませんでした。
ぬりめいず
このサイズでもまだ狭い感があるのは伸びしろがありそうで良いですな。盤面を大きくした時に、寄り道するかしないかの議論が見にくくならないかは気になるところですが、ある程度局所的な判定もできるでしょうおそらく。
黒どっち
黒マスの決まり方が独特で良いですね。矢印がないということも情報になるというか、なってしまうので、後から不用意に黒マスを決めようとすると意図した解き筋が潰れてしまったりしますな。新しいパズルなので、たとえそうだとしても面白く解けると予想しますが。
縦横無陣
処理が難しいルールをうまくまとめましたという印象です。直接要素を決めに行く方法が限られるので、仕上げるのにややテクニックが要るかも。その分、完成した問題は独特な味わいになっていると思います。
ということで以上。今回なんだか感想を書くのが難しかったなーとちょっと思わなくもない私でありましたとさ。