Противникът отговаря
Миналия час търсехте път в граф, в който всички ходове са ваши. В играта е различно: след вашия ход идва ход на противника, който иска обратното на вас. Планът „ще направя А, после Б, после В“ не струва, ако противникът след А играе нещо, което сте пропуснали.
Компютърът решава това с търсене, но с една добавка: на всяко второ ниво в графа не избира той, а противникът. Така през 1997 г. Deep Blue побеждава световния шампион Гари Каспаров — не с разбиране на шаха, а с проверка на огромен брой продължения и една добра оценка на позицията.
Дърво на играта
Дърво на играта
Коренът е текущата позиция. Децата му са позициите след всеки възможен ход на този, който е на ход; техните деца — след всеки отговор на противника; и така до края на играта.
В морски шах първият има 9 хода, вторият — 8, после 7… най-много 9! = 362 880 последователности, а различните позиции са няколко хиляди. Компютърът обхожда всичко за части от секундата и играе безгрешно. В шаха от една позиция има средно около 30 хода и дървото става необозримо още на няколко нива — за това накрая.
Оценка на позицията
За да сравняваме ходове, всяка позиция трябва да получи число. Нека компютърът играе с X. Крайните позиции са лесни:
| Позиция | Оценка |
|---|---|
| X е спечелил | +1 |
| равен резултат | 0 |
| O е спечелил | −1 |
В морски шах това стига — дървото се обхожда до края и всяка позиция получава оценка от децата си. В шаха краят е недостижим и позицията се оценява приблизително — с евристика като в урок 8. Най-простата брои материала: пешка 1, кон и офицер 3, топ 5, дама 9; оценката е „моят материал минус неговия“.
MAX, MIN и минимакс
Компютърът иска най-високата оценка — той е MAX. Противникът иска най-ниската — той е MIN. Ходовете се редуват, значи нивата в дървото се редуват: MAX, MIN, MAX, MIN…
- Оценете листата — крайните позиции или позициите на границата на дълбочината.
- Възел на ниво MIN получава най-малката оценка от децата си: противникът ще избере най-лошото за нас.
- Възел на ниво MAX получава най-голямата: ние избираме най-доброто за нас.
- Повтаряйте отдолу нагоре до корена. Най-добрият ход е детето на корена с най-висока оценка.
На дъската
Ако X има два пътя до победа, но O заплашва да спечели с един ход, в дървото това се вижда веднага: всеки ход на X, който не блокира, има дете с оценка −1 и MIN го избира. Единственият ход с оценка ≥ 0 е блокиращият. Компютърът не „знае“ правилото „блокирай“ — то следва от търсенето.
Гледане напред и границата на дълбочината
Един ход напред е дълбочина 1; вашият ход и отговорът на противника — дълбочина 2. При средно 30 хода на позиция:
| Дълбочина | Позиции за проверка |
|---|---|
| 1 | 30 |
| 2 | 900 |
| 3 | 27 000 |
| 4 | 810 000 |
| 6 | около 729 милиона |
Всяко ниво умножава работата по 30. Затова се търси до определена дълбочина и там позициите се оценяват с евристиката. Колкото по-дълбоко, толкова по-силна игра — но и толкова по-бавна.
- Ефект на хоризонта — на границата оценката не вижда какво следва: „взех кон“ изглежда добре на дълбочина 2, ако загубата на дамата е на дълбочина 3.
- Отрязване — щом за даден ход противникът вече има отговор, по-лош за нас от друг наш ход, останалите му отговори не се гледат. Това е алфа-бета отрязването и удвоява достижимата дълбочина без да променя резултата.
- Deep Blue проверява около 200 милиона позиции в секунда — и въпреки това не стига до края на партията.
Минимакс на C# (за четене)
Дъската е масив от 9 знака: 'X', 'O' или '.'. Методът връща оценката на позицията при най-добра игра на двамата. Той извиква сам себе си за всяко дете — това е рекурсия, която ще учите в Модул 2.
// Оценка на позицията при най-добра игра. Компютърът е X.
static int Minimax(char[] d, bool redNaX)
{
char p = Pobeditel(d); // 'X', 'O' или '.' (няма)
if (p == 'X') return 1;
if (p == 'O') return -1;
if (Array.IndexOf(d, '.') < 0) return 0; // пълна дъска — равен
int nayDobra = redNaX ? -2 : 2; // по-лошо от всичко възможно
for (int i = 0; i < 9; i++)
{
if (d[i] != '.') continue;
d[i] = redNaX ? 'X' : 'O'; // правим хода
int ocenka = Minimax(d, !redNaX); // какво следва след него
d[i] = '.'; // връщаме го обратно
if (redNaX && ocenka > nayDobra) nayDobra = ocenka; // MAX
if (!redNaX && ocenka < nayDobra) nayDobra = ocenka; // MIN
}
return nayDobra;
}- Първите четири реда са листата: победа, загуба, равен.
- Цикълът пробва всеки свободен ход, оценява го и го връща — така една дъска стига за цялото дърво.
- Двата
ifнакрая са цялата разлика между MAX и MIN. - За да намерите най-добрия ход, извикайте
Minimaxза всяко дете на корена и вземете най-високата оценка.
Какво трябва да запомниш
- Дърво на играта: корен — текущата позиция, нива — редуващи се ходове на двамата.
- Оценка: +1 / 0 / −1 в края; при отрязана дълбочина — евристика (например материалът).
- MAX взима най-голямата оценка от децата, MIN — най-малката. Отдолу нагоре до корена.
- Минимакс избира хода с най-добрия гарантиран резултат при най-добра игра на противника.
- Дълбочината е ограничена, защото работата расте степенно; отрязването помага, хоризонтът остава.