Tôi có thể quét mìn không?


29

Minesweeper là một trò chơi giải đố phổ biến, nơi bạn phải khám phá những viên gạch nào là "mỏ" mà không cần nhấp vào những viên gạch đó. Thay vào đó, bạn nhấp vào gạch gần đó để tiết lộ số lượng mỏ liền kề. Một nhược điểm của trò chơi là có thể kết thúc trong một kịch bản có nhiều câu trả lời hợp lệ và bạn chỉ có thể đoán. Ví dụ: lấy bảng sau:

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

Trong định dạng này, một số đại diện cho số lượng mỏ liền kề, một số *đại diện cho một mỏ đã biết và "?" đại diện cho một mỏ tiềm năng. Điều đáng tiếc về câu đố đặc biệt này là có bốn giải pháp tiềm năng riêng biệt và hợp lệ :

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*

Điều này có nghĩa là hội đồng quản trị là không thể giải quyết . Dưới đây là một ví dụ về một bảng có thể giải được :

1121
1??*
12?*
0122

Bảng này có thể giải được vì chỉ có một giải pháp hợp lệ có thể:

1121
1*4*
12**
0122

Nhiệm vụ của bạn là viết một chương trình hoặc chức năng lấy bảng quét mìn hợp lệ và xác định xem nó có thể giải quyết được hay không. Theo "bảng quét mìn hợp lệ", ý tôi là đầu vào sẽ luôn là hình chữ nhật, có ít nhất một giải pháp và không chứa bất kỳ ký tự không hợp lệ nào.

Đầu vào của bạn có thể là một mảng các ký tự, một chuỗi các chuỗi, một chuỗi chứa các dòng mới, v.v ... Đầu ra phải là một giá trị trung thực nếu nó có thể giải quyết được và giá trị giả nếu không. Tôi không cực kỳ lo lắng về hiệu suất, nhưng về mặt lý thuyết của bạn phải hoạt động cho mọi kích thước đầu vào.

Như thường lệ, các sơ hở tiêu chuẩn được áp dụng và giải pháp ngắn nhất tính theo byte sẽ thắng!

Ví dụ:

Các ví dụ sau đây đều có thể giải được:

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

Các ví dụ sau đây đều không thể giải quyết được:

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

Chúng ta có thể giả sử bảng là hình chữ nhật với ít nhất một ô? Ngoài ra, chúng ta có thể cho rằng đầu vào sẽ luôn thừa nhận ít nhất một giải pháp không? (Ví dụ: 2?không có giải pháp, điều đó có nghĩa là nó không thể đến từ một trò chơi thực tế của Minesweeper. Do đó, nó không được coi là "bảng Minesweeper" ... vâng?)
mathmandan

2
Không có gì đáng để trong MineSweeper bạn có một thông tin bổ sung mà ở đây còn thiếu: số lượng mỏ.
edc65

@mathmandan Có, đầu vào sẽ luôn có hình chữ nhật với ít nhất một ô và ít nhất một giải pháp hợp lệ.
DJMcMayhem

Câu trả lời:


20

GNU Prolog, 493 byte

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),[_]).

Một vị từ bổ sung có thể hữu ích để thử nghiệm (không phải là một phần của nội dung gửi):

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

Prolog chắc chắn là ngôn ngữ phù hợp để giải quyết nhiệm vụ này từ quan điểm thực tế. Chương trình này khá nhiều chỉ nêu các quy tắc của Minesweeper và cho phép người giải quyết ràng buộc của GNU Prolog giải quyết vấn đề từ đó.

zilà các hàm tiện ích ( zthực hiện một loại hoạt động giống như nếp gấp nhưng trên bộ ba phần tử liền kề chứ không phải 2; ichuyển 3 danh sách n phần tử thành danh sách n 3 tuple). Chúng tôi lưu trữ nội bộ một ô như , trong đó x là 1 cho một mỏ và 0 cho một nonmine và y là số lượng các mỏ liền kề; thể hiện sự hạn chế này trên bảng. áp dụng cho mọi hàng của hội đồng quản trị; và vì vậy hãy kiểm tra xemx/ycrcz(r,M)M là một hội đồng hợp lệ.

