광산을 쓸어도 될까요?


29

지뢰 찾기 는 타일을 클릭하지 않고 어떤 타일이 "광산"인지 알아 내야하는 인기있는 퍼즐 게임입니다. 대신 근처 타일을 클릭하여 인접한 광산의 수를 나타냅니다. 게임에 대한 한 가지 단점은 여러 가지 유효한 답변이 있고 추측 만 할 수있는 시나리오에서 끝날 수 있다는 것입니다. 예를 들어, 다음 보드를 보자.

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

이 형식에서 숫자는 인접한 광산의 수, *알려진 광산, "?"를 나타냅니다. 잠재적 광산을 나타냅니다. 이 특정 퍼즐의 불행한 점은 네 가지의 분명하고 유효한 잠재적 솔루션이 있다는 것입니다.

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?솔루션이 없기 때문에 실제 지뢰 찾기 게임에서는 얻을 수 없습니다. 따라서 "지뢰 찾기 보드"로 간주되지 않습니다 ... 예?)
mathmandan

2
MineSweeper에서 여기에 누락 된 추가 정보 인 광산 수는 없습니다.
edc65

@mathmandan 예, 입력은 항상 하나 이상의 셀과 하나 이상의 유효한 솔루션으로 직사각형입니다.
DJMcMayhem

답변:


20

GNU 프롤로그, 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).

프롤로그는이 관점에서 실질적인 관점에서이 과제를 해결하는 데 가장 적합한 언어입니다. 이 프로그램은 지뢰 찾기의 규칙을 거의 나타내며 GNU Prolog의 제약 솔버로 문제를 해결할 수 있습니다.

zi유틸리티 기능은 ( z수행 동작 스크롤 형상이지만 세 개의 인접한 소자보다는 2 세트에서의 정렬, i전치 3 개 목록 N 의리스트에 요소 N 3- 튜플). 셀을 내부에로 저장합니다 . 여기서 x 는 광산의 경우 1, 비 광의 경우 0, y 는 인접한 광산의 수입니다. 이 제약 조건을 칠판에 표현합니다. 보드의 모든 행에 적용됩니다 . 및도록 하는지 표시 유효한 판이다.x/ycrcz(r,M)M

불행히도,이 작업을 직접 수행하는 데 필요한 입력 형식은 불합리하므로 파서 (실제 규칙 엔진보다 더 많은 코드를 설명하고 디버깅에 소요되는 대부분의 시간; Minesweeper 규칙 엔진은 거의 작동했습니다)를 포함해야했습니다 처음에는 파서가 생각으로 가득했습니다.) q문자 코드와 형식 간에 단일 셀을 변환 합니다. 보드의 한 줄을 변환합니다 (줄로 알려진 하나의 셀은 남기지 않지만 줄의 각 가장자리에 알 수없는 이웃 광산이 있음).x/ylp전체 보드를 변환합니다 (하단 테두리는 포함하지만 상단 경계는 제외). 이러한 모든 기능은 앞뒤로 실행될 수 있으므로 보드를 구문 분석하고 예쁘게 인쇄 할 수 있습니다. ( p보드의 너비를 지정하는에 대한 세 번째 인수로 약간의 성가신 흔들림 이 있습니다. 이는 Prolog에 매트릭스 유형이 없기 때문에 보드를 직사각형으로 제한하지 않으면 프로그램이 시작됩니다. 보드 주변에서 점진적으로 넓은 테두리를 시도하는 무한 루프.)

m주요 지뢰 찾기 해결 기능입니다. 입력 문자열을 구문 분석하여 올바른 테두리가있는 보드를 생성합니다 (재귀 적 인 경우를 사용하여 p대부분의 보드를 변환 한 다음 기본 케이스를 직접 호출하여 아래쪽 테두리와 동일한 구조를 갖는 위쪽 테두리를 생성합니다). 그런 다음 전화z(r,[R|M])Minesweeper 규칙 엔진을 실행하려면 (이 호출 패턴으로) 유효한 보드 만 생성하는 생성기가됩니다. 이 시점에서 이사회는 여전히 일련의 제약으로 표현되며, 이는 잠재적으로 우리에게 어색합니다. 하나 이상의 보드를 나타낼 수있는 단일 제약 조건이있을 수 있습니다. 또한 각 광장에 최대 하나의 광산이 포함 된 위치를 아직 지정하지 않았습니다. 따라서 우리는 각 사각형의 파형을 명시 적으로 "축소"해야하며, 특히 (단일) 광산이거나 비 광산이어야하며, 가장 쉬운 방법은 파서를 통해 역으로 실행하는 것 var(V)입니다. q(63,V)케이스는 케이스가 ?거꾸로 돌아가는 것을 방지하기 위해 설계되었으므로 보드를 빼면 완전히 알려지게됩니다. 마지막으로 파싱 된 보드를m; m따라서 부분적으로 알려지지 않은 보드를 사용하여 알려진 모든 보드를 일치시키는 생성기가됩니다.

