📜 ⬆️ ⬇️

"Kings of the North" - the battle for gameplay

Can i play tafle
Nine skills i know
I forget infrequently the runes
I keep books and accounts
I can ski on skis
Rowing and shooting well
From the arts, I know both ...

The Orcnean Saga

The story I want to tell is full of mysteries, incomprehensible code, sleepless nights, noise of feline steps ...
This is one of those stories in which the process is much more important than the result. If you need a result, you can find it here , but if you are interested in the details, what ... I am ready to talk about my ordeals.

It all started as usual. Sultan Ratrout , a big fan of board games, found one of my videos on YouTube and asked me to develop for him a collection of the most famous games of the family of checkers . The request surprised me. To say that there are a lot of such programs means only a few understatement. Checkers are available for every taste and for many different platforms. In ZoG alone, there are more than 20 items. The devil, as usual, was hidden in the details.
')
I already wrote that implementing checkers completely correctly and authenticly is not at all easy. The rules for turning drafts into queens (sometimes quite complicated ones) and the “majority rule” (in its various interpretations) turn this development into a real test of attentiveness, and the complexity of implementing the “Turkish strike” rule generally deserves a separate discussion. Alas, on closer examination, the “check” programs for Zillions of Games turned out to be extremely far from ideal.

The first sign was a bug I found in the collection of checkers from Uwe Wiedemann. In its implementation of “Russian drafts”, the piece, which turned out to be in the zone of transformation during the battle, did not continue to move if the next piece could be taken only with the help of the queen. I published a fix and thought to calm down on it, but looking at other options, I realized that it was easier to develop my own set of games than to correct all the errors in existing implementations. Even the version from the ZoG developers themselves, supplied with the program, worked with monstrous errors!

As a result, I decided to create my collection of checkers. Of course, I began with a careful analysis of existing implementations. Of these, he also took graphic resources, correcting minor flaws in a graphic editor (the same set of resources was already used by several authors ). I learned a lot from the program code. For example, in the "Turkish checkers", the lady, during the battle, it is forbidden to change the direction of motion to the opposite. Here is how elegant it is implemented:

Fight lady
(define dama-king-jump ( (while (empty? $1) $1 ) (verify (enemy? $1)) $1 (verify (empty? $1)) $1 (while empty? mark (while empty? (opposite $1) ) capture back (add-partial continuetype) $1 ) )) (define dama-king-continue ( (while (empty? $1) $1 (verify not-last-from?) ) (verify (enemy? $1)) $1 (verify (empty? $1)) $1 (while empty? mark (while empty? (opposite $1) ) capture back (add-partial continuetype) $1 ) )) (game (title "Turkish Dama") ... (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) ) ) ) 


The last-from predicate ? checks whether the current field is the initial field of the previous (partial) move. If we cross such a field, then the direction of movement of the woman has changed to the opposite and the course cannot be performed. Of course, such a check should not be performed within the framework of the very first partial move. Here come the modes of progress. The first move is carried out within the priority mode of jumptype and performs switching in continuetype , within which all subsequent moves must be performed. It turned out (I did not know this before) that the explicitly assigned continuetype is not required to be mentioned in the list of priorities.

Another pleasant surprise was the method of calculating the “long” move, when fighting a lady, when a lady, can stop not only on the field next to the knocked figure (as in “ Thai drafts ”), but also on any free field that follows. It turned out that it is not necessary to write footcloths from “copy-paste”:

Possible description of the fight dyke
 (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) )) ... 


I recall that the problem is that the obvious implementation leads to an error in the process of generating a turn:

This code does not work!
 (define king-jump ( (while (empty? $1) $1 ) $1 (verify enemy?) capture $1 (while empty? (add-partial jumptype) $1 ) )) 



The correct implementation is somewhat reminiscent of the Slamiell painter . From the point of possible completion of the move (several are generated in the cycle), it is necessary to return in the opposite direction and remove the first enemy figure encountered. It's funny, but this is the only way it works!

