📜 ⬆️ ⬇️

Good day for code generation

Long ago, at the dawn of Eternity, somewhere in the 300th Century a mass duplicator was invented ...
Eternity has adapted a duplicator for their needs. At that time, we had only six hundred or seven hundred Sectors built. We faced tremendous challenges to expand our zone of influence. “Ten new Sectors in one biodog” - this was the leading slogan of those years.
The duplicator made these enormous efforts unnecessary. We built one Sector, supplied it with supplies of food, water, energy, filled it with the most advanced automation and launched a duplicator. And now we have in the Sector for each Century.

Isaac Asimov " The End of Eternity "

The fact that the day did not happen is the best, it was clear already in the morning. The usual, rainy gray weather, and it seems that the beginning cold did not improve the mood. Fatigue was observed in the body and, most of all, I wanted to sleep. It was absolutely necessary to be distracted somehow ...

Make Sokoban I wanted for a long time. Rather, (like many others) I have already done it several times, but this was long before I met Zillions of Games . What always bothered me was the development of levels. It’s not at all easy to come up with a good level for a puzzle game, but they also need to be coded! Since the number of levels in Sokoban is no less important than their quality, the work threatened to drag on for a long time.
')
On the other hand, perhaps the levels and not worth it to invent. In fact, nostalgia is best of all at the old levels that have been customary since childhood. Soberly reasoning that during the time that has passed since the 80s of the last century, someone must have been puzzled by the same problem (and more than once, most likely), I decided to look for a description of the original levels on the Internet. I quickly found what I was looking for on Habré , for which I certainly want to thank the distinguished begoon . Torn apart from the DOS version of the program, the listing looks like this:

************************************* Maze: 1 File offset: 148C, DS:00FC, table offset: 0000 Size X: 22 Size Y: 11 End: 14BD Length: 50 XXXXX XXX* X XXX *XXX X * * X XXX X XXX X XXXXXX XX XXX XXXXXXX ..X X * * ..X XXXXX XXXX X@XXXX ..X X XXX XXXXXX XXXXXXXX 

Everything is simple and clear! It remains to translate this into a form understandable to Zillions of Games. All sixty levels. I do not know how anyone, but personally I don’t love to work with my hands so much. The problem is not even to fill it all, then you have to correct the inevitable mistakes! In general, if someone was looking for a suitable task for code generation, then this is it. Let me remind you that code generation is such a “homemade” type of metaprogramming , a bit more brain-friendly to the developer than other types .

