1. Introduction

This is my very first complete work on JavaScript, not counting something totally trivial. I cannot say I never tried anything in the field of pure entertainment before, but, again, nothing worth mentioning.

Falling Blocks is a legendary game with unique combination of extreme simplicity and attractiveness.

Unfortunately, I haven't seen a really playable implementation of the game for a long time. No, I'm not a gamer and did not see much. First of all, I would not risk anything without open source, but what I saw wasn't really playable, due to the lack of most important features and proper look and feel, making no match with the old-time implementations for DOS.

At the same time, HTML with JavaScript using new HTML (HTML5) canvas element is the most attractive platform for simple games. It does not require anything except a browser, should work on all platforms and always comes with source code. So, when I, just be some chance, came across such implementation, I was much pleased. It was apparent that the incomplete work I found was written by quite a qualified author. At the same time, it wasn't yet playable and the quality of code did not satisfy me at the level of its general design, despite of its clarity and general correctness. This was mostly due to the lack of flexibility already put in the initial design and lack of important features. But how can it be a problem if you have a neat source code? So I decided to rewrite it from scratch.

I re-wrote nearly 100% of the code from scratch, but I used all the low-level algorithms developed by Jake and followed most of his algorithmic ideas, as well as the general design of the application, decomposition into major blocks: basic helper methods, game with its event queue, rendering with invalidation mechanism and main application. The original Jake's work was incomplete, but some initial design feature already led the development in wrong direction, and the lack of at least one feature made the game not really playable. But I liked the basics of the solution and really wanted to fix it all, to create a really operational and well maintained product.

I did not follow the original code design with the further decomposition of the game into such parts as "constants", "variable", "logics" and so on. Instead, I designed the separation of the settings objects places in a separate file "settings.js", introduced a separate constructor with prototype methods representing Tetromino elements and other structural elements formally expressed as a set of separate JavaScript objects, such as simple FSM and layout.

So, first of all, it allows to easily customize things which were totally rigid in the original work, first of all, the size of the game in blocks can be modified in reasonable limits. Even playing on the board of the size of some 100 x 100 blocks became quite possible (but, by the way, really irritating :-)).

3. New Features

I mentioned the lack of the feature which rendered the implementation of the game not playable. Unfortunately, the lack of this feature is typical for most of the implementations I saw. What is it? There should be a key press (space bar, originally) which should drop a current tetromino to the bottom, where it still can be moved, if there is a room for that. So, I added this important feature.

I completely changed the layout of the page. Original Jake's design was based on the fixed set of predefined layouts for different page sizes. Probably he thought it would be simpler, but it wasn't. Not only it added superfluous CSS code, but looked ugly. Now, the game looks symmetrical on the Web page of any size. A user can adjust the page size at any time, even during the game play. The layout is recalculated according to the window.innerHeight and the size of the game board in blocks. The recalculation is made to keep the size of the block to an integer (not fractional value), so the relative size of the board compared to the inner height of the page vary to keep all the aspects ratio values at the expense of variable game area margins. In other words, it is designed in the style of a well-resized desktop application.

More importantly, the game can be customized. First of all, the size of the game board in block could not be changed, due to the layout and aspect ratio problems I mentioned above. Now, as I mentioned before, it can be changed in a separate file, as well as the block colors and even shapes. I'll describe it in next section of the article. I actually changed the colors and original orientation, to make the game more playable and closer to its original design.

I also added help showing on the same page at any moment of time.

Internally, I created a different thoroughly structured code design, used JavaScript strict mode and exception handling, and improved performance. I'll briefly describe this design in section 5, but first will describe what can be customized.

4. Customization

The customizable part of the game is placed in a separate file "settings.js".

Game size in blocks can be changed, due to the changes in the layout described above. This declaration can be changed:

const gameSizeInBlocks = { x:10, y:20 }

Key assignment can be changed in the object key. The properties of this object are named by function, not by key name. By default, [Enter] is used to start/pause/continue the game, [Esc] stops currently played game, arrow keys move the current tetromino element ("up" key rotates it), blank space drops it down, [F1] shows and hides help.

Timing of the game can be changed in the object delays. This object defines the delays in seconds before moving a tetromino element by a line: initial, minimal and decrement of the delay applied for acceleration of the game as the user progress. The delay is incremented by a constant value as total number of lines, according to the game rule, grows.

Score rules can be changed in the object scoreRules. The rules define the added score on the drop of each tetromino and when some rows are removed. The rules can be any user-defined functions calculated the added score depending on current count of removed lines, score and the number of lines to be removed at once. By default, a fixed amount of points is added for each dropped line, and the amount of points added for removed lines grows as a power function of the number of lines removed at once. This is done according the original game design, where the player is given the incentive to collect numbers of incomplete rows and then complete up to 4 of them at once.

Finally, tetromino colors and shapes can be changed. I'll explain it in section 6.

5. General Code Design

The central unit of the game is the Tetromino constructor and two methods of its prototype object described in section 7.

The code starts with the file "settings.js" including in HTML first, and the main code is in "application.js".

The object layout gets main DOM elements and implements original layout and the layout behavior on the change of the window size. Next object, game, defines the game logic abstracted from the graphical rendering, which is delegated to the object rendering, which uses HTML5 Canvas feature,

A set of few simple basic utility functions is put below all that, followed by the game's main anonymous function, which is implemented in the IIFE form, which helps to keep all local functions inaccessible from outside the main function. This pattern is used throughout the code. (Please see this article on the IIFE JavaScript design pattern, "Immediately-invoked function expression".)

Another problem elegantly solved by this design pattern is the resolution of the requirements of JavaScript strict mode. It helps to use inner functions and, at the same time, sandwich the main code in the try-catch block, which is important, especially for development.

Main function initialize the game, installs event handlers and starts the first frame; other frames are requested through window.requestAnimationFrame. The use of exception catching is limited to the very top level: on each event handler and main function, according to the structural exception handling philosophy.

As a next step, I'll describe the most interesting part of the code: the code of the algorithms and its implementation.

7. Tetromino Constructor and Prototype

I introduced the Tetromino constructor object for some very good reasons: to improve performance of the code and maintainability at the same time. Related JavaScript features are often referred to as "OOP" and "class", but these are very misleading or at least controversial terms; JavaScript prototype-based object machinery is principally different from "OOP with classes".

These two methods is the heart of the low-level algorithms: they implement well-known "first of" and "all" patterns. They traverse all the blocks in the tetromino shape and the first one breaks the search when some condition supplied by the function argument becomes true.

