我可以扫雷吗?


29

扫雷(Minesweeper)是一种流行的益智游戏,您必须在不单击这些图块的情况下发现哪些图块是“地雷”。而是单击附近的图块以显示相邻地雷的数量。该游戏的一个缺点是,有可能最终导致存在多个有效答案而您只能猜测的情况。例如,使用以下面板:

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

在此格式中,数字代表相邻地雷的数量,an *代表已知地雷,“?” 代表潜在的地雷。关于这个特殊难题的不幸之处在于,存在四个不同且有效的潜在解决方案:

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

该板是可解决的,因为只有一种可能的有效解决方案:

1121
1*4*
12**
0122

您的任务是编写一个程序或函数,该程序或函数需要一个有效的扫雷器板并确定其是否可解决。“有效扫雷板”是指输入将始终为矩形,具有至少一种解决方案,并且不包含任何无效字符。

您的输入可能是字符数组,字符串数组,包含换行符的字符串等。如果输出可解决,则输出必须为真实值,否则为错误值。我并不十分担心性能,但是理论上您的解决方案必须适用于任何大小的输入。

像往常一样,存在标准漏洞,而最短的解决方案以字节为单位!

例子:

以下示例都是可解决的:

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

我们是否可以假设该板是至少有一个单元的矩形?另外,我们是否可以假设输入将始终接受至少一种解决方案?(例如,2?没有解决方案,这意味着它不能来自实际的Minesweeper游戏。因此,它不被视为“ Minesweeper棋盘” ...是吗?)
mathmandan

2
在MineSweeper中缺少这里缺少的其他信息是毫无价值的:地雷数量。
edc65 '16

@mathmandan是的,输入将始终是矩形,至少包含一个单元格和至少一个有效解。
DJMcMayhem

Answers:


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 元素的集合;in个元素的3个列表转置为n个三元组的列表)。我们在内部将一个单元存储为,其中x是一个地雷,0是一个非地雷,y是相邻地雷的数量;在董事会上表达这一约束。适用于董事会的每一行;并检查是否是有效的木板。x/ycrcz(r,M)M

不幸的是,直接进行此工作所需的输入格式不合理,因此我还必须包括一个解析器(它可能比实际的规则引擎占用更多的代码,并且大部分时间用于调试; Minesweeper规则引擎几乎可以正常工作第一次,但解析器中充满了thinkos)。q在字符代码和我们的格式之间转换单个单元格。转换木板的一条线(在该线的每个边缘处留一个已知不是地雷的单元,但相邻地雷的数目未知);x/ylp转换整个板(包括底部边框,但不包括顶部边框)。所有这些功能都可以向前或向后运行,因此可以解析和精美印刷电路板。(在的第三个参数周围有一些烦人的摆动p,它指定了木板的宽度;这是因为Prolog没有矩阵类型,如果我不将木板约束为矩形,程序将进入一个无限循环,尝试逐步扩大董事会范围。)

m是扫雷的主要解决功能。它解析输入字符串,生成具有正确边框的木板(通过使用递归案例p转换大部分木板,然后直接调用基本案例以生成顶部边界,该结构与底部边界相同)。然后它调用z(r,[R|M])运行Minesweeper规则引擎,该引擎(具有此调用模式)成为仅生成有效棋盘的生成器。在这一点上,董事会仍然受到一系列限制,这对我们来说可能很尴尬;我们也许只有一组约束,可以代表一个以上的董事会。此外,我们尚未指定每个方块最多包含一个地雷的位置。因此,我们需要明确地“折叠”每个方格的波形,要求它具体是一个(单个)地雷或一个非地雷,而最简单的方法是将其向后穿过解析器(var(V)q(63,V)机箱的设计可防止?机箱向后跑,因此对面板进行拆卸会使其完全为人所知)。最后,我们从m; m因此,它变成了一个生成器,它接收部分未知的电路板并生成与其一致的所有已知电路板。