그것은 지뢰 찾기를 해결하기에 충분하지만 질문은 모든 솔루션을 찾는 것이 아니라 정확히 하나의 솔루션이 있는지 여부를 명시 적으로 묻습니다. 따라서 s생성기 m를 집합으로 변환 한 다음 집합에 정확히 하나의 요소가 있다고 주장 하는 추가 조건 자를 작성했습니다 . 이는 실제로 정확히 하나의 솔루션이있는 경우 s진실 ( yes) 을 반환 하거나 하나 이상이있는 경우 거짓 ( ) 을 반환 한다는 의미입니다 no.

d솔루션의 일부가 아니며 바이트 수에 포함되지 않습니다. 문자열처럼 문자열 목록을 인쇄하는 기능 m으로, 생성 된 보드를 검사 할 수 있습니다 (기본적으로 GNU Prolog는 문자열을 ASCII 코드 목록으로 인쇄합니다. 두 문자열을 동의어로 취급하기 때문입니다. 읽기가 매우 어렵습니다). 테스트 중에 또는 m실제 Minesweeper 솔버 로 사용하려는 경우에 유용합니다 (제약 솔버를 사용하기 때문에 매우 효율적 임).


11

하스켈, 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 cjd>'/'nm*


7

매쓰, 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유효한 솔루션이 하나만 있는지 확인합니다.

설명

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

펄, 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 / mg ; # 각 줄의 시작과 끝에 "A"를 추가하십시오.                     

# 문제를 실제로 해결하는 기능 sub t { my $ _ = pop ;
 
     # 매개 변수를 가져 와서 $ _에 저장하십시오 (정규 표현식의 기본 인수). if ( / \? / ) { # 다른 알 수없는 문자가있는 경우. for $ i ( 0 .. 8 , "*" ) { # 모든 가능성을 시도하십시오 
            t ( s / \? / $ i / r ) # 첫 번째 알 수없는 문자가 교체 된 재귀 호출 } } else {
        
            
        
     # 더 이상 알 수없는 문자이므로 보드가 유효한지 확인합니다. 
        $ r = 1 ; # 마지막에 r == 1이면 보드가 유효하고 그렇지 않으면 보드에 존재하는 각 숫자에 대해 $ i ( / \ d / g ) { # 이 아닙니다 # 다음 정규 표현식은 숫자가 둘러싸여 있는지 확인합니다 중 하나에 의해 # 너무 많거나 너무 적은 광산. # (작동 방식 : 마술!) 
         $ 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 개의 숫자로 구성된 3 개의 그룹이 정렬되어 있으며 [^V]{$c}관심있는 숫자는 중간에 있습니다.
그런 다음 9 개의 문자 중 폭탄 "$1$2$3"=~y%*%%의 수를 센다 *:와 다른 경우 $i일치하는 빈 문자열을 추가하고 ( ""=> 즉석 일치, 정규식이 true를 반환) 그렇지 않으면 일치하도록 시도하여 실패를 강제합니다 R( 문자열에있을 수 없습니다).
정규식이 일치하면 보드가 유효하지 않으므로로 설정 $r됩니다 0.$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)어디에 있고 보드 의 셀 수입니다 (중간에 정규 표현식의 복잡성을 세지 않고는 아마 나쁩니다). 내 컴퓨터에서 최대 4까지 빠르게 작동 하고 5 느리게 시작하고 6 분 동안 몇 분이 걸리며 더 큰 숫자는 시도하지 않았습니다.n?m?


3

자바 스크립트 (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

모든 입력이 유효 할 것으로 예상되면-적어도 하나의 솔루션으로-바이트 변경 s==1을 저장할 수 있습니다.s<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는 당신이 그 바이트를 골프로 칠 수 있다고 말했다.
Destructible Lemon

@DestructibleWatermelon 감사합니다. 모든 것을 수정하고 더 많은 바이트를 절약했습니다.
edc65

0

자바 스크립트 (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이 일치하지 않으므로 여전히 <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
        )
)

"일치하지 않는다"는 오타 인 것 같습니다.
l4m2
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.