📜 ⬆️ ⬇️

Dagaz: Kicks to common sense (part 7)

image - Do you know your main sin?
- What the heck? I adore all seven ...
but now ... I'm ready to give vent to anger!

Malcolm Reynolds " Mission Serenity "


As my personal “ trouble rating ” is rapidly moving toward completion, it is becoming increasingly difficult to get the desired behavior from Zillions of Games . Some rules are very easy to formulate, but they can spoil a lot of blood for the developer. The “rule of wrath” is one of them.

4. For the fook!


The possibility of removing figures from the board (yours or your opponent) is very useful. There is no need to go far for examples. I already wrote about the game " Focus ", described by Martin Gardner in his " Mathematical Leisure ". Playing it is not easy, but the fun of the game can somewhat overshadow the fact of having a very simple win-win strategy for the second player. Repeating each opponent's turn symmetrically about the center of the board, he can delay the game to infinity:
')


With each move, the second player restores the central symmetry, broken by the previous move. Since the figures cannot move along the diagonals, the first player cannot break the symmetry in an unrecoverable (in one move) way, hence his opponent cannot lose, repeating all the moves exactly. Obviously, this deficiency can be dealt with only by breaking the initial symmetrical arrangement of the figures.

As the first move, each player can remove one piece from the board (of course, you should prohibit the second player from deleting a piece symmetrically located in relation to the move made by the first player). In ZRF, there is no possibility of forming moves that are limited only to removing some pieces from the board, but you can cheat. This code will produce the desired result:

Removing a figure
(define capture-enemy ( (verify enemy?) (verify (not is-protected?)) mark sym (set-attribute is-protected? true) back capture add )) ... (piece ... (drops (move-type capturing) (capture-enemy) ) ) 


Here sym is the direction connecting the board fields in pairs symmetrically. In fact, we are not deleting, but putting some kind of auxiliary figure on the board. The figure that was previously placed on the target field is automatically deleted, and we delete the added figure itself with the capture command, before the completion of the move with the add command. In ZSG notation, such a move looks like this:

 1. White t e6 x e6 

It is even easier to perform the "switching" of the figure by changing the color of the enemy to your own (just commenting out the capture command in the above code snippet), but here the first ambush lays in us. You can put your piece on top of the opponent, but not vice versa! ZoG allows you to play only your own pieces. Fortunately, in this case, this is not a very significant limitation. Voluntary removal of your pieces from the board (and even more so replacing them with enemy pieces) is not the best idea. Since we are unlikely to succeed in convincing the adversary to do so nobly, the matter will end in a gigantic imbalance at the very beginning of the game.


Of course, “Focus” is not the only game in which moves that perform the removal of arbitrary pieces are in demand. In the 30s of the last century, the game " Shahboy " was actively promoted in the USSR, in which the figures on the playing field symbolized the forces of infantry, aviation and artillery. Artillery, in this game, was a formidable force, since it could destroy enemy figures, literally “on the spot”. This could completely destroy the game balance, but the problem was solved in a very original way.

The weakest figure of the "infantryman" in this game, reaching the opposite edge of the board, did not turn into any other figure. In fact, the game would have looked very strange if an infantryman, having reached the enemy’s camp, would turn into a tank or plane (not to mention the “headquarters”). After going through the entire battlefield, the "infantryman" was removed from the board, but had the right to "carry out sabotage" - remove any opponent from the board to choose from (except for the "headquarters", of course). Another rule allowed the “infantryman” to jump over the black fields of the board (not occupied by other figures), which allowed him to effectively “seep” through artillery barriers.



In various versions of " Mills ", which is a transitional form between " tic-tac-toe " and games of the family of checkers , the removal of arbitrary enemy pieces is performed when you line up three of your chips. At the beginning of the game, the chips belonging to the players are alternately laid out on an empty board, after which they are allowed to move along the lines of the board. In existing ZoG implementations, the removal of the opponent's piece is combined with the move building the “mill” (a line of 3 pieces in a row).

Taking an arbitrary shape in the Mill
 (define take-piece a1 (while (on-board? next) (if enemy? mark (not-in-a-mill ns) back (if (flag? eatit) (not-in-a-mill ew) back (if (flag? eatit) (not-in-a-mill in out) back (if (flag? eatit) capture add (set-flag writeit false) ) ) ) ) next ) ) (define check-mill (set-flag mill false) (check-dir1 ns)(check-dir2 ns)(check-dir3 ns) (check-dir1 ew)(check-dir2 ew)(check-dir3 ew) (check-dir1 in out)(check-dir2 in out)(check-dir3 in out) to (if (flag? mill) (take-piece) ) ) (define shift ( mark $1 (verify empty?) (set-flag writeit true) (check-mill) (if (flag? writeit) add ) ) ) 


