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!

    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. 1 player always goes at one time
    2. Almost always, a player can think for an infinitely long time (we’ll separately consider when there will be time limits per move)
    3. 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/
    OUT: load /path/
    IN:  start
    OUT: start
    IN:  result
    OUT: Finished ; Win Player1; Details Player1 78, Player2 38
    OUT: Loaded /path/
    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, ....]


    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
    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.

    Also popular now: