地雷を一掃できますか?


29

マインスイーパは、どのタイルが「地雷」であるかを、それらのタイルをクリックせずに発見する必要がある人気のパズルゲームです。代わりに、近くのタイルをクリックして、隣接する鉱山の数を表示します。このゲームの欠点の1つは、複数の有効な回答があり、推測するだけのシナリオになる可能性があることです。たとえば、次のボードを取り上げます。

1110
2*31
3*??
2*4?
112?

この形式では、数字は隣接する地雷の数を*表し、は既知の地雷を表し、「?」潜在的な鉱山を表します。この特定のパズルの不幸な点は、4つの明確で有効な潜在的なソリューションがあることです。

1110    1110    1110    1110    
2*31    2*31    2*31    2*31
3*4*    3*5*    3**2    3**1
2*42    2*4*    2*4*    2*42
112*    1121    1121    112*

これは、ボードが解決できないことを意味します。解けるボードの例を次に示します。

1121
1??*
12?*
0122

有効な解決策は1つしかないため、このボードは解決可能です。

1121
1*4*
12**
0122

あなたの仕事は、有効なマインスイーパボードを取得し、それが解決可能かどうかを判断するプログラムまたは関数を作成することです。「有効な掃海艇ボード」とは、入力が常に長方形で、少なくとも1つのソリューションがあり、無効な文字が含まれていないことを意味します。

入力には、文字の配列、文字列の配列、改行を含む文字列などがあります。出力は、解ける場合は真偽値、そうでない場合は偽の値でなければなりません。パフォーマンスについてはあまり心配していませんが、あなたのソリューションはあらゆるサイズの入力に対して理論的に機能する必要があります。

いつものように、標準の抜け穴が適用され、バイト単位の最短ソリューションが勝ちます!

例:

次の例はすべて解決可能です。

1121
1??*
12?*
0122

1110
1???
1110
0000

1110
3???
??20
*310

****
****
****
****

0000
0000
0000
0000

1100
*100
2321
??*2
13*2
1221
1*10
1110

1121
2*??
2*31
2220
1*10

次の例はすべて解決不可能です。

1110
2*31
3*??
2*4?
112?

01??11*211
12??2323*1
1*33*2*210
12?2122321
13?3101**1
1***101221

1***
3*52
2*31
12??
02??
01??

00000111
000012*1
00001*21
22101110
**100111
?31123*1
?311**31
**113*20

ボードが少なくとも1つのセルを持つ長方形であると想定できますか?また、入力が常に少なくとも1つの解決策を受け入れると仮定できますか?(たとえば、2?ソリューションがないため、マインスイーパの実際のゲームからは得られません。したがって、「マインスイーパボード」とは見なされません...はい?)
mathmandan

2
MineSweeperでは、ここに欠けている追加情報、つまり鉱山の数があることは何の価値もありません。
edc65 16

@mathmandanはい、入力は常に少なくとも1つのセルと少なくとも1つの有効なソリューションを持つ長方形になります。
DJMcMayhem

回答:


20

GNU Prolog、493バイト

z(_,[_,_]).
z(F,[A,B,C|T]):-call(F,A,B,C),z(F,[B,C|T]).
i([],[],[],[]).
i([H|A],[I|B],[J|C],[H-I-J|T]):-i(A,B,C,T).
c(A/_-B/_-C/_,D/_-_/T-E/_,F/_-G/_-H/_):-T#=A+B+C+D+E+F+G+H.
r(A,B,C):-i(A,B,C,L),z(c,L).
q(63,V):-var(V).
q(42,1/_).
q(X,0/Y):-Y#=X-48.
l([],[0/_]).
l([H|T],[E|U]):-q(H,E),l(T,U).
p([],[[0/_,0/_]],0).
p([],[[0/_|T]],N):-M#=N-1,p([],[T],M).
p([H|T],[[0/_|E]|U],N):-p(T,U,N),l(H,E).
m([H|A],B):-length(H,N),p([],[R],N),p([H|A],M,N),z(r,[R|M]),p(B,M,N).
s(A):-setof(B,m(A,B),[_]).

テストに役立つ可能性のある追加の述語(提出の一部ではない):

d([]).
d([H|T]):-format("~s~n",[H]),d(T).

