
Board Game Scripting Language Approaches
It just so happened that I wrote games only for myself, and I have never dealt with them professionally.
But the experience of writing DSL (Domain Specific language) to reduce the routine of writing completely different code is at least some.
This is exactly what I want to share: how to arrange the immensity.

Our good hub-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 a software product again.
GlukKazan is working on an open project Dagaz, about which he shares excellent articles (for example, here: 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 the rules of the game to the engine.
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, a minimum of constructions.
3) Description of the rules should be easy for reading to igrodelo and for writing their games
4) For most cases, games can be written by supplementing / modifying 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 spent efforts at all, since the routine cannot be reduced, it is easier to write games immediately ready.
But this is not so.
Everything is much simpler!
Well, let's imagine that we are demiurges, and are capable, no, have already written this scripting language. Yes, yes, already. Let's see what this language can and what it can do.
For example, the Dagaz project (and its predecessors Axiom and ZoG ) focuses on board games. However, it’s quite difficult to explain to a person how the board game differs from the non-board game. What can I say, to describe it in precise definitions in some programming language is even more difficult.
Therefore, the first and most important rule - do not put restrictions where they do not exist!
We will not consider board games, but board games.
Let's take a look at the following list, which we want to describe and play with our engine.
They are very different at first glance. What unites them all? Is there really anything?
Yes. What unites them is that
Everything, only these restrictions on the engine!
So, in essence, we want to have an engine that should be able to ask 2 main questions: who is walking now, what possible actions can he do.
And the engine must be able to perform one action - to complete the selected action.
Well, maybe one more action is to choose a preliminary action (for example, if the time allotted for a move ends, and the move has not yet been made, so as not to end the game or choose a random action, you can choose a preliminary action in this case).
No need to put any more restrictions, because anyway, someone will want to get around them.
But what if we write poker? And in Fool you can throw a card, but you can not throw. Saying Paz, or choosing your suit is also an action. Putting a field in Carcassonne is also an action.
Where is Tetris? Tetris, it will probably be very difficult to do, because it is a real-time game, and hardly necessary. Modifying the engine can be easy, but not relevant.
I do not recommend following the principle - I will get all the data from the script and will model it in the engine. Because in this way, it is easier to use the .ini file as a configuration, and not to make the scripting language worse, since there will be just as much sense.
And how to get a card for the Fool when he fought back? Just the next we will walk again and there will be only 1 option of the move - take a card. If it is possible to add values for the action of the car, then do not ask the player which option to choose.
Let the engine know nothing about the game. It is just a conveyor for queries and a beautiful display. No more, but no less.
No matter how simple communication with the script is, you won’t be able to manage without settings.
Fool can be played together, three, four, four, five and even six. You can play each for itself, 2x2, 3x3.
We need to send the settings. Add another command. Ok, two, you need to check the status
It must be remembered that we create games. And any games are united by what
Let's create some more teams that reflect new abilities - load the game, start the game, find out the result of the game
Do not chase excessive versatility.
We will remember that the bots visualize 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 visualization message language does not need to be customized to the exchange language.
Suppose you have created that for each object necessary for visualization there is a visualization format (hereinafter referred to as PV), which describes which image (or which images) should be displayed, in which part of the screen, what will overlap with what.
We first need to display everything. So, you need a team to visualize the situation, which produces a list of objects in the PV format.
After all, we should see a chessboard with pieces, or what was given to us for the Fool.
By the way, you often need to visualize something that does not appear as figures in the game itself - for example, for the Fool, you need to display the number of cards rivals are closed, as well as the deck.
In addition, you need to know how to visualize possible moves.
That is, there should be a team to visualize the 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 shapes, and removing the shapes from the existing ones).
For example, in Fool, you must remove the selected card from the deck, but this card should appear on the table.
And in chess, the selected piece should become the “chosen” color, and the same piece will appear in the place of the selected square. In the event of an attack, moreover, the beaten figure should disappear.
In addition, you need to know when to display one or another possible action.
Actually, either insert into the previous command, or add a new command.
AI modules are preferably written separately, but integrated into a common script.
Conversation AI, in principle, will not be very difficult.
To implement this is much more difficult than to describe. It may well be necessary forks.
It is necessary that the engine understands whether it makes mistakes in the conversation with the script or not. Communication should be mutual, especially when the interaction represents a client-server application.
Essentially, one team is needed: the last team accepted.
The same command should be answered when the answer is not important. That is, installations, actions taken.
And then someone set a time limit for thinking, the player will sleep this time, it’s like, and the engine already looked like a player by chance.
Do not forget that the engine may respond with an error.
Well, we followed a lot of what our script should be able to do, and what we don’t give a damn about.
All, not ALL logic falls on the script. So and only so we will achieve the full universality of any board games.
Any compromise here leads to limitations, because of which more than once or twice you will have to dance with tambourines.
But we wanted an easy, simple language! And we are offered to put everything on igrodelov.
If you can’t, but really want it, then you can! It should be difficult for a programmer, not a game maker. And so it will be.
Be a little game maker yourself!
One of the most amazing features of C that still amazes me is that the type string is a library function.
No need to wait until igrodely come up with what a board, field or deck of cards. Write in a scripting language these concepts themselves.
Ordinary people just write, take a board, and create such fields here.
That is why it is necessary that all written scripts can be changed on top.
I didn’t want a static board for the game, but a dynamic one, please change the board so that its status will be checked every time on the move.
Even in spite of a bunch of written libraries by the programmer himself, the script code will become lightweight, but not easy.
How to remove unnecessary pieces of code?
We need to understand that we need a sublanguage, the DSL of this scripting language, written in the language itself.
It sounds scary, but not everything is the devil that is painted.
For me, one of the most beautiful libraries is the Parsec parser for Haskell. This is much prettier than string implementations in C.
Although they planned to include lines in the language, they did not plan to write a parser when they were Haskell.
In fact, they created only the language level 2 sublanguage there to compile the parser (in fact, there are still at least 4 additional levels).
1) Token / character level. Almost no one bothers to write their functions at this level, which is incomprehensible to the majority, there are enough library functions with a head.
For example, is it the
end of the line - eof
take 1 token / character - anyToken
Try the parser, if it falls, pretend that it did not consume tokens - try p
Or: try the parser, if it drops without consuming tokens, use the second parser - p1 <| > p2
Look forward: try the parser, pretend that it did not use tokens, if the parser is successful - lookAhead p
(in reality, there are much more functions).
The combination of these tokenistic functions allows you to generate very complex parsers.
2) The level of parsers. So intuitive, supplemented by a whole zoo of functions: such as
many parsers - many p
many, at least 1 parser - many1 p
shared parser - p `sepBy` sepp
line - string
between parsers - between p1 p2
...
Actually, when the library user comes in, then he sees that "everything is built before us", there is already a ready-made designer, just take and collect what you need and at the level that interests you .
The same goes for our language. I should not think about how to write a filter function, it should already be written there.
Just take and use, so the scripting language should speak to its users!
If I want checkers, but so that 1 or 2 checkers fit in the cell - I need to recreate only the description of the cell.
I want a square board - I load myself a square, I want a hexagonal - I load a 6-corner.
It is necessary for me that depending on the removal of pawns from the board, the fields of the board itself are destroyed - we make the board dynamic and monitor the destruction of the pawns.
We all love to eat deliciously, but not everyone can cook deliciously. It takes skill and time.
Igrodely are such lovers of soups.
They still need to ease the share.
Necessary in our Skipt language yet ... meta-language. Breathe out, just game templates.
Chess-like-games, Checkers-like-games, fool-like-games, card-games, ...
Give the opportunity to simply configure ready-made solutions, and not engage in programming.
Well, if you look at our Wishlist at the beginning of the article, you can be horrified, we wanted to have a super-universal scripting language, and at the same time be essentially a configuration of ready-made solutions, it is easy to change the logic of written games.
In fact, we understood what we want, what we don’t want, realized that the main part of the written code should be written in a scripting language, and the engine should be left with visualization and reaction.
There is no difference between chess, Balda and the fool.
Who is walking? Player 1
There is no difference between the dots and the spots of crystal repainting. There is a limited selection of actions each time.
It just needs to be understood, and then it will become clear how to explain this to the computer.
But the experience of writing DSL (Domain Specific language) to reduce the routine of writing completely different code is at least some.
This is exactly what I want to share: how to arrange the immensity.

Our good hub-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 a software product again.
GlukKazan is working on an open project Dagaz, about which he shares excellent articles (for example, here: 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 the rules of the game to the engine.
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, a minimum of constructions.
3) Description of the rules should be easy for reading to igrodelo and for writing their games
4) For most cases, games can be written by supplementing / modifying 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 spent efforts at all, since the routine cannot be reduced, it is easier to write games immediately ready.
But this is not so.
Everything is much simpler!
Demiurge and the black box
Well, let's imagine that we are demiurges, and are capable, no, have already written this scripting language. Yes, yes, already. Let's see what this language can and what it can do.
Do not put restrictions where they are not
For example, the Dagaz project (and its predecessors Axiom and ZoG ) focuses on board games. However, it’s quite difficult to explain to a person how the board game differs from the non-board game. What can I say, to describe it in precise definitions in some programming language is even more difficult.
Therefore, the first and most important rule - do not put restrictions where they do not exist!
We will not consider board games, but board games.
Let's take a look at the following list, which we want to describe and play with our engine.
- Chess
- Split
- Carcassonne
- Points
- Fifteen
- Dominoes
- Repainting crystals
- Fool
They are very different at first glance. What unites them all? Is there really anything?
Yes. What unites them is that
- 1 player always goes at one time
- Almost always, a player can think for an infinitely long time (we’ll separately consider when there will be time limits per move)
- Each time there is a limited number of possible actions. The game of the Point is an exception (because the point can be placed anywhere on an endless board). For convenience, we will cut the Points a little, but 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 main questions: who is walking now, what possible actions can he do.
IN: who
OUT: Player1
IN: actions
OUT: [SomeAction1, SomeAction2 Param21, ...]
And the engine must be able to perform one action - to complete the selected action.
action SomeAction3
Well, maybe one more action is to choose a preliminary action (for example, if the time allotted for a move ends, and the move has not yet been made, so as not to end 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 anyway, someone will want to get around them.
Think wider
But what if we write poker? And in Fool you can throw a card, but you can not throw. Saying Paz, or choosing your suit is also an action. Putting a field in Carcassonne is also an action.
Where is Tetris? Tetris, it will probably be very difficult to do, because it is a real-time game, and hardly necessary. Modifying the engine can be easy, but not relevant.
Do not castrate scripting language
I do not recommend following the principle - I will get all the data from the script and will model it in the engine. Because in this way, it is easier to use the .ini file as a configuration, and not to make the scripting language worse, since there will be just as much sense.
And how to get a card for the Fool when he fought back? Just the next we will walk again and there will be only 1 option of the move - take a card. If it is possible to add values for the action of the car, then do not ask the player which option to choose.
IN: who
OUT: Player3
# атакует 8й бубна (хотя лучше было бы двумя 10ками - бубовой и чирвой)
IN: actions
OUT: [Attack 8Diamonds, Attack 10Diamonds 10Hearts, ...]
IN: action Attack 8Diamonds
OUT: action Attack 8Diamonds
IN: who
OUT: Player4
# забрать со стола, перевести или отбиться? Перевёл
IN: actions
OUT: [TakeTable, PassShowOnly QueenDiamonds, Defend QueenDiamonds, Pass 6Diamonds, PassShowOnly 6Diamonds,...]
IN: action Pass 6Diamonds
OUT: action Pass 6Diamonds
IN: who
OUT: Player1
# предлагает отбить королём или тузом, забрать стол или перевести
IN: actions
OUT: [TakeTable, Defend KingDiamonds 6Diamonds, Defend KingDiamonds 8Diamonds, Defend AceDiamonds 6Diamonds, Defend AceDiamonds 8Diamonds, PassShowOnly KingDiamonds, Pass KingDiamonds, PassShowOnly AceDiamonds, Pass AceDiamonds]
IN: action Defend AceDiamonds 6Diamonds
OUT: action Defend AceDiamonds 6Diamonds
IN: who
OUT: Player3
# предлагает подкинуть 3му игроку
IN: actions
OUT: [Pass, Attack AceHearts]
IN: action Pass
OUT: action Pass
IN: who
OUT: Player2
# предлагает спасовать, даже автоматически, нечего подкидывать
IN: actions
OUT: [auto Pass]
IN: action Pass
OUT: action Pass
IN: who
OUT: Player4
# пасуем
IN: actions
OUT: [auto Pass]
IN: action Pass
OUT: action Pass
IN: who
OUT: Player1
# предлагает отбить королём или забрать всё
IN: actions
OUT: [Defend KingDiamonds, TakeTable]
IN: action Defend KingDiamonds
OUT: action Defend KingDiamonds
IN: who
OUT: Player3
#берёт из колоды 1 карту, кто ходил и не хватает
IN: actions
OUT: [auto Take AceHearts]
IN: action Take AceHearts
OUT: action Take AceHearts
IN: who
OUT: Player1
#берёт из колоды 2 карты кто отбивался
IN: actions
OUT: [auto Take 6Hearts 10Clubs]
IN: action Take 6Hearts 10Clubs
OUT: action Take 6Hearts 10Clubs
Let the engine know nothing about the game. It is just a conveyor for queries and a beautiful display. No more, but no less.
Where without settings
No matter how simple communication with the script is, you won’t be able to manage without settings.
Fool can be played together, three, four, four, five and even six. You can play each for itself, 2x2, 3x3.
We need to send the settings. Add another command. Ok, two, you need 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 what
- Game may not start
- 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 flies from cutlets. Visualization language
Do not chase excessive versatility.
We will remember that the bots visualize 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 visualization message language does not need to be customized to the exchange language.
Suppose you have created that for each object necessary for visualization there is a visualization format (hereinafter referred to as PV), which describes which image (or which images) should be displayed, in which part of the screen, what will overlap with what.
We first need to display everything. So, you need a team to visualize the situation, which produces a list of objects in the PV format.
After all, we should see a chessboard with pieces, or what was given to us for the Fool.
IN: view
OUT: [ (Piece1, ФВ), (King, ФВ), ..... ]
By the way, you often need to visualize something that does not appear as figures in the game itself - for example, for the Fool, you need to display the number of cards rivals are closed, as well as the deck.
In addition, you need to know how to visualize possible moves.
That is, there should be a team to visualize the 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 shapes, and removing the shapes from the existing ones).
For example, in Fool, you must 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 selected piece should become the “chosen” color, and the same piece will appear in the place of the selected square. In the event of an attack, moreover, the beaten figure should disappear.
In addition, you need to know when to display one or another possible action.
Actually, either insert into the previous command, 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
AI modules are preferably written separately, but integrated into a common script.
Conversation 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]
To implement this is much more difficult than to describe. It may well be necessary forks.
Mutual understanding
It is necessary that the engine understands whether it makes mistakes in the conversation with the script or not. Communication should be mutual, especially when the interaction represents a client-server application.
Essentially, one team is needed: the last team accepted.
IN: it
OUT: result
The same command should be answered when the answer is not important. That is, installations, actions taken.
And then someone set a time limit for thinking, the player will sleep this time, it’s like, and the engine already looked like a player by chance.
Do not forget that the engine may 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 followed a lot of what our script should be able to do, and what we don’t give a damn about.
All, not ALL logic falls on the script. So and only so we will achieve the full universality of any board games.
Any compromise here leads to limitations, because of which more than once or twice you will have to dance with tambourines.
But we wanted an easy, simple language! And we are offered to put everything on igrodelov.
If you can’t, but really want it, then you can! It should be difficult for a programmer, not a game maker. And so it will be.
Be igrodelov
Be a little game maker yourself!
One of the most amazing features of C that still amazes me is that the type string is a library function.
No need to wait until igrodely come up with what a board, field or deck of cards. Write in a scripting language these concepts themselves.
Ordinary people just write, take a board, and create such fields here.
That is why it is necessary that all written scripts can be changed on top.
I didn’t want a static board for the game, but a dynamic one, please change the board so that its status will be checked every time on the move.
# DynamicBoard
import StaticBord
let board {
constuctor {let board = prevous board}
on_player_change {let board = recalculate}
}
To Caesar Caesarean, and to God the God
Even in spite of a bunch of written libraries by the programmer himself, the script code will become lightweight, but not easy.
How to remove unnecessary pieces of code?
We need to understand that we need a sublanguage, the DSL of this scripting language, written in the language itself.
It sounds scary, but not everything is the devil that is painted.
For me, one of the most beautiful libraries is the Parsec parser for Haskell. This is much prettier than string implementations in C.
Although they planned to include lines in the language, they did not plan to write a parser when they were Haskell.
In fact, they created only the language level 2 sublanguage there to compile the parser (in fact, there are still at least 4 additional levels).
1) Token / character level. Almost no one bothers to write their functions at this level, which is incomprehensible to the majority, there are enough library functions with a head.
For example, is it the
end of the line - eof
take 1 token / character - anyToken
Try the parser, if it falls, pretend that it did not consume tokens - try p
Or: try the parser, if it drops without consuming tokens, use the second parser - p1 <| > p2
Look forward: try the parser, pretend that it did not use tokens, if the parser is successful - lookAhead p
(in reality, there are much more functions).
The combination of these tokenistic functions allows you to generate very complex parsers.
2) The level of parsers. So intuitive, supplemented by a whole zoo of functions: such as
many parsers - many p
many, at least 1 parser - many1 p
shared parser - p `sepBy` sepp
line - string
between parsers - between p1 p2
...
Actually, when the library user comes in, then he sees that "everything is built before us", there is already a ready-made designer, just take and collect what you need and at the level that interests you .
The same goes for our language. I should not think about how to write a filter function, it should already be written there.
Just take and use, so the scripting language should speak to its users!
If I want checkers, but so that 1 or 2 checkers fit in the cell - I need to recreate only the description of the cell.
I want a square board - I load myself a square, I want a hexagonal - I load a 6-corner.
It is necessary for me that depending on the removal of pawns from the board, the fields of the board itself are destroyed - we make the board dynamic and monitor the destruction of the pawns.
Instant soup or no limit to perfection
We all love to eat deliciously, but not everyone can cook deliciously. It takes skill and time.
Igrodely are such lovers of soups.
They still need to ease the share.
Necessary in our Skipt language yet ... meta-language. Breathe out, just game templates.
Chess-like-games, Checkers-like-games, fool-like-games, card-games, ...
Give the opportunity to simply configure ready-made solutions, and not 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 Wishlist at the beginning of the article, you can be horrified, we wanted to have a super-universal scripting language, and at the same time be essentially a configuration of ready-made solutions, it is easy to change the logic of written games.
In fact, we understood what we want, what we don’t want, realized that the main part of the written code should be written in a scripting language, and the engine should be left with visualization and reaction.
There is no difference between chess, Balda and the fool.
Who is walking? Player 1
IN: who
OUT: Player1
There is no difference between the dots and the spots of crystal repainting. There is a limited selection of actions each time.
# chess
action Castle King Rook2
# checkers
action Attack Men7 D2 F4
# splut
action Pull Troll E5 D5
# points
action Add 10-14
# carcassonn
action Put Title1Tree Place8-17
#15-puzzle
action Push 8
# dominos
action Put Title1-6 Place4-1 Left
# crystall painting
action Color Yellow
# durak
action New QuenHearts
It just needs to be understood, and then it will become clear how to explain this to the computer.