Small toy "Minesweeper" not in 30 lines

    Hello.


    The last time I showed you how you can quickly and easily write a game "snake" on the programming language the FBD , download the program to the controller and finally get the equipment involved in the management and automatic regulation of electricity and heat for all of us to do more, and something or something "useful."

    However, from people who had first-hand knowledge of the work of operational personnel at power plants, I received an important comment that the “snake” is categorically not suitable for ACS TP for objective reasons. Firstly, despite the fact that all the automation at the station is controlled by the automated process control system and it also regulates and protects, the realities of life are such that the operator is also required to monitor the process for prompt intervention as necessary. Therefore, the toy should be such that (in contrast to the previously presented) does not completely occupy the operator’s attention, allow him to switch between applications and do anything without damage to the game. And secondly, the toy "snake" itself is very dynamic and requires quick (and at high levels generally instant and jewelry) pressing the buttons, which can easily lead to a small error: for example, instead of controlling a toy, you can accidentally control some important technological equipment, which in this mode of operation could not be touched. Of course, nothing bad will happen. in any case, the protection will work, but shutting down the turbine or shutting down the boiler with protection, these are things that lead to significant financial losses and unnecessary work to put them back into operation.

    All this leads to the obvious idea that you need to implement calm toys on the logic. As an option - all kinds of solitaire games or the well-known "Minesweeper". Due to the fact that solitaires of all kinds require images of cards that were not immediately found on the Internet (of course, drawings of cards are easy to find, but I had special requirements for the size and quality as well as the design of the cards), it was decided to implement the Sapper toy .

    In addition to this, there is another important reason in the implementation of this particular game. Namely, the desire to once again demonstrate how easy and fun to program in FBD.

    A few introductory words


    But first, a few introductory words (for those who are not too tired to read).

    Probably everyone who reads this post to the end will say (or think): “Bullshit! Yes, I will write the same thing in C (C ++, Delphi, JS, etc.) in 30 lines of code. " And I agree with that. But there is one thing. Before you write anything in a high-level language of 30 lines of code, you just need to
    I could not resist
    learn this high level language.
    And anyone can start writing programs on FBD. Moreover, basic FBD programming skills are taught to preschool children.
    I think many will remember how in childhood ...
    ... under the guise of educational games from letters cubes they collected the words:
    For example, from letters ...

    Gathered the word ...


    In other words, FBD programming is simple and intuitive.
    Small remark
    Here I say FBD out of the old habit, although according to one of the main developers of all this:
    Deranged , December 20, 2013 at 12:25
    This is not FBD. There is support for data types, including structures. Data is also divided into potential (instantaneous value) and commands (buffered values). There is also built-in support for the quality of values. Feedbacks can be made, they are distinguished by a zebra. In this case, the default values ​​of the data type are taken as initial values ​​(prescribed in the type itself).
    In general, there is a wagon of everything, I also planned to push in there control of the execution order in the form of ties and support for the conditions. Then there would be a full Frankenstein of FBD, SFC and conventional flowcharts.


    Of course, there are serious algorithms for this language, a struggle for controller resources, optimization of technical program execution, memory optimization, and overall performance. Smart people puzzle over how to create perfect algorithms from the simplest blocks. But since Since this article is purely exploratory in nature, we will omit all these subtleties and finally go directly to the programming itself.

    But before that a little question on quick wits. Who will guess - "respect and respect", for the impatient - the answer is in the spoiler below.
    So - the game "Snake" and the game "Minesweeper" is one and the same game in terms of programming in FBD. Why? And what do they have in common that allows such a statement?
    Answer
    Everything is very simple. Both in the game "Snake" and in the game "Minesweeper" there is a field consisting of cells. Just the cells in this field take different values ​​and are displayed differently. That is ... in other words, we can take the program of the game "Snake", its graphical interface, and in half an hour or an hour of work we can remake this all into the game "Minesweeper", because in fact we will have to change only one algorithm and redraw one cell of the field ..

    Start programming


    So, we begin to program our task.
    My personal opinion is that 80% of FBD programming is to clearly imagine what we want to get in the end. And since only we achieve such an understanding, then 80% of the tasks are solved and there is literally a little work to sketch the code and comb it. Now I will try to demonstrate this principle in practice.

    Suppose we have a field for the “sapper” by analogy with the “snake” 20x20 cells. Therefore, we need 400 memory cells, in each of which each cell of the field will be processed. To structure the program, we break all the cells into rows. Thus, we need twenty row algorithms, each of which will consist of 20 cell algorithms. Those. we have come to understand the core of the program.

    Algorithm "String"


    Consider the string algorithm in more detail. Or rather, what data we need to have at the input and which data at the output of this algorithm. I suggest starting from the end - i.e. from the output.

    Algorithm outputs

    - Firstly, we need an output data line that determines the state of each cell for rendering it in the human-machine interface (or more simply in the operator station).
    - Secondly, we need a vector showing which cells contain bombs and which cells are safe.
    - Thirdly, we need a sign that the player “stepped” on the field with the bomb and the game is lost, let's call this exit “Big_Bada_Boom”.
    - Fourth, for convenience, the “sapper” has a chip that when it hits a cell that does not have a neighborhood with a bomb, all empty cells bordering it automatically open. Those. we need an output that sends the opposite command to "open the cell" by the program itself.
    - Fifth, for the algorithm for installing bombs, we need an output showing how many bombs are in this row. Of course, this value can be obtained directly from the vector described in clause 2, it is enough to simply add all the nonzero bits. But for convenience, we’ll add this function inside the macro.
    - Sixth, for statistics, we need to know how many more closed cells are left in the row.
    That's all we need for the full functionality of the sapper game.

    Only 6 outputs with data.

    Now let's think about what we need to have at the entrance in order to be able to form the outputs we need.

    Algorithm Inputs

    - It is logical to assume that the main input of the algorithm is a command from the Open Cell player. After all, this is the main meaning of the game.
    - For clarity of programming, we’ll use one more input - “Put a flag on a cell”, marking that there is a bomb and not allowing you to accidentally click on this cell and explode.
    - To form the status of each cell (and we remember that if there is no bomb in the cell, then it shows how many bombs are in the cells bordering it), you will need to start the vectors from the line above and the line below. Let's make the algorithm universal and add three inputs: an input for a vector with bombs in a row above the current, an input for a vector with bombs in the current row and an input for a vector with bombs in a row below the current.
    - To start a new game and rewrite the values ​​of the cells, add the logical input “New game”.
    - As mentioned earlier, if the cell is empty and there are no cells with bombs nearby, then the neighboring cells should automatically open. Add the “Open with program” input for this.
    - Well, of course, what kind of game is this without a mined field. So we need the "Install Bombs" entry.

    A total of 8 inputs.

    That's all that we need from the main algorithm of the program.
    We quickly fill the macro with our inputs and outputs and get this algorithm:
    or if we expand it:
    Now we fill our algorithm with meaning. As I already said, the core of filling our main algorithm is the “Memory Cell” macro, which will be 20 pieces.

    Memory Cell Algorithm


    Let’s try to think over what we may need from each cell of the game field “sapper”, and what we need to submit a macro for this. Let's start again from the end, i.e. from the exits. And when we formulate all the data that we need, it will become clear what we need to have at the input in order to receive it.

    Algorithm outputs

    - First, the cell must have an output showing the status of this cell. Have you ever wondered how many states each cell of a field can take in a sapper game?
    Field Cell States
    Answer 12. this is the case - 12 states corresponding to 12 different cell mappings.
    I encoded them as follows:
    -1 - the cell is closed.
    0-8 - the cell is open and shows the number of bombs in neighboring cells.
    9 - the cell is open and there was a bomb in it.
    10 - the cell is closed and a flag is set on it.

    - Separately, for calculations and other processing, we will make the logic outputs "Cell closed" and "Bomb is installed in the cell."
    - As already mentioned - if an empty cell is opened, then it should automatically open the neighboring cells. accordingly, we still need the logical sign “Open neighbors”.
    - Well, in the end, if the player made a mistake and “stepped” on the field with the bomb, then it is necessary to form a logical sign that the game is lost. let's call it “Bada_Boom”.
    Total of 5 exits.

    Algorithm Inputs

    Let's try to determine what we need in order to form the required outputs.
    - First of all, the algorithm must receive a command from the player “Open Field”.
    - For simplicity, as a separate team, we also set the “Set a Flag" command here.
    - As I said earlier - each cell in the field should know how many bombs are planted in neighboring cells. we’ll have a separate entrance called “Neighbors” for this.
    - In order to randomly establish the beginning of the bomb, you need the logical sign "Here is the bomb" by which the cell will be mined.
    - Well, of course, to start a new game, there should be a reset sign that resets all the information in the cells.

    We stuff this macro and get the following algorithm:

    Now we program the memory cell algorithm itself. It is extremely simple:

    A brief explanation of the principle of the algorithm
    - A vector formed from the state of neighbors comes to the “BitDescipher1” algoblock, which is unpacked using this algorithm, and then the number of units (bombs) in the vector is calculated on the “Slug1” algoblock. The question immediately arises: why is there 9 bits, when the cell has only 8 neighbors maximum. The answer is: for versatility. The fact is that, as will be seen later, I wrote another macro to find the neighbors of each cell. And in order to make the macro universal for all cases (and a cell can have not only 8 neighbors when it is in the center of the field, but only 3 when it is in the corner, or 5 when it is on the edge of the field) tap nine bits.
    - The trigger “RStrig1” is used to set and reset the “flag” on the cell. The first command cockes the trigger, and the second command resets. As you can see from the diagram, the cocked trigger blocks the passage of the “Open” command on the “I2” algoblock since the value from the trigger output is set to the input of the “AND” algorithm with inversion. Those. while the trigger is cocked, “False” arrives at the second input of the “And” algorithm and the output of the “And” algorithm is also “False” regardless of the value at the first input. It turns out that the “Open” command cannot reset the “RS2” trigger and we are insured against accidentally opening a cell that we do not want to open.
    - The “RS2” trigger serves to form the status of the cell: is it closed or open. As you can see from the diagram, the trigger is cocked by the Reset command (start of a new game) and is reset only when the Open command arrives.
    - Trigger “RS1” is used to indicate the presence of a bomb in this cell. It is cocked when the “Here is the bomb” program arrives from the program, which sets the bombs in the cell and is reset when a new game begins.
    - The “I3” algorithm is one of the most important in this scheme. the value of the “RS1” trigger (the bomb is installed here) and the arrival of the “open” command in this cell is added to it by AND. If both conditions are met, then “True” is formed at the output, meaning that the player has stepped on the field with a bomb. This value is fed to the output of "Bada_Boom", which then are collected from all cells at the output of "Big_Bada_Boom" and mean a loss.
    - Three “Choice” algorithms arranged in cascade form the cell status. On the “Choice1” algorithm, it is formed what to output if the cell is opened: the numbers “0-8” corresponding to the number of bombs in the cells in the neighborhood, or the number “9” corresponding to the mined cell. If the field is still closed (the “RS2” trigger is cocked), then the “-1” algorithm is set to “-1”, which, as we agreed earlier, corresponds to the status of the closed cell. If the field is closed (the “RS2” trigger is cocked) and at the same time (the “I4” algorithm) the “RStrig1” trigger is cocked, then on the “Choice3” algorithm the value is forcibly changed to “10” corresponding to the set flag.
    - The latest comparison algorithm is used to automatically open neighboring cells. If we got zero at the “Vybry3” output, it means the player opened the cell, next to which there are no bombs. Then a team is sent to open neighboring cells.

    This primitive algorithm is the basis of the whole game. There were only some combing programs and dopilivaniya functionality.

    Now let's start the “String” macro, which consists of 20 “Memory cells”.

    The macro itself and the description. The picture is large.

    The macro itself is extremely simple. 20 algorithms “memory cell” associated with its inputs and outputs of a common macro. For the status of the cell, this relationship goes directly, for the rest - either through the “BitShifter” algorithms for packing logical values ​​into a vector, or through adders to calculate the number of necessary elements in a row, or through the “OR” algorithm to generate an output command to open neighboring cells or end games ("Big_Bada_Boom").
    Special attention is paid only to the blocks "Open", "Transformation of the vector" and "Open neighbors".
    We consider each such block separately.
    Let's start with the block: Open

    It is easy to see that here they simply add up to the “OR” command from the player and from the program when it opens the neighbors of an empty cell.

    Let's consider the block: Vector transformation

    The idea is very simple. Three vectors come to us and we need to calculate how many mines surround each cell in a specific position. To do this, I intended to simply shift the vector to the right by (position - 2) and count the number of ones in the first three bits of each vector after the shift. But then I came across a funny thing. But what if the cell’s position is first? then, by analogy with the rest, I have to shift the vector by a negative value (i.e. not right, but left). Of course, all the bits moved to the left will be zero, but the general principle of counting will be preserved. However, the subtleties of the implementation of the algorithm did not allow this, so I had to put a check on the position of the cell and for the first position of the cell to carry out a separate processing. As a matter of fact, this processing consists only in processing the second bit in the current line and the third bits in all lines. Remember, I wrote that for the standard macro I needed to form 9 bits of the neighbors, and not 8, as required by the logic. This is due precisely to this situation.

    The most observant readers have already noticed that a clearly unnecessary operation is being performed here. I get bits describing the neighbors, pack them into a vector, and then unpack and sum them again. Those. packing and unpacking operations are clearly redundant. But I left this option for debugging and visualization.

    Let's consider the block: Open neighbors

    Here, in general, everything is very simple. If the cell is empty, then it should open not one, but as many as 8 neighboring cells. For unification and simplification of life, let it not open 8 neighboring cells, but 9 (yes, and itself, too. In the FBD language, sometimes even the simplest assumption, which does not affect the program’s work or resources, greatly simplifies life). Thus, the macro algorithm is simple. We check the position of the cell. If the cell is in the second or more positions, we form a vector from the first consecutive bits and shift them (remember, by analogy with the processing of the vector, only not to the right, but to the left, because we do not bring some part of the vector to the beginning, but on the contrary, we shift the initial vector by the part we need) by (position -2). If we have the first cell, then we form the initial vector of two bits and shift it by (position -1) = 0 i.e.
    Then we just have to add all the prepared vectors according to “OR” and give them to the main output.


    I would also like to mention another macro. Before starting the game, we need to randomly place mines in the field. But what if there is no random number generator and the "Random" command is not available. Of course, there are many algorithms for generating pseudo-random sequences on the Internet. But they are quite complex and their implementation in itself deserves a separate article. Therefore, I had to go the usual way and make a macro “Random Generator”.

    A brief description of how the macro works
    So, we do not have a random number generator. Well. we will do it ourselves!
    The idea is primitive and was already used by me in the previous program, just here it got some development.
    On the integrator, a value very quickly changing along the saw is formed with a custom range (the inputs “Upper threshold” and “Lower threshold” are responsible for the range). Further, on the “Seconds” algorithm, we calculate the controller’s operating time from the moment of start, which is also continuously increasing. We divide the controller operating time by our rapidly changing number, while taking the remainder of the division. Next, we select the digits we need from the remainder (for the sake of chance, I took 4-5 decimal places for the X coordinate, and 6-7 decimal places for the Y coordinate). The discharges are very simple. We multiply the remainder by dividing by 1,000,000 and divide by the remainder by 100. As a result, we get the value [0..100) in the remainder. It should be understood that this value can arbitrarily close to 100, but it will never be the same. The random value we need lies in the range [1..20], therefore we divide our remainder by 5 (the DelOst3 algorithm), take the integer part from the division, and convert it to an integer using the type converter. As already mentioned, the dividend will never be 100, which means our quotient lies in the range of natural numbers [0..19]. We solve the problem simply by adding one to the result. So our random number is ready. Due to the fact that it is not known at what point the player will click on the “New Game” button and what value the integrator will have at that moment, as well as the fact that we take values ​​in 4-7 decimal places, we can confidently say that we got a good random number generator. we take the integer part from division and convert it to an integer using the type converter. As already mentioned, the dividend will never be 100, which means our quotient lies in the range of natural numbers [0..19]. We solve the problem simply by adding one to the result. So our random number is ready. Due to the fact that it is not known at what point the player will click on the “New Game” button and what value the integrator will have at that moment, as well as the fact that we take values ​​in 4-7 decimal places, we can confidently say that we got a good random number generator. we take the integer part from division and convert it to an integer using the type converter. As already mentioned, the dividend will never be 100, which means our quotient lies in the range of natural numbers [0..19]. We solve the problem simply by adding one to the result. So our random number is ready. Due to the fact that it is not known at what point the player will click on the “New Game” button and what value the integrator will have at that moment, as well as the fact that we take values ​​in 4-7 decimal places, we can confidently say that we got a good random number generator.

    Putting all the pieces together and adding beauty


    So, all parts of our program are ready. We connect them together and do the strapping:
    Drawing the main program. Large.

    A little explanation
    As you can see from the figure, the most important working algorithm here is RuchSelector1, which outputs a logical unit at the output for one cycle and launches a new game. The launch of a new game consists in the fact that the signal is sent to all the "String" macros to the "New_game" input and this signal sets the "SR2" trigger in the "Memory_cell" macro and resets all other triggers responsible for the presence of bombs and flags in the cell .
    At the same time, the same output of the “Rukselector1” algorithm resets the “RS1” trigger (“Game_over” - this trigger is cocked when the player opens the cell in which the bomb was hidden, and the “RS3” (Victory) trigger, which cocked if the player wins, that is, detection of all bombs hidden in the field. At the same time, the same signal is sent to the input of the “Memory1” algorithm to record the number of installed bombs in the current game (the player can change the number of bombs in the field at any time, but in the program these settings will only come with the start of a new game s).
    Then, with a delay for the cycle (the “Delay1” algorithm) (which is needed to reset all the triggers in the memory cells), the “RS2” trigger (“Put_bombs”) is cocked, which includes the procedure for setting bombs. Those. transferring our random coordinates generated at the outputs of the "Generator_Random" macros to the "Column" and "Row" algorithms, which send a command to trigger the "RS1" trigger in the "Memory cell" at the given coordinates.
    Monitoring the number of installed bombs is simple. The total number of installed bombs is calculated and as soon as it is compared with the number set on the “Memory1” algorithm, the “SR2” trigger is reset, thereby stopping the installation of new bombs.

    Algorithms 36-40 serve to verify the correctness of a given number of bombs by a player. Here, for debugging, I limited the minimum and maximum number of bombs to 0 and 400 pieces, respectively. When setting a number outside this range, it is automatically equated to the nearest border.

    Verification of the fulfillment of victory conditions is extremely simple. All closed cells are considered. As soon as their number becomes equal to the specified number of bombs and the trigger "RS1" ("Game_over") is not cocked, the trigger "RS3" ("Victory") is cocked.

    We only need to consider 2 algorithms:
    The "OR2" algorithm. On it, by logical OR, the outputs of all three triggers are collected. And according to the output of the "OR2" algorithm (when it is equal to one), a lock on the player’s actions is set. Those. while at least one of the triggers has been cocked (i.e., either the player has already won, or he has already lost, or mines are being set for a new game), the player is prohibited from any pressing in the field. He can neither open cells nor check the boxes.
    The “Stopwatch1” algorithm serves as a stopwatch for counting the time spent on the game. Count the number of closed cells. At the initial moment of each game, it is 400. As soon as the number of closed cells becomes less than 400, a timer starts, which stops when one of the three triggers is cocked.

    That's the whole program.

    We fasten the graphic interface


    This is done in just a couple of minutes. 12 pictures are drawn for the cell (I used paint for this) and their display is set depending on the status of the cell. Then this cell is copied 400 times (there is no need to be scared, because 400 cells are just nine operations Ctrl + c - Ctrl + v) and you get a minefield. We find on the Internet the first image that comes across that is meaningful for the logo of the “sapper”. We fasten the timer display and the "New Game" button.
    And now the hardest part is to choose pictures for victory and defeat in the game. Then I took the first pictures I got in the search engine for the requests “atomic explosion” and “Victory”. And that’s it - the game is ready.
    Starting position:

    We play:

    Losing

    Warning questions: yes, that black thing that looks like a bug with legs is a bomb so painted.

    And we win!

    To summarize


    Anyone who was able to overpower this post as a whole probably noticed that we wrote a simple, but still not a “Hello, World!” Program, and at the same time we did not need absolutely any directories, help, sitting on the forums of programmers, smoking manuals, etc. All that was needed, we realized in a couple of hours (and personally, most of the time I spent on a beautiful arrangement of algoblocks for pictures), while using algorithms of addition, subtraction, multiplication and the logic algorithms “AND”, “OR” that were clear even to a student. Plus involved some of the simplest RS-triggers. The most complex algorithm used in the program is the "Integrator". And then we used it simply as an adder with feedback and border checking. Those. in fact, you can replace the integrator with the “Addition” algorithm and two “Comparison” algorithms.
    In other words, programming on FBD is simple and fun, and at the initial level, no knowledge or skills are required at all except understanding basic logic. The main thing is a clear idea of ​​the final result. If it is, then the program itself is its logical conclusion. If, however, “So far what we want to get” is not yet presented, then perhaps it is worth more clearly formulating the task. I must say right away that all of the above does not apply to all projects implemented on FBD, but to simple small tasks, such as the one given in our example. Because for huge projects with tens of thousands of signals, it’s probably not possible to connect all this in the head. But in this case, a huge project is divided into small subtasks,
    Because FBD language is universal and we used the simplest algorithms in the program, which are basic for any implementation, then this program can be reproduced with minimal effort on any controller, both domestic and foreign (for example, controllers from Siemens or ABB).

    From what remains unrealized

    Three functions of the classic sapper game remained unrealized.
    The first is the ability to set the size of the field in the settings. This is done easily. As a matter of fact, simply the “memory cell” algorithms are copied as many times as there are cells in the field. There is only one problem: the lack of dynamic memory, i.e. inability to "on the fly" add or remove any data. And from this a simple conclusion follows - it is very simple to make the changing field sizes - first you need to program the task for the maximum field size, and then just set the logical attribute to the idle cells, which by this attribute will not be displayed in the graphical interface and will not be processed during the game.
    The second is the substitution of the cell at the first click, if the bomb was originally in it. Those. in our program, unlike the “sapper” in Windows, there is a chance to lose on the first click. This is also done easily, it is enough to check the received signal for the “Game_over” trigger and the number of closed cells. If we have the number of closed cells 399 and a signal has arrived to cock the “Game_over” trigger, then you need to block this signal, reset the cell that was clicked on and run the algorithm for randomly setting another mine, while blocking the ability to set the mine in an already open cell.
    Well, as you can see from the description, the function of collecting statistics is not implemented. Those. the best result is not remembered, the name of the person who set the record. The ratio of victories and defeats, etc. But this is all so trivial that it is done literally with a couple of mouse clicks, so I leave this function to everyone to implement.

    Thanks to everyone who read this post to the end. I hope it was interesting.

    Also popular now: