what can be a gift for the New Year or Christmas? / Hebrew

what can be a gift for the New Year or Christmas? / Hebrew

From the person beeline cloud Happy New Year to all Khabr readers! We have prepared an article for you about an unusual gift. We will be glad if you share your stories in the comments and tell us what interesting technological gifts you had to give or receive on the eve of the New Year!

Christmas was a few weeks away and I couldn’t decide on a gift for my sister. Her out-of-the-box question about whether there was a tracking-free and ad-free Tetris app gave me the great idea to get her my version of the game for Christmas.

I was confident that I would be able to develop a program before the holidays. The implementation of the well-known game turned out to be quite simple. Despite this, it turned into a fun competition for the whole family, where you could compete for the top spot on the leaderboard.

development

In app development, I prefer to use either Progressive Web Apps (PWAs) or wrap a web app natively using the same Capacitor. I chose PWA for this game. Works on any platform (even iOS) and requires almost no configuration. Besides, it was close at hand template for PWA projectswhich made work even easier. Offline mode was the main PWA feature needed for this app. All files are cached, so you can play offline.

There are many fans of all kinds of JavaScript frameworks. However, in my opinion, vanilla JavaScript works great in most situations and avoids any dependencies, which makes it much more enjoyable to work with. That’s why I chose him.

Gameplay

There is not much to say about the gameplay. This is Tetris. Everyone knows how it works. However, many versions of it have been created, and there is no single set of rules. Therefore, I decided to use the settings that seemed reasonable to me.

I took the 10×20 game area as a basis and spent a lot of time writing the canvas size change function. It was necessary to maintain the correct aspect ratio while using as much screen space as possible.

function resizeCanvas()
{
    var windowWidth = window.innerWidth;
    var windowHeight = window.innerHeight;
    
    // the playing area is half as wide as it is tall
    var newWidth = Math.floor( windowHeight/2 );
    
    // if the window is more than two times taller than wide, use the full width and leave a bit of space at the bottom
    if ( windowHeight > windowWidth*2 )
    {
        newWidth = windowWidth;
    }
    
    this.tileSize = Math.floor( newWidth/10 );
    
    // resize the drawing surface
    this.canvas.width = Math.floor( this.tileSize * 10 * devicePixelRatio );
    this.canvas.height = Math.floor( this.tileSize * 20 * devicePixelRatio );
    
    // resize the canvas element
    this.canvas.style.width = this.tileSize*10+"px";
    this.canvas.style.height = this.tileSize*20+"px";
    
    // save the canvas dimensions
    this.canvasWidth = this.tileSize*10;
    this.canvasHeight = this.tileSize*20;
    
    // scale the drawing surface ( important for high resolution screens )
    this.ctx.scale( devicePixelRatio , devicePixelRatio );
}

While studying the various rules, I came across standard rotation system (SRS)which is currently the Tetris rotation guide. It defines where and how characters spawn and, most importantly, how they rotate.

Standard rotation system. Image from Tetris Wiki.

The biggest stumbling block was how to display map lines and falling fragments. For the map I used an array with -1 for empty fields and 0 to 6 for the different colors the painted field can be.

this.map = new Array( this.width * this.height );
this.map.fill( -1 );

It took me a long time to figure out how to properly visualize the falling shapes, and I ended up hard-coding 4 rotation states for all 7 shapes. While hard-coding something is usually frowned upon, it turned out to be a great decision that works great for this game.

const L_state1 = [
0, 0, 1,
1, 1, 1,
0, 0, 0
];
const L_state2 = [
0, 1, 0,
0, 1, 0,
0, 1, 1
];
const L_state3 = [
0, 0, 0,
1, 1, 1,
1, 0, 0
];
const L_state4 = [
1, 1, 0,
0, 1, 0,
0, 1, 0
];
const L_piece = [ L_state1 , L_state2 , L_state3 , L_state4 ];

