«Короли севера» — битва за геймплей

«Короли севера» — битва за геймплей

254
ПОДЕЛИТЬСЯ

Могу я в Тафл играться
Девять умений я знаю
Забываю нечасто руны
Ведаю книжки и счёт
Умею скользить я на лыжах
Гребу и стреляю хорошо
Из искусств мне ведомы оба…

"Сага о оркнейцах" 

История, которую я желаю поведать, полна загадок, малопонятного кода, бессонных ночей, шума кошачьих шагов…
Это одна из тех историй, в которых процесс еще важнее результата. Раз для вас нужен итог, его можно отыскать тут, но раз для вас увлекательны подробности, что же… я готов поведать о собственных мытарствах.

Sultan Ratrout, большой любитель настольных игр, отыскал один из моих роликов на YouTube и попросил создать для него коллекцию более узнаваемых игр семейства шашек. Начиналось всё как традиционно. В одном лишь ZoG — наиболее 20 наименований. Просьба меня удивила. Огласить, что таковых программ много — значит только несколько преуменьшить. Бес, по обыкновению, оказался укрыт в деталях. Шашки имеются на хоть какой вкус и для множества разных платформ.

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

Даже вариант от самих разрабов ZoG, поставляемый совместно с програмкой, работал с страшенными ошибками! Я опубликовал исправление и задумывался было на этом успокоиться, но взглянув на остальные варианты, сообразил, что проще создать собственный набор игр чем поправить все ошибки в имеющихся реализациях. Первой ласточкой оказался баг, обнаруженный мной в коллекции шашек от Uwe Wiedemann. В его реализации «Русских шашек», шашка, оказавшаяся в зоне перевоплощения в ходе боя, не продолжала ход в случае, раз последующую шашку можно было взять лишь ходом дамки.

Очевидно, начал я с внимательного анализа имеющихся реализаций. Из программного кода я вызнал много новейшего. Из их же взял и графические ресурсы, подправив маленькие огрехи в графическом редакторе (тот же набор ресурсов уже употреблялся ранее несколькими создателями). К примеру, в «Турецких шашках», дамке, в ходе боя, запрещается изменять направление движения на противоположное. В итоге, я решил сделать свою коллекцию шашек. Вот как элегантно это реализуется:

$1))
$1
(verify (empty? (opposite $1)
)
capture
back
(add-partial continuetype)
$1
)
))

