📜 ⬆️ ⬇️

The dark side of the ZRF

Those who read my series of articles devoted to Zillions of Games might have the impression that I was completely satisfied with this product. Of course, it is not. ZoG is unique in that it allows you to quickly and practically “on the knee” to develop a prototype of almost any logical game, but this does not mean that it is perfect. Today, I want to talk about what I do not like about this project.
Of course, this criticism is not needed by itself. I am fully aware that with completely stopped product development (by the way, this is one of those moments that I don’t like), such criticism, as a means of feedback from the developer, is completely useless. Therefore, I am not going to write any letters to the creators of the product - the engine has long been gone.

ZoG showed a possible course of action, the very possibility of creating such a universal game engine, but if you want something to be done, you will have to do it yourself. This work is not easy and I am not at all sure that I will be able to cope with it (at least alone). In any case, as the very first step, it will be useful to understand what is bad with what is already there. Why start a new product development? I will try to tell about it ...

Compared to the variety of all sorts of rules, which I described in the previous article , the concept of Chess looks very simple. In part, this illusion is supported by the fact that, most of us, are familiar with Chess from early childhood. Of course, this simplicity is deceptive! And the point here is not even in the exotic rules of " taking on the aisle " and castling (because of which the cascade and set-attribute commands had to be added to ZRF). I propose to deal with a deeper question underlying the game itself. What should the King do under the check?

First of all, the King cannot remain in check! The next move, we must eliminate the threat (if this is not possible, the situation has a different name - mate). We can leave the King on the field who is not under combat, shut off another figure from the Shah (this is not possible if the Shah is set by the Horse) or eat an attacking figure. With the latest version, everything is also not easy. It is possible to eat an attacking piece by the King only if it is not protected by another piece, and in the case of a double check, there is one of the attacking pieces, not leading the King out from the battlefield of the other, also impossible. All this may seem obvious, but I want to make it clear that the implementation of such behavior can be very complex. How is this implemented in ZRF?
')
very simple:

(loss-condition (White Black) (checkmated King)) 

If the magic word checkmated appears in the condition of a player’s defeat, the core of the program knows how to fix the checkmate, and, at the same time, how to take the King out of the check. But such “stitching” of such complex logic into the core of a product may not be the most successful thought, when implementing a universal engine. The concept of mate itself is far from being found in all the games of the Chess family, and it is still necessary to take the King out from under the “Shah” to them. I have already said earlier that the processing logic of checkmated , can lead to the King’s not quite adequate behavior, and the refusal to use this condition almost completely breaks the whole endgame game in ZoG.

It is quite natural that the situation with the correct matting is complicated in various “national” varieties of Chess. So in Shogi , it is forbidden to checkmate a pawn (in this case, you can checkmate with a regular pawn and shake a pawn), and in Shatar , the process of mating is even more confused (for example, you cannot checkmate Kone). It is obvious that the capabilities of one single checkmated keyword are completely insufficient for a correct description of the rules of these games.

With Checkers, everything is also not at all smooth. The main difference from Chess is the obligatory take and the possibility of a series of takes. To implement these rules, partial moves ( add-partial ) and move priorities ( move-priorities ) are introduced in ZRF. The mechanism of priorities is not very convenient to use, but it works. A player will not be able to make a non-priority move if he has the possibility of a priority move But this is not the end! In some types of Checkers (for example, in international 100-cell Checkers), from all possible moves with a take, the player must choose the option of taking the maximum number of pieces (in this case, ladies can be taken into account or not taken into account).

This logic is reintroduced into the core:

 (option "maximal captures" true) 

In addition to this option, there are a number of other options used by AI ZoG:


As a control parameter, an option is passed a boolean value that enables or disables the option. For some options, it is allowed to pass the value 2 or forced . For example, the following command:

 (option "maximal captures" 2) 

... includes a "maximum capture" mode, taking into account the ladies.

There is no system in the list of options provided. Moreover, in the same list there are options that have nothing to do with AI, for example, " animate captures ", " highlight goals ", " show moves list ", ... This mechanism can hardly be called universal.