Thật không may, định dạng đầu vào cần thiết để thực hiện công việc này trực tiếp là không hợp lý, vì vậy tôi cũng phải bao gồm một trình phân tích cú pháp (có thể chiếm nhiều mã hơn công cụ quy tắc thực tế và phần lớn thời gian để gỡ lỗi; công cụ quy tắc Minesweeper hoạt động khá nhiều lần đầu tiên, nhưng trình phân tích cú pháp đầy thinkos). qchuyển đổi một ô duy nhất giữa một mã ký tự và chúng tôi chuyển đổi toàn bộ bảng (bao gồm cả viền dưới cùng, nhưng không bao gồm ô trên cùng). Tất cả các chức năng này có thể được chạy tiến hoặc lùi, do đó có thể phân tích cú pháp và in đẹp bảng. (Có một số vặn vẹo khó chịu xung quanh với đối số thứ ba đểx/y định dạng . lchuyển đổi một dòng của bảng (để lại một ô được biết là không phải là mỏ, nhưng với một số lượng không xác định của các mỏ lân cận, ở mỗi cạnh của dòng là một đường viền);pp, trong đó chỉ định chiều rộng của bảng; điều này là do Prolog không có loại ma trận và nếu tôi không buộc bảng thành hình chữ nhật, chương trình sẽ đi vào một vòng lặp vô hạn để thử các đường viền rộng hơn xung quanh bảng.)

mlà chức năng giải quyết Minesweeper chính. Nó phân tích chuỗi đầu vào, tạo ra một bảng có đường viền chính xác (thông qua việc sử dụng trường hợp đệ quyp để chuyển đổi hầu hết bảng, sau đó gọi trực tiếp trường hợp cơ sở để tạo đường viền trên cùng, có cùng cấu trúc với đường viền dưới). Sau đó, nó gọiz(r,[R|M])để chạy công cụ quy tắc Minesweeper, mà (với mẫu cuộc gọi này) trở thành một trình tạo chỉ tạo các bảng hợp lệ. Tại thời điểm này, hội đồng quản trị vẫn được thể hiện dưới dạng một tập hợp các ràng buộc, điều này có khả năng gây khó xử cho chúng tôi; có lẽ chúng ta có thể có một tập các ràng buộc có thể đại diện cho nhiều bảng. Ngoài ra, chúng tôi chưa chỉ định bất cứ nơi nào mà mỗi ô vuông chứa tối đa một mỏ. Như vậy, chúng ta cần phải "thu gọn dạng sóng" một cách rõ ràng của từng ô vuông, yêu cầu nó phải cụ thể là một (một) mỏ hoặc một nonmine, và cách dễ nhất để làm điều này là chạy nó qua trình phân tích cú pháp ngược ( var(V)trên trường hợp chạy ngược lại, và do đó làm suy yếu bảng buộc nó phải được biết đầy đủ). Cuối cùng, chúng tôi trả lại bảng được phân tích cú pháp từq(63,V)trường hợp được thiết kế để ngăn chặn ; do đó trở thành một máy phát điện có một bảng chưa biết một phần và tạo ra tất cả các bảng đã biết phù hợp với nó.?mm

Điều đó thực sự đủ để giải quyết Minesweeper, nhưng câu hỏi rõ ràng yêu cầu kiểm tra xem có chính xác một giải pháp hay không, thay vì tìm tất cả các giải pháp. Như vậy, tôi đã viết một biến vị ngữ bổ sung sđơn giản là chuyển đổi trình tạo mthành một tập hợp, và sau đó xác nhận rằng tập hợp đó có chính xác một phần tử. Điều này có nghĩa là ssẽ trả về truey ( yes) nếu thực sự có chính xác một giải pháp, hoặc falsey ( no) nếu có nhiều hơn một hoặc ít hơn một.