Prologは、実用的な観点からこのタスクを解決するための間違いなく正しい言語です。このプログラムは、Minesweeperのルールを述べているだけで、GNU Prologの制約ソルバーにそこから問題を解決させます。

zおよびiユーティリティ関数です(z一種のフォールドのような操作を行いますが、2 iつではなく3つの隣接する要素のセットに対して行います。n要素の3つのリストをn 3タプルのリストに転置します)。我々は、内部セルを格納する場合、xは nonmineための鉱山を0 1であり、yは、隣接する鉱山の数です。ボード上のこの制約を表現します。ボードのすべての行に適用されます。そして、有効なボードかどうかを確認します。x/ycrcz(r,M)M

残念ながら、この作業を直接行うために必要な入力形式は不合理なので、パーサーを含める必要がありました(おそらく実際のルールエンジンよりも多くのコードを占め、デバッグに多くの時間を費やしました。Minesweeperルールエンジンはほとんど機能しました)初めてですが、パーサーは思考でいっぱいでした)。q文字コードと形式の間で単一のセルを変換します。ボードの1行を変換します(地雷ではないことがわかっているが、隣接する地雷の数が不明な1つのセルを境界線として各線の端に残します)。x/ylpボード全体を変換します(下の境界線を含みますが、上の境界線は除きます)。これらの機能はすべて、順方向または逆方向に実行できるため、ボードの解析と印刷の両方が可能です。(pボードの幅を指定するの3番目の引数には、うっとうしい動きがいくつかあります。これは、Prologにはマトリックスタイプがないため、ボードを長方形に制限しないと、プログラムはボードの周囲の境界を徐々に広げようとする無限ループ)

m主な掃海艇の解法関数です。入力文字列を解析し、正しい境界を持つボードを生成します(再帰的なケースを使用してpほとんどのボードを変換し、ベースケースを直接呼び出して、下のボーダーと同じ構造を持つ上のボーダーを生成します)。それから呼び出しますz(r,[R|M])マインスイーパルールエンジンを実行します。このエンジンは(この呼び出しパターンで)有効なボードのみを生成するジェネレーターになります。この時点で、取締役会はまだ制約のセットとして表されていますが、これは私たちにとっては厄介な可能性があります。おそらく、複数のボードを表すことができる制約の単一のセットを持つことができます。さらに、各正方形に最大1つの鉱山が含まれることをまだ指定していません。そのため、各正方形の「波形を明示的に折りたたむ」必要があります。具体的には(単一の)鉱山または非鉱山のいずれかである必要があります。これを行う最も簡単な方法はvar(V)q(63,V)ケースは、?ケースが逆走するのを防ぐように設計されているため、ボードを分解すると、強制的に完全に認識されます)。最後に、解析されたボードをm; mしたがって、部分的に未知のボードを取得し、それに一致するすべての既知のボードを生成するジェネレーターになります。

マインスイーパを解くにはこれで十分ですが、すべての解決策を見つけるのではなく、明確に1つの解決策があるかどうかを確認するように質問されます。そのためs、ジェネレータmをセットに単純に変換し、セットに要素が1つだけあることをアサートする追加の述語を書きました。これは、実際に正確に1つの解がある場合はstrue(yes)を返し、no2つ以上または1つより少ない場合はfalse()を返すことを意味します。

dソリューションの一部ではなく、バイトカウントに含まれていません。マトリックスのように文字列のリストを印刷するための関数です。これにより、生成されたボードを検査することができmます(デフォルトでは、GNU Prologは2つの同義語を扱うため、ASCIIコードのリストとして文字列を印刷します。この形式読みにくいです)。テスト中、またはm実用的な掃海艇ソルバーとして使用する場合に便利です(制約ソルバーを使用するため、非常に効率的です)。


11

Haskell、193 169 168バイト

c '?'="*!"
c x=[x]
g x|t<-x>>" ",w<-length(words x!!0)+1=1==sum[1|p<-mapM c$t++x++t,and[sum[1|m<-[-1..1],n<-[j-w,j,j+w],p!!(m+n)=='*']==read[d]|(j,d)<-zip[0..]p,d>'/']]

使用例:g "1121 1??* 12?* 0122"-> True