Here you should pay attention to the use of the writeit flag. The formation of a “quiet” move (with the add command) in the macro of moving the token ( shift ) occurs only if no option has been found to remove the enemy token. In turn, each add command, executed after a capture ( capture ) in the take-piece macro, leads to the formation of an independent variant of the move (at the same time prohibiting the formation of a “quiet” move).

This is a very interesting implementation of the Mill algorithm, which, unfortunately, does not cover all possible situations of this game. According to some variants of the rules, the simultaneous alignment of several rows of chips should ensure the possibility of removing the corresponding number of opponent's chips. An attempt to solve this problem by the above algorithm leads to a “combinatorial explosion”. In my implementation of the similar African game " Bolotud " I decided to use a different approach:

Taking figures in the '' Marsh ''
 (define my-friend? (and (in-zone? inner $1) (not (position-flag? from? $1)) (friend? $1) ) ) (define check-side (set-flag is-checked? false) (if (my-friend? $1) mark $1 (if (my-friend? $1) (set-flag is-checked? true) (set-flag is-accepted? false) (set-position-flag is-marked? true $2) (set-position-flag is-marked? true $3) $1 (set-position-flag is-marked? true $2) (set-position-flag is-marked? true $3) ) back (if (flag? is-checked?) (set-position-flag is-marked? true $2) (set-position-flag is-marked? true $3) ) ) ) (define check-middle (if (and (my-friend? $1) (my-friend? $2)) (set-flag is-accepted? false) mark $1 (set-position-flag is-marked? true $3) (set-position-flag is-marked? true $4) $2 $2 (set-position-flag is-marked? true $3) (set-position-flag is-marked? true $4) back (set-position-flag is-marked? true $3) (set-position-flag is-marked? true $4) ) ) (define shift-man ( (set-position-flag from? true) (verify (in-zone? inner)) $1 (verify (in-zone? inner)) (verify empty?) (set-flag is-accepted? true) (check-side $1 $2 $4) (check-side $2 $3 $1) (check-side $4 $1 $3) (check-middle $2 $4 $1 $3) (if (not-flag? is-accepted?) mark a0 (while (on-board? next) next (if (and enemy? (position-flag? is-marked?)) (set-attribute is-capturing? true) ) ) back ) add )) ... (piece (name Man) ... (attribute is-capturing? false) (drops (move-type droptype) (drop-man) ) (moves (move-type normaltype) (shift-man nesw) (shift-man eswn) (shift-man swne) (shift-man wnes) ) ) 


In this game, the alignment of a series of three figures, as well as in the "Mill", gives the opportunity to fight the enemy figure, but not any, but only "adjacent to the three from the side." In order to understand what is meant by this, we had to play the game of this game by the following entry . I must say that the quality of this notation is terrible. Scan from some unknown book I did not bother to clean up from recognition errors. However, having a board in front of my eyes, it was possible to restore the game’s turn (some of the players’s moves did not seem particularly clever to me).

The code snippet above is based on an analysis of this batch. Check-side and check-middle macros not only check the formation of triples, but also mark fields that are “under attack” with the is-marked positional flag ? . Since all flags (including positional flags) are automatically cleared at the beginning of each turn, it is not necessary to perform any kind of “garbage clearance” after the turn calculation has been completed. For the same reason, do you have to transfer the received information about a possible combat to the next turn, by setting the is-capturing attribute ? . In the next move, it is allowed to remove any piece that has this attribute set. This approach seems to me more flexible, moreover, the deletion of a piece, performed in a separate move, is more obvious than the choice from a long list of possible moves formed by the “Mill”.



I took graphic resources from the implementation of another African game . The situation was completely anecdotal. When I almost completely implemented the game algorithm, ZoG presented a surprise:

 (loss-condition (White Black) (pieces-remaining 2)) 

The party in “Bolotuda” begins with the installation of figures in pairs on an empty board. The above line ended the game when any of the players added a second piece. It was necessary to implement a one-time "reserve" in order for the figures to be taken into account by ZoG already at the stage of adding figures to the board. Since there was already no strength to draw and customize a board with a “reserve” of forces, I took the appropriate definition of a board from Yotai.

By the way, "Yotai" also fits the topic of today's conversation. This game is even more like checkers. The captures in it are carried out by the usual "jumping over" through the figure of the enemy. As in checkers, several take-offs are allowed “per chain” per turn. The difference is that for each piece taken, the player has the right to take another (any) opponent's piece. Such a “positive feedback” leads to the fact that any advantage gained quickly leads to victory (draws in this game are rare), and the very nature of the game is very suitable for temperamental inhabitants of the African continent.

But one should not think that such rules are found only in exotic games. Many of those who played checkers as a child will surely remember the rule that a figure who “missed” the possibility of a fight could be removed from the board for a fook. This rule is by no means the invention of “Russian drafts”! Likewise, a “huffing” shape can be made in the English Checkers . Many researchers agree that a similar “rule of anger” already existed in the progenitor of all drafts games - Alquerque .



With regard to the “rule of anger” itself, the story delivered a practically unequivocal sentence. The application of this rule in drafts makes it almost impossible at least some complex combinational game. Indeed, in most cases, it is much more profitable for a player to give one piece “for a fook” than to allow himself to be lured into a trap, as a result of which he may lose much more! Not surprisingly, in most modern versions of drafts, the “rule of anger” was abolished.

Currently, it is used only in some African variants, such as Damii , which is played on the territory of the Republic of Ghana . Like many other African games, this version of checkers is played “for speed” and “yawn” in it - an important element of tactics. As for the rest, the Damii rules are similar to the " International Drafts ".

As a kind of “intellectual gymnastics”, I decided to implement checkers “with blunders” on ZRF. The first step was obvious - since the player was required to ensure maximum freedom of choice, I turned off take priorities, the majority rule and allowed interruption of the take chain. In this game, the player is not obliged to “eat” the opponent's pieces, but if his checkers are “underweight”, the opponent has the right to remove any of them from the board. I decided to mark the figures subject to the “rules of anger” with attributes. Immediately formed the following simple algorithm:

  1. At the end of your turn, for all enemy pieces, perform a check on the possibility of fighting your pieces (pieces that can perform a battle, mark with an attribute)
  2. When the enemy takes a take, clear the attributes on all of his pieces (when performing a quiet move, do not touch the attributes)
  3. If any of the opponent’s pieces is marked with an attribute, take it (clearing the attributes on all other pieces)
  4. Make your next move

On paper everything was smooth. Of course, ZoG immediately began to make its own adjustments. The first thing you have to deal with when implementing something more or less complicated in ZRF is not a very intelligible model of moving figures. Up to the end of the move, the figure in ZoG remains “as if” in its initial position and, in most cases, it is devilishly uncomfortable. It is necessary to “remember” that the starting field is “in fact” empty, and the final one is filled with a moving figure. The result is the following macros:

ZoG Fight
 (define my-friend? (and (not (position-flag? from? $1)) (or (position-flag? to? $1) (friend? $1) ) ) ) (define my-empty? (and (not (position-flag? to? $1)) (or (position-flag? from? $1) (empty? $1) ) ) ) 


Of course, positional flags from? and to? it is also required to fill in correctly, and the macros themselves did not immediately come to their final version. The next was the problem of deleting marked shapes. Taking the figure “for the fuk”, the player must make one more move, without transferring the move to the opponent. I did not want to change the order of moves in turn-order (there were reasons for that) and I decided to block the possibility of a subsequent move by placing a special invisible figure on the field (deleted after the normal move). In order for the player to skip a move, he had to activate the corresponding option:

 (option "pass turn" forced) 

Further, one pulled another. The " pass turn " option controls the player's ability to pass a move (that is, adds a blank move to the list of generated moves). When you set the value " forced ", its action is even more cunning - the pass is possible only in the absence of any other possible moves (and the skip of a move, in this case, is performed automatically). Unfortunately, by its very nature, this option is completely incompatible with the following condition for completing the game:

 (loss-condition (First Second) stalemated) 

And this is very bad, since the completion of the game by the defeat of the player, in the absence of the possibility of a move, is perhaps the most important thing from the checkers. Somewhere in another, more ideal world, I would like the following construction to work (but these are empty dreams):

 (loss-condition (First Second) (and stalemated (total-piece-count 0 Lock) ) ) 

Skip the move had to do "manually." Actually, it's not as scary as it sounds. It is only required to implement the “button”, which the player will press on, performing “skipping a turn”. The a8 field (on a 64-square board) is a suitable place to place it (since normal figures do not enter it). There is one minus truth - in contrast to the honest " pass turn forced ", this button itself will not be "pressed". On the other hand, in the processing code of this move, you can put any additional logic, such as clearing attributes (in the current implementation, this was not needed, but not always so lucky). In the end, with the " English drafts " everything turned out, but long-range queens - is another matter:

