Робота над помилками

— Люди забули цю істину, — мовив Лис,
— але ти не забувай:
       ти назавжди у відповіді за всіх, кого приручив…
 
       Антуан де Сент-Екзюпері "Маленький принц"


Не помиляється тільки той, хто нічого не робить. З часом, помилки накопичуються. Та що там говорити, прямо зараз у мене на руках є з десяток use case-ів, які призводять до різних помилок, в одних тільки "Квантових хрестики-ноликах" і зовсім не вистачає духу, щоб ними зайнятися (код там дійсно адовый). Але іноді я знаходжу час, щоб щось виправити. І знаєте що? Виправляти старі помилки нітрохи не менш цікаво, ніж робити нові!

Старий Ур в нових одежинах

Ур — одна з моїх самих перших ігор (якщо не на Zillions of Games, Axiom вже точно). Це гра, з якої почалося моє захоплення настільними іграми! Зрозуміло, мова йде не про реконструкціях Белла або Мюррея. При всіх своїх перевагах, ці вчені мужі, по всій видимості, рідко грали в настільні ігри (у всякому разі, за своїми правилами). Єдиний, на мій погляд, відтворюватися варіант правил, для цієї гри, був розроблений Дмитром Скирюком.

Виглядала гра страшненько

На щастя, в творчий процес практично відразу включився Nomad1, розробивши чудову реалізацію гри під Android і iOS. Слід зауважити, що в розробці брав участь ще один з авторів Хабра. Без чудових музичних тем, створених 1eqinfinity, гра і наполовину не вийшла б настільки атмосферної. Ресурси Android-версії дозволили значно поліпшити зовнішній вигляд ZoG-версії, але один з важливих моментів не давався мені ні в яку.

вся справа в «стопки» фігурЗа правилами запропонованим Дмитром Скирюком, на дошці є кілька позицій, що дозволяють встановлювати до чотирьох ігрових фішок «один на одного». Працює така стопка за принципом LIFO — фішка зайшла на позицію першою йде останньою. Цей механізм дозволяє затримувати просування фішок супротивника, «притискаючи» їх своїми. Саме з цим виникли проблеми. В рамках моделі Zillions of Games, кожне поле дошки може містити не більше однієї фігури.

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

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


Почав я з ПулукаДавно хотів зробити цю гру. У «Пулуке» фішки можуть ставитися один на одного, але, на відміну від «Фокуса», немає обмеження на розмір стопки. Оскільки «складові» фігури використовувати не можна було, з'явилася ідея намалювати тайли фішок з невеликим накладенням один на одного. І тут мені здорово допомогли напрацювання від ще однієї ігри. Справа в тому, що в дошку ZRF можна визначати двома способами. Найпростіше визначити grid:
(grid
(start-rectangle 0 54 50 104)
(dimensions
("a/b/c/d/e/f/g" (18 0)) ; files
("10/9/8/7/6/5/4/3/2" (0 50)) ; ranks
)
)
Число 18 (в дужках) — це зміщення, на яке зсувається тайл фігури при зміні позиції по горизонталі. Оскільки ширина самого тайла дорівнює 30, фігури «накладаються» один на одного. Виглядає це не дуже добре:



На щастя, є спосіб виправити ситуацію! Цей невеликий скрипт вже один раз допоміг мені з «Чейзом», допоможе і тепер. Він не дуже правильний, некоректно працює з від'ємними числами і численними описами grid-ів, але економить мені купу часу. Ось як виглядає гра після його застосування:



Секрет простий — тепер, кожна позиція описується індивідуально. Порядок опису позицій визначає z-послідовність «накладання» тайлів один на одного:
(positions
...
(g5 108 304 158 354)
(f5 90 304 140 354)
(e5 72 304 122 354)
(d5 54 304 104 354)
(c5 36 304 86 354)
(b5 18 304 68 354)
(a5 0 304 50 354)
...
)

На жаль, із-за специфічного для «Пулука» групового переміщення фігур, картинка «ламається». Накрита фігура переміщується другий і отрісовиваємих пізніше фігури переміщуваного гравцем. Зробити з цим нічого не можна, так працює Zillions of Games:



Пулук просто не підходить для такого способу відображення фігур. А ось Ур — підходить!

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


Підхід працює, але в деяких випадках картинка ламається:

загалом-то, тут все зрозуміло. Ми маємо справу з прямокутними тайлами, частково намальованими «прозорим» кольором, але коли ZoG розмальовує «прозорий» колір бере частину зображення дошки (фонового малюнка), а не фігури перекрила те ж місце. Я вже стикався з подібним коли малював фігури для "MarGo" і знаю як з цим боротися. Потрібно всього лише намалювати додаткові тайли з зафарбованими правими-нижніми кутами. Колір заливки повинен збігатися з кольором лежить нижче фігури. 

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

