
So, this article is dedicated to those who like to solve non-standard tasks on tools not intended for this. Here I will describe the main problems encountered during the creation of the analogue of the game Gravity defied using a streaming text editor (sed).
Further, it is assumed that the reader is at least a little familiar with the syntax of sed and the writing of scripts under bash.The peaceful evening of December ceased to be peaceful when I received a message from a teacher of approximately the following content:
On sed:
Gravity defied
...
It must be cool
Frankly, for the first half hour I was sitting with the thought of how this is possible at all. But then I managed to pull myself together and I began to understand.
Attempts to google on sed games led to
arkanoid and sokoban .
')
Before we begin to analyze the problems, I want to share the
repository with the project and the
video demonstration of the result
So,
Problem one: the view in the memory of sed should somehow keep the current state of the game. We have two places for magic
hold space and pattern space .
Hold space will store the state of the game between iterations (iteration I will call the processing of one incoming character), and in the pattern space we will change the state of the game.
Algorithm like this:
- Moving on to the action that is tied to the symbol that we received at the input
- Write to the pattern space the contents of hold space.
- Change the contents of the pattern space in accordance with the logic of action
- Write the contents of the pattern space in hold space
- We apply effects to the pattern space (at this step we are from our “service” state of the game to what the user will see)
- We display the contents of the pattern space on the screen
- Repeat with p.1 for each character entered
To simplify the analysis of the entered text, we take it on faith that of the entire incoming line, only the first character is important to us.
The first thing is initialization. Create a print label that will create the game field at the initial time. From the moment of launching the game, only once a situation will arise when an empty line is passed to the sed input: the very start of the game.
In this way,
/^$/b print ... :print
At this stage, it all depends on your imagination. You decide what each character is responsible for. My B is an empty place, F and S are bike wheels, in A, D, P, U is a road (four kinds, for beauty, but more on that later).
We need to display everything received on the screen. As you can see, at the end of print, we go to the end label.
end is the overall completion of
any action.
:end
Note: ^ [[H is not worth copying, it is an escape sequence. For example, in vim it is entered like this: Ctrl + V Ctrl + ESC [H
Run our script with
sed -nf gravity.sed
. Congratulations on a static picture!
When we have a field, simply write commands that will move our improvised wheels left and right:
s/FB/BF/ s/SB/BS/
Moving up is a little more difficult, but we are not afraid of difficulties, right?
s/B(.{39})F)/F\1B/
Here the whole point is in the figure 39. This is the number of characters in the line.
Add a couple of tags and “bind” them to the right keys, and voila, we have some abstract bike (well, two wheels) for which there are no borders and physics. But if you want to write a maze, then you just need it.
Checking the game is not difficult, but pressing Enter after each character entered is a pleasure below average, so you need to automate this process.
Problem two: clocking
Since the “heart” of the game is sed, we need a shell that will hit enter for us every time we press a button. The endless cycle is the most it.
Sample code:
(while true do read -s -n 1 key
The game will now be a little more joyful, but it still has a big drawback: the player can influence the course of time. The faster the player pokes the keys, the faster the course of the game. We are not satisfied with this, so we need
clocking . Now we have two data sources - a clock generator and a user. The simplest solution that comes to mind is to use the -t key for read. If the user does not enter anything for the specified number of seconds, then read will not block the script. This decision did not suit me: on SunOS read, it refused to accept a fractional number of seconds, and a dynamic game with one frame per second is somehow strange. The second solution is to use a named pipe:
A bit of explanation:
pkill is a good way to kill a clock and sleep.
And if you don’t understand why you need this sleep, you can check without it: with the first echo pipe it will close and sed will catch the EOF. Along the way, we prohibit the user to write a clocking character — we drive a bike here, and not time.
Problem Three: Physics
We have a clocking symbol that is called at constant intervals. It is in the handler of this symbol that you can register the whole physics of the game. I cannot give general advice here, all physics is a set of regulars who check everything that is checked.
Problem four: post-processing
Immediately after we went to the end label and saved the changes to the hold space, we can begin to apply effects. I mentioned earlier that I use four types of roads. I came to this by trial and error. In the first versions, the roads were of the same type: R, and at the post-processing stage I tried to write regulars that would do an ascent / descent depending on the relative position of the roads.
The idea was rejected: the algorithm constantly failed, it is easier to register the type of roads.
We arm ourselves with a
table of ANSI escape
sequences , I also additionally used the Unicode table and it turned out ...
s/A/^[[107;38;5;82m█^[[0m/g s/D/^[[107;38;5;82m▚^[[0m/g s/P/^[[107;38;5;82m▀^[[0m/g s/U/^[[107;38;5;82m▞^[[0m/g
There are pitfalls here too: when using unicode, the search pattern should not contain the exact number of characters. Unicode characters are recognized as two characters and the logic of such a regular breaks down.
Problem Five: small space
Not so many symbols fit on the screen, and I would like to make a map more. Here comes the Scroll Buffer. This is a place invisible to the user, which will keep a piece of the continuation of the card. For a comfortable scrolling, you should number the lines, and at the very end add a line that numbers the zone, for example, z1.
Work algorithm:
- If any part of the player is closer than N characters to the right edge of the card, go to the next item.
- Delete the second symbol of the card (the first one is a frame)
- By the end of each line, before the number we add
- If we have exactly M characters #, then we execute the next item, otherwise we skip
- We check the current zone number and replace all # with the map corresponding to this zone, change the name of the zone to the name of the next zone
- Go to the end mark.
- At the post-processing stage, we cut the visible part so that the # symbols never fall into the visible area, as well as delete auxiliary data, for example, the zone number.
Hooray! Now we have the basic knowledge of how to create a game on sed. What for? Because we can.
PS Assignment courtesy of Zhmilyov S.A. I hope the next generations will take part of my experience and do something more wonderful. x)