Russian checkers (with '' blunders '')
 (define check-huff (if (and (on-board? $1) (my-friend? $1)) $1 (if (and (on-board? $1) (my-empty? $1)) (set-flag is-huffing? true) ) $2 ) ) (define check-huff-2 (set-flag is-huffing? false) (if (and (on-board? $1) (empty? $1)) $1 (check-huff $1 $2) $2 ) (if (and (flag? is-huffing?) (not is-huff?)) (set-attribute is-huff? true) ) ) ... (define check-huff-6 (set-flag is-huffing? false) (if (and (on-board? $1) (empty? $1)) $1 (check-huff-5 $1 $2) $2 ) (if (and (flag? is-huffing?) (not is-huff?)) (set-attribute is-huff? true) ) ) (define check-long-enemies (set-position-flag to? true) mark a0 (while (on-board? next) next (if enemy? (if is-huff? (set-attribute is-huff? false) ) (check-huff-1 sw ne) (check-huff-1 se nw) (check-huff-1 ne sw) (check-huff-1 nw se) (if (piece? King) (check-huff-2 sw ne) (check-huff-2 se nw) (check-huff-2 ne sw) (check-huff-2 nw se) (check-huff-3 sw ne) (check-huff-3 se nw) (check-huff-3 ne sw) (check-huff-3 nw se) (check-huff-4 sw ne) (check-huff-4 se nw) (check-huff-4 ne sw) (check-huff-4 nw se) (check-huff-5 sw ne) (check-huff-5 se nw) (check-huff-5 ne sw) (check-huff-5 nw se) (check-huff-6 sw ne) (check-huff-6 se nw) (check-huff-6 ne sw) (check-huff-6 nw se) ) ) ) back ) (define king-jump ( (check-lock) (set-position-flag from? true) (while (empty? $1) $1 ) (verify (enemy? $1)) $1 (set-position-flag from? true) (verify (empty? $1)) $1 (while empty? (clear-enemy-huffs) mark (while empty? (opposite $1) ) (verify enemy?) capture back (clear-huffs) (clear-lock) (set-flag more-captures false) (king-captured-find $1) (king-captured-find $2) (king-captured-find $3) (if (flag? more-captures) (set-attribute is-huff? true) (add-partial jumptype) else (check-long-enemies) (set-attribute is-huff? false) (add-partial notype) ) $1 ) )) (define king-shift ( (check-lock) (set-position-flag from? true) (while (empty? $1) (clear-enemy-huffs) $1 (check-long-enemies) (clear-lock) add ) )) 


This option even worked until it came to the ladies. The ladies behaved mysteriously:



The riddle was solved simply. The long-distance queen can stop on any field along the route, therefore for each of them a check of the possibility of a battle by enemy figures must be carried out. The flag, for the queen on h8 , was set when the black lady passed the b2 field, but was not reset at the beginning of the next iteration of the cycle. Similar games with attribute values ​​have no place in the turn generation cycle. Good old "copy-paste" once again came to the rescue:

Corrected implementation
 (define king-jump-1 ( (check-lock) (set-position-flag from? true) (while (empty? $1) $1 ) (verify (enemy? $1)) $1 capture (set-position-flag from? true) $1 (verify empty?) (clear-huffs) (clear-lock) (set-flag more-captures false) (king-captured-find $1) (king-captured-find $2) (king-captured-find $3) (if (flag? more-captures) (set-attribute is-huff? true) (add-partial jumptype) else (check-long-enemies) (set-attribute is-huff? false) (add-partial notype) ) )) ... (define king-jump-6 ( (check-lock) (set-position-flag from? true) (while (empty? $1) $1 ) (verify (enemy? $1)) $1 capture (set-position-flag from? true) $1 (verify empty?) $1 (verify empty?) $1 (verify empty?) $1 (verify empty?) $1 (verify empty?) $1 (verify empty?) (clear-huffs) (clear-lock) (set-flag more-captures false) (king-captured-find $1) (king-captured-find $2) (king-captured-find $3) (if (flag? more-captures) (set-attribute is-huff? true) (add-partial jumptype) else (check-long-enemies) (set-attribute is-huff? false) (add-partial notype) ) )) (define king-shift-1 ( (check-lock) (set-position-flag from? true) $1 (verify empty?) (check-long-enemies) (clear-lock) add )) ... (define king-shift-7 ( (check-lock) (set-position-flag from? true) $1 (verify empty?) $1 (verify empty?) $1 (verify empty?) $1 (verify empty?) $1 (verify empty?) $1 (verify empty?) $1 (verify empty?) (check-long-enemies) (clear-lock) add )) (variant (title "Russian Checkers (with huffs)") ; (option "maximal captures" true) ; AI Bug ; (option "pass partial" false) (piece (name Checker) (image First "images/wiedem/CheckerWhite.bmp" Second "images/wiedem/CheckerBlack.bmp") (attribute is-huff? false) (drops (move-type normaltype) (capture-huff) ) (moves (move-type jumptype) (long-checker-jump nw sw ne) (long-checker-jump ne se nw) (long-checker-jump sw se nw) (long-checker-jump se ne sw) (move-type normaltype) (long-checker-jump nw sw ne) (long-checker-jump ne se nw) (long-checker-jump sw se nw) (long-checker-jump se ne sw) (long-checker-shift nw) (long-checker-shift ne) (move-type notype) ) ) (piece (name King) (image First "images/wiedem/CheckerKingWhite.bmp" Second "images/wiedem/CheckerKingBlack.bmp") (attribute is-huff? false) (moves (move-type jumptype) (king-jump-1 nw sw ne) (king-jump-1 ne se nw) (king-jump-1 sw se nw) (king-jump-1 se ne sw) (king-jump-2 nw sw ne) (king-jump-2 ne se nw) (king-jump-2 sw se nw) (king-jump-2 se ne sw) (king-jump-3 nw sw ne) (king-jump-3 ne se nw) (king-jump-3 sw se nw) (king-jump-3 se ne sw) (king-jump-4 nw sw ne) (king-jump-4 ne se nw) (king-jump-4 sw se nw) (king-jump-4 se ne sw) (king-jump-5 nw sw ne) (king-jump-5 ne se nw) (king-jump-5 sw se nw) (king-jump-5 se ne sw) (king-jump-6 nw sw ne) (king-jump-6 ne se nw) (king-jump-6 sw se nw) (king-jump-6 se ne sw) (move-type normaltype) (king-jump-1 nw sw ne) (king-jump-1 ne se nw) (king-jump-1 sw se nw) (king-jump-1 se ne sw) (king-jump-2 nw sw ne) (king-jump-2 ne se nw) (king-jump-2 sw se nw) (king-jump-2 se ne sw) (king-jump-3 nw sw ne) (king-jump-3 ne se nw) (king-jump-3 sw se nw) (king-jump-3 se ne sw) (king-jump-4 nw sw ne) (king-jump-4 ne se nw) (king-jump-4 sw se nw) (king-jump-4 se ne sw) (king-jump-5 nw sw ne) (king-jump-5 ne se nw) (king-jump-5 sw se nw) (king-jump-5 se ne sw) (king-jump-6 nw sw ne) (king-jump-6 ne se nw) (king-jump-6 sw se nw) (king-jump-6 se ne sw) (king-shift-1 ne) (king-shift-1 nw) (king-shift-1 se) (king-shift-1 sw) (king-shift-2 ne) (king-shift-2 nw) (king-shift-2 se) (king-shift-2 sw) (king-shift-3 ne) (king-shift-3 nw) (king-shift-3 se) (king-shift-3 sw) (king-shift-4 ne) (king-shift-4 nw) (king-shift-4 se) (king-shift-4 sw) (king-shift-5 ne) (king-shift-5 nw) (king-shift-5 se) (king-shift-5 sw) (king-shift-6 ne) (king-shift-6 nw) (king-shift-6 se) (king-shift-6 sw) (king-shift-7 ne) (king-shift-7 nw) (king-shift-7 se) (king-shift-7 sw) (move-type notype) ) ) ) 


It may seem that this was the end of my troubles and I received a correct working implementation of Russian Checkers with Yaws, but everything is not so simple. First, the inclusion of the " maximal captures " option brought the bug I described in one of the previous articles to life. At some point in the game, under AI control, the program ceased to see the possibility of taking its pieces (and since the code that performed the check for the enemy had seen such an opportunity, he took checkers "for the fuk" out of the blue).

I have already learned how to deal with a similar bug in the Checkers Collection and even posted the appropriate correction , but for some reason this method did not work in the “with yawns” version.



But this is only part of the problem. In the illustration above, the white woman should take the pieces on d6 and f6 , hitting the battle of h8 . Of course, she can “yawn” by completing a quiet move or remaining in place, but in this case, it can be taken “for a fook.” The difficulty lies in the fact that with any technical means I cannot “force” the white lady to continue the fight. She can go to f4 or g3 and the “yawn” mark will still be cleared. Some things to implement on the ZRF correctly simply impossible.

Source: https://habr.com/ru/post/258437/


All Articles