Ось як це виглядає
(define refresh
(if (im-white?)
(if friend?
(create White $1)
else
(create Black $1)
)
else
(if friend?
(create Black $1)
else
(create White $1)
)
)
)

(define check-refresh
(if (on-board? down)
(if (or (and (flag? is-enemy?) not-enemy?)
(and (not-flag? is-enemy?) enemy?)
)
(refresh $3)
else
(refresh $2)
)
else
(refresh $1)
)
)

(define pre-action
...
(if (on-board? down)
mark down
(while (and (on-board? up) (not-empty? up))
(if (or (piece? King) (piece? KingE) (piece? KingF))
(check-refresh King KingF KingE)
else
(check-refresh Man ManF ManE)
)
(set-flag is-enemy? enemy?)
up
)
back
)
)


Фактично, всі фігури «стопки» пересоздаются з тими ж типами, в рамках виконання ходу знімає верхню фішку. Це вже цілком працездатний рішення. Зрозуміло, якщо уважно стежити за анімацією, можна помітити короткочасне руйнування картинки при знятті фішки або анімацію переміщення фігури з залитим «куточком», але по завершенні ходу картинка відновлюється в цілком прийнятний стан. Мабуть, це найкраще, чого можна досягти засобами Zillions of Games.

На жаль, є ще одна проблема
Жовті прямокутники — це регіони пов'язані з фігурами. Можна бачити, що в «стопки» верхні регіони перекривають нижні. В цьому і криється проблема. Для того, щоб «клікнути» по фішці лежить в основі стопки, потрібно потрапити мишею у вузеньку смужку шириною в кілька пікселів. В цьому трохи допомагає курсор миші — він змінює форму коли наводиться на фігуру здатну виконати хід, але тут є велика засідка.

Оболонка Zillions of Games підтримує досить хитру опцію, пов'язану з UI. При включенні «smart moves», гравцеві вже не обов'язково перетягувати фігури по дошці мишею. Якщо ми клікаємо по фігурі, здатної виконати єдиний хід — хід буде виконано автоматично, без усякого перетягування. У разі Ура, це дуже зручно (а ось в Шахах, іноді, буває не дуже). Але в опції є і зворотна сторона — якщо ми клікаємо по пустому полю, на яке може бути виконаний єдиний хід, то цей хід буде виконаний! Тепер уявімо собі стопку з трьох фігур. Верхній прямокутник істотно перевершує по площі двухпиксельные куточки і, якщо в нього можливий хід, то трохи дрогнувшая рука може запросто призвести до поразки у всій партії.

В якості бонусу, я отримав несподіваний подарунок. Взагалі, я планував переписати Axiom-версію слідом за ZRF, але це не знадобилося! Через спрощення логіки гри, AI став справлятися з грою набагато краще. Досить сказати, що іноді він у мене виграє, а в більшості випадків, не встигає провести лише одну фішку. Він все ще гірше Android-версії, але з ним вже цілком приємно грати.

Полювання на «Слона»

була не моя помилка, але вже дуже свербіли руки її виправити. Не знаю чому. Мова йде про одну досить оригінальною шахмато-подібної грі, нібито популярною серед пігмеїв Ітурі. Зі слів автора опис гри було виявлено його другом в щоденниках французького місіонера Maurice Morceau, датованих 1821 роком:

The first written reference to the game of Elephant Hunt was found in the diaries of father Maurice Morceau, a French missionary who disappeared without a trace in 1821 while on a mission to the Ituri forest. His personal effects, including the diaries, were later found in a cannibal village by an anthropological expedition.
На жаль, ми маємо справу лише з авторською реконструкцією. Опис гри було очевидно неповним, доступу до першоджерела немає, а контакт з одним згодом був втрачений. Зокрема, викликає сумнів використання в грі переміщення "ходом коня" — занадто абстрактного для подібної традиційної гри:

The author did that mention the Elephant moved on the 5x5 field on which the 10x10 field for the Pygmies was 'overlaid by halving', and that the Pygmies moved 'by hopping about, like our chess-knight' but I personally doubt they actually made a Knight-move, which is sort of abstract. However, other possible alternatives (like D and/or A) seem to me to be out of the question, as the Pygmies cannot possibly win if colorbound.
До речі, в цій цитаті використовується "Ralph Betza's «funny notation»", про яку я вже писав раніше. Чому мене так зацікавила ця гра? Спробуємо розібратися.


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

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

Трохи коду
(define Еле-shift (
$1
(not verify-friend?)
(verify (not-friend? ul)) 
(verify (not-friend? ur)) 
(verify (not-friend? dl)) 
(verify (not-friend? dr))
(set-attribute vulnerable?
(and (attacked? ul) (attacked? ur) (attacked? dl) (attacked? dr)))
(capture ul) (capture ur) (capture dl) (capture dr)
add
))

(define Shaman-shift2 (
$1 (if empty? add $2)
(verify (and not-friend? (or (empty? c) (vulnerable? c))))
(if (not-empty? c) (capture c))
add
))

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