Alternative solution
 (define king-jump ( (while (empty? $1) $1 ) (verify (enemy? $1)) $1 (verify (empty? $1)) $1 (while empty? mark (while empty? (opposite $1) ) capture back (add-partial continuetype) $1 ) )) 


If you smile, then we are moving in the right direction. But hey, we haven’t considered the “Turkish strike” yet! In order not to "take" the enemy pieces again, they need to somehow mark. This can be done in two ways. You can change the shape type, or use one of its attributes (the latter method is not available in Axiom ). Upon completion of the compound move, you can delete all previously marked shapes.

Turkish strike (use of attributes)
 (define checker-captured-find mark (if (on-board? $1) $1 (if (and enemy? (empty? $1) (not captured?)) (set-flag more-captures true) ) ) back ) (define checker-jump ( $1 (verify enemy?) (verify (not captured?)) (set-attribute captured? true) $1 (verify empty?) (set-flag more-captures false) (checker-captured-find $1) (checker-captured-find $2) (checker-captured-find $3) (if (not (flag? more-captures)) mark a0 (while (on-board? next) next (if captured? capture) ) back ) (if (in-zone? promotion) (add King) else (add-partial jumptype) ) )) ... (piece (name Checker) (image First "images/wiedem/CheckerWhite.bmp" Second "images/wiedem/CheckerBlack.bmp") (attribute captured? 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) ) ) 


For simplicity, here is the English version of checkers. The solution with the "long-range" ladies looks more complicated. The most obvious moment in this code is the definition of the moment of completion of the compound move. The solution is quite straightforward - to determine whether the move continues, we simply search for the figure that we can “eat” further, in one of three directions (since, from the ZoG point of view, the directions are not interconnected, all three must be passed to the macro) . This should work! But it does not work:



What's the matter? Maybe something is wrong with the attributes? Let's try changing the shape type:

Turkish strike (change of type of figures)
 (define checker-captured-find mark (if (on-board? $1) $1 (if (and enemy? (empty? $1) (not (piece? 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? more-captures)) mark a0 (while (on-board? next) next (if (piece? XChecker) capture) ) back ) (if (in-zone? promotion) (add King) else (add-partial jumptype) ) )) ... (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") ) 




Already better! At least this zombie is not trying to eat us! That is exactly what this error looks like in the official Russian Drafts editorial from the developers of Zillions of Games. The last piece taken is not removed from the board. She certainly can not walk, but prevents the movement of other figures! In order to figure out what's the matter, let's upload the protocol of this “mini-game” to the ZSG file:

ZSG log
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

It's all in the last move. We “mark” the figure on the f6 field for deletion, but do not delete it along with the others! Obviously, the program simply cannot do this. When using attributes, the log looks more complicated, but the essence remains the same. We cannot remove a piece with which we did something within the same partial move. Whether we changed the attribute or the type of the figure - it does not matter. Immediate deletion of such a figure is incompatible with the capabilities of ZSG notation!

The solution to the problem is obvious, although it does not make the code more “readable.” You do not need to “mark” the last figure, you just need to “take” it:

Fight in Russian checkers
 (define russian-checker-jump ( (verify (not captured?)) $1 (verify enemy?) (verify (not captured?)) $1 (verify empty?) (set-flag more-captures false) (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? more-captures)) (opposite $1) (if enemy? capture ) $1 (capture-all) ) (if (in-zone? promotion) (if (flag? more-captures) (add-partial King jumptype) else (add King) ) else (if (flag? more-captures) (add-partial jumptype) else add ) ) )) 


Having dealt with all these problems, I quickly “stamped” a gentleman's set of games, including mandatory Turkish , Armenian , English and Russian checkers. The exotic was represented by the Ossetian and Khakass checkers, but the collection still lacked a “zest”.

I decided to stake this position for the " Northern Drafts ". This variant differs from the “Russian checkers” that are usual to us by just one rule: the “eaten” lady does not get out of the board, but “goes down in rank” to ordinary checkers. This seemingly small difference radically changes the nature of the game in the endgame. Suffice it to say that two ladies quite easily catch one, even if it is on the “big” - the main diagonal of the board!