When a new figure is dropped, it is given data based on its type. They are contained in a beautiful two-dimensional array, the first index of which is the state of rotation, and the second is a two-dimensional representation of its shape. This makes for very elegant shape-independent collision detection. After simple exceptions related to going abroad, the function only needs to check if the fields on the map are free.

Piece.prototype.isValidPosition = function( state , xIn , yIn )
{
    for ( let y = 0 ; y < 3 ; ++y )
    {
        for ( let x = 0 ; x < 3 ; ++x )
        {
            let xTile = xIn + x; 
            let yTile = yIn + y;
            if ( this.data[state][y*3+x] == 1 )
            {
                // check if out of bounds left or right
                if ( xTile < 0 || xTile >= this.tetris.width )
                {
                    return false;
                }
                // check if at the bottom
                if ( yTile >= this.tetris.height )
                {
                    return false;
                } 
                // check if all map tiles are free
                if ( this.tetris.map[yTile*this.tetris.width+xTile] != -1 )
                {
                    return false;
                }
            }
        }
    }
    return true;
}

It is easy to calculate whether the figure can move to the right or any other direction. If the valid position is x+1, then the value of x is incremented.

Piece.prototype.canMoveRight = function()
{
    return this.isValidPosition( this.stateCounter , this.x+1 , this.y );
}

Rotation is handled similarly. If this rotation state is a valid position, the figure is rotated.

var nextRotationState = this.stateCounter + 1;
if ( nextRotationState == 4 ) nextRotationState = 0;

if ( this.isValidPosition( nextRotationState , this.x , this.y ) )
{
    this.stateCounter++;
    if ( this.stateCounter == 4 ) this.stateCounter = 0;
}

However, there are also special cases of rotation. For example, if an I-shaped figure is vertical and in close proximity to a wall, it cannot rotate normally, because part of it will be outside the playing area. When rotating, the figure will have to be moved to the side, if this position is not blocked. Besides, there are others special movements, which involve the rotation of the figure in a limited space. The most famous of them is the T-shaped rotation. However, I did not implement it in the first version of the program, deciding that the existing complex game moments are enough for now.

The same data is used in the drawing of the falling figure. Individual tiles that make up its shape are drawn. At first, I wanted to represent the figure as a single image in various stages of rotation. But then he came to the conclusion that painting individual tiles would be a good solution.

for ( let y = 0 ; y < 3 ; ++y )
{
    for ( let x = 0 ; x < 3 ; ++x )
    {
        if ( this.data[this.stateCounter][y*3+x] == 1 )
        {
            ctx.drawImage( image , (xPos+x)*tileSize , (yPos+y)*tileSize , tileSize , tileSize );
        }
    }
}

If a piece cannot continue down, a check is made to see if it creates any completed lines with its neighbors, and if so, they are removed and everything else is moved down.

// check for full lines
var linesCleared = 0;
for ( let y = 0 ; y < this.height ; ++y )
{
    var lineFilledCounter = 0;
    for ( let x = 0 ; x < this.width ; ++x )
    {
        if ( this.map[y*this.width+x] != -1 )
        {
            ++lineFilledCounter;
        }
    }
    if ( lineFilledCounter == this.width )
    {
        // clear the line
        for ( let x = 0 ; x < this.width ; ++x )
        {
            this.map[y*this.width+x] = -1;
        }
        // copy everything else down
        for ( let yInner = y-1 ; yInner >= 0 ; --yInner )
        {
            for ( let xInner = 0 ; xInner < this.width ; ++xInner )
            {
                this.map[(yInner+1)*this.width+xInner] = this.map[yInner*this.width+xInner];
            }
        }
        ++linesCleared;
    }
}

In Tetris, points are awarded for each cleared line. The more lines are written off at the same time, the more points you get. In one of official games I saw a convenient system of their calculation and decided to use it. However, the levels and drop timers in my Tetris are different, so the results can’t be compared to other versions of the game, which I don’t think is a bad thing. I wanted to create not a complete clone, but rather my own, somewhat unique version of the game.