仕組み:可能性のあるすべてのボードのリストを作成します。リストは?、いずれかの*またはに置き換えられます!!後で無視します)。これはを介して行われmapM cますが、インデックスが範囲外にならないように、入力文字列にスペースを追加して追加する前に。そのようなボードごとに、すべての要素(index j)をループして有効なボードであるかどうかを確認d>'/'し、隣接(index nm)でも番号()である場合、カウント*して数と比較します。最後に、有効なボードのリストの長さを確認してください。


7

Mathematica、214 192 190 180 176 174 168 165バイト

0&/@Cases[b="*";If[!FreeQ[#,q="?"],(x#0@MapAt[x&,#,#&@@#~Position~q])/@{b,0},BlockMap[If[#[[2,2]]==b,b,Count[#,b,2]]&,#~ArrayPad~1,{3,3},1]]&@#,#/.q->_,All]=={0}&

U + F4A1(個人使用)が含まれています。この名前のない関数は、可能なすべての組み合わせを見つけ"?"(つまり、すべて"?"のを"*"またはで置き換え0)、有効なソリューションが1つだけかどうかを確認します。

説明

b="*";

に設定b"*"ます。

!FreeQ[#,q="?"]

q文字列に設定します"?""?"入力にあるかどうかを確認します。

If[ ..., (x#0 ... ,0}, BlockMap[ ... ]]

もしTrue...

(x#0@MapAt[x&,#,#&@@#~Position~q])/@{b,0}

q(= "?")の最初の出現をb(= "*")または0(つまり2つの出力)に置き換え、関数全体を再度適用します。


もしFalse...

#~ArrayPad~1

入力を1つのレイヤーで埋め込みます0

BlockMap[If[#[[2,2]]==b,b,Count[#,b,2]]&, ... ,{3,3},1]

入力をオフセット1の3 x 3行列に分割します。各分割に対して、中間値がb(= "*")の場合、出力がb(= "*")であり、中間値がb(= "*")でない場合、 outputは、入力のb(= "*")の数です。この手順は、すべての数値セルを再評価します。


Cases[ ... ,#/.q->_,All]

すべての結果から、入力に一致するものを見つけます

0&/@ ... =={0}

入力の長さが1かどうかを確認します。


7

Perl、215バイト

213バイトのコード+ -p0フラグ(2バイト)。

/.*/;$c="@+";$_=A x$c."
$_".A x$c;s/^|$/A/mg;sub t{my($_)=@_;if(/\?/){for$i(0..8,"*"){t(s/\?/$i/r)}}else{$r=1;for$i(/\d/g){$r&=!/(...)[^V]{$c}(.$i.)[^V]{$c}(...)(??{"$1$2$3"=~y%*%%!=$i?"":R})/}$e+=$r}}t$_;$_=$e==1

コードのアイデアは、あらゆる可能性をテストし、有効な完全に満たされたボードにつながる唯一のものがあるかどうかを確認することです。

より読みやすく、コードは次のようになります。

/.*/;$c="@+ "; #行のサイズを数えます。; $ c = "@ +" ; #行のサイズを数えます。           
$ _ = A x $ c。 "\ n $ _"。A x $ c; #「A」の行を最初と最後に追加します。= A x $ c "\ n $ _" A x $ c ; #「A」の行を最初と最後に追加します。  
s / ^ | $ / A / mg; #各行の最初と最後に「A」を追加します。/ ^ | $ / A / mg ; #各行の最初と最後に「A」を追加します。        

#実際に問題を解決する機能#実際に問題を解決する機能
sub t { sub t { 
    私の$ _ =ポップ; #パラメーターを取得し、$ _(regexのデフォルト引数)に保存します。私の$ _ = pop ; #パラメーターを取得し、$ _(regexのデフォルト引数)に保存します。 
    if(/ \?/){#別の未知の文字がある場合。if / \?/ { #別の不明な文字がある場合。    
        for $ i(0..8、 "*"){#あらゆる可能性を試す以下のために$ iが0 8 "*" { #は、すべての可能性を試してみてください  
            t(s / \?/ $ i / r)#最初の未知の文字が置換された再帰呼び出しs / \?/ $ i / r #最初の未知の文字が置き換えられた再帰呼び出し  
        }}
    } else {#未知の文字はもうないので、ここでボードが有効かどうかを確認します} else { #未知の文字はもうないので、ここでボードが有効かどうかを確認します 
        $ r = 1; #最後にr == 1の場合、ボードは有効です。それ以外の場合は無効です。= 1 ; #最後にr == 1の場合、ボードは有効です。それ以外の場合は無効です。  
        for $ i(/ \ d / g){#ボードに存在する各番号に対してfor $ i / \ d / g { #ボードの各番号に対応  
            #次の正規表現は、数値が次のいずれかに囲まれているかどうかを確認します #次の正規表現は、数値が次のいずれかに囲まれているかどうかを確認します 
            #地雷が多すぎる、または少なすぎる。#地雷が多すぎる、または少なすぎる。
            #(仕組み:魔法!)#(仕組み:魔法!)
         $ r&=!/(...)[^ V] {$ c}(。$ i。)[^ V] {$ c}(...)(?? {"$ 1 $ 2 $ 3" =〜y% * %%!= $ i? "":R})/ &=!/(...)[^V]{$c}(.$i.)[^V]{$c}(...)(??{"$1$2$3"=~y%*%%! = $ i? "":R})/ 
        }}
        $ e + = $ r#有効なボードの数を増やします。+ = $ r #有効なボードの数を増やします。
    }}
}}
t $ _; #前の関数を呼び出す; #前の関数を呼び出す 
$ _ = $ e == 1#有効なボードが1つだけかどうかを確認します(-pフラグのおかげで$ _は暗黙的に出力されます)。= $ e == 1 #有効なボードが1つだけかどうかを確認します(-pフラグにより​​、$ _が暗黙的に出力されます)。 

中央の正規表現について:

/(...)[^V]{$c}(.$i.)[^V]{$c}(...)(??{"$1$2$3"=~y%*%%!=$i?"":R})/

[^V]「\ nを含む任意の文字」の略であることに注意してください。
したがって、アイデアは次のとおりです。1行に3文字、次に3文字($i中央に)、次に3 文字。おかげ[^V]{$c}で、私たちが興味を持っている数字は真ん中にあります。
そして、それらの9文字のうち(爆弾)の"$1$2$3"=~y%*%%数をカウントし*ます:が異なる場合$i、一致する空の文字列を追加します(""=>インスタントマッチング、正規表現はtrueを返します)、そうでない場合、一致しようとすることで強制的に失敗しますR(文字列に含めることはできません)。
正規表現が一致する場合、ボードは有効ではないため$r0with に設定し$r&=!/.../ます。
そして、それが私たちがいくつかを追加する理由ですA各行のどこでも:したがって、ボードの端近くにある数字の端の場合を心配する必要はありません:それらはA隣人としてあり、それは地雷ではありません(もちろん、ほぼすべてのcharが機能しますが、を選択しましたA)。

次のようなコマンドラインからプログラムを実行できます。

perl -p0E '/.*/;$c="@+";$_=A x$c."\n$_".A x$c;s/^|$/A/mg;sub t{my($_)=@_;if(/\?/){for$i(0..8,"*"){t(s/\?/$i/r)}}else{$r=1;for$i(/\d/g){$r&=!/(...)[^V]{$c}(.$i.)[^V]{$c}(...)(??{"$1$2$3"=~y%*%%!=$i?"":R})/}$e+=$r}}t$_;$_=$e==1' <<< "1121
1??*
12?*
0122"

複雑さは最悪のことができませんでした:それはですO(m*9^n)どこnの数である?ボード上には、およびm(おそらくかなり悪いです途中で正規表現の複雑さをカウントせずに、)ボード上のセルの数です。私のマシンでは、最大4 ?で非常に高速に動作し、5で遅くなり始め、6で数分かかりますが、それ以上の数では試しませんでした。


3

JavaScript(ES6)、221 229

g=>(a=>{for(s=i=1;~i;g.replace(x,c=>a[j++],z=j=0).replace(/\d/g,(c,p,g)=>([o=g.search`
`,-o,++o,-o,++o,-o,1,-1].map(d=>c-=g[p+d]=='*'),z|=c)),s-=!z)for(i=a.length;a[--i]='*?'[+(c=a[i]<'?')],c;);})(g.match(x=/\?/g)||[])|!s

すべての入力が有効であることが予想される場合-それは、少なくとも1溶液である-そして、私は変更バイトを保存することができs==1s<2

少ないゴルフ

g=>{
  a = g.match(/\?/g) || []; // array of '?' in a
  s = 1; // counter of solutions
  for(i=0; ~i;) // loop to find all configurations of ? and *
  {
    // get next configuration
    for(i = a.length; a[--i] = '*?'[+( c = a[i] < '?')], c; );
    z = 0; // init at 0, must stay 0 if all cells count is ok
    g
    .replace(/\?/g,c=>a[j++],j=0) // put ? and * at right places
    .replace(/\d/g,(c,p,g)=>(
       // look for mines in all 8 directions
       // for each mine decrease c
       // if c ends at 0, then the count is ok
       [o=g.search`\n`,-o,++o,-o,++o,-o,1,-1].map(d=>c-=g[p+d]=='*'),
       z|=c // z stays at 0 if count is ok
    )) // check neighbour count
    s-=!z // if count ok for all cells, decrement number of solutions
  }
  return s==0 // true if exactly one solution found
}

テスト

F=
g=>(a=>{for(s=i=1;~i;g.replace(x,c=>a[j++],z=j=0).replace(/\d/g,(c,p,g)=>([o=g.search`
`,-o,++o,-o,++o,-o,1,-1].map(d=>c-=g[p+d]=='*'),z|=c)),s-=!z)for(i=a.length;a[--i]='*?'[+(c=a[i]<'?')],c;);})(g.match(x=/\?/g)||[])|!s

out=x=>O.textContent+=x+'\n'

Solvable=['1121\n1??*\n12?*\n0122'
,'1110\n1???\n1110\n0000'
,'1110\n3???\n??20\n*310'
,'****\n****\n****\n****'
,'0000\n0000\n0000\n0000'
,'1100\n*100\n2321\n??*2\n13*2\n1221\n1*10\n1110'
,'1121\n2*??\n2*31\n2220\n1*10']
Unsolvable=['1110\n2*31\n3*??\n2*4?\n112?'
,'01??11*211\n12??2323*1\n1*33*2*210\n12?2122321\n13?3101**1\n1***101221'
,'1***\n3*52\n2*31\n12??\n02??\n01??'
,'00000111\n000012*1\n00001*21\n22101110\n**100111\n?31123*1\n?311**31\n**113*20']
out('Solvable')
Solvable.forEach(t=>out(t+'\n'+F(t)+'\n'))
out('Unsolvable')
Unsolvable.forEach(t=>out(t+'\n'+F(t)+'\n'))
<pre id=O></pre>


Opは、そのバイトをゴルフできると言っています。
破壊可能なレモン

@DestructibleWatermelonのおかげで、私はすべての改訂版といくつかのより多くの実際のバイト保存
edc65

0

JavaScript(Node.js)、167バイト

s=>g=(r=c='',[p,...q]=s,w)=>w?0:p?(g(r+0,q,p=='*')+g(r+1,q,1/p),c==1):c-=-![...s].some((p,i)=>p>' '&&[-1,1,-q,-1-q,-2-q,q,q+1,q+2].map(j=>p-=~~r[i+j])|p,q=s.search`
`)

オンラインでお試しください!

opは「入力は常に長方形で、少なくとも1つのソリューションがあります」と言いますが、誤ったサンプル3は一致しないため、<2ソリューションではなく1ソリューションが必要です

s=>(        // p.s. Here "block" can also mean \n
  c=0,          // possible mine count
  g=(           // recursive
    r='',       // mine states
    [p,...q]=s, // known info to check possible state for a block
    w           // invert condition, stop if true
  )=>
    w?0:
      p?(       // for each block
        g(r+0,q,p=='*')+   // possibly not bomb if doesn't say so
        g(r+1,q,1/p),      // number/newline can't be bomb
        c==1               // only one bomb
      ):
        c-=-![...s].some(  // no block doesn't satisfy
          (p,i)=>
            p>' '&& // \n don't mean number
                    // other symbols turn into NaN when counting
            [-1,1,-q,-1-q,-2-q,q,q+1,q+2].map(j=>p-=~~r[i+j])
                    // subtract each neighbor, OOB = 0
            |p,     // difference between intended and actual
            q=s.search('\n') // how many blocks in a line
        )
)

「一致しない」はタイプミスのようで、修正すると4つのソリューションになります
l4m2
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.