A similar lack of a systematic approach passes through the entire ZRF as a “red thread”. For example, there is a convenient relative-config command for defining “3 in a row” positions. Here is how it is used in " Tic-tac-toe ":

 (win-condition (XO) (or (relative-config man n man n man) (relative-config man e man e man) (relative-config man ne man ne man) (relative-config man nw man nw man) ) 

Simple and convenient. The only problem is that this command is allowed to be used only inside the game completion checks win-condition , loss-condition or draw-condition . In addition, it reacts to any occurrence of the specified configuration (no matter on whose course it occurs). As a result, in the implementation of " Mills " I have to fuss:

three-story checks
 (define Check-for-3 (set-flag first? false) (set-flag second? false) (set-flag third? false) (set-flag fourth? false) a1 (while (and (on-board? next)(not-flag? fourth?)) (if friend? (if (not-flag? first?) (set-flag first? true) else (if (not-flag? second?) (set-flag second? true) else (if (not-flag? third?) (set-flag third? true) else (set-flag fourth? true) ) ) ) ) next ) (verify (not-flag? fourth?)) (if (am-Black?) (change-type Jumping z0) ;set permanent flag else (change-type Jumping z1) ;set permanent flag ) ) 


Something similar had to do to me, with the implementation of one of the options Thud . But the possibility of using the relative-config analog, when performing a move, would be very useful in many games, for example, in Chaturanga with its “triumph of elephants”. In Hasami Shogi, such an opportunity would allow one to correctly implement complex rules for locking figures at the edge and in the corner of the board.

Another extremely useful feature would be to implement mark / back commands without limiting the nesting level . The mark command remembers the current position in the course of calculating the move, and the back allows you to return to it. It would seem, what could be more natural than using the stack to save positions? But no, nested mark calls are not supported! Sorry, it would be convenient ...

But back to the fundamental. What happens in Chess if we put a piece on the field occupied by an opponent? It is well known to everyone - the opponent's figure will be removed from the board. This is how everything is implemented in ZoG (and this behavior cannot be changed). If we give the add command on a busy field, the figure that was placed there earlier will be removed from the board. You do not need to execute the capture command (moreover, if we execute capture , the figure that we walk will be deleted). A board cell cannot contain more than one shape.

But this is far from true for all games! For example, in Russian Chess it would be much more convenient to store an ordered list of pieces in a cell of the board. The same goes for the Post Bars . Of course, I cannot say that this served as an insurmountable obstacle in the implementation of the latter. The pillars were implemented , but the complexity of the decision did not have a positive effect on its performance or on the quality of the game.

And this is not the only (and not even the most important) trouble of the add command! In ZRF, the add family commands combine two functions:

  1. Indication of the field on which the figure is placed, performed the move
  2. Completing the formation of a variant (or part) of the course

Do you already understand what the problem is? From this completely inappropriate combination of two fundamentally different actions in the same team, it immediately follows that a move (move or reset) must be completed by putting a piece on some board field (in this case, other pieces can be eaten). There are no other options! Try to implement on ZRF Andernach . I tried it three times, nothing happened! The fact is that, in this version of the game, when taken, the figure changes color to the color of the taken figure. This means that the move we end is no longer his figure ...

It would have cost the developers to divide the add command into the actual team of setting the piece on the board and the command of explicitly ending the move ( end-move for example) and heaps of problems could have been avoided! For example, it would be possible to capture enemy pieces without moving their pieces. By the way, in ZSG (notation of ZoG moves) there is such an opportunity, but it is used only for the “initial setup” of the board. In general, this moment, indeed, seriously complicates the development process using ZRF.

The fact that you can only walk with your figure is also not always true. For example, in the " Stavropol checkers ", the player can walk the opponent's pieces. In ZRF this can be achieved by making all the pieces neutral (belonging to the third player not participating in the game), but this solution is not at all obvious and rather cumbersome.

Another disadvantage of ZRF is the complete lack of any arithmetic operations in it. It's not scary, as long as we restrict ourselves to Chess and Checkers, but there are games in which the execution of arithmetic operations is necessary when calculating the turn! Of course, I'm talking about ritmomachia . This medieval game is not particularly popular in our time, but it definitely challenges the versatility of the game engine. Here are the conditions under which the figures can be fought in this game:


Arithmetic can be useful not only when calculating the course. In this game, for example, to determine the winner, it is required to calculate the area of ​​all the squares built on the board. Also, the ability to associate numeric values ​​(eg hit points) with figures and fields of the blackboard can be useful. ZRF allows only boolean values ​​(attributes) to be attached to figures; as for fields, boolean flags attached to them can only be used locally when calculating the course. Global numerical values ​​(not associated with fields or figures) could be used to implement a variety of counters (for example, playing time).

The standard ZoG does not have such features, but they are implemented in the Axiom Development Kit . This is worth to tell more. The fact is that ZoG allows you to use extensions that can be developed, for example, using C ++. The cost of using such extensions is to completely eliminate the use of the built-in AI ZoG. In fact, the extension uses ZoG exclusively as a means of visualizing moves. When developing such an extension, AI has to take care of itself.

Of course, this feature is not very suitable for ordinary ZoG users. Axiom developers took care of them by providing, with the extension, their own AI. In addition, this version of the AI ​​is much better than the staff copes with the games on the "seizure of the territory" and "connection", such as Hex . In addition, as a programming language, Axiom uses Forth Script , which provides support for arithmetic operations.

Unfortunately, being an extension of ZoG, Axiom is forced to use an interface designed to interact with extensions. I will allow myself to bring it here:

Engine.h
 // Engine.h // // Copyright 1998-2000 Zillions Development // // Header file for plug-in DLL engine to Zillions #include "EngineDLL.h" DLL_Result FAR PASCAL DLL_Search(long lSearchTime, long lDepthLimit, long lVariety, Search_Status *pSearchStatus, LPSTR bestMove, LPSTR currentMove, long *plNodes, long *plScore, long *plDepth); DLL_Result FAR PASCAL DLL_MakeAMove(LPCSTR move); DLL_Result FAR PASCAL DLL_StartNewGame(LPCSTR variant); DLL_Result FAR PASCAL DLL_CleanUp(); DLL_Result FAR PASCAL DLL_IsGameOver(long *lResult, LPSTR zcomment); DLL_Result FAR PASCAL DLL_GenerateMoves(LPCSTR moveBuffer); 


EngineDLL.h
 // EngineDLL.h // // Copyright 1998-2000 Zillions Development // // Shared DLL plug-in for DLL engine and Zillions #include "windows.h" typedef enum { kKEEPSEARCHING = 0, kSTOPSOON = 1, kSTOPNOW = 2 } Search_Status; typedef enum { DLL_OK = 0, DLL_OK_DONT_SEND_SETUP = 1, // only supported in 1.0.2 and higher! DLL_GENERIC_ERROR = -1, DLL_OUT_OF_MEMORY_ERROR = -2, DLL_UNKNOWN_VARIANT_ERROR = -3, DLL_UNKNOWN_PLAYER_ERROR = -4, DLL_UNKNOWN_PIECE_ERROR = -5, DLL_WRONG_SIDE_TO_MOVE_ERROR = -6, DLL_INVALID_POSITION_ERROR = -7, DLL_NO_MOVES = -8 } DLL_Result; enum { UNKNOWN_SCORE = -2140000000L, LOSS_SCORE = -2130000000L, DRAW_SCORE = 0, WIN_SCORE = 2130000000L }; // ***** REQUIRED ROUTINES // DLL_Search // // The DLL should search from the current position. If it returns DLL_OK it should // also return the best move found in str; however, it should not make the move // internally. A separate call to MakeAMove() will follow to make the move the // engine returns. // // -> lSearchTime: Target search time in milliseconds // -> lDepthLimit: Maximum moves deep the engine should search // -> lVariety: Variety setting for engine. 0 = no variety, 10 = most variety // -> pSearchStatus: Pointer to variable where Zillions will report search status // -> bestMove: Pointer to a string where engine can report the best move found so far // -> currentMove: Pointer to a string where engine can report the move being searched // -> plNodes: Pointer to a long where engine can report # of positions searched so far // -> plScore: Pointer to a long where engine can report current best score in search // -> plDepth: Pointer to a long where engine can report current search depth // // Returns DLL_OK or a negative error code typedef DLL_Result (FAR PASCAL *SEARCH)(long lSearchTime, long lDepthLimit, long lVariety, const Search_Status *pSearchStatus, LPSTR bestMove, LPSTR currentMove, long *plNodes, long *plScore, long *plDepth); // DLL_MakeAMove // // The DLL should try to make the given move internally. // // -> move: notation for the move that the engine should make // // Returns DLL_OK or a negative error code typedef DLL_Result (FAR PASCAL *MAKEAMOVE)(LPCSTR move); // DLL_StartNewGame // // The DLL should reset the board for a new game. // // -> variant: The variant to be played as it appears in the variant menu // // Returns DLL_OK, DLL_OK_DONT_SEND_SETUP, DLL_OUT_OF_MEMORY_ERROR, or // DLL_GENERIC_ERROR typedef DLL_Result (FAR PASCAL *STARTNEWGAME)(LPCSTR variant); // DLL_CleanUp // // The DLL should free memory and prepare to be unloaded. // // Returns DLL_OK, DLL_OUT_OF_MEMORY_ERROR, or DLL_GENERIC_ERROR typedef DLL_Result (FAR PASCAL *CLEANUP)(void); // ***** OPTIONAL ROUTINES // DLL_IsGameOver // // This optional function is called by Zillions to see if a game is over. If // not present, Zillions uses the goal in the ZRF to decide the winner. // // -> lResult: Pointer to the game result which the DLL should fill in when // called. If the game is over the routine should fill in WIN_SCORE, // DRAW_SCORE, or LOSS_SCORE. Otherwise the routine should fill in // UNKNOWN_SCORE. // -> zcomment: Pointer to a 500-char string in Zillions which the DLL can optionally // fill in, to make an announcement about why the game is over, such // as "Draw by third repetition". The DLL should not modify this // string if there is nothing to report. // // Returns DLL_OK or a negative error code typedef DLL_Result (FAR PASCAL *ISGAMEOVER)(long *lResult, LPSTR zcomment); // DLL_GenerateMoves // // You can use GenerateMoves in your DLL to tell Zillions the legal moves for // any position in the game. // // -> moveBuffer: Pointer to a 1024-char sting which the DLL should fill in when // called. Initial call should be with moveBuffer set to "". Each call // to GenerateMoves should fill in the next available move from the // current position, with a final "" when no more moves are available. // All moves must be in valid Zillions move string format. // // Returns DLL_OK or a negative error code typedef DLL_Result (FAR PASCAL *GENERATEMOVES)(LPCSTR moveBuffer); 


As you can see, this interface is designed exclusively for transmitting the ZoG core, the moves generated by the AI. A move must be formed (in an undocumented) ZSG notation. Additionally, the extension can determine the condition of the completion of the game, but it does not control the correctness of moves - this part remains in ZRF! From this simple fact it follows that all the shortcomings of ZRF, about which I spoke above (except for the lack of support for arithmetic) remain in force.

In fact, in the case of Axiom, the situation is even worse. The interface for interacting with extensions does not provide access to the rules of the game written in ZRF (let me remind you that we cannot do without them). Since Axiom AI, for its work, should have access to these rules, they have to be duplicated in the Forth Script language! There is, however, a utility that automates this process. All this makes the development, with the use of Axiom, is not at all a simple matter.

Continuing my story about the shortcomings of ZRF, I just can not get past the games with incomplete information . There are implementations of such games on ZRF, but are they fair? They just hide some of the information from the person . AI perfectly sees all the figures! You must agree that such a “one gate” game has little in common with the fact that all players have incomplete information. Apparently, this is one of the reasons why so few card games are implemented for ZoG. Not very interesting to play with someone who knows all your cards. There is another side to this question. I have already mentioned Battle vs Chess more than once. Most of the missions in the campaigns of this game are based on the fact that people and computers play according to different rules. For example, in the “Point of no return” mission it is required to lure the enemy's pieces to the mines located on the field. But if the AI ​​knows the location of the mines, it simply will not go to these fields! What is the point of making a move, as a result of which you simply lose a piece? In order for the game to proceed as it was intended by the developers, the computer must “think” that it plays by the usual rules. This is also a variant of the game with incomplete information.

Another sore point of ZRF is the lack of support for coalitions. Enochian chess can be a good touchstone in this respect. This game is played "couple for a couple." Allied kings do not threaten each other and can easily be located on neighboring cells. If one of the Allied kings is taken (there is no mate in Enochian chess), his pieces are “frozen”. They cannot walk, they cannot be taken. They just stand and block the fields. But the second king can regain control of these figures, ascending the lost ally to the “throne”! As a result, the sequence of moves remains the same, but the player goes "for himself, and for that guy." It is very difficult to implement all this in ZRF.

We talked a lot about AI, now you can talk a little bit about what might not seem very important - about the design. As an illustration, I propose to consider the game Surakarta . Yes, it is implemented on ZRF, but try to watch how it plays . All clear? I do not really. The capture, in this game, is carried out according to completely unique rules. The figure must "ride" on one or more side loops and hit the opponent's figure from behind. But ZoG does not know how to animate such a complex movement! As a result, the party turns into a rebus. It would be nice, at the level of the game, to be able to connect your visualizer. But, even in a ZRF file, the rules of visualization are mixed with the rules of AI. It is clear that it was easier for developers, but now, 3D-visualization, so simple, is no longer connected (if only because it requires completely different resources).

Summarizing this long article, you can see that most of the problems rest on closed source codes of the product. If the source codes were open, it would be possible to "finish" the functionality and "tie" their visualizers. It would be possible to achieve a better game for individual games, such as Chess, by adding their libraries of debuts and heuristics. It would be possible to port the product to various platforms, including iOS and Android. But there are no sources. And if we have to write our own, we should not forget that we should not repeat the mistakes of others. It is worth making the source open!

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


All Articles