No sooner said than done!
 open(my $f, '>', 'levels_1_10.zrf'); my $n = 0; my $k = 0; my $x = 0; my $y = 0; my %p; my %b; while (<>) { chomp; my $s = $_; if (/^\s*X/) { $y++; my $i = 0; my @a = split(//, $s); foreach $c (@a) { $i++; if ($c ne ' ') { my $p; if ($i > 26) { $p = chr(ord('A') + $i - 27); } else { $p = chr(ord('a') + $i - 1); } my $key = $p . $y; $c =~ tr/X*.@/WBTY/; $p{$key} = $c; if ($i > $x) { $x = $i; } } } } else { if ($y) { $n++; if ($n > 10) { $k++; $n = 1; close($f); my $a = $k * 10 + 1; my $b = ($k + 1) * 10; open($f, '>', "levels_${a}_${b}.zrf"); } my $l = $k * 10 + $n; if ($n > 1) { printf $f "(variant\n"; } else { printf $f "(include \"sokoban.inc\")\n\n"; printf $f "(game\n"; } printf $f " (title \"Sokoban (Level $l)\")\n"; if ($n == 1) { printf $f " (common-level)\n"; } printf $f " (board\n"; printf $f " (image \"images/sokoban/black-${x}x${y}.bmp\")\n"; printf $f " (grid\n"; printf $f " (common-grid)\n"; printf $f " (dimensions\n"; printf $f " (\""; for (my $i = 1; $i <= $x; $i++) { if ($i > 1) { printf $f "/"; } my $p; if ($i > 26) { $p = chr(ord('A') + $i - 27); } else { $p = chr(ord('a') + $i - 1); } printf $f "$p"; } printf $f "\" (25 0)) ; files\n"; printf $f " (\""; for (my $i = 1; $i <= $y; $i++) { if ($i > 1) { printf $f "/"; } printf $f "$i"; } printf $f "\" (0 25)) ; ranks\n"; printf $f " )\n"; printf $f " )\n"; printf $f " )\n"; printf $f " (board-setup\n"; printf $f " (You\n"; printf $f " (W"; foreach $pos (keys %p) { if ($p{$pos} eq 'W') { printf $f " $pos"; } } printf $f ")\n"; printf $f " (B"; foreach $pos (keys %p) { if ($p{$pos} eq 'B') { printf $f " $pos"; } } printf $f ")\n"; printf $f " (T"; foreach $pos (keys %p) { if ($p{$pos} eq 'T') { printf $f " $pos"; } } printf $f ")\n"; printf $f " (Y"; foreach $pos (keys %p) { if ($p{$pos} eq 'Y') { printf $f " $pos"; } } printf $f ")\n"; printf $f " )\n"; printf $f " )\n"; printf $f ")\n\n"; $b{"black-${x}x${y}.bmp"}->{x} = $x * 25; $b{"black-${x}x${y}.bmp"}->{y} = $y * 25; $x = 0; $y = 0; %p = (); } } } close($f); foreach $b (keys %b) { printf "$b - $b{$b}->{x} $b{$b}->{y}\n"; } 


With a slight movement of the hand, generic levels, files, 10 levels each. It looks as follows (no, sokoban.inc , at the very beginning of the file - this is not the name of the company, but simply a downloadable file, with the necessary definitions created manually). Some people don’t like Perl , and many others may find my programming style not very elegant (which are only “magic” constants scattered around the code), but I think that for a program that (possibly) will be launched only once - this is quite acceptable solution.

In any case, we received (almost for nothing) the desired levels, but (for now) we can not start them. For complete happiness, we lack that " sokoban.inc " and graphic resources, of course. We quickly create the last ones in paint (s) (not especially bothering and painting multicolored, monotonously filled squares), and the first one contains, so far, not so much useful. Moving the "loader" will be programmed later, now we just want to admire the levels!

sokoban.inc - minimalist version
 (define common-grid (start-rectangle 0 0 25 25) ) (define common-level (move-sound "Audio/Pickup.wav") (release-sound "Audio/Pickup.wav") (capture-sound "") (option "prevent flipping" true) (option "animate captures" false) (players You) (turn-order You) (piece (name W) (image You "images/sokoban/w.bmp") ) (piece (name B) (image You "images/sokoban/b.bmp") ) (piece (name b) (image You "images/sokoban/b.bmp") ) (piece (name T) (image You "images/sokoban/t.bmp") ) (piece (name Y) (image You "images/sokoban/y.bmp") ) (piece (name y) (image You "images/sokoban/g.bmp") ) (win-condition (You) (pieces-remaining 0 B) ) ) 


The walls, the boxes, the places for placing the boxes and, of course, the “loader” itself are all figures. Some of them, then, will even move. All this is beautiful, but we are already waiting for another ambush! Did you notice the file names of the black-NNxMM.bmp type in the level descriptions? These are the backdrops of levels. All that is required of them is to provide a background for displaying figures on it. The only problem is that all these backdrops are of different sizes (thanks to the developers of Sokoban) and this size is very important for the correct display of the levels (for this you should thank the ZoG developers).

We re-arm ourselves with paint- and, trying to confound Malevich , draw black rectangles of various shapes and sizes. Of course they are not sixty pieces. They are only fifty-four, but this is not much easier! Paradoxically, but the fact is that more than 90% of our distribution will be occupied by empty, radically black rectangles (if they were not monochrome, then all 99% might well have taken it). Now you can admire the levels themselves:


We quickly run through all the levels (just to make sure that we didn’t plow up anywhere with the code generator), after which we are embraced by a design fever. We start with the yellow boxes. The two diagonal lines make them much more attractive (and the triangles drawn on the sides - generally pull on the exclusive). Drawing brickwork, we begin to understand that 25x25 is a fig size for a tile. 24 is a much more correct meaning (it’s funny that by simply swapping digits from it, you can easily get a universal answer to an unknown question ). Again we take the paint in our hands and patiently resize all the black rectangles (the result is worth the effort). Lastly, we redraw the “loader” itself (this also does not do without a gradient fill).

Then everything is quite simple. It is necessary to teach the figures to move. The only technical difficulty (very small) is that the locations of the boxes are also figures. This means that when we go through them and move boxes, they must be deleted (and then automatically restored when they exit the corresponding field). Of course, one could simply draw them on the backs, but after that the latter would no longer be monochrome (having grown solidly in size) and, in any case, 54 backs are still better than all 60. That's all ! Enjoying the result:



PS
Already in the evening, Howard McCay sent me a very unexpected and very elegant addition to my implementation of Yonin Shogi , published back in 2014. Looking back, I understand that this was not the worst day of my life.

Source: https://habr.com/ru/post/268215/


All Articles