Pull to refresh

Шахматы льда и пламени

Reading time 13 min
Views 18K
          Игра эта зовется кайвассой. Ее завезли в Дощатый город на волантинской торговой галере, а сироты разнесли ее вверх и вниз по Зеленой Крови. При дорнийском дворе все помешались на ней…
          Десять фигур, каждая из которых ходит по-разному, а доска меняется с каждой игрой, смотря как игроки перемешают свои квадратики. 

                                                   Джордж Мартин «Пир стервятников» 

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

Большинство версий «Кайвассы» (за все не скажу, мог что-то и пропустить) — это всё те же, привычные нам с детства, шахматы, только с катапультами и драконами. Да, фигуры ходят непривычно, а по доске, щедрой рукой, можно разбросать горы и водоёмы, но принципы самой игры не меняются — шахматное взятие и король, которого необходимо съесть. Zane Fisher подошёл к вопросу более творчески. На мой взгляд, его версия игры гораздо более глубока, в тактическом плане. За счёт чего? Вот давайте вместе и посмотрим.


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

Это был вызов!
Так уж получилось, что в чисто техническом плане, реализовать подобные блуждания на ZRF непросто. Ход фигуры состоит из нескольких шагов в произвольном направлении, как правило, с дополнительным условием не посещения уже пройденных полей. Я делал игру с подобной механикой, но в тот раз использовал механизм частичных ходов (да и то не слишком удачно, в редких случаях фигура могла загнать себя в тупик). Когда я совсем уж было думал, что без Axiom здесь не обойтись, решение само, вдруг, неожиданно пришло мне в голову:

Много кода
(define check-target
  (if (position-flag? is-target?)
      (set-flag is-succeed? true)
  )
)

(define check-target-dir
  (if (and (on-board? $1)(position-flag? is-target? $1))
      (set-flag is-succeed? true)
  )
)

(define check-dir-3
  (if (on-board? $1)
     $1 
     (if (not (or enemy? (piece? Mount)))
         (check-target)
         (if (not-speared?)
             (check-target-dir $2)
             (check-target-dir $3)
             (check-target-dir $4)
         )
     )
     (opposite $1)
  )
)

(define check-branch-3
  mark
  (if (on-board? $1)
     $1 
     (if (not (or enemy? (piece? Mount)))
         (check-target)
         (if (not-speared?)
             (check-dir-3 $1 $1 $2 $3)
             (check-dir-3 $2 $1 $2 $4)
             (check-dir-3 $3 $1 $2 $5)
         )
     )
  )
  back
)

(define move-3 (
  (check-pass)
  (set-position-flag is-target? true)
  START
  (while (on-board? next)
     next
     (set-flag is-succeed? false)
     (if (or empty? (piece? Point))
         (check-branch-3 w sw nw se ne)
         (check-branch-3 nw w ne se e)
         (check-branch-3 ne nw e w se)
         (check-branch-3 e ne se nw sw)
         (check-branch-3 se e sw ne w)
         (check-branch-3 sw se w e nw)
         (if (flag? is-succeed?) add)
     )
  )
))

(piece
   (name LightHorse)
   ...
   (moves
      (move-3)
      ...
   )
)


На самом деле, всё просто. Вместо того чтобы пытаться проложить путь от текущего поля (при таком подходе было бы сложно бороться с дубликатами генерируемых ходов), можно его пометить позиционным флагом, а затем, перебрав все поля доски (благо — их немного, доска небольшая), попытаться добраться до отмеченного поля, за заданное число шагов. Звучит не особо впечатляюще, но для меня это было поворотным пунктом. С этого момента я поверил, что игру можно реализовать на чистом ZRF.

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