The choice was made, it remains to bring it to life. The first version played something like this:



Very brutal, but not quite as desired. When there are only a couple of pieces in the game (like in Chu Shogi ) that can “eat” any opponent's piece and return to the site within one turn, it can be funny, but when there are many such pieces and they are long-range ... the gameplay seriously suffers. It became clear that it was necessary to somehow prohibit the possibility of re-“eating” the figure during one move, but how to do it?

In ZoG, the choice is small. The last-from predicate ? , will not allow to solve the problem, as the woman can “eat” the figure on the return movement and not reaching the starting field of the previous turn. Positional flags, allowing to bind a boolean value to an arbitrary field, alas, did not fit. There was a great temptation to use them, since the documentation guarantees their automatic cleaning at the beginning of each turn.

Unfortunately, this clearing occurs at the beginning of each partial move and, as a means of transmitting information between partial moves, positional flags are completely useless! Attributes remain. You can mark “taken” pieces by setting attributes, but in this case, we need to take care of clearing them between compound moves.



Another bug was connected with this (it was not easy to notice and localize it). Although the attribute clearing was performed when the pieces were moved, the attribute of the shape that performed the move did not seem to be cleared. The ZSG log again helped to deal with this (in the list of moves displayed by the program, information about changes in attribute values ​​is hidden):

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

In the third line you can see that the attributes are cleared, but the attribute is cleared at position c7 , while the shape has already moved from this field to b6 ! After making the necessary corrections, everything began to work as planned:

north-shift
 (define clear-all mark a0 (while (on-board? next) next (if (piece? Checker) (set-attribute captured? false) ) ) back ) (define north-shift ( (clear-all) $1 (verify empty?) (if (in-zone? promotion) (add King) else + (set-attribute captured? false) add ) )) 




There was one more problem. At the beginning of the game, the Northern Checkers were no different from the “Russians,” and you had to live to see the ladies. Thus, the “Northern Kings” variant was born, in which the game began with ladies! The mass availability of long-range queens at the very beginning of the game, as well as their phenomenal vitality, provided the game with a completely unique, “explosive” gameplay. I consider this game the “pearl” of my collection:



On this joyous note could be finished. In fact, the collection is ready, everything works, it has a couple of unique games. But a few moments seriously darken my joy. Firstly, these constant “rake dances” make the code so confusing that I don’t vouch for the fact that there are no mistakes left in it (and it gnaws at night). In addition, I did not manage to solve some of the problems.

In Ossetian Kena, checkers can jump over friendly figures. I would like to be able to interleave such moves with the battle of enemy figures in random order, but this does not work! Even without taking into account the fact that for such moves ( even theoretically ), it is impossible to make the take mandatory, the very attempt to “loop” the moves in this way leads to an immediate error.

Ossetian Kenes
  (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) ) ) 


It is necessary to remove these comments and everything breaks down! Even if you mark friendly figures, forbidding repeated jumps over them, this does not help. If we talk about the "marks", then the great success is that in none of the variants of the checkers is it necessary to prohibit the repetition of the passage through the empty fields. This is good luck, since it is impossible to implement such a ban in ZoG (in any case, in a simple way). Positional flags could help in this, but ... apparently not in our universe.

ZoG, in turn, is always ready to put a new surprise:



Yes, yes, the program behaves differently depending on whether it is running under AI control or in manual mode! Long-distance ladies, the rule of the majority and the Turkish strike - when all the "three riders" get together ... AI can start behaving strangely. Moreover, even when I was shooting this video, the error did not manifest itself deterministically - sometimes one piece was taken, sometimes two. I do not know how to deal with this. We have to compromise with the conscience and disable one of the three options. For example, in the "International drafts" had to abandon the "rule of majority".

In any, even the largest, a barrel of honey, there is always a place for a small spoon of tar ...

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


All Articles