dkhông phải là một phần của giải pháp và không được bao gồm trong bytecount; đó là một chức năng để in một danh sách các chuỗi như thể nó là một ma trận, cho phép kiểm tra các bảng được tạo bởi m(theo mặc định, GNU Prolog in các chuỗi như một danh sách các mã ASCII, vì nó xử lý hai từ đồng nghĩa; khá khó đọc). Nó hữu ích trong quá trình thử nghiệm hoặc nếu bạn muốn sử dụng mnhư một bộ giải Minesweeper thực tế (bởi vì nó sử dụng một bộ giải ràng buộc, nó rất hiệu quả).


11

Haskell, 193 169 168 byte

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>'/']]

Ví dụ sử dụng: g "1121 1??* 12?* 0122"-> True.

Cách hoạt động: làm cho danh sách của tất cả các bảng có thể với các ?thay thế bởi một trong hai *hoặc !( !phương tiện bỏ qua sau). Điều này được thực hiện thông qua mapM c, nhưng trước khi chúng tôi bổ sung và nối thêm một loạt khoảng trắng vào chuỗi đầu vào để việc lập chỉ mục của chúng tôi sẽ không nằm ngoài phạm vi. Đối với mỗi bảng như vậy, hãy kiểm tra xem đó có phải là một bảng hợp lệ hay không bằng cách lặp qua tất cả các phần tử (chỉ mục j) và nếu đó là một số ( d>'/') cũng trên các hàng xóm của nó (chỉ mục n, m), hãy đếm *và so sánh với số. Cuối cùng kiểm tra độ dài của danh sách các bảng hợp lệ.


7

Mathicala, 214 192 190 180 176 174 168 165 byte

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}&

Chứa U + F4A1 (Sử dụng riêng). Hàm không tên này tìm thấy tất cả các kết hợp có thể cho "?"(tức là thay thế tất cả "?"s bằng "*"hoặc 0) và kiểm tra xem chỉ có một giải pháp hợp lệ.

Giải trình

b="*";

Đặt bthành "*".