Корінь зла тут:

(if empty? add ...)

Якщо клітина порожня — то ми на неї встаємо (і рухаємося далі, але це в іншій реальності). Фокус в тому, що клітина «під слоном» справді порожня. Вона в іншому grid-е, маленька і «слон» не вмістився б в ній, при всьому бажанні. Це виправити досить легко:

(define my-empty?
(and not-friend? (or (empty? c) (vulnerable? c)))
)
...
(if (my-empty?)
(if (not-empty? c) (capture c))
add 
$2)
...

Далі — більше:


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

(set-attribute vulnerable?
(and (attacked? ul) (attacked? ur) (attacked? dl) (attacked? dr)))

Все досить прозоро і це б навіть працювало, якщо б у грі брали участь одні тільки «пігмеї». Виконуючи бій «ходом коня», вони не атакують поля квадрати 2x2, на якому знаходяться. Шаман — інша справа! Він «покриває» 3 з 4 полів квадрата, на якому стоїть. Залишається «покрити» поле на якому розташувався сам шаман і западня готова. І не важливо, що шаман буде з'їдений — атрибут уразливості буде вже встановлений і буде діяти весь наступний хід!

Зрозумівши помилку, можна легко виправити. Сам патч досить багатослівний, але його суть проста. Ми відмовляємося від використання предиката attacked? в цій грі (він тут просто не підходить) і замінюємо його ручними перевірками. Більш гнучкий підхід дозволяє передбачити спеціальну обробку для «шамана». За зовнішнім виглядом гри, на жаль, зробити нічого не можна. Картинка постійно «ламається». Ця гра не дуже підходить для Zillions of Games (але сама ідея, безумовно, цікава).

Не оглядайся!

У процесі роботи над новими іграми, я дізнаюся платформу Zillions of Games краще. В голову приходять абсолютно нові рішення, до яких я не міг додуматися раніше. Часто вони виявляються настільки гарні, що дозволяють виправити інші, більш старі ігри. Так, в одній з статей Дмитра Скірюка, описується дуже оригінальна гра "Поединок", в якій замість шахових фігур використовуються гральні кубики.



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

Подібна різновид переміщення (з можливою зміною напрямку на кожному кроці) в настільних іграх зустрічається досить часто. Це і Thunderclap в "Ko Shogi" і тихі ходи фігур в "Nine-Tile Cyvasse" і багато іншого. Головна пов'язана з ним проблема — необхідність заборони повернення на раніше пройдені поля. Якщо максимальна дистанція не перевищує трьох кроків (як у грі "Mana" Клода Лероя), достатньо заборонити зміна напрямку руху на строго протилежне (для цього в ZRF є чудовий предикат not-last-from?).

Строго кажучи, він не завжди рятуєВ реалізації Mana на ZRF цей прийом працює, але може призвести до аварійного завершення гри. У грі можлива (хоча і малоймовірне) ситуація, в якій один або два перших часткових ходу заведуть фігуру в глухий кут, з якого немає виходу. Це відбувається через те, що кожний частковий хід вважається самостійним, незалежним від інших. Zillions of Games не може побачити проблему, поки не упреться в неї носом». Jocly, розглядає весь складовою хід цілком, дозволяє побудувати більш коректну реалізацію.

Якщо фігура може переміщатися більш ніж на 3 кроки, not-last-from? вже не рятує. Необхідно якось позначати раніше пройдені поля. У ZRF, для прив'язки до полів булевих значень, використовуються так звані позиційні прапори». На жаль, вони не є частиною ігрового стану і автоматично очищаються на початку кожного часткового ходу.

Вихід, зрозуміло, є
(define clear-mark
mark a0
(while (on-board? next) 
next
(if (and enemy? (piece? Mark))
capture
)
)
back
)

(define step (
(create Mark)
$1 (verify (my-empty?))
(clear-mark)
(add-partial $2 $3)
))


На відвіданих полях можна розміщувати невидимі фігури (вже вони то безумовно є частиною стану). Головне не забувати їх вчасно видаляти! Цей прийом настільки універсальний, що може стати в нагоді у безлічі абсолютно несхожих ігор. В першу чергу, в голову приходять різноманітні ігри-переходи, такі як "Salta" або "Traverse", але одними лише іграми сімейства "Халма" справа не обмежується. Той самий прийом дозволив довести до робочого стану "Luzhanqi" і давним давно написані "Осетинські шашки".

Замість післямови...

Продовжуючи розмову про шашках, хочу зауважити, що «робота над помилками» далеко не завжди зводиться до суто технічних моментів. Дуже часто в процесі роботи над грою, я дізнаюся щось нове. Іноді такі «подробиці» буквально перевертають всі мої уявлення про гру, змушуючи глянути на неї по-новому. Як приклад, хочу розповісти про одну з найцікавіших різновидів "Турецьких шашок".


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

Відповідь я опублікую ввечері.

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

0 коментарів

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