这确实足以解决Minesweeper,但该问题明确要求检查是否只有一个解决方案,而不是查找所有解决方案。这样,我写了一个额外的谓词s,它将谓词简单地转换m为一个集合,然后断言该集合恰好具有一个元素。这意味着如果确实存在一个正确的解决方案,s则将返回true(yes),如果存在多个解决方案,则将返回false()no

d不是解决方案的一部分,也不包含在字节数中;这是一个打印字符串列表的功能,就好像它是一个矩阵一样,这使得可以检查由生成的板m(默认情况下,GNU Prolog将字符串打印为ASCII代码列表,因为它将这两个同义对待;这种格式相当难以阅读)。它在测试期间很有用,或者如果您想m用作实用的Minesweeper求解器(因为它使用了约束求解器,那么它非常高效)。


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,但是在我们在输入字符串之前添加一堆空格之前,我们的索引不会超出范围。对于每一次这样板检查,如果它是通过遍历所有元素(索引有效的板j),如果它是一个数(d>'/')也超过其邻国(指数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(专用)。这个未命名的函数查找所有可能的组合"?"(即用或替换所有"?"s ),并检查是否只有一个有效的解决方案。"*"0

说明

b="*";

设置b"*"

!FreeQ[#,q="?"]

设置q为字符串"?"。检查"?"输入中是否存在。

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

如果True...

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

用(= )或(即两个输出)替换第一次出现的q(= ),然后再次应用整个功能。"?"b"*"0


如果False...

#~ArrayPad~1

用一层填充输入0

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

将输入分为偏移量为1的3 x 3矩阵。对于每个分区,应用一个函数,如果中间值为b(= "*"),则输出为b(= "*"),如果中间值为(= ),则输出为b(= "*")。输出是输入中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 = “ @ +” ; #计算一行的大小。
$ _ = A x $ c “ \ n $ _” 一个x $ c ; #在开头添加一行“ A”,在末尾添加另一行。
s / ^ | $ / A / 毫克; #在每行的开头和结尾添加“ A”。                     

#实际解决问题的函数sub t { my $ _ = pop ; #获取参数,并将其存储在$ _中(正则表达式的默认参数)。if / \?/ { #如果还有另一个未知字符。$ I 0 8 “*” { #想尽一切可能性
            牛逼小号/ \?/ $ I / [R #reccursive通话,其中第一未知字符已取代} } 其他{
 
     
        
            
        
     #没有更多的未知字符,因此在这里我们检查棋盘是否有效
        $ r = 1 ; #如果r == 1在端部,则该板是有效的,否则这不是$ I / \ d / { #对于每个存在的板的数目#以下正则表达式检查是否有一个数字包围通过#太多或太少的地雷。#(运作方式:魔术!)
         $ r &=!/(...)[^V]{$c}(.$i.)[^V]{$c}(...)(??{"$1$2$3"=~y%*%%! = $ i?“”:R})/ } 
        $ e + = $ r #增加有效板的数量。} } 
t $ _ ;  
          
            
            
             
        
    
 #调用上一个函数
$ _ = $ e == 1 #检查是否只有一个有效的板(由于-p标志,$ _被隐式打印)。 

关于中间的正则表达式:

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

请注意,[^V]仅代表“任何字符,包括\ n”。
因此,想法是:一行上3个字符,然后下一个3($i中间是),然后下一个3。这3组3个数字对齐了,这要归功于[^V]{$c}我们感兴趣的数字在中间。
然后,"$1$2$3"=~y%*%%计算*这9个字符之间的(炸弹)数量:如果与字符不同$i,则添加一个空字符串以进行匹配(""=>即时匹配,正则表达式返回true),否则,通过尝试匹配R((不能在字符串中)。
如果正则表达式匹配,那么主板是无效的,所以我们设置$r0$r&=!/.../
这就是为什么我们添加一些A每行周围的每个地方:因此,我们不必担心数字的边沿情况在棋盘边缘附近:他们将以A邻居的身份出现,这不是我的(当然,任何字符都可以工作,我选择了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>


欧普说你可以打那个字节。
破坏的柠檬

@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说“输入将始终是矩形的,至少有一个解决方案”,但错误样本3不匹配,因此我仍然需要1个解决方案,而不是<2个解决方案

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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.