Management

I mentioned above that the game is implemented as a PWA. Platform independence required the support of different control schemes. Of these, the sensory one was the priority, as my sister would most likely prefer it. However, I myself am used to using the keyboard.

The implementation of keyboard control was very simple. Arrow keys for moving, rotating, as well as for a soft fall of the figure, while the spacebar for a hard one.

However, with touch controls, things are a bit more complicated. As far as I know, there is no built-in support for JavaScript touch gesture recognition, so I had to develop such logic myself. Swiping left and right moves the part, and a simple touch rotates it. Everything is clear with this. However, there were still two functions that needed control — soft and hard fall. Stock had two reset directions (up and down), but having to reset up to move something down seemed extremely illogical. As a result of the reset down I used for sharp drops. Thus, it was necessary to abandon the soft reset of the figure using touch control. However, it is not necessary so often, and if necessary, you can wait a little while the figure falls by itself. It’s a little annoying at low speeds, but overall not a huge problem.

In addition, I added large overlay buttons for mobile devices that are included in the settings. In addition to the standard controls, there was also a soft fall button. However, no one in my family used them, I am not sure that they were needed at all.

Graphics

Creating art objects usually takes up a lot of game development time. Fortunately, Tetris does not require a sophisticated artistic approach.

At first, I wanted to abandon drawings altogether and use standard forms. But I didn’t really like the way it looked and ended up settling on squares with rounded corners. Also I chose classic colorsto avoid confusion.

Otherwise, the design turned out to be quite minimalistic. The user interface looked very neat, especially when playing on a small screen, but even on a large monitor it looked quite decent. In some versions of Tetris, in-game elements are placed next to the game area – for example, a score or the next piece. However, due to the vertical arrangement of smartphone screens, I had no room for them. I had to use the upper part of the playing area. If the pieces are piled up so high that they cover it, no problem, you’re still doomed.

As a decoration, I added a winter Christmas theme in the form of falling snow in the main menu.

Sound effects

The biggest problem in the creation of the game was the sound effects (however, as always). I spent a lot of time trying to create sounds with help jfxr or record something using the microphone. However, all my attempts led to nothing but disappointment, despite the many settings Audacity. I liked only the sound effect when moving the fragment left and right. It was an edited version of a mechanical keyboard keystroke recording. The rest of the voice acting left much to be desired.

I did not write any music for the game. Anything different from the original Tetris theme would feel wrong. Also, I didn’t want to use anything copyrighted.

Record table

Perhaps the best decision I made was to include player rating in the game. Just a few days after Christmas, the game turned into a fierce intra-family competition for the first place in the standings. I scored a maximum of 70,000 points while testing the program. Never thought that anyone would manage to score more than 100,000 points. However, at the time of this writing, my sister is in first place with a score of 294,636. I, despite being a developer, currently have the lowest score (albeit with the fewest attempts). I’m probably better at creating games than competing in them.

Leaderboard at the time of writing. Image of the author.

Legal information

Not sure if I can share the game link in this article. Although I created it myself, the mechanics are clearly copied and the current title is similar to Tetris. As far as I know, the idea of ​​the game itself cannot be copyrighted – after all, there are almost thousands of shooters. But still, I’m still not sure how legal it is. If anyone has this information please let me know.

What could be nicer than a well-received gift? Judging by how many hours my family members (myself included) have already spent on this game, this might have been one of my best Christmas surprises ever.

And the very process of creating the program also brought me a lot of pleasure. Can it be compared to a boring search on Amazon for a possibly completely unnecessary thing?

My creation, of course, does not claim the title of the best Tetris of all times and nations. But the game does not track its users, works on most platforms, has an offline mode, and is just a lot of fun to play.

beeline cloud – Secure cloud provider. We develop cloud solutions so that you provide customers with the best services.

Related posts