Більше ніж Го

Кидаючи в воду камінці, дивись на кола, утворені ними;
інакше таке кидання буде пустою забавою.


                      Козьма Прутков "Плоди роздуми
 

Ця гра — справжній довгобуд. Я почав працювати над нею ще в червні! Не можна сказати, щоб я кожен день надривався, але крові вона попсувала мені чимало. На сьогоднішній день, це мій самий складний проект в Axiom. За обсягом (вельми нетривіального) коду, MarGo порівнянна хіба що з Ритмомахией.

Що особливого в цій грі? Варто було через неї так мучитися? Я розповім, а ви самі вирішуйте.

Поверхневе подібність

Спроби «удосконалити» Го робилися неодноразово, але вони рідко бували вдалими. MarGo, на мій погляд, саме така рідкість! Головне, що підкуповує в цій грі, так це той факт, що вона є набезліччю Го. Я вже писав про те, як здавалося б прості правила Го призводять до несподівано складним тактичних побудов. Не буду повторюватися, скажу лише, що все що я писав про Го, справедливо і для MarGo теж (до тих пір, поки ми не виходимо за межі площини). Ось так, наприклад, виглядає знаменита "защелка" — найпростіша жертва одного каменю, з метою отримання трьох:



До речі сказатиВикористання будь-яких «зачіпок», на позиції вище, абсолютно зайве! Чорні вже приречені, «доїдати» їх немає необхідності. Але чому в MarGo використовуються такі маленькі дошки? Головна причина полягає в тому, що гра йде не на площині, а в об'ємі. Гравці будують піраміди і навряд чи у когось вистачить терпіння займатися цим на дошці 19x19. Стандартна для MarGo дошка 9x9, в Го використовується, в основному, для навчання новачків!

Для мене це стало великим полегшенням, оскільки вже при використанні дошки 9x9, я зіткнувся з проблемами нестачі пам'яті. Мені довелося оптимізувати її використання, прибравши з масиву зайві рядки. Цей момент досить очевидний — ладу піраміду, ми ніколи не зможемо розмістити фігури на більшій частині полів тривимірної дошки.

При визначенні статусу груп, значення мають лише порожні пункти, розташовані в площині дошки. Кожен камінь «живий» групи повинен бути пов'язаний (безпосередньо або через інші камені) хоча б з одним з таких пунктів (званих "дамэ"). Як тільки останнім з дамэ буде закрито, група стає мертвою і знімається з дошки.

Як це працює?Основа функціональності Го — механізм видалення каменів, які потрапили в оточення. Він працює досить просто. Для початку, необхідно визначити камені, які є живими безумовно (вони сусідять з порожніми пунктами на дошці). Потім, слід додавати до групи «живих» каменів всі камені (того ж кольору), що є сусідами з будь-якими з доданих раніше.

Все б нічого, якби не безглузда семантика виконання ходу в ZoG/Axiom. На всьому протязі побудови ходу, вміст дошки виглядає так, як і на момент початку розрахунку (всі зміни, виконані ходом стануть видимі лише після його завершення). У простих випадках, з цим можна боротися, але наша гра ніколи простою не була! В силу того, що ми створюємо ілюзію тривимірності, додавання на дошку всього однієї фігури може призводити до переміщення великої кількості фігур-тайлів. Обробляти всі ці «особливі випадки» спеціальним чином — абсолютно нереально! Мені довелося розділити додавання нової фігури на дошку і подальше видалення «вбитих» фігур:

{players
{player} W
{player} B
{player} ?C {random}
players}

{turn-order
{turn} W {of-type} normal
{turn} ?C {for-player} W {of-type} clean
{turn} B {of-type} normal
{turn} ?C {for-player} B {of-type} clean
turn-order}

Щоб «очищення» виконувалася автоматично, довелося створити ще одного гравця, який діє від імені власників фігур. Його хід полягає в розміщенні на одній з невикористовуваних полів дошки (таких полів залишилося багато, не дивлячись на оптимізацію) спеціальної (невидимої) фігури. Всі дії з видалення «мертвих» груп виконуються в якості «побічного ефекту» цього ходу:

: drop-m ( -- )
тут a1 = verify ( Це поле? )
drop ( Ставимо фігуру )
['] my-enemy? ( Обробляємо фігури противника )
init-alive ( Додаємо в масив безумовно живі фігури )
proceed-alive ( і всіх їхніх сусідів )
capture-all ( Видаляємо всі фігури не опинилися в побудованому масиві )
captured-tiles @ 0= IF ( Якщо не видалено жодної фігури )
['] my-friend? ( Повторюємо ті ж дії для дружніх фігур )
init-alive 
proceed-alive 
capture-all
ENDIF
add-move ( Завершуємо генерацію ходу )
;

Цей код вирішує відразу два завдання:

  1. Видалення всіх ворожих груп, убитих останнім ходом
  2. Самогубство дружньої групи, якщо хід був самовбивчим і нікого з ворогів вбити не вдалося
Більше подробиць
TOTAL [] alive[] ( Масив живих каменів )
VARIABLE alive-count ( і його розмір )

: not alive? ( -- ? ) ( Пошук в масиві )
TRUE
0 BEGIN
DUP alive-count @ < IF
DUP alive[] @ here = IF
SWAP DROP FALSE SWAP
TRUE
ELSE
1+
FALSE
ENDIF
ELSE
TRUE
ENDIF
UNTIL DROP
;

: add-alive ( -- ) ( Додавання в масив )
not alive? alive-count @ TOTAL < AND IF ( не забуваємо перевіряти повтори! )
here alive-count @ alive[] !
alive-count ++
ENDIF
;

: check-alive ( 'op 'dir -- ) ( Перевірка дружності сусіднього каменю )
EXECUTE IF
EXECUTE IF
add-alive
ENDIF
ELSE
DROP
ENDIF
;

: init-alive ( 'op -- 'op ) ( Ініціалізація масиву )
0 alive-count !
0 BEGIN ( Всі шукані камені розташовані в площині дошки! )
DUP empty-at? IF
DUP to OVER ['] n check-alive
DUP to OVER ['] s check-alive
DUP to OVER ['] w check-alive
DUP to OVER ['] e check-alive 
ENDIF
1+ DUP PLANE >=
UNTIL DROP
;

: proceed-alive ( 'op -- 'op ) ( Додавання сусідів "живих" каміння )
0 BEGIN ( і їхніх сусідів теж )
DUP alive-count @ < IF
DUP alive[] @ to OVER ['] n check-alive
DUP alive[] @ to OVER ['] s check-alive
DUP alive[] @ to OVER ['] w check-alive
DUP alive[] @ to OVER ['] e check-alive
1+ FALSE
ELSE
TRUE
ENDIF
UNTIL DROP
;



Невеликий розмір дошки призводить до високої конкуренції за дамэ. Ті з вас, хто грає в Го, повинні знати, що сутички на малій дошці (9x9) можуть бути набагато більш запеклими, ніж гра на стандартній дошці (19x19). З самого першого ходу, гравці входять у щільне зіткнення і змушені безперервно вирішувати завдання «життя і смерті». Така гра в MarGo, але якщо б цим і обмежувалося, я не став би про неї розповідати.

Основна відмінність

Назва гри складається з двох слів: «мармур» (кульки) і «Go». Разом виходить — «гра в Го кульками». У чому відмінність від традиційної гри? В її тривимірності! Спроби гри в Го на тривимірних дошках робилися неодноразово (я писав про програму, що дозволяє грати на довільних графах), але, в більшості таких випадків, серйозно страждав ігровий баланс. Винятком, мабуть, є лише дошка, що імітує кристалічну решітку алмазу. Вона багато в чому подібна до класичної плоскої дошці. Кожен вузол має від 2 до 4 сусідів:


Підхід MarGo абсолютно відмінний. Кулька не може просто так «висіти в повітрі». Щоб «піднятися над дошкою» він повинен спиратися на чотири інших кульки (свої або супротивника). Зрозуміло, для того, щоб цей кулька залишався живим, він повинен контактувати з групою, що має вихід хоча б на одне дамэ. До ортогональним сполук, що лежить в площині дошки, додаються вертикальні напрямки, що з'єднують кульки знаходяться в основі піраміди з її вершиною.

Ця зв'язок працюють в обидві сторони. Доступ до дамэ не тільки дає життя «верхівці» піраміди, але й може рухатися далі, в нижні шари, огинаючи фігури супротивника лежать в площині дошки. Одне це може зробити гру більш цікавою, але є ще один, набагато менш очевидний момент. Поки камінь на вершині піраміди залишається «живим», каміння з її підстави не можуть бути знищені, навіть якщо вони належать «мертвої» групі!



Такі камені, позбавлені доступу до дамэ і пригнічені фігурами іншого кольору, називаються «зомбі» і вони залишаються на дошці, коли інша частина «убитої» групи її покидає. Зомбі прирівнюються до захопленим камінню (знятим з дошки), але поки що вони залишаються на дошці, вони можуть «передавати» доступ до дамэ іншим камінню (якщо він раптом з'явиться). Крім того, зомбі можна повернути до життя, знявши верхівку піраміди.

Вторгнення в тривимірністьЯ вже писав про те, що для створення ефекту тривимірності, мені довелося розділити кожну фігуру на 4 тайла. Це працює, але заковика полягає в тому, що фігури-тайли доводиться розташовувати на «тривимірної» дошці досить химерним чином. Уникнути цього ніяк не можна. Всі тайли, видимі «зверху», повинні знаходитися в одній площині. У цієї схеми розміщення фігур є і свої додаткові плюси, але навігація в ній аж надто заковыриста. Для початку, мені знадобилася мандала для медитацій:

4 A A
3 8 8/9 9
2 5 5/6 6/7 7
1 1 1/2 2/3 3/4 4
+ A B C D E F G H
1 1/5/8|A|9|7|4
2 1/2 2/3 3/4
3 5/6 6/7
4 8/9

Згоден, картинка — так собі, але вона дозволила мені якось орієнтуватися. Мені були потрібні напрямки для природного переміщення всередині цієї схеми. На щастя, з точки зору Axiom, напрямки — це всього лише функції, змінюють положення маркера поточної позиції в якості побічного ефекту свого виклику (основним результатом виклику такої функції є повернення булевского значення, що означає успішність переміщення). Загалом, напряму можна було ігнорувати і, незабаром, у мене виникла ціла їх ієрархія:



На схемі показані лише функції, провідні строго на «північ» (в якомусь сенсі). Частину з них я вже використовував раніше. Напрямок 'n', наприклад, мені довелося ввести коли я викидав із масиву «дошки» зайві рядки, з метою оптимізації використання пам'яті. Головна частина «ракетної науки» — функція, що дозволяє переміщатися в площині, паралельній площині дошки:

Навіть не буду намагатися це коментувати
: common-internal ( 'dir -- ? )
here is-plane? IF
get-height SWAP EXECUTE IF
get-height -
DUP 0= IF
DROP TRUE
ELSE
0> IF
FALSE
ELSE
BEGIN d NOT empty? OR UNTIL
empty? IF u verify ENDIF
TRUE
ENDIF 
ENDIF
ELSE
DROP FALSE
ENDIF
ELSE
here OVER EXECUTE NOT empty? OR IF
to BEGIN u NOT UNTIL
EXECUTE
ELSE
2DROP TRUE
ENDIF
ENDIF
;


Велика частина згодом знайдених помилок була пов'язана саме з цією функцією (і я до сих пір не впевнений, що виправив їх всі). На її основі конструюються такі переміщення як north-internal, south-internal і т. д.:

: north-internal ( -- ? ) ['] n common-internal ;
: south-internal ( -- ? ) ['] s common-internal ;
: west-internal ( -- ? ) ['] w common-internal ;
: east-internal ( -- ? ) ['] e common-internal ;

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

: wrap-direction ( 'dir -- ? )
here ( Запам'ятовуємо становище "маркера" )
SWAP EXECUTE IF ( і викликаємо базову функцію переміщення )
DROP TRUE ( якщо все пройшло успішно, відкидаємо збережене значення )
ELSE
to FALSE в іншому випадку, повертаємо "маркер" у вихідну позицію )
ENDIF
;

: north ( -- ? ) ['] north-internal wrap-direction ;
: south ( -- ? ) ['] south-internal wrap-direction ;
: west ( -- ? ) ['] west-internal wrap-direction ;
: east ( -- ? ) ['] east-internal wrap-direction ;

Залишилося зовсім небагато. Крім «горизонтальних» переміщень, що лежать у площині дошки, необхідні напрямки, що ведуть з однієї площини в іншу («вгору» і «вниз»):

Ще трохи дивного коду без коментарів
: up-internal ( -- ? ) 
here is-plane? IF
FALSE
ELSE
d NOT empty? OR IF
BEGIN u NOT UNTIL
ENDIF
TRUE
ENDIF
;

: down-internal ( -- ? ) 
here is-plane? IF
d NOT empty? OR IF
FALSE
ELSE
BEGIN d NOT empty? OR UNTIL
empty? IF
u verify
ENDIF
TRUE
ENDIF
ELSE
u verify
here is-plane? NOT
ENDIF
;

: up ( -- ? ) ['] up-internal wrap-direction ;
: down ( -- ? ) ['] down-internal wrap-direction ;


На цьому все! Тепер у нас є повний комплект напрямів, необхідних для виявлення «зв'язкових» груп.

Зомбі — цікава нова сутність, породжена простими і логічними правилами гри. Цікава, але не єдина! MarGo припасла інші сюрпризи.

Мости і ущелини

Каміння на вершині піраміди «передають» доступ до дамэ дружнім камінню, можливо опинилися б в оточенні, відбувається все на площині. З'являється ще один спосіб уникнути оточення! Але, постійте, це саме та причина, по якій грати в Го трьох вимірах не дуже-то цікаво. Групи стає надто важко вбити! Є спосіб все виправити.



В Го таке з'єднання вважається принципово не разрезаемым, незалежно від можливих помилок побудував його гравця. Камені, що стоять поруч, живуть і помирають разом. Їх неможливо розділити, але тільки не в MarGo! У цій грі, що стоять впритул камені можна «розрізати», спорудивши над ними міст. Камені, що опинилися внизу, по різні боки мосту, перетворюються на дві розділені групи.

По той бік мостуСуть «розрізання» полягає в тому, що ми не можемо рухатися в обраному напрямку. Це означає, що нам доведеться розширити ієрархію напрямків, про яку я писав вище, додавши новий напрямок, що контролює наявність «над головою» моста, побудованого з чужих фігур. Оскільки правило «розрізання мостами» дуже схоже на опцію (гра може бути цікавою і без нього), визначимо прапор-константу, що керує її роботою:

TRUE CONSTANT BRIDGE-CUTTING

Далі все просто
: is-covered? ( -- ? )
player up empty? AND IF NOT
player <>
ELSE
DROP FALSE
ENDIF
;

: check-bridge? ( 'dir piece-type -- ? )
piece-type SWAP equal-types? IF
here
is-covered? IF
SWAP OVER to
EXECUTE IF
is-covered?
To SWAP
ELSE
to FALSE
ENDIF
ELSE
to DROP FALSE
ENDIF
ELSE
DROP FALSE
ENDIF
;

: common-cutting ( 'dir 'dir piece-type 'dir piece-type -- ? )
BRIDGE-CUTTING IF
check-bridge? IF
2DROP TRUE
ELSE
check-bridge?
ENDIF
ELSE
2DROP 2DROP
FALSE
ENDIF
IF DROP FALSE ELSE EXECUTE ENDIF
;

: north-cutting ( -- ) ['] north ['] east nw-piece ['] west ne-piece common-cutting ;
: south-cutting ( -- ) ['] south ['] east sw-piece ['] west se-piece common-cutting ;
: west-cutting ( -- ) ['] west ['] south nw-piece ['] north sw-piece common-cutting ;
: east-cutting ( -- ) ['] east ['] south ne-piece ['] north se-piece common-cutting ;


Вся магія прихована в check-bridge?. Для визначення «мосту» над головою, «дивимося» наверх, в пошуках чужого тайла. Те ж робимо і на сусідньому тайлі. Якщо обидва тайла «покриті» фігурами чужого кольору (різними), «перерубаем» відповідний напрям, замінюючи повернуте їм значення помилковим.

Думаєте, що на цьому сюрпризи скінчилися? Як би не так!

найскладніший кейс

Самогубні ходи заборонені (і здебільшого марні). Гравець не може помістити свій камінь «оточення», якщо при цьому не бере ні одного каменю противника. В Го, з цим все просто. Якщо ми беремо якусь групу каменів, вона повинна контактувати з вставленим каменем і її «вбивство» відкриє дамэ, необхідні нам для виживання. Але в MarGo є зомбі!



Навіть взявши камінь на вершині піраміди, білий камінь все одно опиниться «в оточенні», створивши тим самим неприпустиму позицію! Два чорних каменю становлять «віртуальну групу», захищену «зомбі» лежачим у її основі. Забавно, що це захист дуже ефемерна. Якщо білому, з якоїсь причини, вдасться взяти будь-який з сусідніх з ним чотирьох каменів, захист «віртуальної групи» перестане діяти. Це не просто штучне побудова. Віртуальні групи — важлива тактична складова гри MarGo! Що ви скажете, наприклад, про статус цієї позиції?



Виглядає як повністю оточена група (з одним «оком»), яку почав поїдати білий. В принципі, так воно і є, але «з'їсти» групу чорних не так просто. Білий не може просто сходити в лівий нижній кут. Оскільки два білих каменю оточують лише «зомбі», такий хід буде вважатися самовбивчим. Але і чорним не варто поспішати з поїданням вторгся білого каменю:



Чорний додає на дошку камінь, який не є «зомбі», руйнуючи тим самим захист «віртуальної групи». Білий отримує право сходити в той самий пункт, де знаходився його щойно з'їдений камінь, забираючи п'ять чорних каменів. Виходить, що найкраще рішення для чорних — не робити нічого? Звичайно ж, це не так. Білий має ще одну можливість для вбивства групи.



Якщо чорний проігнорує цю загрозу — його група приречена! Поєднавши свої групи, білий гравець отримає можливість безпечно зайняти останнє дамэ. Захист від такого вторгнення очевидна. Кращий хід супротивника — це і твій найкращий хід!

Дійсно складноЯк я вже писав вище, додавання гравцем каменю і видалення «мертвих» груп мені довелося розділити на два наступних один за одним ходу (інакше все виходило зовсім складно). Крім інших незручностей, це означає, що я не можу заборонити гравцеві зробити хід, просто на підставі того, що він «самоубийственен». В принципі, це не дуже велика біда. Дозволимо зробити хід і нехай доданий камінь просто помре (можливо з групою інших каменів свого кольору), але це тільки частина проблеми! Пам'ятаєте, я писав щось на зразок такого коду?

: drop-m ( -- )
тут a1 = verify
drop
['] my-enemy? init-alive proceed-alive check-zombies capture-all
captured-tiles @ 0= IF
['] my-friend? init-alive proceed-alive check-zombies capture-all
captured-tiles @ NEGATE
update-variables
ELSE
captured-tiles @
update-variables
ENDIF
add-move
;

Намагаємося прибрати камені супротивника і, якщо це не вдалося, намагаємося прибрати свої (попутно підраховуючи взяті камені). Так от, цей код не працює! Дійсно, якщо розглянути наш хитрий кейс, то можна помітити, що камені супротивника знімаються, але доданий камінь все одно залишається в оточенні! Це дійсно проблема. Для того щоб все працювало нормально, ми повинні зняти мертві камені супротивника, потім свої і, якщо це вдалося, повернути камені супротивника на своє місце. Так, у Axiom є функції, що дозволяють створити копію дошки, внести в неї якісь зміни, а потім все відкотити назад, але мені не хотілося б їх тут використовувати! На щастя, є інший, прекрасно функціонуючий механізм відкоту змін.

Розділимо код видалення «мертвих» груп на дві частини
{players
{player} W
{player} B
{player} ?C {random}
players}

{turn-order
{turn} W {of-type} high-priority
{turn} ?C {for-player} W
{turn} B {of-type} high-priority
{turn} ?C {for-player} B
turn-order}

{move-priorities
{move-priority} normal-priority
{move-priority} low-priority
move-priorities}

{moves w-drop
{move} drop-w {move-type} high-priority
{move} drop-nw {move-type} high-priority
moves}

{moves n-drop
{move} drop-n {move-type} high-priority
{move} drop-ne {move-type} high-priority
moves}

{moves e-drop
{move} drop-e {move-type} high-priority
{move} drop-se {move-type} high-priority
moves}

{moves s-drop
{move} drop-s {move-type} high-priority
{move} drop-sw {move-type} high-priority
moves}

{moves m-drop
{move} clear-e {move-type} normal-priority
{move} clear-f {move-type} low-priority
moves}

{pieces
{piece} M {drops} m-drop
{piece} tw {drops} w-drop
{piece} zw
{piece} ww
{piece} bw
{piece} tn {drops} n-drop
{piece} zn
{piece} wn
{piece} bn
{piece} te {drops} e-drop
{piece} ze
{piece} we
{piece} be
{piece} ts {drops} s-drop
{piece} zs
{piece} ws
{piece} bs
pieces}


З більш високим (normal) пріоритетом буде виконуватися код видалення мертвих груп противника (clear-e), а з низьким (low) — видалення своїх мертвих груп (рівень high, поза списку пріоритетів, резервуємо для звичайного додавання каменів на дошку). Тепер все працює як треба. Спочатку генератор ходів намагається виконати більш пріоритетний clear-e, в кінці якого ми перевіряємо, чи не потрапив доданий камінь в оточення (забороняючи хід, якщо це відбулося). Якщо пріоритетний хід провалив якусь з перевірок, генератор ходів сам відкочує всі зміни, і відпрацьовує низкоприоритетный clear-f. Цей код завжди виконується успішно. Іноді побічним ефектом його виконання є видалення «самоубитых» груп.

Код очищення теж ускладнився
: clear-e ( -- )
0 captured-count !
тут a1 = verify
drop
['] my-enemy? init-alive proceed-alive check-zombies capture-all
captured-tiles @ 0> verify
captured-tiles @ update-variables
['] my-friend? init-alive proceed-alive check-zombies check-not-captured
add-move
;

: clear-f ( -- )
0 captured-count !
тут a1 = verify
drop
['] my-friend? init-alive proceed-alive check-zombies capture-all
captured-tiles @ 0> IF
captured-tiles @ NEGATE
update-variables
ELSE
DROP
ENDIF
add-move
;


В цілому, трошки замороченно, але це працює.

Без Ко Го немає

Ну ладно, є ще один непростий кейс, благополучно успадкований від більш традиційної версії гри. Оскільки, як я вже говорив, у площині дошки всі правила року залишаються в силі, цілком можна зробити наступний фокус:



Якщо якось не обламати всю цю веселухи, гравці отримають можливість пожирати камені один одного до нескінченності. Зрозуміло, MarGo допустити такого не може і забороняє ходи, що ведуть до повторення попередньої позиції. правила гри, мова йде про ситуаційному суперко.



Чорний не може з'їсти білий камінь негайно і змушений ходити в іншій частині дошки. Наступним ходом, білий може з'єднатися.

З життя невидимокна Жаль, мені не реалізувати виявлення ні позиційного ні ситуаційного суперко. Для цього необхідна інформація про попередніх позиціях (хоча б хеші), а у мене його немає! На щастя, всі ці "циклічні до", "вічні життя" та інші екзотичні позиції не роблять погоди. У реальному Ко-боротьбі, практично завжди, фігурує просте До. Його і будемо ловити.

Щоб заборонити хід в «порожній» пункт, треба зробити його не порожнім
: drop-marks ( -- )
0 BEGIN
DUP captured-count @ < IF
mark OVER captured[] @ create-piece-type-at
1+ FALSE
ELSE
TRUE
ENDIF
UNTIL DROP
;

: clear-marks ( -- )
0 BEGIN
DUP empty-at? IF NOT
DUP piece-type-at mark = IF
DUP capture-at
ENDIF
ENDIF
1+ DUP PLANE >=
UNTIL DROP
;


Ми можемо розміщувати на дошку невидимі тайли, щоб зробити неможливим хід в обрану позицію. При виконанні ходу в будь-який дозволений пункт, будемо просто видаляти ці перешкоди. Тут нам на руку грає одну з важливих властивостей MarGo. Будь-яка Ко-боротьба завжди буде відбуватися в площині підстави дошки! Щоб додані «порожні» тайли не заважали визначенню статусу груп, змінимо функцію визначення порожнечі сайту:

: my-empty-at? ( pos -- ? )
DUP curr-pos !
- empty-at? IF
+ DUP empty-at? SWAP piece-type-at mark = OR IF
TRUE
ELSE
...
ENDIF
;

Залишилося додати До-позначку на дошку. Ми робимо це, на місці знятого каменю противника, за умови, що, якщо б цей камінь не був знятий, «самоубившаяся» група складалася б з одного, щойно доданого каменю. Звучить складно? Загалом-то так воно і є.

Що за кадром?

Зрозуміло, не все пішло гладко. Деякі правила реалізувати я просто не зміг. Самогубні ходи, наприклад, MarGo безумовно заборонені. Це означає, що гравець не має права зробити хід позбавляє групу (можливо що складається лише з одного, щойно доданого каменю) останнього дамэ, за умови, що не бере цим ходом камені супротивника.

Я був змушений розділити ходи виконують додавання каменю і видалення взятих каменів, що позбавив мене всякої можливості заборони «самогубних» ходів. Це може здатися дрібницею. Зрештою, хоча «самогубні» ходи і заборонені в більшості варіантів правил Го, правила Інга їх допускають. На те є вагома причина. Існують (дуже рідкісні) позиції, в яких вбивство власної групи дозволяє гравцеві врятуватися в абсолютно безнадійній ситуації.



Секі не принесе очок, але в порівнянні з повною втратою групи, це серйозна підмога, адже супротивник не отримає очок теж! На жаль, одне тягне за собою інше. У MarGo (на відміну від Го) гравцям забороняється пропускати хід. Але, потрапивши в безвихідну ситуацію, гравець, практично завжди, буде мати можливість зробити хід, що приводить до самогубства щойно доданого каменю. Що це як не пропуск ходу? І це те, що я також не можу заборонити.

У будь-якому випадку, ці відмінності не настільки значущі, щоб про них варто переживати. З AI справа набагато гірше. У поточній реалізації його просто немає! Додаток можна використовувати як «інтелектуальну» дошку для гри двох осіб або для розбору етюдів, але пограти з нею не вийде. Справа навіть не в самій складності AI для Го (розробникам ZoG довелося використовувати DLL-engine і грає він не дуже добре). Перш ніж думати про AI, необхідно реалізувати, принаймні, логіку підрахунку очок.

Метою гри Го є не «поїдання» каменів супротивника (хоча вони теж йдуть у залік), а захоплення території (про те, чим це відрізняється від китайських правил, я писав тут). Територією гравця вважаються всі порожні пункти, дістатися від яких можна лише до постатей свого кольору. MarGo розширює і це поняття. До традиційних пунктів території, розташованим в площині дошки, додаються пункти на «майданчиках», складаються із чотирьох каменів свого кольору. Якщо подумати, це теж територія. На цих пунктах можна розмістити камені і «дістатися» від них можна лише до своїх каменів. Підкреслю, що дамэ такі пункти не є! Життя групи забезпечують лише вільні пункти, розташовані в площині дошки.



Підрахунок території, визначеній таким чином, справа складна, але цілком вирішуване. На жаль, цим справа не обмежується. MarGo додає до території ті пункти, які гравець може заповнити «потенційно». Наприклад, якщо у гравця є квадрат 4x4, що складається з 16 фігур свого кольору, то на його «майданчиках» він зможе розмістити до 4 каменю (в свою чергу утворюють нову площадку, для ще одного каменю). Крім цього, якщо початковий квадрат 4x4 не заповнений, внутрішні його пункти додаються також до території. Все це звучить логічно, але поки я боюся навіть братися за таку задачку на ForthScript.



Підрахунок захоплених каменів — теж справа нехитра. Крім каменів, захоплених в процесі гри, до них, ясна річ, додаються «зомбі» (ладно, їх можна порахувати), а також каміння, складові групи, які виявилися, до кінця гри, в безнадійній ситуації (а ось тут все погано). Зазвичай, перед підрахунком очок, гравцям надається можливість ручного видалення «мертвих» груп, але в ZoG це та ще задачка!

Навіть якщо мені вдасться вирішити всі ці проблеми, то це зовсім не означає, що я зможу реалізувати хоч скільки небудь сильний AI для цієї гри. Хоча спробувати, напевно, варто.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.