С понятием «зацепления» тесно связано «вооружение» фигуры. Все фигуры делятся на легко-, тяжело- и не вооружённые. «Ополченец» (Rabble) — лёгкая фигура, не может просто так атаковать тяжёлую, например «Слона» (Elephant). Для того чтобы атаковать, ему требуется «зацепление» цели ещё одной лёгкой (или тяжёлой) фигурой. С другой стороны, «Слон» легко может атаковать «Ополченца» (за исключением, разве что случая, когда он находится в воде). Также он может в одиночку «зацепить» любую тяжёлую фигуру противника, даже «Дракона» (Dragon). Не вооружённые (unarmored) фигуры («Арбалетчик» и «Катапульта») могут быть взяты без «зацепления».

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

Ещё код
(define set-engaged
   (if (flag? is-light-engaged?)
       (set-flag is-heavy-engaged? true)
    else
       (set-flag is-light-engaged? true)
   )
)

(define check-escape
   (if (or enemy? (piece? Mount))
       (set-flag is-escaped? true)
   )
)

(define check-other
   mark
   (set-flag is-escaped? false)
   (if (on-board? $1)
       $1 (check-escape)
       (if (and friend? (not-in-zone? water) 
            (or (piece? Rabble) (piece? LightHorse) (piece? HeavyHorse) (piece? Elephant) (piece? Crossbow) 
                 (piece? Dragon) (piece? Tower) (piece? King)))
           (set-engaged)
           (if (or (piece? HeavyHorse) (piece? Elephant) (piece? Dragon) (piece? Tower))
               (set-flag is-heavy-engaged? true)
           )
           (if (piece? Tower)
               (set-flag is-enemy-tower? true)
           )
       )
   )
   (if (and (on-board? $1) (not-flag? is-escaped?))
       $1 (check-escape)
       (if (and friend? (not-in-zone? water) (or (piece? Elephant) (piece? Crossbow) (piece? Trebuchet) (piece? Dragon)))
           (set-engaged)
           (if (or (piece? Elephant) (piece? Dragon))
               (set-flag is-heavy-engaged? true)
           )
       )
   )
   (if (and (on-board? $1) (not-flag? is-escaped?))
       $1 (check-escape)
       (if (and friend? (not-in-zone? water) (or (piece? Crossbow) (piece? Trebuchet)))
           (set-engaged)
       )
   )
   (if (and (on-board? $1) (not-flag? is-escaped?))
       $1 (check-escape)
       (if (and friend? (not-in-zone? water) (piece? Trebuchet))
           (set-engaged)
       )
   )
   back
)

(define check-spears
   (if (on-board? $1)
       (if (and (friend? $1) (not-in-zone? water $1) (piece? Spears $1))
           (set-engaged)
       )
   )
)

(define check-friend-tower
   (if (on-board? $1)
       (if (and (enemy? $1) (piece? Tower $1))
           (set-flag is-light-engaged? false)
       )
   )
)

(define check-engaged
   (verify (not-piece? Mount))
   (set-flag is-enemy-tower? false)
   (set-flag is-light-engaged? false)
   (set-flag is-heavy-engaged? true)
   (if (or (piece? Crossbow) (piece? Trebuchet))
       (set-flag is-light-engaged? true)
   )
   (if (or (piece? HeavyHorse) (piece? Elephant) (piece? Dragon) (piece? Tower))
       (set-flag is-heavy-engaged? false)
   )
   (check-spears sw) (check-spears se)
   (check-other w) (check-other e) (check-other nw) (check-other ne) (check-other sw) (check-other se)
   (if (and (not-piece? Tower) (not-piece? Crossbow) (not-piece? Trebuchet) (not-flag? is-enemy-tower?))
       (check-friend-tower w) (check-friend-tower e) 
       (check-friend-tower nw) (check-friend-tower ne) 
       (check-friend-tower sw) (check-friend-tower se)
   )
   (verify (and (flag? is-light-engaged?) (flag? is-heavy-engaged?)))
)

(define common-1 (
  $1 (verify enemy?)
  (check-engaged)
  add
))

(piece
   (name King)
   ...
   (moves
      (common-1 w) (common-1 e) (common-1 nw) (common-1 ne) (common-1 sw) (common-1 se)
      ...
   )
)