(define dama-king-continue (
(while (empty? $1))
$1
(verify (empty? $1)
$1
(verify not-last-from?)
)
(verify (enemy? (move-priorities jumptype normaltype)

(piece
(name King)
(image First "images/wiedem/CheckerKingWhite.bmp"
Second "images/wiedem/CheckerKingBlack.bmp")
(moves
(move-type jumptype)
(dama-king-jump n)
(dama-king-jump e)
(dama-king-jump w)
(dama-king-jump s)

(move-type continuetype)
(dama-king-continue n)
(dama-king-continue e)
(dama-king-continue w)
(dama-king-continue s)

(move-type normaltype)
(king-shift n)
(king-shift e)
(king-shift w)
(king-shift s)
)
)
) Бой дамкой(define dama-king-jump (
(while (empty? $1)
$1
)
(verify (enemy? (opposite $1)
)
capture
back
(add-partial continuetype)
$1
)
))

(game
(title "Turkish Dama")
… mark
(while empty? $1))
$1
(while empty? mark
(while empty? $1))
$1
(while empty?

Предикат last-from? Тут на помощь приходят режимы выполнения хода. Выяснилось (я не знал этого ранее), что назначаемый очевидно continuetype не требуется упоминать в перечне ценностей. инспектирует, является ли текущее поле исходным полем предшествующего (частичного) хода. 1-ый ход осуществляется в рамках приоритетного режима jumptype и выполняет переключение в continuetype, в рамках которого должны выполняться все следующие ходы. Раз мы пересекаем такое поле — означает направление движения дамки поменялось на противоположное и ход делать нельзя. Очевидно, таковая проверка не обязана выполняться в рамках самого первого частичного хода.

Иной приятной нежданностью оказался метод расчёта «длинного» хода, при бое дамкой, когда дамка, может останавливаться не лишь на поле последующем конкретно за сбитой фигурой (как в "Тайландских шашках"), но и на любом последующем за ним вольном поле. Выяснилось, что необязательно писать портянки из «копи-паста»:

Вероятное описание боя дамкой(define king-jump-1 (
(while (empty? $1)
$1
)
$1
(verify enemy?)
capture
$1
(verify empty?)
(add-partial jumptype)
))

(define king-jump-2 (
(while (empty? $1)
$1
)
$1
(verify enemy?)
capture
$1
(verify empty?)
$1
(verify empty?)
(add-partial jumptype)
))

(define king-jump-3 (
(while (empty? $1)
$1
)
$1
(verify enemy?)
capture
$1
(verify empty?)
$1
(verify empty?)
$1
(verify empty?)
(add-partial jumptype)
))

Напомню, что неувязка заключается в том, что тривиальная реализация приводит к ошибке, в процессе генерации хода:

(add-partial jumptype)
$1
)
)) Этот код не работает!(define king-jump (
(while (empty? $1)
$1
)
$1
(verify enemy?)
capture
$1
(while empty?

Верная реализация чем-то припоминает метод маляра Шлемиэля. Это смешно, но лишь таковой метод и работает! От точки возможного завершения хода (в цикле их генерируется несколько), нужно возвратиться в противоположном направлении и удалить первую встреченную вражескую фигуру.

(opposite $1)
)
capture
back
(add-partial continuetype)
$1
)
)) $1))
$1
(while empty? Другое решение(define king-jump (
(while (empty? $1)
$1
)
(verify (enemy? mark
(while empty? $1))
$1
(verify (empty?

Но погодите, мы еще не разглядывали «турецкий удар»! По завершении составного хода, можно удалить все ранее помеченные фигуры. Сделать это можно 2-мя методами. Можно поменять тип фигуры, или применять один из её атрибутов (крайний метод недоступен в Axiom). Раз вы улыбаетесь, означает мы двигаемся в правильном направлении. Для того, чтоб не «взять» фигуры противника повторно, их нужно как-то пометить.

false)
(moves
(move-type jumptype)
(checker-jump nw sw ne)
(checker-jump sw se nw)
(checker-jump ne se nw)
(checker-jump se ne sw)

(move-type normaltype)
(checker-shift nw)
(checker-shift ne)
)
) (empty? Турецкий удар (внедрение атрибутов)(define checker-captured-find
mark
(if (on-board? next)
next
(if captured? more-captures))
mark
a0
(while (on-board? capture)
)
back
)
(if (in-zone? promotion)
(add King)
else
(add-partial jumptype)
)
))
… true)
$1
(verify empty?)
(set-flag more-captures false)
(checker-captured-find $1)
(checker-captured-find $2)
(checker-captured-find $3)
(if (not (flag? $1)
$1
(if (and enemy? (piece
(name Checker)
(image First "images/wiedem/CheckerWhite.bmp"
Second "images/wiedem/CheckerBlack.bmp")
(attribute captured? $1) (not captured?))
(set-flag more-captures true)
)
)
back
)

(define checker-jump (
$1
(verify enemy?)
(verify (not captured?))
(set-attribute captured?

Решение достаточно прямолинейно — чтоб найти, имеет ли ход продолжение, просто исполняем поиск фигуры, которую мы можем «съесть» дальше, в одном из 3-х направлений (так как направления, с точки зрения ZoG, никак не взаимосвязаны, все три приходится передавать в макрос). Это обязано работать! Самый не тривиальный момент в этом коде — определение момента завершения составного хода. Для простоты, тут рассматривается британский вариант шашек. Но это не работает: Решение с «дальнобойными» дамками смотрится труднее.

В чём дело? Попробуем изменять тип фигуры: Может быть что-то не так с атрибутами?

XChecker)))
(set-flag more-captures true)
)
)
back
)