!FreeQ[#,q="?"]

Đặt qthành chuỗi "?". Kiểm tra xem có "?"trong đầu vào.

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

Nếu True...

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

Thay thế lần xuất hiện đầu tiên của q(= "?") bằng b(= "*") hoặc 0(tức là hai đầu ra) và áp dụng lại toàn bộ chức năng.


Nếu False...

#~ArrayPad~1

Pad đầu vào với một lớp 0.

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

Phân vùng đầu vào thành 3 x 3 ma trận có offset 1. Đối với mỗi phân vùng, hãy áp dụng một hàm sao cho nếu giá trị giữa là b(= "*"), thì đầu ra là b(= "*") và nếu giá trị giữa không b(= "*"), đầu ra là sốb (="*" ) trong đầu vào. Bước này đánh giá lại tất cả các ô số.


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

Từ tất cả các kết quả, tìm những kết quả khớp với đầu vào

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

Kiểm tra xem đầu vào có dài 1 không.


7

Perl, 215 byte

213 byte mã + -p0cờ (2 byte).

/.*/;$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

Ý tưởng của mã là kiểm tra mọi khả năng và xem liệu có một và chỉ một dẫn đến một bảng đầy đủ có hiệu lực hay không.

Dễ đọc hơn, mã trông như sau:

/.*/ ; $ c = "@ +" ; # đếm kích thước của một dòng. 
$ _ = A x $ c . "\ n $ _" . A x $ c ; # thêm một dòng "A" ở đầu và một dòng khác ở cuối. 
s / ^ | $ / A / mg ; # thêm "A" ở đầu và cuối mỗi dòng.                     

# Chức năng thực sự giải quyết vấn đề phụ t { my $ _ =
 
     pop ; # Nhận tham số, lưu trữ trong $ _ (đối số mặc định cho regex). if ( / \? / ) { # nếu có một char không xác định khác. cho $ i ( 0 .. 8 , "*" ) { # thử mọi khả năng 
            t ( s / \? / $ i / r ) # cuộc gọi reccursive trong đó char không xác định đầu tiên đã được thay thế } } khác { 
        
            
        
      # nhân vật không xa lạ hơn, vì vậy ở đây chúng tôi kiểm tra nếu hội đồng quản trị có giá trị 
        $ r = 1 ; # nếu r == 1 ở cuối, sau đó hội đồng quản trị là hợp lệ, nếu không nó không cho $ i ( / \ d / g  
         ) { # cho mỗi số hiện tại của hội đồng quản trị # kiểm tra regex sau nếu có một số được bao quanh bởi # quá nhiều hoặc quá ít mỏ. # (cách thức hoạt động: ma thuật!) 
         $ r & =! / (...) ... "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" " = $ i? "": R}) / } 
        $ e + = $ r # Tăng số lượng bảng hợp lệ. } } 
t $ _ ;  
            
            
             
        
    
 # Gọi hàm trước 
$ _ = $ e == 1 # Kiểm tra xem chỉ có một bảng hợp lệ ($ _ được in ngầm nhờ cờ -p). 

Về regex ở giữa:

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

Lưu ý rằng [^V]chỉ viết tắt của "bất kỳ ký tự nào, bao gồm \ n".
Vì vậy, ý tưởng là: 3 char trên một dòng, sau đó 3 trên tiếp theo (với $iở giữa), sau đó 3 trên tiếp theo. 3 nhóm gồm 3 số được căn chỉnh, nhờ [^V]{$c}và số chúng tôi quan tâm nằm ở giữa.
Và sau đó, "$1$2$3"=~y%*%%đếm số lượng *(bom) trong số 9 ký tự đó: nếu khác với $i, chúng tôi thêm một chuỗi trống để khớp ( ""=> khớp tức thì, regex trả về đúng), nếu không, chúng tôi buộc thất bại bằng cách thử khớp R( mà không thể có trong chuỗi).
Nếu các trận đấu regex, sau đó hội đồng quản trị là không hợp lệ, vì vậy chúng tôi thiết lập $rđể 0$r&=!/.../.
Và đó là lý do tại sao chúng tôi thêm một sốAở khắp mọi nơi trên mỗi dòng: vì vậy chúng tôi không phải lo lắng về các trường hợp cạnh của các số nằm gần các cạnh của bảng: chúng sẽ có Ahàng xóm, không phải là mỏ (tất nhiên, gần như bất kỳ char nào cũng có thể hoạt động, Tôi chọnA ).

Bạn có thể chạy chương trình từ dòng lệnh như thế:

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"

Sự phức tạp không thể tồi tệ nhất: đó là O(m*9^n)nơi nlà số ?trên bảng, và mlà số tế bào trên bảng (không đếm sự phức tạp của các regex ở giữa, mà có lẽ khá xấu). Trên máy của tôi, nó hoạt động khá nhanh lên đến 4 ?và bắt đầu chậm hơn 5, mất vài phút cho 6 và tôi đã không thử với số lượng lớn hơn.


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

Nếu tất cả các đầu vào được dự kiến ​​là hợp lệ - đó là với ít nhất 1 giải pháp - thì tôi có thể lưu một byte thay đổi s==1vớis<2

Ít chơi gôn

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
}

Kiểm tra

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 đã nói rằng bạn có thể đánh gôn byte đó.
Lemon phá hủy

@DeststallibleWateriwi cảm ơn, tôi đã sửa đổi tất cả và lưu thêm một số byte thực sự
edc65

0

JavaScript (Node.js) , 167 byte

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`
`)

Hãy thử trực tuyến!

Mặc dù op nói "đầu vào sẽ luôn là hình chữ nhật, có ít nhất một giải pháp", mẫu 3 sai không khớp, vì vậy tôi vẫn yêu cầu 1 giải pháp chứ không phải <2 giải pháp

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
        )
)

có vẻ như "không khớp" là một lỗi đánh máy, sửa nó làm cho nó có 4 giải pháp
l4m2
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.