├── favicon
├── favicon.ico
├── favicon-16x16.png
├── favicon-32x32.png
├── apple-touch-icon.png
├── mstile-150x150.png
├── android-chrome-192x192.png
├── android-chrome-256x256.png
└── safari-pinned-tab.svg
├── sharing
└── sharing.png
├── sound
└── korobeyniki.mp3
├── browserconfig.xml
├── site.webmanifest
├── index.html
├── styles.css
└── script.js
/favicon/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sacret/game/HEAD/favicon/favicon.ico
--------------------------------------------------------------------------------
/sharing/sharing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sacret/game/HEAD/sharing/sharing.png
--------------------------------------------------------------------------------
/sound/korobeyniki.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sacret/game/HEAD/sound/korobeyniki.mp3
--------------------------------------------------------------------------------
/favicon/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sacret/game/HEAD/favicon/favicon-16x16.png
--------------------------------------------------------------------------------
/favicon/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sacret/game/HEAD/favicon/favicon-32x32.png
--------------------------------------------------------------------------------
/favicon/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sacret/game/HEAD/favicon/apple-touch-icon.png
--------------------------------------------------------------------------------
/favicon/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sacret/game/HEAD/favicon/mstile-150x150.png
--------------------------------------------------------------------------------
/favicon/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sacret/game/HEAD/favicon/android-chrome-192x192.png
--------------------------------------------------------------------------------
/favicon/android-chrome-256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sacret/game/HEAD/favicon/android-chrome-256x256.png
--------------------------------------------------------------------------------
/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | #da532c
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/site.webmanifest:
--------------------------------------------------------------------------------
1 | {
2 | "name": "",
3 | "short_name": "",
4 | "icons": [
5 | {
6 | "src": "favicon/android-chrome-192x192.png",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | },
10 | {
11 | "src": "favicon/android-chrome-256x256.png",
12 | "sizes": "256x256",
13 | "type": "image/png"
14 | }
15 | ],
16 | "theme_color": "#ffffff",
17 | "background_color": "#ffffff",
18 | "display": "standalone"
19 | }
20 |
--------------------------------------------------------------------------------
/favicon/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
25 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Sacret Game
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
▣
47 |
▣
48 |
▣
49 |
▣
50 |
51 |
▣
52 |
53 |
▣
54 |
▣
55 |
▣
56 |
57 |
▣
58 |
▣
59 |
▣
60 |
▣
61 |
▣
62 |
63 |
▣
64 |
▣
65 |
▣
66 |
67 |
▣
68 |
▣
69 |
70 |
76 |
77 |
▣
78 |
▣
79 |
▣
80 |
▣
81 |
▣
82 |
83 |
▣
84 |
▣
85 |
▣
86 |
87 |
▣
88 |
▣
89 |
▣
90 |
91 |
▣
92 |
▣
93 |
▣
94 |
95 |
96 |
▣
97 |
▣
98 |
▣
99 |
▣
100 |
101 |
102 |
103 |
104 |
105 |
111 |
112 |
ON / OFF
113 |
S / P
114 |
SOUND
115 |
RESET
116 |
117 |
149 |
150 |
SACRET GAME
151 |
☻
152 |
153 |
154 |
155 |
156 |
157 |
--------------------------------------------------------------------------------
/styles.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=Montserrat+Alternates:wght@400;700&family=Orbitron&display=swap');
2 |
3 | @keyframes blinkingAnimation {
4 | 0% { opacity:1; }
5 | 50% { opacity:0; }
6 | 100% { opacity:1; }
7 | }
8 |
9 | body {
10 | min-height: 100vh;
11 | display: flex;
12 | align-items: center;
13 | justify-content: center;
14 | margin: 0;
15 | }
16 |
17 | .brick {
18 | display: flex;
19 | align-items: center;
20 | justify-content: center;
21 | flex-direction: column;
22 | user-select: none;
23 | }
24 |
25 | .top {
26 | display: flex;
27 | align-items: center;
28 | justify-content: center;
29 | width: 270px;
30 | height: 300px;
31 | padding-bottom: 15px;
32 | background-color: #b5b0ac;
33 | border: 1px solid #76716d;
34 | border-radius: 20px 20px 0 0;
35 | box-shadow:
36 | inset 0px 0px 15px 15px #9b9590,
37 | inset 0px 0px 65px 10px rgba(255, 255, 255, 0.5),
38 | 0px 0px 15px -5px #9b9590;
39 | }
40 |
41 | .center {
42 | z-index: 10;
43 | width: 272px;
44 | height: 70px;
45 | margin: -15px 0;
46 | background-color: #b5b0ac;
47 | border-right: 1px solid #76716d;
48 | border-left: 1px solid #76716d;
49 | border-radius: 20px;
50 | box-shadow:
51 | inset 0px 0px 15px 15px #9b9590,
52 | inset 0px 0px 65px 10px rgba(255, 255, 255, 0.5),
53 | 0px -8px 15px -5px #9b9590,
54 | 0px 8px 15px -5px #9b9590;
55 | }
56 |
57 | .bottom {
58 | display: flex;
59 | flex-direction: column;
60 | align-items: center;
61 | width: 270px;
62 | height: 340px;
63 | background-color: #b5b0ac;
64 | border: 1px solid #76716d;
65 | border-radius: 0 0 20px 20px;
66 | box-shadow:
67 | inset 0px 0px 15px 15px #9b9590,
68 | inset 0px 0px 65px 10px rgba(255, 255, 255, 0.5),
69 | 0px 0px 15px -5px #9b9590;
70 | }
71 |
72 | .top-left,
73 | .top-right {
74 | display: flex;
75 | justify-content: center;
76 | flex-wrap: wrap;
77 | width: 32px;
78 | margin: 0 5px;
79 | }
80 |
81 | .bg-cell {
82 | display: flex;
83 | margin-top: 3px;
84 | width: 11px;
85 | height: 11px;
86 | font-size: 14px;
87 | line-height: 10px;
88 | }
89 |
90 | .frame {
91 | border: 5px solid transparent;
92 | outline: 4px solid #dea901;
93 | }
94 |
95 | .screen {
96 | display: flex;
97 | width: 156px;
98 | background-color: #989f7e;
99 | border: 1px solid #000;
100 | outline: 1px solid #000;
101 | box-shadow:
102 | inset 0px 0px 10px 3px #898f70,
103 | 0px 0px 5px 5px #9b9590;
104 | }
105 |
106 | #game {
107 | display: flex;
108 | flex-wrap: wrap;
109 | width: 110px;
110 | height: 220px;
111 | outline: 2px solid #000;
112 | }
113 |
114 | #game-info {
115 | width: 46px;
116 | font-family: 'Orbitron', sans-serif;
117 | font-size: 10px;
118 | text-align: right;
119 | word-break: break-all;
120 | }
121 |
122 | .invisible {
123 | visibility: hidden;
124 | }
125 |
126 | .blinking {
127 | padding: 5px 3px 0 5px;
128 | font-size: 7px;
129 | font-weight: 900;
130 | animation: blinkingAnimation .5s infinite;
131 | }
132 |
133 | .inactive {
134 | pointer-events: none;
135 | }
136 |
137 | .score-text {
138 | padding: 5px 3px 0 5px;
139 | font-size: 5px;
140 | font-weight: 900;
141 | }
142 |
143 | #score,
144 | #hi-score,
145 | #speed {
146 | padding: 0 3px;
147 | font-size: 10px;
148 | }
149 |
150 | #next-figure {
151 | display: flex;
152 | flex-wrap: wrap;
153 | width: 44px;
154 | height: 44px;
155 | padding: 5px 2px 0;
156 | line-height: 11px;
157 | }
158 |
159 | .cell {
160 | display: flex;
161 | height: 11px;
162 | width: 11px;
163 | font-size: 14px;
164 | line-height: 10px;
165 | }
166 |
167 | .filled {
168 | opacity: 1;
169 | }
170 |
171 | .empty {
172 | opacity: 0.1;
173 | }
174 |
175 | .horizontal-buttons {
176 | display: flex;
177 | justify-content: space-around;
178 | width: 200px;
179 | margin-top: 40px;
180 | }
181 |
182 | .horizontal-texts {
183 | display: flex;
184 | justify-content: space-between;
185 | width: 200px;
186 | margin-top: 5px;
187 | }
188 |
189 | .control-buttons {
190 | display: flex;
191 | justify-content: space-between;
192 | width: 220px;
193 | margin-top: 15px;
194 | }
195 |
196 | .arrow-buttons {
197 | display: flex;
198 | flex-direction: column;
199 | align-items: center;
200 | width: 110px;
201 | }
202 |
203 | .arrow-symbols {
204 | display: flex;
205 | flex-direction: column;
206 | align-items: center;
207 | height: 28px;
208 | font-size: 12px;
209 | line-height: 10px;
210 | }
211 |
212 | .arrow-symbol {
213 | display: flex;
214 | justify-content: space-between;
215 | width: 32px;
216 | }
217 |
218 | .arrow-symbol:first-child,
219 | .arrow-symbol:last-child {
220 | justify-content: center;
221 | }
222 |
223 | .arrow-symbol:nth-child(2) > span:first-child {
224 | transform: rotate(-90deg);
225 | }
226 |
227 | .arrow-symbol:nth-child(2) > span:last-child {
228 | transform: rotate(90deg);
229 | }
230 |
231 | .arrow-symbol:last-child {
232 | transform: rotate(180deg);
233 | }
234 |
235 | .line-buttons {
236 | display: flex;
237 | justify-content: space-between;
238 | align-items: center;
239 | width: 110px;
240 | margin: 5px 0;
241 | }
242 |
243 | .rotate-button-container {
244 | display: flex;
245 | align-items: center;
246 | }
247 |
248 | .rotate-symbol {
249 | font-size: 40px;
250 | transform: scale(-1);
251 | padding: 5px;
252 | }
253 |
254 | .rotate-button {
255 | display: flex;
256 | flex-direction: column;
257 | align-items: center;
258 | width: 60px;
259 | }
260 |
261 | .rotate-text {
262 | position: relative;
263 | width: 60px;
264 | height: 20px;
265 | }
266 |
267 | .rotate-text-letter {
268 | position: absolute;
269 | }
270 |
271 | .rotate-text-letter:nth-child(1) {
272 | left: 0;
273 | bottom: 0;
274 | transform: rotate(320deg);
275 | }
276 |
277 | .rotate-text-letter:nth-child(2) {
278 | left: 10px;
279 | bottom: 7px;
280 | transform: rotate(335deg);
281 | }
282 |
283 | .rotate-text-letter:nth-child(3) {
284 | left: 20px;
285 | bottom: 10px;
286 | transform: rotate(350deg);
287 | }
288 |
289 | .rotate-text-letter:nth-child(4) {
290 | left: 30px;
291 | bottom: 10px;
292 | transform: rotate(365deg);
293 | }
294 |
295 | .rotate-text-letter:nth-child(5) {
296 | left: 41px;
297 | bottom: 7px;
298 | transform: rotate(390deg);
299 | }
300 |
301 | .rotate-text-letter:nth-child(6) {
302 | left: 50px;
303 | bottom: 0;
304 | transform: rotate(405deg);
305 | }
306 |
307 | .info {
308 | display: flex;
309 | justify-content: space-between;
310 | width: 220px;
311 | margin-top: 20px;
312 | padding: 0 0 10px;
313 | border-bottom: 4px solid #dea901;
314 | }
315 |
316 | .title {
317 | font-family: 'Montserrat Alternates', sans-serif;
318 | font-size: 26px;
319 | font-weight: 700;
320 | transform: rotate(330deg);
321 | text-align: end;
322 | }
323 |
324 | .logo {
325 | color: #e11511;
326 | font-size: 60px;
327 | }
328 |
329 | .button {
330 | border-radius: 50%;
331 | border: 1px solid rgba(232, 196, 62, 0.9);
332 | background-color: #dea901;
333 | box-shadow:
334 | 0px 6px 5px 0px #9b9590,
335 | 0px 1px 3px 0px #262422,
336 | inset 5px 5px 10px 0px rgba(232, 196, 62, 0.9);
337 | cursor: pointer;
338 | -webkit-tap-highlight-color: transparent;
339 | }
340 |
341 | .button:hover,
342 | .button:focus {
343 | box-shadow:
344 | 0px 6px 5px 0px #9b9590,
345 | 0px 1px 3px 0px #262422,
346 | inset 5px 5px 10px 1px rgb(232, 196, 62);
347 | }
348 |
349 | .button:active {
350 | box-shadow:
351 | 0px 3px 5px 0px #9b9590,
352 | 0px 1px 3px 0px #262422,
353 | 0px 0px 5px 0px #7f7a75,
354 | 0px 0px 3px 0px #161514,
355 | inset 5px 5px 10px 1px rgb(248, 210, 70);
356 | }
357 |
358 | .button.small {
359 | width: 20px;
360 | height: 20px;
361 | }
362 |
363 | .button.medium {
364 | width: 30px;
365 | height: 30px;
366 | }
367 |
368 | .button.big {
369 | width: 50px;
370 | height: 50px;
371 | }
372 |
373 | .text {
374 | font-family: 'Orbitron', sans-serif;
375 | font-size: 9px;
376 | font-weight: 900;
377 | text-align: center;
378 | white-space: nowrap;
379 | }
380 |
381 | .text.small {
382 | width: 45px;
383 | }
384 |
385 | .text.big {
386 | font-size: 12px;
387 | font-weight: 900;
388 | }
389 |
390 | :focus {
391 | outline: none !important;
392 | }
393 |
--------------------------------------------------------------------------------
/script.js:
--------------------------------------------------------------------------------
1 | class Game {
2 | static #DEBUG = window.location.search.includes('debug');
3 | static #ROW_LENGTH = 20;
4 | static #COLUMN_LENGTH = 10;
5 | static #START_COLUMN = 3;
6 |
7 | static #FIGURES = {
8 | // I
9 | 0: [
10 | [true, true, true, true]
11 | ],
12 | // L
13 | 1: [
14 | [true, false, false],
15 | [true, true, true]
16 | ],
17 | // J
18 | 2: [
19 | [false, false, true],
20 | [true, true, true]
21 | ],
22 | // O
23 | 3: [
24 | [true, true],
25 | [true, true]
26 | ],
27 | // S
28 | 4: [
29 | [false, true, true],
30 | [true, true, false]
31 | ],
32 | // T
33 | 5: [
34 | [false, true, false],
35 | [true, true, true]
36 | ],
37 | // Z
38 | 6: [
39 | [true, true, false],
40 | [false, true, true]
41 | ],
42 | };
43 | static #FIGURES_ROTATION = {
44 | 0: [{
45 | row: -2,
46 | column: 1,
47 | }, {
48 | row: 1,
49 | column: -1,
50 | }, {
51 | row: -1,
52 | column: 2,
53 | }, {
54 | row: 2,
55 | column: -2,
56 | }],
57 | 1: [{
58 | row: -1,
59 | column: 0,
60 | }, {
61 | row: 0,
62 | column: 0,
63 | }, {
64 | row: 0,
65 | column: 1,
66 | }, {
67 | row: 1,
68 | column: -1,
69 | }],
70 | 2: [{
71 | row: -1,
72 | column: 0,
73 | }, {
74 | row: 0,
75 | column: 0,
76 | }, {
77 | row: 0,
78 | column: 1,
79 | }, {
80 | row: 1,
81 | column: -1,
82 | }],
83 | 3: [{
84 | row: 0,
85 | column: 0,
86 | }, {
87 | row: 0,
88 | column: 0,
89 | }, {
90 | row: 0,
91 | column: 0,
92 | }, {
93 | row: 0,
94 | column: 0,
95 | }],
96 | 4: [{
97 | row: -1,
98 | column: 0,
99 | }, {
100 | row: 0,
101 | column: 0,
102 | }, {
103 | row: 0,
104 | column: 1,
105 | }, {
106 | row: 1,
107 | column: -1,
108 | }],
109 | 5: [{
110 | row: -1,
111 | column: 0,
112 | }, {
113 | row: 0,
114 | column: 0,
115 | }, {
116 | row: 0,
117 | column: 1,
118 | }, {
119 | row: 1,
120 | column: -1,
121 | }],
122 | 6: [{
123 | row: -1,
124 | column: 0,
125 | }, {
126 | row: 0,
127 | column: 0,
128 | }, {
129 | row: 0,
130 | column: 1,
131 | }, {
132 | row: 1,
133 | column: -1,
134 | }],
135 | };
136 | static #FIGURES_COUNT = 7;
137 | static #FIGURE_MAX_HEIGHT = 4;
138 | static #CHANGE_DELAY_INTERVAl = 10;
139 |
140 | static #START_DELAY = Game.#DEBUG ? 5000 : 1000;
141 | static #DIFF_DELAY = 50;
142 | static #SPEED_DELAY = 100;
143 | static #MIN_DELAY = 0;
144 | static #SPLASH_DELAY = 20;
145 |
146 | static #CELL = '▣';
147 | static #HI_SCORE_LABEL = 'hi-score';
148 | static #HI_SCORE = localStorage.getItem(Game.#HI_SCORE_LABEL);
149 |
150 | static #AUDIO = new Audio('./sound/korobeyniki.mp3');
151 |
152 | static #SCORES_LEVEL = {
153 | 0: 0,
154 | 1: 100,
155 | 2: 300,
156 | 3: 700,
157 | 4: 1500,
158 | };
159 |
160 | static #EMPTY_FIELD = [...Array(Game.#ROW_LENGTH)].map(
161 | row => [...Array(Game.#COLUMN_LENGTH)].map(column => false),
162 | );
163 |
164 | static #SPLASH_SCREEN = [
165 | [true, true, true, false, true, true, true, false, false, false],
166 | [false, true, false, false, true, false, false, false, false, false],
167 | [false, true, false, false, true, true, true, false, false, false],
168 | [false, true, false, false, true, false, false, false, false, false],
169 | [false, true, false, false, true, true, true, false, false, false],
170 | [false, false, false, false, false, false, false, false, false, false],
171 | [false, false, false, true, true, true, false, true, true, true],
172 | [false, false, false, false, true, false, false, true, false, true],
173 | [false, false, false, false, true, false, false, true, true, true],
174 | [false, false, false, false, true, false, false, true, true, false],
175 | [false, false, false, false, true, false, false, true, false, true],
176 | [false, false, false, false, false, false, false, false, false, false],
177 | [true, true, true, false, true, true, true, false, false, false],
178 | [false, true, false, false, true, false, false, false, false, false],
179 | [false, true, false, false, true, true, true, false, false, false],
180 | [false, true, false, false, false, false, true, false, false, false],
181 | [true, true, true, false, true, true, true, false, false, false],
182 | [false, false, false, false, false, false, false, false, false, false],
183 | [false, false, false, false, false, false, false, false, false, false],
184 | [false, false, false, false, false, false, false, false, false, false],
185 | ];
186 |
187 | #scores = 0;
188 | #figuresCount = 0;
189 | #currentRow = 0;
190 | #currentColumn = Game.#START_COLUMN;
191 | #rotations = 0;
192 |
193 | #currentTimeoutId = 0;
194 | #delay = Game.#START_DELAY;
195 | #currentDelay = this.#delay;
196 |
197 | #figure = null;
198 | #figureIndex = null;
199 | #nextFigure = null;
200 | #nextFigureIndex = null;
201 | #figuresHistory = [4, 6, 4, 6]; // S Z S Z
202 | #field = Game.#EMPTY_FIELD;
203 | #figureField = Game.#EMPTY_FIELD;
204 |
205 | #isGameInProgress = false;
206 | #isGameOn = false;
207 | #isSoundOn = false;
208 |
209 | #moveLeftButtonInterval = 0;
210 | #moveRightButtonInterval = 0;
211 |
212 | #randomizer = null;
213 |
214 | constructor(gameElem, gameInfoElem) {
215 | this.gameElem = gameElem;
216 | this.gameInfoElem = gameInfoElem;
217 |
218 | this.gameInfoElem.innerHTML = `
219 | Hi-Score
220 | 0
221 | Score
222 | 0
223 |
224 | Speed
225 | 1
226 | PAUSED
227 | `;
228 |
229 | this.scoresElem = document.getElementById('score');
230 | this.hiScoresElem = document.getElementById('hi-score');
231 | this.nextFigureElem = document.getElementById('next-figure');
232 | this.speedElem = document.getElementById('speed');
233 | this.pausedElem = document.getElementById('paused');
234 |
235 | this.#setHiScore();
236 |
237 | this.#randomizer = this.#getRandomFigure();
238 |
239 | Game.#AUDIO.loop = true;
240 | Game.#AUDIO.volume = 0.5;
241 | };
242 |
243 | #setHiScore = () => {
244 | this.hiScoresElem.innerText = Game.#HI_SCORE || 0;
245 | };
246 |
247 | #clearCurrentTimeout = () => {
248 | clearTimeout(this.#currentTimeoutId);
249 | };
250 |
251 | #changeDelay = () => {
252 | if (this.#figuresCount % Game.#CHANGE_DELAY_INTERVAl === 0) {
253 | this.#delay = this.#delay - Game.#DIFF_DELAY > Game.#SPEED_DELAY
254 | ? this.#delay - Game.#DIFF_DELAY
255 | : Game.#SPEED_DELAY;
256 | this.speedElem.innerText = Math.round(this.#figuresCount / Game.#CHANGE_DELAY_INTERVAl);
257 | }
258 | };
259 |
260 | * #getRandomFigure() {
261 | let rand = [0, 1, 2, 5][Math.floor(Math.random() * 4)]; // I L J T
262 | yield Game.#FIGURES[rand];
263 |
264 | this.#figuresHistory = [4, 6, 4, rand]; // S Z S ...
265 |
266 | while(true) {
267 | for (let roll = 0; roll < 4; ++roll) {
268 | rand = Math.floor(Math.random() * Game.#FIGURES_COUNT);
269 | if (!this.#figuresHistory.includes(rand)) break;
270 | }
271 |
272 | this.#nextFigureIndex = rand;
273 | this.#figuresHistory.shift();
274 | this.#figuresHistory.push(rand);
275 |
276 | yield Game.#FIGURES[rand];
277 | }
278 | };
279 |
280 | #getCell = (val) => `${Game.#CELL}`;
281 |
282 | #getStrField = (field1, field2) => {
283 | let strField = '';
284 |
285 | for (let i = 0; i < Game.#ROW_LENGTH; i++) {
286 | for (let j = 0; j < Game.#COLUMN_LENGTH; j++) {
287 | strField += this.#getCell(field1[i][j] || field2[i][j]);
288 | }
289 | }
290 |
291 | return strField;
292 | };
293 |
294 | #getAnimation = (splashStep, defaultSplashCell) => {
295 | const ranges = [19, 28, 47, 55, 73, 80, 97, 103, 119, 124, 139, 143, 157, 160, 173, 175, 187, 188, 199];
296 | const currentRange = ranges.find(range => splashStep <= range);
297 | const currentRangeIndex = ranges.findIndex(range => range === currentRange);
298 |
299 | const setStaticCell = (right, up, left, down, i, j) =>
300 | (i < up || i > Game.#ROW_LENGTH - 1 - down || j < left || j > Game.#COLUMN_LENGTH - 1 - right)
301 | ? !defaultSplashCell
302 | : defaultSplashCell;
303 |
304 | return [...Array(Game.#ROW_LENGTH)].map(
305 | (row, i) => [...Array(Game.#COLUMN_LENGTH)].map((column, j) => {
306 | let splashCell = defaultSplashCell;
307 |
308 | switch (currentRangeIndex) {
309 | case 0: case 4: case 8: case 12: case 16: { // up
310 | const rightLinesCount = (currentRangeIndex + 0) / 4;
311 | const leftLinesCount = rightLinesCount;
312 | const upLinesCount = leftLinesCount;
313 | const downLinesCount = upLinesCount;
314 |
315 | splashCell = setStaticCell(rightLinesCount, upLinesCount, leftLinesCount, downLinesCount, i, j);
316 |
317 | if (currentRange - splashStep + downLinesCount <= i && j === Game.#COLUMN_LENGTH - 1 - rightLinesCount) {
318 | splashCell = !defaultSplashCell;
319 | }
320 |
321 | break;
322 | }
323 | case 1: case 5: case 9: case 13: case 17: { // left
324 | const rightLinesCount = (currentRangeIndex + 3) / 4;
325 | const leftLinesCount = rightLinesCount - 1;
326 | const upLinesCount = leftLinesCount;
327 | const downLinesCount = upLinesCount;
328 |
329 | splashCell = setStaticCell(rightLinesCount, upLinesCount, leftLinesCount, downLinesCount, i, j);
330 |
331 | if (currentRange - splashStep + leftLinesCount <= j && i === upLinesCount) {
332 | splashCell = !defaultSplashCell;
333 | }
334 |
335 | break;
336 | }
337 | case 2: case 6: case 10: case 14: case 18: { // down
338 | const rightLinesCount = (currentRangeIndex + 2) / 4;
339 | const leftLinesCount = rightLinesCount - 1;
340 | const upLinesCount = rightLinesCount;
341 | const downLinesCount = upLinesCount - 1;
342 |
343 | splashCell = setStaticCell(rightLinesCount, upLinesCount, leftLinesCount, downLinesCount, i, j);
344 |
345 | if (splashStep - ranges[currentRangeIndex - 1] + downLinesCount >= i && j === leftLinesCount) {
346 | splashCell = !defaultSplashCell;
347 | }
348 |
349 | break;
350 | }
351 | case 3: case 7: case 11: case 15: { // right
352 | const rightLinesCount = (currentRangeIndex + 1) / 4;
353 | const leftLinesCount = rightLinesCount;
354 | const upLinesCount = rightLinesCount;
355 | const downLinesCount = upLinesCount - 1;
356 |
357 | splashCell = setStaticCell(rightLinesCount, upLinesCount, leftLinesCount, downLinesCount, i, j);
358 |
359 | if (splashStep - ranges[currentRangeIndex - 1] + downLinesCount >= j && i === Game.#ROW_LENGTH - 1 - downLinesCount) {
360 | splashCell = !defaultSplashCell;
361 | }
362 |
363 | break;
364 | }
365 |
366 | default: {
367 | break;
368 | }
369 | }
370 |
371 | return splashCell;
372 | }),
373 | );
374 | };
375 |
376 | #printSplash = (callback) => {
377 | const allSplashSteps = Game.#ROW_LENGTH * Game.#COLUMN_LENGTH;
378 | let splashStep = 0;
379 |
380 | const printCallback = (defaultSplashCell) => {
381 | const currentSplashStep = splashStep > allSplashSteps - 1
382 | ? splashStep - allSplashSteps
383 | : splashStep;
384 | const splashAnimation = this.#getAnimation(currentSplashStep, defaultSplashCell);
385 |
386 | this.gameElem.innerHTML = this.#getStrField(Game.#SPLASH_SCREEN, splashAnimation);
387 | }
388 |
389 | const intervalId = setInterval(() => {
390 | printCallback(splashStep > allSplashSteps);
391 | splashStep++;
392 |
393 | if (!this.#isGameOn) {
394 | clearInterval(intervalId);
395 | }
396 |
397 | if (splashStep === allSplashSteps * 2 - 1) {
398 | clearInterval(intervalId);
399 | callback();
400 | }
401 | }, Game.#SPLASH_DELAY);
402 | }
403 |
404 | #printField = () => {
405 | this.gameElem.innerHTML = this.#getStrField(this.#field, this.#figureField);
406 | };
407 |
408 | #printNextFigure = () => {
409 | const nextFigure = this.#nextFigure;
410 |
411 | let strField = '';
412 |
413 | for (let i = 0; i < Game.#FIGURE_MAX_HEIGHT; i++) {
414 | for (let j = 0; j < Game.#FIGURE_MAX_HEIGHT; j++) {
415 | strField += this.#getCell(nextFigure && nextFigure[i] && nextFigure[i][j]);
416 | }
417 | }
418 |
419 | this.nextFigureElem.innerHTML = strField;
420 | };
421 |
422 | #checkEndGame = () => {
423 | const field = this.#field;
424 | let rowFilledCount = 0;
425 |
426 | for (let i = 0; i < Game.#ROW_LENGTH; i++) {
427 | if (field[i].some(elem => elem)) {
428 | rowFilledCount++;
429 | }
430 | }
431 |
432 | return rowFilledCount === Game.#ROW_LENGTH;
433 | };
434 |
435 | #mergeFields = () => {
436 | const newField = [];
437 | const field = this.#field;
438 | const figureField = this.#figureField;
439 |
440 | let rowCount = 0;
441 |
442 | for (let i = 0; i < Game.#ROW_LENGTH; i++) {
443 | let row = [];
444 | for (let j = 0; j < Game.#COLUMN_LENGTH; j++) {
445 | row[j] = field[i][j] || figureField[i][j];
446 | }
447 | if (!row.every(elem => elem)) {
448 | newField[rowCount] = row;
449 | rowCount++;
450 | }
451 | }
452 |
453 | const rowDiff = field.length - newField.length;
454 | let emptyField = [];
455 |
456 | if (rowDiff) {
457 | emptyField = [...Array(rowDiff)].map(
458 | row => [...Array(Game.#COLUMN_LENGTH)].map(column => false)
459 | );
460 | this.#scores += Game.#SCORES_LEVEL[rowDiff];
461 | }
462 |
463 | this.#field = [...emptyField, ...newField];
464 | };
465 |
466 | #getMaxColumn = (
467 | currentFigure,
468 | currentRow = this.#currentRow,
469 | currentColumn = this.#currentColumn,
470 | ) => {
471 | const figureRows = currentFigure.length;
472 | const figureColumns = currentFigure[0].length;
473 |
474 | let maxColumn = Game.#COLUMN_LENGTH - figureColumns;
475 |
476 | for (let i = 0; i < figureRows; i++) {
477 | let prevFilledColumn = -1;
478 |
479 | for (let j = figureColumns - 1; j >= 0; j--) {
480 | if (!currentFigure[i][j] || j < prevFilledColumn) continue;
481 |
482 | prevFilledColumn = j;
483 |
484 | const fieldColumnIndex = currentRow + i >= 0
485 | ? this.#field[currentRow + i]
486 | .findIndex((elem, index) => {
487 | return (index > currentColumn) && elem;
488 | })
489 | : -1;
490 |
491 | if (fieldColumnIndex !== -1 && fieldColumnIndex - j - 1 < maxColumn) {
492 | maxColumn = fieldColumnIndex - j - 1;
493 | }
494 | }
495 | }
496 |
497 | return maxColumn;
498 | };
499 |
500 | #getMinColumn = (
501 | currentFigure,
502 | currentRow = this.#currentRow,
503 | currentColumn = this.#currentColumn,
504 | ) => {
505 | const figureRows = currentFigure.length;
506 | const figureColumns = currentFigure[0].length;
507 |
508 | let minColumn = 0;
509 |
510 | for (let i = 0; i < figureRows; i++) {
511 | let prevFilledColumn = figureColumns;
512 |
513 | for (let j = 0; j < figureColumns; j++) {
514 | if (!currentFigure[i][j] || j > prevFilledColumn) continue;
515 |
516 | prevFilledColumn = j;
517 |
518 | const fieldColumnIndex = currentRow + i >= 0
519 | ? this.#field[currentRow + i]
520 | .findLastIndex((elem, index) => {
521 | return (index < currentColumn + figureColumns) && elem;
522 | })
523 | : -1;
524 |
525 | if (fieldColumnIndex !== -1 && fieldColumnIndex - j + 1 > minColumn) {
526 | minColumn = fieldColumnIndex - j + 1;
527 | }
528 | }
529 | }
530 |
531 | return minColumn;
532 | };
533 |
534 | #getMaxRow = (
535 | currentFigure,
536 | currentRow = this.#currentRow,
537 | currentColumn = this.#currentColumn,
538 | ) => {
539 | const figureRows = currentFigure.length;
540 | const figureColumns = currentFigure[0].length;
541 |
542 | let maxRow = Game.#ROW_LENGTH - figureRows;
543 |
544 | for (let j = 0; j < figureColumns; j++) {
545 | let prevFilledRow = -1;
546 |
547 | for (let i = figureRows - 1; i >= 0; i--) {
548 | if (!currentFigure[i][j] || i < prevFilledRow) continue;
549 |
550 | prevFilledRow = i;
551 |
552 | const fieldColumn = this.#field.map(row => row[currentColumn + j]);
553 | const fieldRowIndex = fieldColumn
554 | .findIndex((elem, index) => {
555 | return (index > currentRow) && elem;
556 | });
557 |
558 | if (fieldRowIndex !== -1 && fieldRowIndex - i - 1 < maxRow) {
559 | maxRow = fieldRowIndex - i - 1;
560 | }
561 | }
562 | }
563 |
564 | return maxRow;
565 | };
566 |
567 | #getMinRow = (
568 | currentFigure,
569 | currentRow = this.#currentRow,
570 | currentColumn = this.#currentColumn,
571 | ) => {
572 | const figureRows = currentFigure.length;
573 | const figureColumns = currentFigure[0].length;
574 |
575 | let minRow = 0;
576 |
577 | for (let i = 0; i < figureRows; i++) {
578 | let prevFilledRow = figureRows;
579 |
580 | for (let j = 0; j < figureColumns; j++) {
581 | if (!currentFigure[i][j] || i > prevFilledRow) continue;
582 |
583 | prevFilledRow = i;
584 |
585 | const fieldColumn = this.#field.map(row => row[currentColumn + j]);
586 | const fieldRowIndex = fieldColumn
587 | .findLastIndex((elem, index) => {
588 | return (index < currentRow + figureRows) && elem;
589 | })
590 |
591 | if (fieldRowIndex !== -1 && fieldRowIndex + i + 1 > minRow) {
592 | minRow = fieldRowIndex + i + 1;
593 | }
594 | }
595 | }
596 |
597 | return minRow;
598 | };
599 |
600 | #getNextPosition = (currentFigure) => {
601 | const { row, column } = Game.#FIGURES_ROTATION[this.#figureIndex][this.#rotations % 4];
602 |
603 | let nextRow = this.#currentRow + row < 0 ? 0 : this.#currentRow + row;
604 | let nextColumn = this.#currentColumn + column < 0 ? 0 : this.#currentColumn + column;
605 |
606 | const maxRow = this.#getMaxRow(currentFigure, nextRow, nextColumn);
607 | const minRow = this.#getMinRow(currentFigure, nextRow, nextColumn);
608 |
609 | const maxColumn = this.#getMaxColumn(currentFigure, nextRow, nextColumn);
610 | const minColumn = this.#getMinColumn(currentFigure, nextRow, nextColumn);
611 |
612 | if (minRow > maxRow) {
613 | nextRow = -1;
614 | } else if (nextRow >= maxRow) {
615 | nextRow = maxRow;
616 | } else if (nextRow <= minRow) {
617 | nextRow = minRow;
618 | }
619 |
620 | if (minColumn > maxColumn) {
621 | nextColumn = -1;
622 | } else if (nextColumn >= maxColumn) {
623 | nextColumn = maxColumn;
624 | } else if (nextColumn <= minColumn) {
625 | nextColumn = minColumn;
626 | }
627 |
628 | return {
629 | nextRow,
630 | nextColumn,
631 | };
632 | };
633 |
634 | #setFigureField = () => {
635 | this.#figureField = [...Array(Game.#ROW_LENGTH)].map(
636 | (_, row) => [...Array(Game.#COLUMN_LENGTH)].map((_, column) => {
637 | const figureRows = this.#figure.length;
638 | const figureColumns = this.#figure[0].length;
639 |
640 | if (row < this.#currentRow
641 | || row >= this.#currentRow + figureRows
642 | || column < this.#currentColumn
643 | || column >= this.#currentColumn + figureColumns
644 | ) {
645 | return false;
646 | }
647 |
648 | return this.#figure[row - this.#currentRow][column - this.#currentColumn];
649 | }),
650 | );
651 |
652 | this.#printField();
653 | };
654 |
655 | moveFigureLeft = () => {
656 | if (!this.#isGameInProgress) return;
657 |
658 | const minColumn = this.#getMinColumn(this.#figure);
659 | this.#currentColumn = this.#currentColumn <= minColumn
660 | ? minColumn
661 | : this.#currentColumn - 1;
662 |
663 | this.#setFigureField();
664 | };
665 |
666 | moveFigureRight = () => {
667 | if (!this.#isGameInProgress) return;
668 |
669 | const maxColumn = this.#getMaxColumn(this.#figure);
670 | this.#currentColumn = this.#currentColumn >= maxColumn
671 | ? maxColumn
672 | : this.#currentColumn + 1;
673 |
674 | this.#setFigureField();
675 | };
676 |
677 | moveFigureDown = (e) => {
678 | if (!this.#isGameInProgress) return;
679 |
680 | this.#currentDelay = Game.#SPEED_DELAY;
681 | this.#clearCurrentTimeout();
682 | this.continue();
683 | };
684 |
685 | stopMovingFigureDown = () => {
686 | if (!this.#isGameInProgress) return;
687 |
688 | this.#currentDelay = this.#delay;
689 | };
690 |
691 | moveFigureDownQuickly = () => {
692 | if (!this.#isGameInProgress) return;
693 |
694 | this.#currentDelay = Game.#MIN_DELAY;
695 | this.#clearCurrentTimeout();
696 | this.continue();
697 | };
698 |
699 | onMoveLeftButtonMouseDown = (e) => {
700 | this.#moveLeftButtonInterval = setInterval(() => {
701 | this.moveFigureLeft();
702 | }, 50);
703 | e.preventDefault();
704 | };
705 |
706 | onMoveLeftButtonMouseUp = () => {
707 | clearInterval(this.#moveLeftButtonInterval);
708 | };
709 |
710 | onMoveRightButtonMouseDown = (e) => {
711 | this.#moveRightButtonInterval = setInterval(() => {
712 | this.moveFigureRight();
713 | }, 50);
714 | e.preventDefault();
715 | };
716 |
717 | onMoveRightButtonMouseUp = () => {
718 | clearInterval(this.#moveRightButtonInterval);
719 | };
720 |
721 | rotateFigure = () => {
722 | if (!this.#isGameInProgress) return;
723 |
724 | const newFigure = [];
725 | const figureRows = this.#figure.length;
726 | const figureColumns = this.#figure[0].length;
727 |
728 | for (let i = 0; i < figureRows; i++) {
729 | for (let j = 0; j < figureColumns; j++) {
730 | if (!newFigure[j]) {
731 | newFigure[j] = [];
732 | }
733 | newFigure[j][figureRows - 1 - i] = this.#figure[i][j];
734 | }
735 | }
736 |
737 | const { nextRow, nextColumn } = this.#getNextPosition(newFigure);
738 |
739 | if (nextRow !== -1 && nextColumn !== -1) {
740 | this.#figure = newFigure;
741 | this.#currentRow = nextRow;
742 | this.#currentColumn = nextColumn;
743 | this.#rotations++;
744 | this.#setFigureField();
745 | }
746 | };
747 |
748 | continue = () => {
749 | if (!this.#isGameInProgress) return;
750 |
751 | let isGameEnd = false;
752 |
753 | const maxRow = this.#getMaxRow(this.#figure);
754 | this.#setFigureField();
755 |
756 | if (this.#currentRow >= maxRow) {
757 | this.#mergeFields();
758 | isGameEnd = this.#checkEndGame();
759 |
760 | if (isGameEnd) {
761 | if (this.#scores > Game.#HI_SCORE) {
762 | localStorage.setItem(Game.#HI_SCORE_LABEL, this.#scores);
763 | this.#setHiScore();
764 | }
765 | this.#clearCurrentTimeout();
766 | this.#isGameInProgress = false;
767 | alert(`Game over, your scores: ${this.#scores}`)
768 | } else {
769 | this.scoresElem.innerText = this.#scores;
770 |
771 | if (this.#currentDelay === Game.#MIN_DELAY) {
772 | this.#currentDelay = this.#delay;
773 | }
774 |
775 | this.#currentRow = 0;
776 | this.#currentColumn = Game.#START_COLUMN;
777 |
778 | this.#figure = this.#nextFigure;
779 | this.#figureIndex = this.#nextFigureIndex;
780 | this.#nextFigure = this.#randomizer.next().value;
781 |
782 | this.#rotations = 0;
783 | this.#figuresCount++;
784 | this.#figureField = Game.#EMPTY_FIELD;
785 |
786 | this.#changeDelay();
787 | this.#printNextFigure();
788 | }
789 | } else {
790 | this.#currentRow++;
791 | }
792 |
793 | if (!isGameEnd) {
794 | this.#currentTimeoutId = setTimeout(this.continue, this.#currentDelay);
795 | }
796 | };
797 |
798 | start = () => {
799 | this.continue();
800 | };
801 |
802 | reset = () => {
803 | this.#clearCurrentTimeout();
804 | this.#randomizer = this.#getRandomFigure();
805 |
806 | this.#scores = 0;
807 | this.#figuresCount = 0;
808 | this.#currentRow = 0;
809 | this.#currentColumn = Game.#START_COLUMN;
810 |
811 | this.#currentTimeoutId = 0;
812 | this.#delay = Game.#START_DELAY;
813 | this.#currentDelay = this.#delay;
814 |
815 | this.#field = Game.#EMPTY_FIELD;
816 | this.#figure = this.#randomizer.next().value;
817 | this.#figureIndex = this.#nextFigureIndex;
818 | this.#nextFigure = this.#randomizer.next().value;
819 | this.#figureField = Game.#EMPTY_FIELD;
820 |
821 | this.scoresElem.innerText = this.#scores;
822 |
823 | this.#isGameInProgress = true;
824 | document.getElementById('button-game-pause').classList.remove('inactive');
825 | this.pausedElem.classList.add('invisible');
826 |
827 | this.#printNextFigure();
828 | this.start();
829 | };
830 |
831 | togglePause = () => {
832 | if (!this.#isGameOn) return;
833 |
834 | this.#isGameInProgress = !this.#isGameInProgress;
835 |
836 | if (this.#isGameInProgress) {
837 | this.continue();
838 | this.pausedElem.classList.add('invisible');
839 | } else {
840 | this.pausedElem.classList.remove('invisible');
841 | }
842 | };
843 |
844 | toggleOn = () => {
845 | this.gameElem.classList.toggle('invisible');
846 | this.gameInfoElem.classList.toggle('invisible');
847 | const pauseButton = document.getElementById('button-game-pause');
848 |
849 | this.#isGameOn = !this.#isGameOn;
850 |
851 | if (this.#isGameOn) {
852 | if (Game.#DEBUG) {
853 | this.#isGameInProgress = true;
854 | this.reset();
855 | } else {
856 | this.#printSplash(() => {
857 | this.#isGameInProgress = true;
858 | this.reset();
859 | });
860 | }
861 | } else {
862 | this.#isGameInProgress = false;
863 | pauseButton.classList.add('inactive');
864 | this.pausedElem.classList.add('invisible');
865 | this.#isSoundOn = false;
866 | Game.#AUDIO.pause();
867 | Game.#AUDIO.currentTime = 0;
868 | }
869 | };
870 |
871 | toggleSound = () => {
872 | if (!this.#isGameOn) return;
873 |
874 | if (!this.#isSoundOn) {
875 | Game.#AUDIO.play();
876 | } else {
877 | Game.#AUDIO.pause();
878 | }
879 |
880 | this.#isSoundOn = !this.#isSoundOn;
881 | };
882 | };
883 |
884 | const gameElem = document.getElementById('game');
885 | const gameInfoElem = document.getElementById('game-info');
886 |
887 | const game = new Game(gameElem, gameInfoElem);
888 |
889 | const KEY_LEFT_ARROW = 'ArrowLeft';
890 | const KEY_UP_ARROW = 'ArrowUp';
891 | const KEY_RIGHT_ARROW = 'ArrowRight';
892 | const KEY_DOWN_ARROW = 'ArrowDown';
893 | const KEY_SPACE = 'Space';
894 |
895 | document.addEventListener('keydown', (e) => {
896 | const event = e || window.event;
897 | const code = event.code;
898 |
899 | if (code === KEY_LEFT_ARROW) {
900 | game.moveFigureLeft();
901 | } else if (code == KEY_RIGHT_ARROW) {
902 | game.moveFigureRight();
903 | } else if (code === KEY_DOWN_ARROW) {
904 | game.moveFigureDown();
905 | } else if (code === KEY_UP_ARROW) {
906 | game.rotateFigure();
907 | } else if (code === KEY_SPACE) {
908 | game.moveFigureDownQuickly();
909 | }
910 | });
911 |
912 | document.addEventListener('keyup', (e) => {
913 | const event = e || window.event;
914 | const code = event.code;
915 |
916 | if (code === KEY_DOWN_ARROW) {
917 | game.stopMovingFigureDown();
918 | }
919 | });
920 |
921 | document.getElementById('button-game-on').addEventListener('click', game.toggleOn);
922 | document.getElementById('button-game-pause').addEventListener('click', game.togglePause);
923 | document.getElementById('button-game-reset').addEventListener('click', game.reset);
924 | document.getElementById('button-game-sound').addEventListener('click', game.toggleSound);
925 |
926 | document.getElementById('button-left').addEventListener('mousedown', game.onMoveLeftButtonMouseDown);
927 | document.getElementById('button-left').addEventListener('mouseup', game.onMoveLeftButtonMouseUp);
928 | document.getElementById('button-left').addEventListener('touchstart', game.onMoveLeftButtonMouseDown);
929 | document.getElementById('button-left').addEventListener('touchend', game.onMoveLeftButtonMouseUp);
930 |
931 | document.getElementById('button-right').addEventListener('mousedown', game.onMoveRightButtonMouseDown);
932 | document.getElementById('button-right').addEventListener('mouseup', game.onMoveRightButtonMouseUp);
933 | document.getElementById('button-right').addEventListener('touchstart', game.onMoveRightButtonMouseDown);
934 | document.getElementById('button-right').addEventListener('touchend', game.onMoveRightButtonMouseUp);
935 |
936 | document.getElementById('button-up').addEventListener('click', game.moveFigureDownQuickly);
937 | document.getElementById('button-rotate').addEventListener('click', game.rotateFigure);
938 |
939 | document.getElementById('button-down').addEventListener('mousedown', game.moveFigureDown);
940 | document.getElementById('button-down').addEventListener('mouseup', game.stopMovingFigureDown);
941 | document.getElementById('button-down').addEventListener('touchstart', game.moveFigureDown);
942 | document.getElementById('button-down').addEventListener('touchend', game.stopMovingFigureDown);
943 |
--------------------------------------------------------------------------------