This break one of the major performance improvements as original code traversed all blocks of a given shape in all cases. (Also note, that the first and all are created only once; that's why this prototype assignment is done outside of the constructor.)

As soon as the anonymous function passed to tetromino.first returs true, the function first also returns true immediately, breaking from the loop traversing the tetromino blocks. This indicates that the first obstacle has been encountered, which could be one of the walls or another block. Detecting the very first obstacle makes further consideration of obstacle redundant, so the function willHitObstacle returns true at this point.

The use of the function Tetromino.all is simpler: all blocks of the shape are traversed. This is used, in particular, for drawing the tetromino elements on the HTML canvas.

8. Inner Functions and Event Handling: Passing "this"

Let's look at one more interesting detail: now an event handlers are added. It's enough to consider just one. This is how it can be done:

Will it work? One little problem is that the handler function is implemented as a member of the game object. So will the below code also work?

document.onkeydown = game.keydown;

Not quite. The problem is that the keydown function uses not only the event argument, but also the implicit argument this, which is used to access other members of the game object. If the event handler is added the way showed above, this this argument will still be passed, as always, but it will, not too surprisingly, reference… document object. Didn't I created some artificial problem for myself? Not at all. This problem is easily solved this way:

document.onkeydown = function(event) { game.keydown(event); };

Note that the event argument should be explicitly passed.

Similar story goes with inner functions. Look at the short fragment of the object rendering:

In the beginning of my design, several drawing methods like drawRows or drawState were defined as rendering properties, until I figured out that they won't be used anywhere but in draw, so it's better to hide them from outside context by making them inner functions. From the code fragment shown above, one can see that they use implicit this argument to access members of the object rendering. Why direct calls (commented out in the code sample) won't work? In JavaScript, this argument passed to an inner function would be the outer function object, draw instead of rendering. The work-around is to use the function's call function, which simply passes this explicitly: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call

Of course, it would be possible to add an explicit argument and pass rendering, but using already-always-passing implicit this argument is a natural and economic solution. After all, I wanted to explain a very general technique for dealing with inner functions.

9. Future Development?

I have no plan on adding mouse/touch support to the game. I actually implemented experimental version with mouse control and decided to remove it: it basically worked in different variants, but playing with mouse is really awkward, inconvenient.

It would be really more practical to add the support for the accelerometer and gyroscope for client computers having this equipment. But this rather would be the job for native Windows, Linux or Android. As to the use of any Web technologies, I find it important to… wait. I strongly believe that the use of any devices should be incorporated in the public applications only when they become standardized with W3 Consortium, even so widespread devices as Web cameras or fingerprint readers. In my opinion, all such devices can be used when appropriate W3 drafts or just proposals evolve into standards and get implemented in the major browsers. Please see, for example:

I don't want to break JavaScript isolation and interact with file system for the sake of storing the setting. By some very good reasons, this is considered unsafe. As I designed the application rather for "home use", not for playing online, it would quite possible and pretty easy to generate a setting file from UI on the fly and make it downloadable, so the user could replace it manually.

I think the only legitimate way would be using Web local storage for the game setting. Apparently, this is would be one additional feature to implement. Another one would be the option to populate the game with blocks when it starts, with chosen average density and up to certain height. This is the popular feature of the original game which I would love to implement, as I think this is the most interesting way to play it. By a number of reasons, it is less trivial than the rest, so I'm only thinking about it.

[Update]

Interactive on-line editor of game settings and using Web local storage for permanent data storage is implemented in v. 7.0.

I invite anyone to send any suggestions or spin-off any kind of derived work.

10. Versions

1.0: February 15, 2015: First fully-functional version, as described in the article.

1.1: February 19, 2015: Functionally the same version, with version information, links to the information on the game, license, contributors and original publication, added to the help box. This is done for the possibility to publish the product on a stand-along Web page, apart from this article, still showing this legally sensitive information.

4.0: January 20, 2019: Fixed behavior after a tetromino is dropped down (with blank space key): now its location freezes, so it cannot be moved anymore; move keys affect next tetromino element.

4.1: January 23, 2019: Implemented more advanced handling of Space character.
Now it drops down current tetromino only if the key is not auto-repeat space or if Ctrl+Space is pressed.KeyboardEvent.repeat may be not implemented in all browsers, so this property is simulated using game.repeatedKeyDropDown property. Help is updated accordingly.

7.0: February 1, 2019: Many new features.

Added "Download source code" and "Settings" commands.

New "Settings" page provides interactive and convenient way to customize game size in blocks, timing (speed and speed growth), tetromino colors and key assignments, as well as "clutter". Custom data is saved in a browser's local storage and can be removed at any time.

"Clutter" is the feature typical for best old implementations of classical Tetris which adds interest to the game. The game field is cluttered with random tetrominoes up to certain height (specified by the user in percents via settings or immediately before the game). Then the user can try to clean up the clutter.

Many convenience feature and better help. In particular, custom key assignments are reflected in help.

7.1: February 5, 2019: Fixed the problem with browsers not allowing localStorage (DOM Storage) — implemented the fallback: playing works, but not storing of custom data in local storage.
The problem was revealed by testing on: Microsoft Edge 42.17134.1.0, EdgeHTML 17.17134, 2018.

One important lesson I learned from this exercise is: It's very important to derive the right practices by looking into the very fundamental features and stay away from the illusions which are too easy to overcome the mind which is not properly cleared. It is very important not to fall into the distractions created by hypes and plainly incompetent but pretty convincing people. What kind of distractions? Some of those described here: http://davidwalsh.name/javascript-objects-distractions.

I think all that myth busting if very useful, but this is… a whole different story.

License

This article, along with any associated source code and files, is licensed under The MIT License

As to the patterns, you can find some combination of different encapsulation techniques, some resembling some variants of module patterns.
Generally, this is not exactly what it meant to be.
By the way, revealing module pattern reveals some of the encapsulated objects in a compound object returned from an outer function. Even though I do it, too, this is not the typical case. I don't think this technique should be used in all cases, as it has its own issues. It should be used only if it looks as a convenient way of solving some problems.

I always design techniques by myself, possibly using some known ideas.

I never recommend setting design goals based on some particular design pattern. I think the design should start with the ultimate goals of your product, business, etc., and only when you depict some suitable design option, you can look around and see what other people do in similar cases. The goal is not to support the popularity of one or another pattern by following it, but solving the problems of your product. That is, knowing design patterns can be very useful (and probably even more useful is understanding of anti-patterns), but none of them should become a Procrustean bed.

First of all, thank you for your error report.
But you did not show the file name. You also did not report what browser you've used.

Sorry, but Microsoft is not anything serious. Even the latest Microsoft Edge does not support new HTML5 elements and essential parts of JavaScript API. Microsoft never provided standard ECMAScript support; and I'm not sure it does it now. They recently announced that they give up and plan to migrate to Blink engine.

I can see Microsoft Edge error in setting.js line 127. In this line, const localStorageJson = localStorage.getItem(settingsEditor.localStorageKey);.
Okay, it means that localStorage is not supported. No wonder. Until some recent Edge version, they did not even support const.
You can turn off the feature and still play by entering in the previous line: “return effectiveSettings;”. That's all. You will only miss customization of game parameters.

[UPDATE]
You don't need to do the patch I described above. Instead, play or download and use newer Tetris version I recently updated; you will need v. 7.1 or later.

At the same time, it is possible that you can configure your browser to support local storage or “DOM Storage”. However, you reported some different error.

But real work-around is not using any Microsoft browsers. I have many real browsers installed; they all work: Firefox, Chrome, Chromium, Opera, Pale Moon, Vivaldi… They are open-source of at least freeware.

Thank you for the report again. I'll try to adjust code to cover pathological cases and at least provide smooth fallback operation for those Microsoft “wanna-be-browsers”, but I won't be able to do anything with IE — better consider it completely dead. I will inform you when it's ready.

I am using Firefox as my default browser, but it is not getting that far.
The error is in the application.js file. It says it is a Windows Script error.
Maybe I am just doing it all wrong.
Thanks for the reply,
Steve

Since 2020, Microsoft gave up its no good EdgeHTML and created new Edge based on Blink and V8 engines (Chromium-based). Since that time, it supports everything and is a good browser for Tetris, too.
Pay attention: newest Tetris on Canvas has multi-touch screen support, very interesting!

I understand. But it's boring because you are not playing in real way.

First, tempo is increasing, believe me. Just accumulate some score first. At 200+ points it will be even too fast.
More importantly, you can modify scoring and timing function in "setting.js", as I mentioned in the article.

But! Tetris started from empty field is boring in principle. It's never interesting, no matter what timing is.

I always play starting with filling it with random blocks up to some 70-80% and then try to clean it up. I don't think anything else presents any gaming interest.

I though about automating random startup filling with tetromino blocks, but it's not so easy to develop the algorithm which would make it realistic and with selectable density. So, I just manually drop several blocks by whitespace. Please try it.

Finally, I found enough time to advance Tetris on Canvas to v.7.0, where all promised features are implemented, as well as some more.
You can play it in Tetris on Canvas Life Play.

Now have a settings page which you can store permanently in your browser's local storage (which you can later clean off Tetris data completely).
On this setting page, you can introduce and customize initial clutter of tetrominos, edit their colors, size of the game in blocks, change time delays (speed) and even key assignments, all done interactively and conveniently.