Как я уже сказал, много писанины, но с этого момента, разработка игры стала, по большей части, механической работой. Был, правда, ещё момент с Rabble, но об этом ниже.

Дистанция «зацепления» совпадает с максимальной дистанцией хода фигуры лишь в самых простых случаях (Rabble, Spears и King). Обычной является ситуация, при которой расстояние, на котором возможно «зацепление», меньше максимального хода фигуры (Light Horse, Heavy Horse, Dragon). Впрочем, есть исключение. «Слон» (Elephant) — тяжёлая фигура перемещающаяся лишь на одну клетку за ход, но «зацепить» вражескую фигуру он может на расстоянии двух клеток! Более того, он может её съесть, передвинувшись на две клетки, но лишь при условии, что путь не загораживают какие либо преграды (в отличии от других фигур, «Слон» не может проходить через клетки, занятые другими фигурами. Все эти особенности делают игру ещё более интересной, в тактическом плане. Посмотрите, например решение одной из задач учебника:



Расстояние «зацепления», для конницы — единичка. Чтобы взять вражескую фигуру (при отсутствии других фигур, выполняющих «зацепление»), придётся подойти вплотную, но во время боя, есть свобода выбора — остановиться на поле взятой фигуры или двигаться дальше (наподобие дамки в шашках). Максимальная дистанция — три шага для Light Horse и два для Heavy Horse. Детальные параметры всех фигур можно посмотреть в руководстве к игре:

  • Rabble (x6) — light armor, movement allowance 1, engagement range 1
  • Spears (x3) — light Armor, Movement Allowance 1, Engagement Range 1
  • Light Horse (x3) — light Armor, Movement Allowance 3, Engagement Range 1
  • Heavy Horse (x2) — heavy armor, Movement Allowance 2, Engagement Range 1
  • Elephant (x2) — heavy armor, Movement Allowance 1, Engagement Range 2
  • Crossbows (x2) — unarmored, Movement Allowance 2, Engagement Range 3
  • Trebuchet (x1) — unarmored, Movement Allowance 1, Engagement Range 4 (min. 2)
  • Dragon (x1) — heavy armor, Movement Allowance 4, Engagement Range 2
  • Tower (x2) — heavy armor, Movement Allowance 0, Engagement Range 1
  • King (x1) — light armor, Movement Allowance 1, Engagement Range 1

Там же можно найти информацию об особенностях каждой из фигур. Некоторые из этих примечаний вводят в смущение. Например, для «Арбалетчика» (Crossbows) написано следующее: "Crossbows cannot capture". Для чего может понадобиться фигура неспособная брать фигуры противника? Вновь, всё дело в «зацеплении»! Crossbows — не защищённая фигура неспособная к ближнему бою, но она может «захватывать» фигуры противника на большом расстоянии. Следующая задача из учебника это иллюстрирует. Если «Арбалетчик» сдвинется так, чтобы «захватить» все цели, «Всадник» сможет побить их за три хода:


К сожалению, в задачке есть досадный недочёт

Дистанции «тихого хода» Light Horse вполне достаточно, чтобы переместиться на позицию, с которой он может убить все три вражеских фигуры без посторонней помощи. В своей реализации, я исправил это, переместив фигуру «Всадника» ниже.

Другая слабо защищённая (unarmored) фигура — «Катапульта» (Trebuchet). Здесь со взятием всё в порядке! Фигура бьёт издалека, на дистанцию от двух до четырёх шагов (противника расположившегося вплотную «Катапульта» побить не может). Уникальность этой фигуры в том, что после выполнения взятия «Катапульта» продолжает оставаться в тылу. Чтобы взять фигуру противника, она перемещается на один шаг в противоположном направлении (конечно, если там есть свободное место)! Это важное стратегическое оружие, «Катапульту» необходимо всячески оберегать!

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



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

«Крепость» (Tower) — ещё одна очень странная фигура. Она не двигается! Совсем. В общем-то это даже логично. Где (кроме японских мультиков) вы видели двигающиеся крепости? Задача крепости — защита (и она с этой задачей прекрасно справляется). Фигура находящаяся вблизи дружеской крепости не может быть «зацеплена». Чтобы её убить, сначала придётся разрушить крепость, а это не просто. Кроме того, «Король» (King) умеет «прыгать» сквозь дружескую «Крепость», оказываясь по другую сторону от неё за один ход.

Есть мнение, что крепость справляется со своей задачей слишком хорошо

Это последняя задачка из учебника, которая, по идее, должна решаться за семь ходов. Поймите меня правильно. Короля, в этой позиции, можно съесть «Всадником» всего за три хода! При условии, что он не будет ни на что реагировать, когда «Всадник» подскачет к нему вплотную. В реальной жизни, так не бывает. Для меня очевидно, что сдвинув «Катапульту» вправо и взяв мешающего ей «Ополченца» при помощи «Слона», задачу можно было бы решить за отведённое число ходов. Но мешают крепости! Пока «Копейщики» рядом с ними, они не могут быть «взяты под прицел», а они закрывают «Короля»! В оригинальных правилах, говорится следующее:
A piece that is adjacent to one or more opposing Towers cannot engage any pieces except the adjacent Tower(s)
Возможно, здесь имелась в виду ситуация, когда фигура может «зацепить» и «Крепость» и охраняемую ей фигуру (то есть стоит вплотную к ним обеим). Не знаю. Это хорошая тема, над которой стоит подумать. Пока же, я разрешил «Дракону» (Dragon) брать фигуру, находящуюся под защитой крепости, с расстояния в два шага (в радиусе действия его «зацепления», но не вплотную). На мой взгляд, это немного оживляет игру.

Стоит рассказать о двух самых слабых фигурах, к которым, в равной степени, подходит выражение «мал, да удал». «Копейщик» (Spears) — это, в каком-то смысле, аналог шахматной пешки. Может идти только вперёд и атакует всего два поля перед собой (правда, при этом, ни во что не превращается). Чем может быть полезна такая фигура? Конечно же, у неё есть секрет. Контролируемые ею два поля (всего два) ни одна из вражеских фигур не может «проскочить» за один ход. Например, это означает, что конница не может атаковать «Копейщика» с фронта, даже если тот «зацеплен» другой фигурой. Сначала она должна подойти вплотную. Spears — это превосходный защитный юнит, напоминающий «Телохранителя» (Хиа) из монгольской игры Хиашатар.

С «Ополченцами» (Rabble) всё обстоит немного проще. Они могут ходить (и «бить») на один шаг, в любую сторону, как «Король». Фокус заключается в том, что игрок имеет право сделать два «тихих» хода «Ополченцами» подряд. Это атакующий юнит. Сделав два «тихих» хода, можно создать две угрозы, в разных концах доски. Одного «Ополченца», скорее всего съедят, но другим можно будет организовать прорыв. Это решение мне тоже безумно нравится.

Хотя и доставило мне некоторое количество проблем, в части реализации
Тут вот в чём дело, порядок ходов в ZRF (да и в ZoG в целом) жёстко задан. Если бы каждому из игроков всегда приходилось делать по два хода (как в "Марсельских шахматах"), это было бы просто. Как-то вот так:
(turn-order White White Black Black)
Но нам-то требуется, чтобы право повторного хода предоставлялось только после «тихого» хода Rabble и чтобы вторым ходом был тоже «тихий» ход, но уже другого Rabble. И никак иначе! И, кстати, право, а не обязанность. Вот тут, мне пришлось пойти на компромиссы. Было понятно, что без механизма пропуска хода (pass) здесь не обойтись, но автор на этот счёт выразился предельно чётко: "He must move a piece, or forfeit the game". К счастью, в ZoG предусмотрен режим, при котором пропуск хода выполняется (автоматически), лишь при отсутствии любых разрешённых ходов (это конечно тоже не совсем правильно, поскольку, при использовании данной опции, игроку не будет засчитано поражение, при отсутствии возможности хода).

Собственно, код
(define rabble-1 (
  (set-position-flag from-pos? true)
  (verify (not-enemy? a8))
  (verify (not is-moved?))
  $1 (verify (or empty? (piece? Point)))
  (set-flag other-rabble? false)
  mark START
  (while (on-board? next)
     next
     (if (not-position-flag? from-pos?)
         (if (and friend? (piece? Rabble))
             (set-flag other-rabble? true)
         )
     )
  )
  back
  (if (flag? other-rabble?)
      (if (empty? a8)
          (create Point a8)
          (set-attribute is-moved? true)
      )
  )
  (if (not-empty? a8)
      (capture a8)
      mark START
      (while (on-board? next)
          next
          (if is-moved?
              (set-attribute is-moved? false)
          )
      )
      back
  )
  add
))

(piece
   (name Rabble)
   ...
      (attribute is-moved? false)
      (moves
         (rabble-1 nw) (rabble-1 ne) (rabble-1 sw) (rabble-1 se) (rabble-1 w) (rabble-1 e)
      )
   )
)


