Qingming holiday period, idle bored, do a little game play, the current game logic has not found a bug, just look slightly ugly some-.-
Project Address: Https://github.com/Jiasm/tetris
Online demo:http://blog.jiasm.org/tetris/?width=16&height=40 (Modify the URL parameters to adjust the difficulty)
The whole is divided into three pieces for development, using object-oriented programming (in fact, I prefer to use functional programming, but it is more intuitive to use some of the game's state to store objects):
Game
:
- Responsible for generating new blocks.
- Responsible for the processing of block movement
- Judging the bottom of a block
- To remove rows that meet the purge criteria
Render
:
- Responsible for using
Game
the data to render the entire game interface
Controller
:
- Responsible for accepting user input (up and down various actions) and processing
- Feedback the status of the current game to the user
This layering brings a benefit, the logic module of our game Game
does not depend on the current program running environment, but can be, Render
Canvas
DOM
or even console output. We want to migrate to other platforms and only need to be modified Render
.
Project structure
Ignoring some structures that are not directly related to the game
. ├──model│ ├──brick.js│ ├──game.js│ └──index.js├──utils│ ├──buildenum.js│ ├── deepcopy.js│ ├──getshape.js│ ├──index.js│ ├──lineindex.js│ ├──matrixstring.js│ └── rotatearray.js├──enum│ ├──gametype.js│ ├──index.js│ └──pointtype.js├──data│ └── shapes.js├──controller│ └──index.js└──view ├──rendercanvas.js └──index.js
The index.js in each directory is for the convenience of referencing multiple files at the same time, roughly like this:
default as Model1} from './model1 'default as Model2} from './model2 '
Then we can write in the places we use:
Import {model1, model2} from './xxx '
Model
This is where the core logic of the game lies.
A matrix game like Tetris, the most appropriate way to store data is a two-dimensional array.
To be more intuitive, we chose the height of the game as the length of the first layer array:
New Array (height). Fill (new Array (width))// width:2 height:4[ 1, 1 ], 1, 1], 1, 1], 1, 1]
And this choice is more convenient for some logical processing:
- When we move down, we just change the first level subscript of the element.
- To determine whether a bottom is being touched, we only need to determine if there are elements in the current subscript + 1
We define the elements in the array:
0
: Empty, indicating that the current coordinates are blank
1
: A new block that represents the currently active block
2
: Old blocks, blocks that have been fixed to the bottom
Next, we have a problem with how to handle the placement of blocks.
We know that the game will keep loading new squares into the board.
If we remove the corresponding block element in the current two-dimensional array every time we move down, it's too cumbersome to plug it into a new position.
So we initialize two two-dimensional arrays when we initialize the data.
When we load a new block, we plug the element corresponding to the block into one of the two-dimensional arrays.
then wait until we have other operations, such as moving around, down, and so on.
We use the second two-dimensional array directly to overwrite the current array, and then plug the changed subscript block into the array.
so on the data, we have finished moving the block.
class Game { init () { // initialize two matrices this. Matrix = [[], []] this. Oldmatrix = [[], []] } Move () { // Reset Current matrix data this. Matrix = deepcopy (this// dereference // load block data this . matrix[y][x1] = 1 This . matrix[y][x2] = 1 }}
Handling of left and right moves
The movement of the left and right cannot be as moving downward as the simple subscript +1.
We need to determine if the current operation is valid.
For example, if the right side encounters an obstacle or reaches the edge, we must not be able to move again.
// Blend is the shape description of the active brick [[1, 1, 1], [0, 1, 0]] similar to this structure if ( >= width-brickwidth | | = = { = oldmatrix[y + rowIndex ]return row && row[ BrickWidth-1] && _pos && _pos[x + Brickwidth] })) return// obstruction on right side, unable to move
Use logic like this to make sure that the current block does not overwrite the previous block when it is moved to the right.
Fast-down processing
I see some games implemented, seemingly the drop trigger is just a faster descent (this situation only needs to change the speed of the timing drop)-.-Here is the realization that the direct contact bottom
So you will encounter a problem, where can the current bricks drop to a maximum?
[111][0< Span class= "token punctuation", 00][020][ 222"
Just like this data, both 0|2
columns can move down two columns, but this results in overlapping of the middle column.
We must take out the value that has the lowest descending amplitude.
So we have to figure out the last line 1 subscript and the first line 2 subscript, subtract the two subscript, the minimum value is our current block can fall distance.
Handling of rotating blocks
Rotating blocks should be a more complex piece of logic in the game.
It's not just a simple way to change a two-dimensional array of squares from rows to columns, and sometimes we need to determine if a block can be rotated.
Like this, the green strip in the middle is not able to rotate.
So we have to get the rotated data to compare with the data in the current game, to check if there is overlap, and if it does, it means that it cannot be rotated.
Touch Bottom Detection
After each move, we need to check the bottom of the block.
That is, the current block, if there is already an element placeholder, if any, it is already touched bottom, the current element will be fixed into the matrix array.
Similarly, when we judge, we do not need to check all the subscript of the block, only need to check the bottom layer of the valid elements.
[1, 1],[0, 1],[0, 1],
For a block like this, we only need to determine whether the second row of the first column & the fourth row of the second column has elements to complete the check.
Remove rows from
When a row is filled with elements, we need to remove it.
If a block is pinned into the array after the touch bottom detection is triggered, we can then remove the row.
Because if there is no new block entry, this step of removing the line is not necessary.
At the same time, the score count should also be done here, we will record the number of rows removed, and the number of rows obtained is the score.
At this point, all operations on the matrix data are ended.
Game
Objects only maintain such a two-dimensional array, the object itself does not contain any game-related operations, only the corresponding processing when it is called.
A new two-dimensional array is then generated.
Utils
Here are some of the more common methods used to improve the efficiency of development.
For example, to get the bottom level of the block, such as the subscript tool functions.
Enum
It holds the enumeration of States, the state of the game, and the status of the block, similar to the data:
{ 0, 1, 2}
Data
Stores the various blocks of information used in the game.
Squares, ladders, and the like in a two-dimensional array of the corresponding description.
Controller
That's what we're talking about, the module used to interact with the user, Controller
to get the game-related information, and invoke the Render
rendering.
Listen for keyboard events and render some control buttons on the page.
and Game
The drop method of timed triggering.
View
The rendering part of the game interface, currently selected is used canvas
, so only wrote RenderCanvas
.
In this part of the rendering, a little bit of optimization is done to separate the active blocks from the fixed blocks.
This will not re-render the entire game layout when the user moves up or down, rather than rendering the active block canvas
.
Xiao Kee
Two days more time to develop, which has half a day in the repair FlowType
of warning tips ...
After the end, I feel that the main difficulty in achieving this is the square rotation & touch bottom judgment here.
Can clearly manage the game corresponding to the two-dimensional array, this game development will be very smooth.
The interface has yet to be optimized.
Using JavaScript to implement a Tetris