It just so happened that I wrote the games only for myself, and never did it professionally.
But the experience to write DSL (Domain Specific Language) to reduce the routine of writing completely different code, at least some have.
This is what I want to share: how to organize the immensity.

Our good Hubr-user
GlukKazan writes many articles about what wonderful products there are for creating various board games. Such as
Zillions of Games and
Axiom Development Kit .
However, these programs are not universal. And you always want to improve them. But these products are not free, so you have to write the software again.
GlukKazan is working on the open source project
Dagaz , which is divided into excellent articles (for example,
Dagaz: A new beginning ).
')
So, suppose we want to create a universal game engine for board games, and we want to see a scripting language as its basis, which helps to explain to the engine the rules of the game.
How do we want to see him?
1) The language should be as universal as possible in order to describe almost any rules of the game.
2) However, the language should be as simple as possible, with a minimum of constructs.
3) A description of the rules should be easy to read igrodelu and to write their games
4) For most cases, the game can be written, complementing / changing already written
5) Communication (API) with the script should be as simple as possible. So that you can easily write bots and AI.
At first glance, it seems that no one will need the wasted efforts at all, since the routine cannot be reduced, it is easier to write the games immediately ready.
But it is not.
Everything is much easier!
Demiurgi and black box
Well, imagine that we are demiurgists, and are capable, no, have already written this scripting language. Yes, yes, already. Let's see what this language can and can do.
Do not put restrictions where they are not.
For example, the
Dagaz project (and even its predecessors
Axiom and
ZoG ) focuses on board games. However, it is quite difficult even to explain to a person how a board game is different from a short game. What can we say, to describe it in precise definitions in some programming language is even more difficult.
Therefore, the first and foremost rule - do not put restrictions where there are none!
We will consider not board games, but board games.
Let's look at the following list, which we want to describe and play with our engine.
- Chess
- Splyut
- Carcassonne
- Points
- Fifteen
- Dominoes
- Crystal repainting
- Fool
They are very different at first glance. What unites them all? Is there anything?
Yes. What unites them is that
- 1 player always walks at one time.
- Almost always, the player can think indefinitely (consider separately when there will be a time limit per turn)
- Each time there is a limited number of possible actions. The game of the Point is an exception (for the point can be put anywhere on an infinite board). For convenience, we will somewhat trim the points, and not change the engine
Everything, only these restrictions on the engine!
So, in essence, we want to have an engine that should be able to ask 2 basic questions: who is walking now, what possible actions he can do.
IN: who OUT: Player1 IN: actions OUT: [SomeAction1, SomeAction2 Param21, ...]
And the engine should be able to perform one action - to perform the selected action.
action SomeAction3
Well, maybe another action is to choose a preliminary action (for example, if the time allotted for the course ends, and the move has not yet been made, in order not to finish the game or choose a random action, you can choose a preliminary action in this case).
preaction SomeAction5
No need to put any more restrictions, because they still want someone to get around.
Think wider
But what if we write Poker? Yes, and in the Fool, you can throw a card, but you can not throw. Say Paz, or choose your own suit - this is also an action. Putting a field in Carcassonne is also an action.
Where is Tetris? Tetris will probably be very difficult to do, because this is a real-time game, and it is hardly necessary. You can easily modify the engine, but not relevant.
Do not castrate scripting language
I do not recommend to follow the principle - I will get all the data from the script and will simulate it in the engine. For in this way, it is easier to use the
.ini file as a configuration, and not to fence a scripting language, since there will be as much sense.
And how to get a card for the Fool, when he fought off? Just following we will walk again and there will be only 1 course option - take a card. If anything, you can add values for the auto action - that is to say, not asking the player which option to choose.
IN: who OUT: Player3
Let the engine know nothing about the game. It is just a pipeline for queries and beautiful display. No more, but no less.
Where without settings
No matter how simple the communication with the script is, you will not be able to manage without the settings.
In Fool, you can play together, three, four together, five of us and even six of us. You can play every man for himself, 2x2, 3x3.
We need to send settings. Add another command. Ok, two, you have to check the status.
set players = 4 set groups = 2x2 set startFrom = Player3 IN: get name OUT: name = 15-puzzle
Game first
It must be remembered that we create games. And any games are united by the fact that
- The game may not be started.
- Game can go
- The game can be completed and have a final result.
Let's create some more teams that reflect new abilities - load the game, start the game, find out the result of the game.
IN: load /path/chess.game OUT: load /path/chess.game IN: start OUT: start IN: result OUT: Finished ; Win Player1; Details Player1 78, Player2 38 OUT: Loaded /path/chess.game OUT: Started
Separate the flies from the chops. Visualization language
Do not chase for excessive versatility.
Let us remember that bots have visualization on the drum, but the person is very picky and wants to see a very beautiful picture.
The main conclusion from this is that visualization languages and messaging languages are generally different. Chasing the universality of the scripting language is not worth it, because the tasks are different.
The language of the visualization messages does not need to be customized to the language of the exchange.
Suppose you have created that for every object necessary for visualization there is a format of visualization (hereinafter FW), which describes which picture (or which pictures) should be displayed, in which part of the screen, what will be superimposed.
We first need to display everything. So, the team needs a visualization of the situation, which gives a list of objects in the format of PV.
After all, we must see a chessboard with figures, or that we were given out for the Fool.
IN: view OUT: [ (Piece1, ), (King, ), ..... ]
By the way, it is often necessary to visualize what does not appear as figures in the game itself - for example, for the Fool, it is necessary to display the number of closed cards from opponents, as well as the deck.
In addition, you need to know how to visualize possible moves.
That is, there should be a command to visualize actions, which contains a list of all possible actions, and to each of them in one of two options a new description: full or partial (with the addition of the necessary display figures, and the removal of figures from the existing ones).
For example, in the Fool it is necessary to remove the selected card from the deck, but this card should appear on the table.
IN: view actions OUT: [(Attack 8Diamonds, Diff [remove Card5, add (Card5, ), ... ]), ... ]
And in chess, the chosen piece should become the “chosen” color, and the same piece will appear on the spot of the selected cell. In the event of an attack, moreover, the broken figure should disappear.
In addition, you need to know when you need to display a possible action.
Actually, either in the previous team shove, or add a new command.
IN: view actions OUT: [(Attack 8Diamonds, Diff [remove Card5, add (Card5, ), ... ], OnChoose Card5), (Pass, Diff [add PassWord], OnChoose PassLabel), ... ]
Artificial intelligence language
It is desirable to write AI modules separately, but integrate them into a common script.
The conversation of the AI in principle will not be very difficult.
IN: analize OUT: [75 Pass, 44 Attack 6Diamonds, 59 Attack 8Diamonds] IN: analize quick OUT: [10 Pass, 5 Attack 6Diamonds, 20 Attack 8Diamonds]
Implementing this is much harder than describing. It may be necessary forks.
Mutual understanding
It is necessary for the engine to understand whether it makes mistakes in a conversation with the script or not. Communication should be mutual, especially when the interaction is a client-server application.
Essentially, one team is needed: the last command received.
IN: it OUT: result
The same command should be answered when the answer is not important. That is, the settings taken by action.
And then someone put a time limit on thinking, the player will sleep this time, it’s like, and the engine already happened to be like a player.
Do not forget that the engine can respond with an error.
IN: action TakeTable OUT: ERROR action is out list IN: who OUT: ERROR game result is Finished
No gods burn pots
Well, we have done quite a lot about what our script should be able to do, and what we don't care about.
All, not ALL logic rests on the script. So and the only way we will achieve the full universality of any board games.
Any compromise here leads to restrictions, because of which more than once and not two will have to dance with tambourines.
But we wanted an easy, simple language! And we offer everything, everything put on igrodelov.
If you can not, but really want, then you can! It should be hard for a programmer, not an igrodel. And so it will be.
Be igrodelami
Be a little igrodelom yourself!
One of the most amazing properties of C, which still amazes me, is that the type string is a library function.
No need to wait for igrodely come up with what a board, field or deck of cards. Write these concepts in a scripting language.
Ordinary people just write, take the board, and create such fields here.
That is why it is necessary that all written scripts can be changed on top.
I wanted to play not a static board, but a dynamic one, please change the board so that its state will be checked every time.
Caesar's Caesar's, and God of Gods
Even despite the heap of written libraries by the programmer himself, the script code will become lighter, but not easy.
How to remove unnecessary pieces of code?
It is necessary to understand that we need a sublanguage, DSL of this scripting language written in the language itself.
It sounds scary, but not everything is hell that they paint.
For me, one of the most beautiful libraries is the Parsec parser for Haskell. This is much more beautiful than implementing C strings.
At least the lines were planned to be included in the language, but the parser was not planned to be written when Haskel was composed.
In fact, they created there only with the help of language means 2 levels of sublanguage for creating a parser (in fact, there are at least 4 additional levels there).
1) The level of tokens / characters. At this incomprehensible to the majority level, almost no one climbs to write their own functions, library functions are lacking.
For example,
end of line -
eoftake 1 token / character -
anyTokenTry a parser if it falls, to pretend that it did not consume tokens -
try pOr: try a parser, if it falls without consuming tokens, use the second parser -
p1 <|> p2Look forward: try the parser, pretend that it did not use tokens, if the parser is successful -
lookAhead p(in reality there are a lot more functions).
The combination of these tokenistic functions makes it possible to generate very complex parsers.
2) The level of parsers. So intuitive, complemented by a zoo of functions: such as
many parsers -
many pmany, at least 1 parser -
many1 pparser split -
p `sepBy` seppline -
stringbetween parsers -
between p1 p2...
Actually, when the library user comes, he sees that “everything is built before us”, there is a ready-made designer, just take and collect what you need
and at the level that interests you .
The same goes for our language. Should not igrodel think how to write a filter function, it should be already written there.
Just take it and use it, the script language must speak to its users!
If I want checkers, but in order to place 1 or 2 checkers in the cell, I only need to re-create the description of the cell.
I want a square board - I load myself a square board, I want a hexagonal one - I upload a 6-angle one.
I need to, depending on the removal of pawns from the board, the fields of the board itself are destroyed - we make the board dynamic and follow the destruction of the pawns.
Instant soup or no limit to perfection
We all love to eat tasty food, but not everyone can cook tasty food. This requires skill and time.
Igrodely such soup lovers.
They also need to facilitate the share.
It is necessary in our skipt language more ... meta-language. Exhale, just game templates.
Chess-like-games, Checkers-like-games, fool-like-games, card-games, ...
Give the opportunity to simply configure ready-made solutions, rather than engage in programming.
import ChessLikeGame; let params = { previous params; players = 4; board_length = 75 symmetric start positions = true; start_white_positions = [A1, C1,D1, ....] }
Conclusion
Well, if you look at our articles at the beginning of the article, you may be horrified, we wanted to have a super-universal scripting language, and at the same time be essentially configuring ready-made solutions, it is easy to change the logic of written games.
In fact, we understood what we wanted, what we didn’t want, understood that the main part of the written code should be written in a script language, and the engine should be left with visualization and reaction.
There is no difference between chess, Balda and a fool.
Who walks? Player 1
IN: who OUT: Player1
There is no difference between dots and tag crystal-repainting. There is a limited choice of actions each time.
It just needs to be understood, and then it becomes clear how to explain it to a computer.