Всеки, който работи с операционна система Windows, е запознат с играта Minesweeper. Този раздел обсъжда подобна програма.
Пример за прозорец на програмата в края на играта (след като играчът е отворил клетката, в която се намира мината) е показан на фиг. 10.7.
ориз. 10.7. Прозорец на програмата Minesweeper
Правила на играта и представяне на данни
Игралното поле се състои от клетки, всяка от които може да съдържа мина. Задачата на играча е да намери всички мини и да ги маркира с флагчета.
С помощта на бутоните на мишката играчът може да отвори клетка или да постави флаг в нея, като по този начин показва, че клетката съдържа мина. Клетката се отваря с щракване с левия бутон на мишката, отметката се поставя с щракване с десния бутон. Ако има мина в клетката, която играчът е отворил, тогава се получава експлозия (сапьорът е направил грешка, а той, както знаем, прави само една грешка) и играта приключва. Ако в дадена клетка няма мина, тогава в тази клетка се появява число, съответстващо на броя на мините в съседните клетки.
Чрез анализиране на информация за броя на мините в клетките, съседни на вече отворените, играчът може да открие и маркира всички мини с флагове. Няма ограничения за броя на клетките, маркирани с флагове. Въпреки това, за да завършите играта (за да спечелите), флаговете трябва да бъдат поставени само в тези клетки, които имат мини. Неправилно поставена отметка може да бъде премахната чрез щракване с десния бутон върху клетката, в която се намира.
Вероятно всеки от нас някога е играл или поне се е опитвал да играе MineSweeper. Логиката на играта е проста, но по едно време дори обещаха награда за алгоритъма за завършването му. В моя бот логиката има три алгоритъма, които се използват в зависимост от ситуацията на терена. Основният алгоритъм ви позволява да намерите всички клетки със 100 и 0 процента вероятност за намиране на мина. Използвайки само този алгоритъм и отваряйки произволни клетки на случаен принцип при липса на надеждно решение в стандартен миночистач на ниво „Експерт“, можете да постигнете 33% печалби. Някои допълнителни алгоритми обаче могат да повишат тази стойност до 44% (Windows 7). Основен алгоритъм
Основният алгоритъм е следният. Неизвестните клетки (Cell class), съседни на една отворена клетка, се формират в група (Group class), която записва и стойността на клетката, към която е съседна.
Фигурата показва четири групи, някои от които се припокриват, а някои дори съдържат други групи. Нека означим (123,1) - групата се състои от клетки 1,2 и 3, като в същото време в тях има 1 мина. (5678.2) - има 2 мини в четири клетки.
Първо трябва да конвертирате групите. За да направите това:
- Сравняваме всяка група с всяка следваща група.
- Ако групите са еднакви, изтрийте втората.
- Ако една група съдържа друга, тогава извадете по-малката от по-голямата. Тоест имаше две групи (5678.2) и (5.1), сега (678.1) и (5.1); (2345.3) и (5.1) → (234.2) и (5.1)
- Преобразуваме пресичащи се групи, например (123,1) и (234,2), съгласно следния принцип:
- Създайте нова група от пресичащи се клетки (23,?)
- Ние изчисляваме броя на мини в нова група, равен на броя на мини в групата с голям брой мини (234.2) минус оставащия брой клетки в другата група след разделянето на пресичащите се клетки (1,?). Тоест 2-1 = 1. Получаваме (23.1)
- Ако изчисленият брой мини в новата група (23.1) не е равен на броя мини в групата с по-малко мини (123.1), тогава спираме трансформацията
- Изваждаме новообразуваната група от двете пресичащи се групи. (123,1)-(23,1) = (1,0), (234,2)-(23,1) = (4,1).
- Добавете новосформираната група към списъка с групи
- Повторете от стъпка 1, докато няма промени
Метод за създаване и конвертиране на групи
/** * Създава списък от групи от клетки, свързани с една стойност на отворено поле, и също така ги разделя на по-малки, премахвайки дубликати.< width; x++) for (int y = 0; y < height; y++) field[x][y].setGroup(groups); // создание групп boolean repeat; do{ repeat=false; for (int i = 0; i < groups.size() - 1; i++) { // проходим по списку групп */ private void setGroups() ( groups.clear(); for (int x = 0; xГрупова група< groups.size(); j++) { // сравниваем ее с остальными меньшими группами Group groupJ=groups.get(j); if (groupI.equals(groupJ)) // удаляем одинаковые группы {groups.remove(j--);break;} Group parent; // I = groups.get(i);Групово дете; // по-малка група if (groupI.size()>groupJ.size()) // определяне на по-голямата и по-малката група по броя на клетките (parent=groupI;child=groupJ;) else (child=groupI;parent=groupJ ; ) if (parent.contains(child)) ( // ако по-големият съдържа по-малкия parent.subtraction(child); // тогава извадете по-малкия от по-големия repeat=true; // запишете факта, че групите са се променили ) else if (groupI.overlaps(groupJ ) > 0) ( // в противен случай ако групите се пресичат if (groupI.getMines()>groupJ.getMines())// определяне на по-голямата и по-малката група по числото на мините (parent=groupI;child=groupJ;) else (child =groupI;parent=groupJ;) Group overlap = parent.getOverlap(child); // след това вземете резултата от пресичането if (overlap != null) ( // и ако има смисъл (в резултат на пресичането бяха разкрити клетки с 0% или 100%) groups.add(overlap); // тогава направете съответните корекции в списъка parent.subtraction(overlap); .изваждане (припокриване);
)
- В резултат на това ще имаме три вида групи.
- броят на мини е нула
- броят на мини е равен на броя на клетките в групата
Съответно всички клетки от първата група могат да бъдат безопасно отворени, а всички клетки от втората група могат да бъдат маркирани. Това е същността на основния алгоритъм.
Ако няма надеждно решение- Но често има ситуации, когато няма надеждно решение на ситуацията. Тогава на помощ идва следният алгоритъм. Същността му е използването на теорията на вероятностите. Алгоритъмът работи на два етапа:
- Определяне на вероятността в отделни клетки, като се вземе предвид влиянието на няколко отворени клетки
Нека да разгледаме как работи този метод, като използваме пример за ситуация, в която са отворени само две съседни клетки със стойности 4 и 2. Вероятностите за намиране на мини от клетки 4 и 2 поотделно са 4/7=0,57 и 2/7=. 0,28, съответно.
За да изчислим вероятността да намерим мина в клетка до няколко отворени клетки, използваме формулата за изчисляване на вероятността от поне едно събитие:Вероятността за настъпване на събитие A, състояща се в настъпване на поне едно от събитията A 1, A 2,..., A n, независими в съвкупността, е равна на разликата между единица и произведението на вероятностите за противоположни събития. A=1- (1-A 1)*(1-A 2)*....*(1-A n)
Трябва обаче да се помни, че сумата от вероятностите във всяка група клетки трябва да бъде равна на броя на мините в групата. Следователно всички стойности във всяка група трябва да бъдат умножени, така че в крайна сметка сумата им да е равна на броя минути. Това число е равно на броя мини в групата, разделен на текущата сума от вероятностите на клетките на групата:
4/(0,57+0,57+0,57+0,69+0,69+0,69+0,69)=0,895
0,57*0,895=0,485 0,69*0,895=0,618
Тези клетки, които имат вероятност от 0,57, имат 0,485, а тези, които имат вероятност от 0,69 → 0,618
Извършваме подобно изчисление за втората група, като вземаме предвид корекциите от предишната.
2/(0,28+0,28+0,28+0,618+0,618+0,618+0,618)=0,604
0,28*0,604=0,169 0,618*0,604=0,373
Виждаме, че вероятността в общите клетки отново се е променила, така че такава корекция за всяка група трябва да се повтори няколко пъти, докато системата достигне някои стабилни стойности, които ще бъдат истинските вероятности за намиране на мини в клетките.
4/(0,485+0,485+0,485+0,373+0,373+0,373+0,373)=1,357
0,485*1,357=0,658 0,373*1,357=0,506
2/(0,169+0,169+0,169+0,506+0,506+0,506+0,506)=0,79
0,169*0,79=0,134 0,506*0,79=0,4
...и т.н.
Всичко, което остава, е да изберете една от клетките с минимална вероятност и да направите ход.
Този метод в код
/** * Методът прави корекции на вероятностите за намиране на мини в клетките, като взема предвид взаимното влияние на вероятностите на съседните клетки една върху друга */ private void correctPosibilities())( Map
prob= group.getProbabilities(); // вземете списъка с вероятности на всички клетки в групата в проценти Double sum=0.0;
В последния етап на играта броят на немаркираните мини играе голяма роля. Знаейки този номер, можете да ги замените с груба сила в неизвестни клетки и да маркирате подходящите опции. В процеса на търсене на подходящи опции за всяка клетка, ние броим броя на маркировките. Разделяйки получените стойности на общия брой марки, получаваме вероятността да намерим мини за всяка клетка. Например, в тази ситуация, която изглежда имаше само едно надеждно решение, последният метод (LastTurns) намери 3 клетки с 0% вероятност за намиране на мина.LastTurns(9,21) провери 144 подходящи комбинации от 293930 възможни и намери 3 клетки с минимална вероятност от 0%
От гледна точка на сложността на разбирането на идеята, този метод е най-лесният, така че все още няма да го анализирам подробно.
Изпълнението му
/** * Независим алгоритъм за изчисление, използващ банална груба сила. Може да се използва само ако броят на неизвестните клетки е не повече от 30 * @return */ public ArrayList
)
Заключение- На практика, при достатъчен брой проби, изчислените и действителните стойности на вероятностите за намиране на мина в клетка почти съвпадат. Следната таблица показва резултатите от работата на бота на Minesweeper под Windows XP за една нощ, където
- Приблизителен %
- Общ брой отвори на клетките с изчислен %
- Брой удари на мина
1. | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
2. | 31 | 55 | 117 | 131 | 304 | 291 | 303 | 339 | 507 | 435 | 479 | 1201 | 152 | 146 | 118 | 143 | 164 | 141 | 367 | 3968 | 145 | 63 | 47 | 26 | 92 |
3. | 1 | 4 | 9 | 6 | 20 | 19 | 27 | 29 | 56 | 43 | 60 | 147 | 15 | 25 | 14 | 20 | 33 | 26 | 65 | 350 | 14 | 5 | 12 | 4 | 23 |
4. | 3 | 7 | 7 | 4 | 6 | 6 | 8 | 8 | 11 | 9 | 12 | 12 | 9 | 17 | 11 | 13 | 20 | 18 | 17 | 8 | 9 | 7 | 25 | 15 | 25 |
1. | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
2. | 18 | 10 | 24 | 18 | 9 | 11 | 6 | 135 | 8 | 2 | 4 | 2 | 1 | 3 | 16 | 2 | 2 | 1 | 462 | ||||||
3. | 1 | 9 | 2 | 3 | 3 | 2 | 1 | 43 | 1 | 0 | 1 | 0 | 0 | 1 | 4 | 1 | 1 | 0 | 210 | ||||||
4. | 5 | 37 | 11 | 30 | 33 | 18 | 16 | 31 | 12 | 0 | 25 | 0 | 0 | 33 | 25 | 50 | 50 | 0 | 45 |
Голямото несъответствие в областта от 20-22% вероятно се дължи на факта, че често този процент има клетки, които все още не са били отворени наблизо (например в началото на играта), и „Mineweeper“, адаптиран към играча, понякога премахване на мина изпод отворената клетка. Алгоритъмът на работа е реализиран в java и тестван на стандартен миночистач на Windows (7 и XP), приложение във VK и в играта. Между другото, след няколко дни на „технически проблеми“ при достъп до акаунта ми от моя IP, играта промени правилата за награди за отваряне на част от полето: първоначално връщаше 10% от залога за 10% от отвореното поле, след това 5%, след това 2%, а когато спрях да играя, върнах 5%.