Dagaz: Пинки здравому смыслу (часть 6)

    image… мой двойник ожидает в доме Ихи, я встречаю его, я поднимаю мою фишку к [нему]
    Я встречаю его в Прекрасном доме.
    Я поднимаю три фишки и нахожу две фишки, мой двойник позади меня.


                  папирус времён Рамсеса III
     

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

    5. В единстве сила


    Идея о том, что свойства фигур могут зависеть от их взаимного расположения, не нова. По всей видимости, этот принцип использовался в древнеегипетской игре Сенет, известной начиная с 3500 года до н.э. В наше время, точных правил этой игры никто не знает, но существует несколько реконструкций, основанных на анализе материалов, обнаруженных египтологами.

    По версии Тимоти Кендалла, опубликованной в 1978 году, бой фигур в Сенете производился особым образом. Побитая фигура не убиралась с доски, а менялась местами с выполнившей ход. Эта идея хорошо согласуется с названием фигур Ibau — «танцоры» (в отличии от большинства других игр, Сенет использовал метафору танца, а не боевых действий). Игра в Сенет рассматривалась как «тренировка» в прохождении душой её посмертных испытаний (древние египтяне очень серьёзно относились к этому вопросу).

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



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

    Сиджа — ещё одна египетская игра, в которой фигуры действуют «сообща». В этой древней игре, сохранившейся до наших дней, захват фигуры противника осуществляется путём «зажимания» её в «клещи» с двух сторон. Вполне возможно, что именно от этой игры произошли и другие «зажимные» игры, такие как Хнефатафл.



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



    Ещё дальше идут некоторые региональные варианты "Hasami Shogi". В следующем ниже видео показано, каким образом может быть взята «компактная» группа фигур, расположившихся в углу доски. Признаться честно, я пока затрудняюсь с формализацией этих правил. Например, из объяснения непонятно, как брать фигуры у края доски:



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



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



    Конечно, это не предел того, куда может завести воспалённая фантазия изобретателя настольных игр. Связывая фигуры, совсем не обязательно ограничиваться соседними клетками. В «военной» игре Ги Дебора "Кригшпиль", прямые линии от «Арсеналов» и «Передатчиков» символизируют линии снабжения. Потеряв контакт с такой линией (непосредственный или через расположенную рядом фигуру), фигура «замирает», теряя возможность хода, до тех пор, пока контакт не будет восстановлен. Не знаю кому как, а мне это сильно напоминает «энергетическую сеть» протосов из Старкрафта.



    Простота формулировки правил Го обманчива. Корректная их реализация, на языке ZRF, столь же многословна сколь малопонятна. Приведу, в качестве примера, фрагмент реализации "Stoical Go":

    Много непонятного кода
    (define adjacent-to
    	(or
    		(position-flag? $1 n)
    		(position-flag? $1 s)
    		(position-flag? $1 e)
    		(position-flag? $1 w)
    	)
    )
    
    (define set-danger-flag 
    	(if (and (enemy? $1) (not-neutral? $1) (not-position-flag? safe $1))
    		(set-position-flag danger true $1)
    		(set-flag Changed true)
    	)
    )
    
    (define stone
    	(name Stone)
    	(image
    		Black "images\Stoical Go\pieces\b$1.bmp"
    		White "images\Stoical Go\pieces\w$1.bmp"
    	)
    	(attribute makes-capture false)
    	(drops
    		(
    			(verify empty?)
    			(verify (not-position? end))
    
    			(set-flag Capturing false)
    
    			; if next to enemy
    			(if
    				(or
    					(and (enemy? n) (not-neutral? n) )
    					(and (enemy? s) (not-neutral? s) )
    					(and (enemy? e) (not-neutral? e) )
    					(and (enemy? w) (not-neutral? w) )
    				)
    				mark
    
    				; *** Initialize safe
    				; for each point
    				; if enemy
    				; if next to empty
    				; P[safe] = true
    
    				a1
    				(while
    					(not-position? end)
    					(set-position-flag safe empty?)
    					next
    				)
    				back
    				(set-position-flag safe false)
    
    				a1
    				(while
    					(not-position? end)
    					(if
    						(and
    							enemy?
    							not-neutral?
    							(adjacent-to safe)
    						)
    						(set-position-flag safe true)
    					)
    					next
    				)
    
    				; *** Initialize danger
    				; for each adjacent
    				; if enemy
    				; P[danger] = true
    
    				back
    				(set-flag Changed false)
    				(set-danger-flag n)
    				(set-danger-flag s)
    				(set-danger-flag e)
    				(set-danger-flag w)
    				(if
    					(flag? Changed)
    
    					; *** Spread danger, safe
    					; Changed = true
    					; while Changed
    					; Changed = false
    					; for each point
    					; if enemy
    					; if !P[safe]
    					; if any adjacent is enemy with P[safe]
    					; P[safe] = true
    					; if !P[safe] and !P[danger]
    					; if any adjacent is enemy with P[danger]
    					; P[danger] = true
    					; Changed = true
    
    					(while (flag? Changed)
    						(set-flag Changed false)
    						a1
    						(while (not-position? end)
    							(if (and enemy? not-neutral?)
    								(if
    									(and (not-position-flag? safe) (adjacent-to safe))
    									(set-position-flag safe true)
    									(set-flag Changed true)
    								)
    								(if
    									(and
    										(not-position-flag? safe)
    										(not-position-flag? danger)
    										(adjacent-to danger)
    									)
    									(set-position-flag danger true)
    									(set-flag Changed true)
    								)
    							)
    							next
    						)
    					)
    
    					; *** Add captures for stones
    					; for each point
    					; if P[danger] and !P[safe]
    					; capture
    
    					a1
    					(while (not-position? end)
    						(if
    							(and (position-flag? danger) (not-position-flag? safe))
    							capture
    							(set-flag Capturing true)
    						)
    						next
    					)
    
    					back
    				) ; if Changed
    			) ; if next to enemy
    
    
    			;!!!!!!! Find out if suicide
    
    			; if no captures
    
    			(if 
    				(and
    					(not-flag? Capturing)
    					(not
    						(or
    							(empty? n) (empty? s) (empty? e) (empty? w)
    						)
    					)
    				)
    
    				; *** Initialize safe
    				; for each point
    				; P[safe] = empty and not-marked
    
    				a1
    				(while (not-position? end)
    					(set-position-flag safe empty?)
    					next
    				)
    				back
    				(set-position-flag safe false)
    
    				; Changed = true
    				; while Changed and not adjacent to safe
    				; Changed = false
    				; for each point
    				; if friend
    				; if !P[safe]
    				; if any adjacent is friend with P[safe]
    				; P[safe] = true
    				; Changed = true
    
    				(set-flag Valid (adjacent-to safe))
    				(set-flag Changed true)
    				(while
    					(and
    						(not-flag? Valid)
    						(flag? Changed)
    					)
    					(set-flag Changed false)
    					a1
    					(while (not-position? end)
    						(if
    							(and
    								friend?
    								(not-position-flag? safe)
    							)
    							(if (adjacent-to safe)
    								(set-position-flag safe true)
    								(set-flag Changed true)
    							)
    						)
    						next
    					)
    					back
    					(set-flag Valid (adjacent-to safe))
    				)
    
    				; *** Add if not suicide
    				; verify next to safe square
    
    				back
    				(verify (flag? Valid))
    
    			) ; if no captures
    
    			; *** Add stone
    			(if
    				(flag? Capturing)
    				(go last-to)
    				(if
    					(piece? CapturingStone)
    					(verify friend?)
    					(change-type Stone)
    				)
    				was-a-capture
    				(change-type yes)
    				back
    				(add CapturingStone)
    				else
    				was-a-capture
    				(if
    					(piece? yes)
    					(change-type no)
    					(go last-to)
    					(change-type Stone)
    				)
    				back
    				add
    			)
    		)
    	) ; drops
    )
    


    Даже с комментариями, разобраться в этом крайне тяжело (ещё сложнее найти в коде ошибки). С появлением в Axiom массивов, ситуация несколько улучшилась (настолько, насколько это вообще возможно, при использовании языка ForthScript). Вот фрагмент кода, проверяющего связность групп из моей реализации Ordo:

    Чуть лаконичнее, но не менее загадочно
    20		CONSTANT	LS
    10		CONSTANT	SS
    
    LS []		list[]
    VARIABLE	list-size
    SS []		set[]
    VARIABLE	set-size
    VARIABLE	curr-pos
    
    : not-in-list? ( pos - ? )
    	curr-pos !
    	TRUE list-size @
    	BEGIN
    		1- DUP 0 >= IF
    			DUP list[] @ curr-pos @ = IF
    				2DROP FALSE 0
    			ENDIF
    		ENDIF
    		DUP 0> NOT
    	UNTIL DROP
    ;
    
    : not-in-set? ( pos - ? )
    	curr-pos !
    	TRUE set-size @
    	BEGIN
    		1- DUP 0 >= IF
    			DUP set[] @ curr-pos @ = IF
    				2DROP FALSE 0
    			ENDIF
    		ENDIF
    		DUP 0> NOT
    	UNTIL DROP
    ;
    
    : add-position ( -- )
    	list-size @ LS < IF
    		here not-in-list? IF
    			here list-size @ list[] !
    			list-size ++
    		ENDIF
    	ENDIF
    ;
    
    : not-from? ( pos -- ? )
    	DUP from <>
    	SWAP not-in-set? AND
    ;
    
    : check-dir ( 'dir -- )
    	EXECUTE here not-from? AND friend? AND IF
    		add-position 
    	ENDIF
    ;
    
    : check-coherence ( -- ? )
    	here 0 list[] @
    	0 BEGIN
    		DUP list[] @
    		DUP to ['] n  check-dir
    		DUP to ['] s  check-dir
    		DUP to ['] w  check-dir
    		DUP to ['] e  check-dir
    		DUP to ['] nw check-dir
    		DUP to ['] sw check-dir
    		DUP to ['] ne check-dir
    		to ['] se check-dir
    		1+ DUP list-size @ >=
    	UNTIL 2DROP to
    	TRUE SIZE BEGIN
    		1- DUP 0 >= IF
    			DUP not-from? IF
    				DUP from <> OVER friend-at? AND IF
    					DUP not-in-list? IF
    						2DROP FALSE 0
    					ENDIF
    				ENDIF
    			ENDIF
    		ENDIF
    		DUP 0> NOT
    	UNTIL DROP
    	0 list-size !
    ;
    


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

    Проверка связности в Dagaz
    
    (define add-piece-neighbors
       (foreach current-group
           (all
               (if (any n s e w nw sw ne se)
                   (check is-friend?)
                   (if (not (piece-contains current-group))
                       (take-piece current-group)
                   )
               )
           )
       )
    )
    
    (define check-coherence
       (if (exists?
               any-position
               (check is-friend?)
               (take-piece current-group)
               add-piece-neighbors
           )
           (check (not (exists?
               any-position
               (check is-friend?)
               (check (not (piece-contains current-group)))
            ) ) )
       )
    )
    


    После двух предыдущих листингов, в это может быть трудно поверить, но здесь есть всё необходимое, чтобы решить задачу. Выбираем любую дружественную фигуру, добавляем в набор её соседей, затем соседей её соседей и так далее, пока соседи не кончатся. Если по завершении этой процедуры осталась хотя бы одна дружественная фигура, не принадлежащая построенному набору — инвариант нарушен. Очень жаль, что в ZoG и Axiom нельзя решить эту задачу также просто.
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 4

      +2
      Каждый раз, после прочтения очередной вашей статьи, остаётся лёгкая грусть — от ожидания следующей) Спасибо, особенно заинтересовал «Кригшпиль».
        0
        Меня он тоже заинтересовал.
        0
        Хорошая игра, даёт чему задуматься!

        По ходу чтения возник вопрос: как определять «нудность» игр (подобное адресовалось к Сенету)
          0
          Ну понятно, что оценка субъективная. В правилах Кендалла мне не нравятся два момента:

          1. «Сдвоенность» фишек, по сути, ничего не меняет (да, пары разбивать нельзя (а заодно тройки четверки и далее), но перепрыгивать то их можно), Скирюк, как обычно, даёт более интересный вариант — пара позволяет заблокировать движение и фишки противника начинают «топтаться на месте», но сохранить пару тяжелее, пристроится к ней ещё одна своя фишка — откроется «дырка» для противника
          2. Из «Домов» требуется точный бросок (если нет, падаем в воду, а далее — в дом возрождения), но при этом бросок в домах не приоритетен, что позволяет стоять в них «до посинения», при наличии других фишек, а попасть в дом возрождения, если он занят, просто нельзя — продолжаем мокнуть в доме воды (хотя по всему должны были утонуть). В результате этого маразма (и большого начального количества фишек — 7 вместо 5), игра превращается в бесконечный хоровод от домов к дому возрождения. Редким фишка удается из него вырваться.

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

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

        Самое читаемое