Решение не идеально. Двигая первый Rabble мы помечаем его атрибутом, после чего, игрок уже обязан найти и передвинуть какой-то другой свой Rabble (его противник просто пропускает один ход). Во время второго перемещения, кстати, снимается установленный атрибут и если этого не произойдёт, игра, скорее всего, просто остановится. Поэтому, очень важно, ещё на первом ходу, найти хотя бы ещё один другой Rabble и, если такого нет, всю эту магию не включать! Как обычно, задним умом пришла мысль, что хорошо бы еще и проверять возможность хода этим самым другим Rabble. К счастью, это было совсем просто. Так родился патч.

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

В этом месте, у ZoG таки тоже есть свои особенности
Если с «горами» всё было просто (ну, фигуры и фигуры), то «вода» доставила хлопот. Нет, в принципе, её я тоже мог сделать фигурами. Я как-то раз уже так делал и вот к чему всё привело. Это не лаг был, а такая конструктивная особенность! Впрочем, в тот раз выбора не было. Рисование зелёных квадратиков на чёрном фоне отняло бы кучу времени и раза в два раздуло и без того полуторно-мегабайтный дистрибутив, на 90% заполненный радикально-чёрными «задниками».

В общем, в этот раз, воду я рисовал прямо на доске, параллельно отмечая её в качестве игровой зоны в описании игры. К сожалению, таким образом, можно нарисовать хоть и любую, но лишь жёстко фиксированную карту. Ни о каком авторском «Nine Tile» речи уже не идёт. А жаль,… но тут уж ничего не попишешь.

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



Играет, в принципе, тоже вполне вменяемо (особенно, если компьютер помощнее, да время «на обдумывание» выставлено побольше.



Сама игра мне очень понравилась. В тактическом плане, она показалась мне не менее сложной чем Ko Shogi, выгодно отличаясь от последней большей «лаконичностью» и продуманностью. Все фигуры работают! И для игры вполне достаточно относительно небольшой доски. А о том как ослабить «Крепости» я ещё подумаю.

P.S.
В общем, всё просто оказалось с «Крепостями» (это я себе сложностей на пустом месте навыдумывал). В правилах игры написано предельно ясно:
A piece that is adjacent to one or more opposing Towers cannot engage any pieces except the adjacent Tower(s).
То есть, если фигура расположена вплотную к вражеской «Крепости», она может «зацеплять» только «Крепость» и никакую другую фигуру! В результате, девятая задача "учебника" решается именно так, как это было запланировано автором, а обновлённая версия игры выложена на Zillions of Games.
P.P.S.
Как это обычно и бывает, в процессе написания первого постскриптума к этой статье, я внезапно понял, что принял во внимание не все возможные ситуации. Картинка ниже иллюстрирует проблему:


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

Tags:
Hubs:
+13
Comments 6
Comments Comments 6

Articles