(define checker-jump (
$1
(verify enemy?)
(verify (not (piece? XChecker)))
(change-type XChecker)
$1
(verify empty?)
(set-flag more-captures false)
(checker-captured-find $1)
(checker-captured-find $2)
(checker-captured-find $3)
(if (not (flag? $1) (not (piece? promotion)
(add King)
else
(add-partial jumptype)
)
))
… (empty? (piece
(name Checker)
(image First "images/wiedem/CheckerWhite.bmp"
Second "images/wiedem/CheckerBlack.bmp")
(moves
(move-type jumptype)
(checker-jump nw sw ne)
(checker-jump sw se nw)
(checker-jump ne se nw)
(checker-jump se ne sw)

(move-type normaltype)
(checker-shift nw)
(checker-shift ne)
)
)
(piece
(name XChecker)
(image First "images/wiedem/CheckerWhite.bmp"
Second "images/wiedem/CheckerBlack.bmp")
) Турецкий удар (изменение типа фигур)(define checker-captured-find
mark
(if (on-board? $1)
$1
(if (and enemy? XChecker) capture)
)
back
)
(if (in-zone? next)
next
(if (piece? more-captures))
mark
a0
(while (on-board?

Ходить она естественно не может, но мешает движению остальных фигур! Конкретно так и смотрится эта ошибка в официальной редакции «Русских шашек» от разрабов Zillions of Games. Для того, чтоб разобраться, в чём здесь дело, выгрузим протокол данной «мини-игры» в ZSG-файл: Крайняя взятая фигура не удаляется с доски. Уже лучше! По последней мере, этот зомби не пробует нас съесть!

ZSG-лог1. partial 2 Checker a5 — c7 = XChecker on b6
1. partial 2 Checker c7 — e5 = XChecker on d6
1. partial 2 Checker e5 — g7 = XChecker on f6 x b6 x d6

Всё дело в крайнем ходе. При использовании атрибутов, лог смотрится труднее, но сущность остаётся постоянной. Немедленное удаление таковой фигуры — несовместимо с способностями ZSG-нотации! Мы не можем удалить фигуру, с которой что-то делали в рамках того же частичного хода. Меняли ли мы атрибут либо тип фигуры — непринципиально. Разумеется, что программа просто не может этого сделать. Мы «помечаем» фигуру на поле f6 для удаления, но не удаляем её совместно с иными!

Решение трудности разумеется, хотя и не делает код наиболее «читабельным». Последнюю фигуру «помечать» не необходимо, её нужно просто «брать»:

more-captures)
(add-partial King jumptype)
else
(add King)
)
else
(if (flag? Бой в российских шашках(define russian-checker-jump (
(verify (not captured?))
$1
(verify enemy?)
(verify (not captured?))
$1
(verify empty?)
(set-flag more-captures false)
(if (in-zone? capture
)
$1
(capture-all)
)
(if (in-zone? promotion)
(king-captured-find $1)
(king-captured-find $2)
(king-captured-find $3)
else
(checker-captured-find $1)
(checker-captured-find $2)
(checker-captured-find $3)
)
(if (flag? more-captures)
(opposite $1)
(markit)
$1
)
(if (not (flag? promotion)
(if (flag? more-captures)
(add-partial jumptype)
else
add
)
)
)) more-captures))
(opposite $1)
(if enemy?

Экзотика была представлена осетинскими и хакасскими шашками, но коллекции всё ещё не хватало «изюминки». Разобравшись со всеми этими неуввязками, я быстренько «наштамповал» джентельменский набор игр, включающий в себя неотклонимые турецкие, армянские, английские и российские шашки.

Довольно огласить, что две дамки достаточно просто ловят одну, даже в том случае, раз она находится на «большаке» — главной диагонали доски! От обычных нам «Русских шашек» этот вариант различается всего одним правилом: «съеденная» дамка не убирается с доски, а «понижается в звании» до обыкновенной шашки. Эту позицию я решил застолбить за "Северными шашками". Это, казалось бы, маленькое отличие в корне меняет нрав игры в эндшпиле.

Выбор был изготовлен, осталось воплотить его в жизнь. 1-ый вариант играл приблизительно так:

Когда в игре (как в Chu Shogi) есть всего пара фигур, способных «съесть» всякую фигуру противника и возвратиться на место в течение 1-го хода, это может быть весело, но когда таковых фигур много и они дальнобойные… геймплей серьёзно мучается. Стало понятно, что нужно как-то запрещать возможность повторного «поедания» фигуры в течение 1-го хода, но как это сделать? Чрезвычайно брутально, но не совершенно так как хотелось.

Позиционные флаги, дозволяющие привязать булевское значение к произвольному полю, как досадно бы это не звучало, не подошли. Было огромное искушение пользоваться ими, так как документация гарантирует их автоматическую чистку в начале каждого хода. Предикат last-from?, не дозволит решить делему, так как дамка, способна «съесть» фигуру на возвратном движении и не дойдя до стартового поля предшествующего хода. В ZoG выбор невелик.

Можно помечать «взятые» фигуры установкой атрибутов, но, в этом случае, мы должны позаботится о их чистке меж составными ходами. Остались атрибуты. К огорчению, эта чистка происходит в начале каждого частичного хода и, как средство передачи инфы меж частичными ходами, позиционные флаги совсем бесполезны!

Хотя чистка атрибутов и выполнялась при перемещении фигур, атрибут той фигуры, которая выполняла ход, похоже не очищался. С сиим и был связан очередной баг (увидеть и локализовать его было непросто). Разобраться с сиим вновь посодействовал ZSG-лог (в перечне ходов, отображаемых програмкой, информация о изменении значений атрибутов прячется):

partial 3 King d8 — a5 = Checker on c7 @ c7 0 1 ZSG-лог1.
1. partial 3 King a5 — e1 = Checker on c3 @ c3 0 1
Checker c7 — b6 @ c3 0 0 @ c7 0 0 1.
2. partial 3 King e1 — a5 x c3
Checker b6 — c5 @ b6 0 0 2.

В третьей строке можно созидать, что чистка атрибутов делается, но атрибут очищается на позиции c7, в то время как фигура уже переместилась с этого поля на b6! Опосля внесения нужных исправлений, всё стало работать так, как планировалось:

north-shift(define clear-all
mark
a0
(while (on-board? false)
add
)
)) Checker)
(set-attribute captured? false)
)
)
back
)

(define north-shift (
(clear-all)
$1
(verify empty?)
(if (in-zone? next)
next
(if (piece? promotion)
(add King)
else
+ (set-attribute captured?

Массовая доступность дальнобойных дамок в самом начале игры, а также их феноменальная живучесть обеспечили игре совсем неповторимый, «взрывной» геймплей. Оставалась ещё одна неувязка. Так родился вариант «Северные короли», в котором игра начиналась с дамок! В начале игры, «Северные шашки» ничем не отличались от «русских», а до дамок нужно было ещё дожить. Эту игру я считаю «жемчужиной» собственной коллекции:

Но несколько моментов серьёзно омрачают мою удовлетворенность. Во первых, эти неизменные «танцы на граблях» делают код таковым запутанным, что я не поручусь за то, что в нём совершенно не осталось ошибок (и это гложет меня по ночам). На данной удовлетворенной нотке можно было бы и окончить. В самом деле, коллекция готова, всё работает, в ней есть пара-тройка неповторимых игр. Не считая того, часть заморочек мне так и не удалось решить.

Даже без учёта того, что для таковых ходов (даже на теоретическом уровне) сделать взятие неотклонимым нереально, сама попытка «зациклить» ходы таковым образом приводит к немедленной ошибке. В «Ossetian Kena» шашки могут перепрыгивать через дружественные фигуры. Охото иметь возможность перемежать такие ходы с боем вражеских фигур в случайном порядке, но это не работает!

Осетинские Кены (piece
(name Checker)
(image First "images/wiedem/CheckerWhite.bmp"
Second "images/wiedem/CheckerBlack.bmp")
(moves
(move-type jumptype)
(checker-jump n)
(checker-jump e)
(checker-jump w)
(checker-jump s)

(move-type continuetype)
(checker-jump n)
(checker-jump e)
(checker-jump w)
(checker-jump s)
; (ken-jump-variant n)
; (ken-jump-variant e)
; (ken-jump-variant w)
; (ken-jump-variant s)

(move-type normaltype)
(checker-shift n)
(checker-shift e)
(checker-shift w)
(ken-jump-variant n)
(ken-jump-variant e)
(ken-jump-variant w)
(ken-jump-variant s)
)
)

Даже раз помечать дружественные фигуры, запрещая повторные прыжки через их, это не помогает. Позиционные флаги могли бы посодействовать в этом, но… видимо не в нашей вселенной. Стоит убрать эти комменты и всё ломается! Это фортуна, так как воплотить таковой запрет в ZoG нельзя (во всяком случае обычным методом). Раз говорить о «пометках», то большой фортуной является то, что ни в одном из вариантов шашек не требуется запрещать повторное прохождение через пустые поля.

ZoG, в свою очередь, постоянно готова подсунуть новейший сюрприз:

Я не знаю как с сиим биться. Дальнобойные дамки, правило большинства и турецкий удар — когда все «три всадника» собираются совместно… AI может начать вести себя удивительно. При этом, даже когда я снимал этот ролик, ошибка проявлялась не детерминированно — время от времени бралась одна шашка, время от времени две. К примеру, в «Международных шашках» пришлось отрешиться от «правила большинства». Да, да, программа ведёт себя по различному в зависимости от того, выполняется ли она под управлением AI либо в ручном режиме! Приходится идти на компромисс с совестью и отключать одну из 3-х опций.

В хоть какой, даже самой большой, бочке мёда постоянно найдётся место для малеханькой ложечки дёгтя… habrahabr.ru

НЕТ КОММЕНТАРИЕВ

ОСТАВЬТЕ ОТВЕТ

Этот сайт использует Akismet для борьбы со спамом. Узнайте как обрабатываются ваши данные комментариев.