Чи можна підмітати міни?


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

Пролог, безумовно, є правильною мовою для вирішення цього завдання з практичної точки зору. Ця програма в основному просто визначає правила тральщика і дозволяє вирішити проблему обмеження GNU Prolog звідти.

zі iє корисними функціями ( zвиконує щось на зразок складної операції, але на наборах з трьох сусідніх елементів, а не 2; iпереносить 3 списки з n елементів у список n 3-кортежів). Ми внутрішньо зберігаємо осередок як , де x дорівнює 1 для шахти і 0 для немінного, а y - кількість сусідніх мін; висловлює це обмеження на дошці. стосується кожного ряду дошки; і тому перевіряє, чи є дійсною дошкою.x/ycrcz(r,M)M

На жаль, формат введення, необхідний для прямого виконання цієї роботи, є нерозумним, тому мені також довелося включити аналізатор (який, ймовірно, приносить більше коду, ніж фактичний механізм правил, і більшу частину часу, витраченого на налагодження; двигун правил тральщика значно працював перший раз, але парсер був повний тонких). qперетворює одну клітинку між символьним кодом і нашим форматом. перетворює один рядок дошки (залишаючи одну клітинку, яка, як відомо, не шахта, а з невідомою кількістю сусідніх мін, на кожному краю лінії як кордон);x/ylpперетворює всю плату (включаючи нижню межу, але виключаючи верхню). Усі ці функції можна виконувати або вперед, або назад, таким чином можна як розібрати, так і сильно роздрукувати дошку. (Існує деякий роздратований хитання навколо третього аргументу до p, який визначає ширину плати; це тому, що Prolog не має матричного типу, і якщо я не обмежую плату бути прямокутною, програма перейде в нескінченна петля, що намагається прогресивно розширювати межі навколо дошки.)

mє основною функцією вирішення тральщиків. Він аналізує рядок введення, генеруючи дошку з правильною рамкою (за допомогою рекурсивного випадку pконвертувати більшу частину дошки, а потім викликає базовий випадок безпосередньо для створення верхньої межі, яка має таку саму структуру, як нижня межа). Потім дзвонитьz(r,[R|M])запустити двигун правил Minesweeper, який (з такою схемою виклику) стає генератором, що генерує лише дійсні дошки. На даний момент рада все ще виражається як набір обмежень, що потенційно незручно для нас; можливо, ми можемо мати єдиний набір обмежень, який міг би представляти більше однієї дошки. Крім того, ми ще ніде не вказали, що кожен квадрат містить максимум одну шахту. Таким чином, нам потрібно явно "згортати форму хвилі" кожного квадрата, вимагаючи, щоб він був конкретно (шахта) або міну, і найпростіший спосіб зробити це - запустити її через аналізатор назад (the var(V)on the q(63,V)корпус призначений для запобігання ?запуску корпусу назад, і, таким чином, відведення дошки змушує його бути повністю відомим). Нарешті, ми повертаємо розібрану дошку зm; mтаким чином стає генератором, який займає частково невідому плату і генерує всі відомі плати, що відповідають їй.

Цього насправді достатньо, щоб вирішити тральщик, але питання прямо вимагає перевірити, чи існує саме одне рішення, а не знайти всі рішення. Як такий, я написав додатковий предикат, sякий просто перетворює генератор mв набір, а потім стверджує, що в наборі є рівно один елемент. Це означає, що sповерне truthy ( yes), якщо дійсно рівно одне рішення, або falsey ( no), якщо їх більше, ніж одне чи менше.

dне є частиною рішення та не включається до рахунку; це функція для друку списку рядків як би матрицею, що дає змогу оглянути дошки, згенеровані m(за замовчуванням GNU Prolog друкує рядки як список 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, але перед тим, як додати та додати купу пробілів до вхідного рядка, щоб наша індексація не була поза діапазоном. Для кожної такої дошки перевірте, чи є вона дійсною дошкою, переглянувши всі елементи (індекс j), а чи є це число ( d>'/'), також над сусідами (індекс n, m), порахуйте кількість *та порівняйте їх із числом. Нарешті перевірте довжину списку дійсних дощок.


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]

Розділіть вхід на 3 х 3 матриці зі зміщенням 1. Для кожного розділу застосуйте функцію, що якщо середнє значення дорівнює 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 $ _" . A x $ c ; # додайте рядок "A" на початку та інший в кінці. 
с / ^ | $ / А / мг ; # додайте "A" на початку та в кінці кожного рядка.                     

# Функція, яка фактично вирішує проблему sub t { my $ _ = pop ; # Отримайте параметр, збережіть його в $ _ (аргумент за замовчуванням для регулярного вираження). if ( / \? / ) { # якщо є інша невідома таблиця. за $ я ( 0 . 8 , "*" ) { # спробувати всі можливості 
            т ( s / \? / $ я / г ) # reccursive виклик , де був замінений першим невідомий символ } } ще {
 
     
        
            
        
     # більше невідомого символу, тому тут ми перевіряємо, чи дійсна дошка 
        $ r = 1 ; # якщо r == 1 в кінці, то плата є дійсною, інакше вона не для $ i ( / \ d / g ) { # для кожного номеру, присутнього на дошці # наступний регекс перевіряє, чи є число оточеним або занадто багато, або занадто мало мін. # (як це працює: магія!) 
         $ r & =! /(...) evidence^VSense{$c}(.$i.) verified^VSense{$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, ми додаємо порожню рядок для відповідності ( ""=> миттєве співпадіння, регулярний вираз повертає істинне), інакше ми змушуємо помилку, намагаючись зіставити 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)де 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==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 сказав, що ви можете пограти в цей байт.
Руйнуючий лимон

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

Спробуйте в Інтернеті!

Хоча оп скажіть, що "введення завжди буде прямокутним, мати принаймні одне рішення", помилковий зразок 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
        )
)

здається, що "не збігається" - це помилка
друку
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.