Dear residents of Habrohabra!
This time I brought you a story about javascript, atari and canvas! The game is called Robbo and is the port of the 1989 creation of the same name.
The toy itself .
Toy with the sound off .
Version for the game at work .
Link to github .
Management is carried out by arrows. If there are cartridges, then shift + arrow shoots in the right direction.
Story
Who is not interested in the background - scroll right through the next few paragraphs to the section "Technical Implementation".
')
As a child I had an Atari 130 XE computer. Parents bought it when I was 2 years old. According to their stories, it was at this age that I fell off the chair with the joystick, running away from the owl on the second level of Robbo.
Robbo
This game was written by Polish programmer Janus Pelc and released in 1989. It was my favorite childhood game.
At the institute, my mother was taught programming and she decided to teach me basic in 4 years old. Then the first attempt was made to rewrite the robbo independently. The bad thing was that I knew only the operators PRINT, INPUT and POKE. It didn’t lead to anything good / Z couldn’t even imagine how it was possible to write such a game, what I was doing was similar to brute force on all turns. The user clicked to the right and otprintilas state where the character is shifted one cell to the right, that is, it was askiart state machine in its purest form. If I knew about the text quests, then they would be implemented by this method, but I did not know.
The next iteration was in ninth grade. It was already Visual Basic and AMD-K6 II 500. At that time, Upgrade magazine decided to release its first issue with the disk, and I just had some implementation of the game. In MS Paint, a pile of sprites were painted with their trademark pioneers, after which I sent them this toy. A few months later the screw died and the code was lost. In the code there were only arrays, even the structures were not used, one algorithm for traversing the labyrinth, according to the right-hand rule of the NPC, occupied eight A4 pages of code (I'm not joking). There was no normal Internet yet, just a dial-up.
In grade 11, a new version was written on the same Visual Basic. Here the code became noticeably better, I did not describe different behavior for 4 directions of movement.
It's been 8 years. Learning patterns, reading lots of literature and practical experience, learning a string of languages, loving functional style and js in particular, developing HTML 5. And I returned to Robbo again. All these years, I sometimes ran the atari emulator (Atari800Win) in the evenings and played my favorite childhood game. For 2 days I wrote code where there were much fewer lines than before. Then came the epiphany that this should be refactored. Refactoring lasted for several months. This is a non-commercial project, that is, a hobby, because I tried to lick the code, although there are still flaws for which I am ashamed. In more detail with what I encountered, I will describe in the technical part.
GnuRobbo.
There is a similar open source project GnuRobbo. They implemented robbo on c ++ cross-platform (now there is even a working version for Android on Google Play). The last few years, when I was drawn to remember robbo - I ran this particular implementation. When I started working on this project I was tempted to see how they did it, and so, with the general similarity (if you switch the skin in classic), it feels like it's a robbo, but in reality there are a lot of different places. It seems that the development was not saturated with love, no matter how strange it might sound.
Technically popular part.
Any toy where there is a living world is built around some realization of an infinite loop. There is nothing complicated, the usual function call via setInterval. This function is the calculation of the new state of the world and redrawing the changed parts.
Having reviewed the game, I realized that many objects in this world implement the same behavior. So a bomb, a stone, some guns, a starship can be moved by resting them.
The objects themselves turned out to be extremely simple. Here, for example, the door code (door.js):
(function( R ){ 'use strict'; R.objects.Door = {
With this approach, making simple objects came out quickly. But with lava, bombs, explosions, bullets and teleports had to tinker.
Bomb and explosion

A bomb is an object that explodes and carries everything that is one cell away from it. In the original robbo, it really looks like an explosion, and in gnuRobbo they have beaten all the beauty.
A large number of videos were recorded of how the bomb explodes in the emulator, after which the storyboard of the explosion sprites was rewritten on paper. For two days I contemplated the numbers. During this time, I learned that the explosion animation has 5 states; first, the explosion intensifies, and then fades, but it does not do this evenly in all directions, but according to a certain pattern. At the forum, someone has already noticed that the explosions are different and assumed that this is random, but no, this could not be random. In those days, getting pseudo-random numbers was expensive fun.
Having seen enough of the numbers, I derived the regularities and two matrixes of explosions. The matrix is ​​superimposed on neighboring cells and where the number is greater than zero, an explosion object is placed with animation corresponding to the number from the matrix. If there was already an explosion, then a number from the matrix is ​​added to its animation, after which max (5, animation) is made.
1) The moment matrix after the bomb touches the bullet or a nearby explosion:
0, 0, 0, 0, 0, 2, 5, 4, 5
At this point, the bomb is still alive as an object and visually.
2) A matrix is ​​superimposed on the next clock:
5, 4, 5, 4, 3, 2, 0, 0, 0
For one bomb, the result is identical to the original game.
For a few bombs - not yet identical. It looks like the original animation, but still needs some work
(ashamed) .
The explosion itself is represented by an object occupying one cell and reducing its animation by 1 for each step. After the explosion, this object is able to call a callback or put in place a predetermined other object.
In fact, there are many explosions in the game:
The smoke from a burst cartridge, the destruction of an object, a bomb explosion, teleportation from, teleportation to (here the animation of the explosion goes in the reverse order)
Teleport

The teleport works as a teleport itself. Moves a robbo from one part of the card to another. At the entry point, the robbo is dematerialized, and at the exit it materializes back. If he enters on the right, he will come out on the left side. Interesting cases begin when at the exit point the desired exit is blocked, sometimes all sides of the exit are closed, and sometimes the exit point can explode. Teleports are not necessarily connected in pairs, they can be looped and a large number of outputs. In my implementation, I made the opportunity to exit and not in another teleport, but this is for the future when I will make a game with my idea. On the emulator, I checked all these states and wrote down what it leads to. The love of optimization came to me from an assembler, so I reloaded the initial table -> the final state and reduced the selection of the output cell to an expression without a conditional operator. It was more a pick-up game, in this case it is not justified, but since I spent the time, I left it. In fact, it turned out that we need to incrementally perform the xor operation of the current direction from 11 | 10 | 11 | 10.
We have an input direction. Suppose we entered on the right, which corresponds to direction 2 (entered on the right, then went to the left side). The algorithm will try cells 2 (left), 1 (down) (10 ^ 11 = 01), 3 (up) (01 ^ 10 = 11), 0 (right) (11 ^ 11 = 0), and then return to the original state (in case all outputs are blocked).
In the form of an algorithm, it looks like this:
var tryCell, i; for( i = 0; i < 4; i++ ){ tryCell = this.game.getCell( R.addDirection( this.teleportX, this.teleportY, obj.direction ) ); if( tryCell.is( 'Empty' ) ) break; obj.direction = obj.direction ^ ( 3 - i % 2 ); }
Lava.

An object that moves from left to right (or vice versa) as a whole line. It sweeps away everything except walls. At first I tried to implement this behavior at the level of a single cell, like other objects, but it turned out that it is much easier to examine all neighbors on a horizontal line bounded by walls in one step of one object, move them and tell them that in this world step their actions are no longer are needed.
Technical part
The game code is divided into logical modules, the main ones: controller, view, sprite manager, keyboard handler, objects (modulo each individual object and factory of objects).
The model is mixed with the main controller, and the objects themselves are small controllers, responsible only for their behavior. Separately, there is a view that initializes the canvases, monitors the update of the sprites, scrolls the playing field and asks R.sprites to draw the necessary image in the desired position.
Initially, it seemed to me that sorting through the entire playing field and seeing whether an object is alive is not the most optimal approach. So the list of active objects was born. In the step of the world only their behavior is processed.
The general functions of working with the card were brought to the controller, such as:
swap - swap two objects. Actively used when moving single objects (step, single shot, npc)
getCell (x, y) - returns the object with the specified position
getCell (obj) -> returns an object in the coordinates obj.x, obj.y
setCell (x, y, obj, data) - puts the object in the specified position. Obj can be either another object or an object name (for example, 'Explosion'). In the case of an object name, the factory of objects will call.
setCell (obj1, obj2, data) -> does the same, only the coordinates are torn from obj1.x, obj1.y
Everything looked simple until testing started on the original gameplay. Then it turned out that in the original game, if you rest a stone on a bird flying up and down on the right, then in the next step the robbo will die, and if on the left, he will slip over it. I had to sort the actionObjects in accordance with the workaround order of the original game.
Sprites
Redrawing the entire playing field at every step would be wrong in terms of performance (although not critical for modern computers). Here I implemented a hash of variable object sprites, which consists of the key: y coordinates of the object, and there is a hash of all the objects changed from the last drawing on this line with key: object.x, and value: the object itself. By this I killed two birds with one stone: an extra round of the entire field is not done and the modified cell is drawn only once, even if there were several changes (for example, the bird moved down into the cage, and then it was swallowed by lava, all in one step of the world) .
The sprites themselves were initially honestly pulled from gnu robbo, but very soon I realized that this was done in vain. It was easier to do the original immediately than to fiddle around and play pixel-catching (some of them addixelartili to higher resolution, some do-italiasilis, and some came up with their own), they also lacked 80 percent of the sprites of the walls (there were much more of them in the game than I expected) .
With the animation was a similar problem. As a result, I recorded video from the emulator and watched what was going to happen frame by frame. As an example: when taking a step of characters - the animation of the shift of the sprite should take place already in a new place somewhere between the world.
That is, a step was added to the main loop in which no movement of objects occurs, but only an image changes.
A separate chapter deserves a palette of sprites.
I did not immediately notice that the original robbo at each level uses its own set of colors.
This is a very frequent decision of those years of game development and demoscene. There was little memory, but I already wanted visual effects, because in old computers there was a simple opportunity to change the number from the palette to the color itself.
The decision was made in the forehead - all colors torn from the original levels of the game were added to the config of each level, all sprites were reduced to 4 common colors. At the loading level, there is a pixel-by-pixel repainting of the entire picture into a new color scheme.
Just last week I made a separate project (there will be a post, if you're interested), which allows you to change the palette without pixel-by-pixel manipulations. Amazingly, this method works in IE7.
But I have not yet drawn this technology into the current game code.
Conclusion
The game still has some flaws.
In the plans:
- Hit the group bomb blast.
- Make support for older browsers.
- Extract the colors of the general background around the playing field and the numbers from the original game.
- Make ending lives (now it's old school, but then the toy itself is from those times).
- To complete touch control support for mobile platforms, to make smart tv support for playing from the TV remote.
- And, of course, port the final cartoon.
In remote plans:
- Make a level editor with an object editor, where the user can draw his object, draw him a chain of sprites and select various behaviors from the extensive pre-find list. This will make it possible to make robbo sokoban, pakmana (here we must also think about the intercellular movement), boulddash, tetris, and at least the arcanoid.
- Make a common database of levels where users can add their own, while others can vote for their favorites.
I promised myself to post this release before the new year and write in general about the development. It seems to succeed. There are still places for which I am ashamed (in particular, the whole work with loading levels, animation of level changes).
All the upcoming holidays!