+ [class.drop]='drop()'>
diff --git a/src/app/containers/angular-tetris/angular-tetris.component.ts b/src/app/containers/angular-tetris/angular-tetris.component.ts
index 5bdfdb7..16b771f 100644
--- a/src/app/containers/angular-tetris/angular-tetris.component.ts
+++ b/src/app/containers/angular-tetris/angular-tetris.component.ts
@@ -1,81 +1,92 @@
-import { Component, HostListener, OnInit, ElementRef, Renderer2 } from '@angular/core';
-import { TetrisKeyboard } from '@trungk18/interface/keyboard';
-import { SoundManagerService } from '@trungk18/services/sound-manager.service';
-import { KeyboardQuery } from '@trungk18/state/keyboard/keyboard.query';
-import { KeyboardService } from '@trungk18/state/keyboard/keyboard.service';
-import { TetrisQuery } from '@trungk18/state/tetris/tetris.query';
-import { TetrisService } from '@trungk18/state/tetris/tetris.service';
-import { Observable } from 'rxjs';
+import { ClockComponent } from '@angular-tetris/components/clock/clock.component';
+import { GithubComponent } from '@angular-tetris/components/github/github.component';
+import { HoldComponent } from '@angular-tetris/components/hold/hold.component';
+import { KeyboardComponent } from '@angular-tetris/components/keyboard/keyboard.component';
+import { LevelComponent } from '@angular-tetris/components/level/level.component';
+import { LogoComponent } from '@angular-tetris/components/logo/logo.component';
+import { MatrixComponent } from '@angular-tetris/components/matrix/matrix.component';
+import { NextComponent } from '@angular-tetris/components/next/next.component';
+import { PauseComponent } from '@angular-tetris/components/pause/pause.component';
+import { PointComponent } from '@angular-tetris/components/point/point.component';
+import { ScreenDecorationComponent } from '@angular-tetris/components/screen-decoration/screen-decoration.component';
+import { SoundComponent } from '@angular-tetris/components/sound/sound.component';
+import { StartLineComponent } from '@angular-tetris/components/start-line/start-line.component';
+import { TetrisKeyboard } from '@angular-tetris/interface/keyboard';
+import { SoundManagerService } from '@angular-tetris/services/sound-manager.service';
+import { KeyboardService } from '@angular-tetris/state/keyboard/keyboard.service';
+import { TetrisService } from '@angular-tetris/state/tetris/tetris.service';
+import { TetrisStateService } from '@angular-tetris/state/tetris/tetris.state';
+import { AsyncPipe, NgIf } from '@angular/common';
+import {
+ ChangeDetectionStrategy,
+ Component,
+ ElementRef,
+ HostListener,
+ OnInit,
+ Renderer2,
+ inject
+} from '@angular/core';
+
const KeyUp = 'document:keyup';
const KeyDown = 'document:keydown';
@Component({
- selector: 'angular-tetris',
+ selector: 'angular-tetris', // eslint-disable-line @angular-eslint/component-selector
+ standalone: true,
+ imports: [
+ NgIf,
+ AsyncPipe,
+ ClockComponent,
+ GithubComponent,
+ HoldComponent,
+ KeyboardComponent,
+ LevelComponent,
+ LogoComponent,
+ MatrixComponent,
+ NextComponent,
+ PauseComponent,
+ PointComponent,
+ ScreenDecorationComponent,
+ SoundComponent,
+ StartLineComponent
+ ],
templateUrl: './angular-tetris.component.html',
- styleUrls: ['./angular-tetris.component.scss']
+ styleUrls: ['./angular-tetris.component.scss'],
+ changeDetection: ChangeDetectionStrategy.OnPush
})
export class AngularTetrisComponent implements OnInit {
- drop$: Observable
;
- isShowLogo$: Observable;
- filling: number;
-
- get hasCurrent() {
- return !!this._tetrisQuery.current;
- }
-
- constructor(
- private _tetrisService: TetrisService,
- private _tetrisQuery: TetrisQuery,
- private _keyboardService: KeyboardService,
- private _keyboardQuery: KeyboardQuery,
- private _soundManager: SoundManagerService,
- private _el: ElementRef,
- private _render: Renderer2
- ) {}
+ private tetrisState = inject(TetrisStateService);
+ private tetrisService = inject(TetrisService);
+ private keyboardService = inject(KeyboardService);
+ private soundManager = inject(SoundManagerService);
+ private el = inject(ElementRef);
+ private render = inject(Renderer2);
- ngOnInit(): void {
- this.drop$ = this._keyboardQuery.drop$;
- this.isShowLogo$ = this._tetrisQuery.isShowLogo$;
- setTimeout(() => {
- this.resize();
- });
- }
+ drop = this.keyboardService.drop;
+ isShowLogo$ = this.tetrisState.isShowLogo$;
+ filling: number;
@HostListener('window:resize', ['$event'])
resize() {
- let width = document.documentElement.clientWidth;
- let height = document.documentElement.clientHeight;
- let ratio = height / width;
+ const width = document.documentElement.clientWidth;
+ const height = document.documentElement.clientHeight;
+ const ratio = height / width;
let scale = 1;
if (ratio < 1.5) {
scale = height / 960;
} else {
scale = width / 640;
this.filling = (height - 960 * scale) / scale / 3;
- let paddingTop = Math.floor(this.filling) + 42;
- let paddingBottom = Math.floor(this.filling);
- let marginTop = Math.floor(-480 - this.filling * 1.5);
+ const paddingTop = Math.floor(this.filling) + 42;
+ const paddingBottom = Math.floor(this.filling);
+ const marginTop = Math.floor(-480 - this.filling * 1.5);
this.setPaddingMargin(paddingTop, paddingBottom, marginTop);
}
- this._render.setStyle(this._el.nativeElement, 'transform', `scale(${scale - 0.01})`);
- }
-
- private setPaddingMargin(paddingTop: number, paddingBottom: number, marginTop: number) {
- this._render.setStyle(this._el.nativeElement, 'padding-top', `${paddingTop}px`);
- this._render.setStyle(this._el.nativeElement, 'padding-bottom', `${paddingBottom}px`);
- this._render.setStyle(this._el.nativeElement, 'margin-top', `${marginTop}px`);
- }
-
- keyboardMouseDown(key: string) {
- this[`keyDown${key}`]();
- }
-
- keyboardMouseUp(key: string) {
- this[`keyUp${key}`]();
+ this.render.setStyle(this.el.nativeElement, 'transform', `scale(${scale - 0.01})`);
}
@HostListener('window:beforeunload', ['$event'])
unloadHandler(event: Event) {
- if (!!this._tetrisQuery.current) {
+ if (this.hasCurrent) {
event.preventDefault();
event.returnValue = true;
}
@@ -83,153 +94,169 @@ export class AngularTetrisComponent implements OnInit {
@HostListener(`${KeyDown}.${TetrisKeyboard.Left}`)
keyDownLeft() {
- this._soundManager.move();
- this._keyboardService.setKeỵ({
+ this.soundManager.move();
+ this.keyboardService.setKeỵ({
left: true
});
if (this.hasCurrent) {
- this._tetrisService.moveLeft();
+ this.tetrisService.moveLeft();
} else {
- this._tetrisService.decreaseLevel();
+ this.tetrisService.decreaseLevel();
}
}
@HostListener(`${KeyUp}.${TetrisKeyboard.Left}`)
keyUpLeft() {
- this._keyboardService.setKeỵ({
+ this.keyboardService.setKeỵ({
left: false
});
}
@HostListener(`${KeyDown}.${TetrisKeyboard.Right}`)
keyDownRight() {
- this._soundManager.move();
- this._keyboardService.setKeỵ({
+ this.soundManager.move();
+ this.keyboardService.setKeỵ({
right: true
});
if (this.hasCurrent) {
- this._tetrisService.moveRight();
+ this.tetrisService.moveRight();
} else {
- this._tetrisService.increaseLevel();
+ this.tetrisService.increaseLevel();
}
}
@HostListener(`${KeyUp}.${TetrisKeyboard.Right}`)
keyUpRight() {
- this._keyboardService.setKeỵ({
+ this.keyboardService.setKeỵ({
right: false
});
}
@HostListener(`${KeyDown}.${TetrisKeyboard.Up}`)
keyDownUp() {
- this._soundManager.rotate();
- this._keyboardService.setKeỵ({
+ this.soundManager.rotate();
+ this.keyboardService.setKeỵ({
up: true
});
if (this.hasCurrent) {
- this._tetrisService.rotate();
+ this.tetrisService.rotate();
} else {
- this._tetrisService.increaseStartLine();
+ this.tetrisService.increaseStartLine();
}
}
@HostListener(`${KeyUp}.${TetrisKeyboard.Up}`)
keyUpUp() {
- this._keyboardService.setKeỵ({
+ this.keyboardService.setKeỵ({
up: false
});
}
@HostListener(`${KeyDown}.${TetrisKeyboard.Down}`)
keyDownDown() {
- this._soundManager.move();
- this._keyboardService.setKeỵ({
+ this.soundManager.move();
+ this.keyboardService.setKeỵ({
down: true
});
if (this.hasCurrent) {
- this._tetrisService.moveDown();
+ this.tetrisService.moveDown();
} else {
- this._tetrisService.decreaseStartLine();
+ this.tetrisService.decreaseStartLine();
}
}
@HostListener(`${KeyUp}.${TetrisKeyboard.Down}`)
keyUpDown() {
- this._keyboardService.setKeỵ({
+ this.keyboardService.setKeỵ({
down: false
});
}
@HostListener(`${KeyDown}.${TetrisKeyboard.Space}`)
keyDownSpace() {
- this._keyboardService.setKeỵ({
+ this.keyboardService.setKeỵ({
drop: true
});
if (this.hasCurrent) {
- this._soundManager.fall();
- this._tetrisService.drop();
+ this.soundManager.fall();
+ this.tetrisService.drop();
return;
}
- this._soundManager.start();
- this._tetrisService.start();
+ this.soundManager.start();
+ this.tetrisService.start();
}
@HostListener(`${KeyUp}.${TetrisKeyboard.Space}`)
keyUpSpace() {
- this._keyboardService.setKeỵ({
+ this.keyboardService.setKeỵ({
drop: false
});
}
+ @HostListener(`${KeyDown}.${TetrisKeyboard.C}`)
+ keyDownHold() {
+ this.soundManager.move();
+ this.keyboardService.setKeỵ({
+ hold: true
+ });
+ this.tetrisService.holdPiece();
+ }
+
+ @HostListener(`${KeyUp}.${TetrisKeyboard.C}`)
+ keyUpHold() {
+ this.keyboardService.setKeỵ({
+ hold: false
+ });
+ }
+
@HostListener(`${KeyDown}.${TetrisKeyboard.S}`)
keyDownSound() {
- this._soundManager.move();
- this._tetrisService.setSound();
- this._keyboardService.setKeỵ({
+ this.soundManager.move();
+ this.tetrisService.toggleSound();
+ this.keyboardService.setKeỵ({
sound: true
});
}
@HostListener(`${KeyUp}.${TetrisKeyboard.S}`)
keyUpSound() {
- this._keyboardService.setKeỵ({
+ this.keyboardService.setKeỵ({
sound: false
});
}
@HostListener(`${KeyDown}.${TetrisKeyboard.P}`)
keyDownPause() {
- this._soundManager.move();
- this._keyboardService.setKeỵ({
+ this.soundManager.move();
+ this.keyboardService.setKeỵ({
pause: true
});
- if (this._tetrisQuery.canStartGame) {
- this._tetrisService.resume();
+ if (this.tetrisState.canStartGame()) {
+ this.tetrisService.resume();
} else {
- this._tetrisService.pause();
+ this.tetrisService.pause();
}
}
@HostListener(`${KeyUp}.${TetrisKeyboard.P}`)
keyUpPause() {
- this._keyboardService.setKeỵ({
+ this.keyboardService.setKeỵ({
pause: false
});
}
@HostListener(`${KeyDown}.${TetrisKeyboard.R}`)
keyDownReset() {
- this._soundManager.move();
- this._keyboardService.setKeỵ({
+ this.soundManager.move();
+ this.keyboardService.setKeỵ({
reset: true
});
- this._tetrisService.pause();
+ this.tetrisService.pause();
setTimeout(() => {
if (confirm('You are having a good game. Are you sure you want to reset?')) {
- this._tetrisService.reset();
+ this.tetrisService.reset();
} else {
- this._tetrisService.resume();
+ this.tetrisService.resume();
}
this.keyUpReset();
});
@@ -237,8 +264,32 @@ export class AngularTetrisComponent implements OnInit {
@HostListener(`${KeyUp}.${TetrisKeyboard.R}`)
keyUpReset() {
- this._keyboardService.setKeỵ({
+ this.keyboardService.setKeỵ({
reset: false
});
}
+
+ get hasCurrent() {
+ return this.tetrisState.hasCurrent();
+ }
+
+ ngOnInit(): void {
+ setTimeout(() => {
+ this.resize();
+ });
+ }
+
+ keyboardMouseDown(key: string) {
+ this[`keyDown${key}`]();
+ }
+
+ keyboardMouseUp(key: string) {
+ this[`keyUp${key}`]();
+ }
+
+ private setPaddingMargin(paddingTop: number, paddingBottom: number, marginTop: number) {
+ this.render.setStyle(this.el.nativeElement, 'padding-top', `${paddingTop}px`);
+ this.render.setStyle(this.el.nativeElement, 'padding-bottom', `${paddingBottom}px`);
+ this.render.setStyle(this.el.nativeElement, 'margin-top', `${marginTop}px`);
+ }
}
diff --git a/src/app/factory/piece-factory.ts b/src/app/factory/piece-factory.ts
index 4ef2d12..3b87b2c 100644
--- a/src/app/factory/piece-factory.ts
+++ b/src/app/factory/piece-factory.ts
@@ -1,5 +1,5 @@
import { Piece } from '../interface/piece/piece';
-import { PieceDot } from '../interface/piece/Dot';
+import { NonePiece } from '../interface/piece/none';
import { PieceI } from '../interface/piece/I';
import { PieceJ } from '../interface/piece/J';
import { PieceL } from '../interface/piece/L';
@@ -16,21 +16,40 @@ export const SPAWN_POSITION_Y = -4;
providedIn: 'root'
})
export class PieceFactory {
- private _available: typeof Piece[] = [];
+ private available: (typeof Piece)[] = [];
+ private currentBag: (typeof Piece)[] = [];
constructor() {
- //this._available.push(PieceDot);
- this._available.push(PieceI);
- this._available.push(PieceJ);
- this._available.push(PieceL);
- this._available.push(PieceO);
- this._available.push(PieceS);
- this._available.push(PieceT);
- this._available.push(PieceZ);
+ this.available.push(PieceI);
+ this.available.push(PieceJ);
+ this.available.push(PieceL);
+ this.available.push(PieceO);
+ this.available.push(PieceS);
+ this.available.push(PieceT);
+ this.available.push(PieceZ);
}
getRandomPiece(x = SPAWN_POSITION_X, y = SPAWN_POSITION_Y): Piece {
- const idx = Math.floor(Math.random() * this._available.length);
- return new this._available[idx](x, y);
+ if (this.currentBag.length === 0) {
+ this.generateNewBag();
+ }
+ const nextPiece = this.currentBag.pop();
+ return new nextPiece(x, y);
+ }
+
+ getNonePiece(x = SPAWN_POSITION_X, y = SPAWN_POSITION_Y): Piece {
+ return new NonePiece(x, y);
+ }
+
+ generateNewBag() {
+ this.currentBag = this.available.slice();
+ this.shuffleArray(this.currentBag);
+ }
+
+ shuffleArray(array: (typeof Piece)[]) {
+ for (let i = array.length - 1; i > 0; i--) {
+ const j = Math.floor(Math.random() * (i + 1));
+ [array[i], array[j]] = [array[j], array[i]];
+ }
}
}
diff --git a/src/app/interface/callback.ts b/src/app/interface/callback.ts
index 1b176d1..1122a40 100644
--- a/src/app/interface/callback.ts
+++ b/src/app/interface/callback.ts
@@ -1,3 +1 @@
-export interface CallBack {
- (param: T1): void;
-}
+export type CallBack = (param: T1) => void;
diff --git a/src/app/interface/game-state.ts b/src/app/interface/game-state.ts
index e890636..70dd014 100644
--- a/src/app/interface/game-state.ts
+++ b/src/app/interface/game-state.ts
@@ -1,6 +1,6 @@
-export enum GameState {
+export enum GameState { // eslint-disable-line no-shadow
Loading,
Paused,
Started,
- Over,
+ Over
}
diff --git a/src/app/interface/keyboard.ts b/src/app/interface/keyboard.ts
index 066df10..7ba11a2 100644
--- a/src/app/interface/keyboard.ts
+++ b/src/app/interface/keyboard.ts
@@ -1,3 +1,4 @@
+/* eslint-disable no-shadow */
export enum TetrisKeyboard {
Up = 'arrowup',
Down = 'arrowdown',
@@ -6,5 +7,7 @@ export enum TetrisKeyboard {
Space = 'space',
P = 'p',
R = 'r',
- S = 's'
+ S = 's',
+ C = 'c'
}
+/* eslint-enable no-shadow */
diff --git a/src/app/interface/piece/Dot.ts b/src/app/interface/piece/Dot.ts
index c4afa0e..b9cab54 100644
--- a/src/app/interface/piece/Dot.ts
+++ b/src/app/interface/piece/Dot.ts
@@ -2,8 +2,8 @@ import { Piece } from './piece';
import { Shapes } from './shape';
import { PieceRotation, PieceTypes } from './piece-enum';
-const ShapesDot: Shapes = [];
-ShapesDot[PieceRotation.Deg0] = [
+const SHAPES_DOT: Shapes = [];
+SHAPES_DOT[PieceRotation.Deg0] = [
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
@@ -18,6 +18,6 @@ export class PieceDot extends Piece {
[0, 0, 0, 0],
[1, 0, 0, 0]
];
- this.setShapes(ShapesDot);
+ this.setShapes(SHAPES_DOT);
}
}
diff --git a/src/app/interface/piece/I.ts b/src/app/interface/piece/I.ts
index f7bdfb7..64dcf22 100644
--- a/src/app/interface/piece/I.ts
+++ b/src/app/interface/piece/I.ts
@@ -2,15 +2,15 @@ import { Piece } from './piece';
import { PieceRotation, PieceTypes } from './piece-enum';
import { Shapes } from './shape';
-const ShapesI: Shapes = [];
-ShapesI[PieceRotation.Deg0] = [
+const SHAPES_I: Shapes = [];
+SHAPES_I[PieceRotation.Deg0] = [
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
[1, 1, 1, 1]
];
-ShapesI[PieceRotation.Deg90] = [
+SHAPES_I[PieceRotation.Deg90] = [
[1, 0, 0, 0],
[1, 0, 0, 0],
[1, 0, 0, 0],
@@ -25,6 +25,6 @@ export class PieceI extends Piece {
[0, 0, 0, 0],
[1, 1, 1, 1]
];
- this.setShapes(ShapesI);
+ this.setShapes(SHAPES_I);
}
}
diff --git a/src/app/interface/piece/J.ts b/src/app/interface/piece/J.ts
index 1d0e87a..9964f82 100644
--- a/src/app/interface/piece/J.ts
+++ b/src/app/interface/piece/J.ts
@@ -2,27 +2,27 @@ import { Piece } from './piece';
import { Shapes } from './shape';
import { PieceRotation, PieceTypes } from './piece-enum';
-const ShapesJ: Shapes = [];
-ShapesJ[PieceRotation.Deg0] = [
+const SHAPES_J: Shapes = [];
+SHAPES_J[PieceRotation.Deg0] = [
[0, 0, 0, 0],
[0, 1, 0, 0],
[0, 1, 0, 0],
[1, 1, 0, 0]
];
-ShapesJ[PieceRotation.Deg90] = [
+SHAPES_J[PieceRotation.Deg90] = [
[0, 0, 0, 0],
[0, 0, 0, 0],
[1, 1, 1, 0],
[0, 0, 1, 0]
];
-ShapesJ[PieceRotation.Deg180] = [
+SHAPES_J[PieceRotation.Deg180] = [
[0, 0, 0, 0],
[1, 1, 0, 0],
[1, 0, 0, 0],
[1, 0, 0, 0]
];
-ShapesJ[PieceRotation.Deg270] = [
+SHAPES_J[PieceRotation.Deg270] = [
[0, 0, 0, 0],
[0, 0, 0, 0],
[1, 0, 0, 0],
@@ -37,6 +37,6 @@ export class PieceJ extends Piece {
[1, 0, 0, 0],
[1, 1, 1, 0]
];
- this.setShapes(ShapesJ);
+ this.setShapes(SHAPES_J);
}
}
diff --git a/src/app/interface/piece/L.ts b/src/app/interface/piece/L.ts
index 6c12a17..c36df01 100644
--- a/src/app/interface/piece/L.ts
+++ b/src/app/interface/piece/L.ts
@@ -2,27 +2,27 @@ import { Piece } from './piece';
import { Shapes } from './shape';
import { PieceRotation, PieceTypes } from './piece-enum';
-const ShapesL: Shapes = [];
-ShapesL[PieceRotation.Deg0] = [
+const SHAPES_L: Shapes = [];
+SHAPES_L[PieceRotation.Deg0] = [
[0, 0, 0, 0],
[1, 0, 0, 0],
[1, 0, 0, 0],
[1, 1, 0, 0]
];
-ShapesL[PieceRotation.Deg90] = [
+SHAPES_L[PieceRotation.Deg90] = [
[0, 0, 0, 0],
[0, 0, 0, 0],
[1, 1, 1, 0],
[1, 0, 0, 0]
];
-ShapesL[PieceRotation.Deg180] = [
+SHAPES_L[PieceRotation.Deg180] = [
[0, 0, 0, 0],
[1, 1, 0, 0],
[0, 1, 0, 0],
[0, 1, 0, 0]
];
-ShapesL[PieceRotation.Deg270] = [
+SHAPES_L[PieceRotation.Deg270] = [
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 1, 0],
@@ -37,6 +37,6 @@ export class PieceL extends Piece {
[0, 0, 1, 0],
[1, 1, 1, 0]
];
- this.setShapes(ShapesL);
+ this.setShapes(SHAPES_L);
}
}
diff --git a/src/app/interface/piece/O.ts b/src/app/interface/piece/O.ts
index 5524b1c..2882e6c 100644
--- a/src/app/interface/piece/O.ts
+++ b/src/app/interface/piece/O.ts
@@ -2,8 +2,8 @@ import { Piece } from './piece';
import { PieceRotation, PieceTypes } from './piece-enum';
import { Shapes } from './shape';
-const ShapesO: Shapes = [];
-ShapesO[PieceRotation.Deg0] = [
+const SHAPES_O: Shapes = [];
+SHAPES_O[PieceRotation.Deg0] = [
[0, 0, 0, 0],
[0, 0, 0, 0],
[1, 1, 0, 0],
@@ -18,6 +18,6 @@ export class PieceO extends Piece {
[0, 1, 1, 0],
[0, 1, 1, 0]
];
- this.setShapes(ShapesO);
+ this.setShapes(SHAPES_O);
}
}
diff --git a/src/app/interface/piece/S.ts b/src/app/interface/piece/S.ts
index 287443d..d78940f 100644
--- a/src/app/interface/piece/S.ts
+++ b/src/app/interface/piece/S.ts
@@ -2,15 +2,15 @@ import { Piece } from './piece';
import { Shapes } from './shape';
import { PieceRotation, PieceTypes } from './piece-enum';
-const ShapesS: Shapes = [];
-ShapesS[PieceRotation.Deg0] = [
+const SHAPES_S: Shapes = [];
+SHAPES_S[PieceRotation.Deg0] = [
[0, 0, 0, 0],
[1, 0, 0, 0],
[1, 1, 0, 0],
[0, 1, 0, 0]
];
-ShapesS[PieceRotation.Deg90] = [
+SHAPES_S[PieceRotation.Deg90] = [
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 1, 1, 0],
@@ -25,6 +25,6 @@ export class PieceS extends Piece {
[0, 1, 1, 0],
[1, 1, 0, 0]
];
- this.setShapes(ShapesS);
+ this.setShapes(SHAPES_S);
}
}
diff --git a/src/app/interface/piece/T.ts b/src/app/interface/piece/T.ts
index d9a7256..e3843cc 100644
--- a/src/app/interface/piece/T.ts
+++ b/src/app/interface/piece/T.ts
@@ -2,29 +2,29 @@ import { Piece } from './piece';
import { Shapes } from './shape';
import { PieceRotation, PieceTypes } from './piece-enum';
-const ShapesT: Shapes = [];
-ShapesT[PieceRotation.Deg0] = [
+const SHAPES_T: Shapes = [];
+SHAPES_T[PieceRotation.Deg0] = [
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 1, 0, 0],
[1, 1, 1, 0]
];
-ShapesT[PieceRotation.Deg90] = [
+SHAPES_T[PieceRotation.Deg90] = [
[0, 0, 0, 0],
[1, 0, 0, 0],
[1, 1, 0, 0],
[1, 0, 0, 0]
];
-ShapesT[PieceRotation.Deg180] = [
+SHAPES_T[PieceRotation.Deg180] = [
[0, 0, 0, 0],
[0, 0, 0, 0],
[1, 1, 1, 0],
[0, 1, 0, 0]
];
-ShapesT[PieceRotation.Deg270] = [
+SHAPES_T[PieceRotation.Deg270] = [
[0, 0, 0, 0],
[0, 1, 0, 0],
[1, 1, 0, 0],
@@ -39,6 +39,6 @@ export class PieceT extends Piece {
[0, 1, 0, 0],
[1, 1, 1, 0]
];
- this.setShapes(ShapesT);
+ this.setShapes(SHAPES_T);
}
}
diff --git a/src/app/interface/piece/Z.ts b/src/app/interface/piece/Z.ts
index 83299da..5f498bc 100644
--- a/src/app/interface/piece/Z.ts
+++ b/src/app/interface/piece/Z.ts
@@ -2,15 +2,15 @@ import { Piece } from './piece';
import { Shapes } from './shape';
import { PieceRotation, PieceTypes } from './piece-enum';
-const ShapesZ: Shapes = [];
-ShapesZ[PieceRotation.Deg0] = [
+const SHAPES_Z: Shapes = [];
+SHAPES_Z[PieceRotation.Deg0] = [
[0, 0, 0, 0],
[0, 1, 0, 0],
[1, 1, 0, 0],
[1, 0, 0, 0]
];
-ShapesZ[PieceRotation.Deg90] = [
+SHAPES_Z[PieceRotation.Deg90] = [
[0, 0, 0, 0],
[0, 0, 0, 0],
[1, 1, 0, 0],
@@ -25,6 +25,6 @@ export class PieceZ extends Piece {
[1, 1, 0, 0],
[0, 1, 1, 0]
];
- this.setShapes(ShapesZ);
+ this.setShapes(SHAPES_Z);
}
}
diff --git a/src/app/interface/piece/none.ts b/src/app/interface/piece/none.ts
new file mode 100644
index 0000000..2dbcc57
--- /dev/null
+++ b/src/app/interface/piece/none.ts
@@ -0,0 +1,21 @@
+import { Piece } from './piece';
+import { PieceRotation, PieceTypes } from './piece-enum';
+import { Shapes } from './shape';
+
+const NONE_SHAPE: Shapes = [];
+NONE_SHAPE[PieceRotation.Deg0] = [
+ [0, 0, 0, 0],
+ [0, 0, 0, 0]
+];
+
+export class NonePiece extends Piece {
+ constructor(x: number, y: number) {
+ super(x, y);
+ this.type = PieceTypes.None;
+ this.next = [
+ [0, 0, 0, 0],
+ [0, 0, 0, 0]
+ ];
+ this.setShapes(NONE_SHAPE);
+ }
+}
diff --git a/src/app/interface/piece/piece-enum.ts b/src/app/interface/piece/piece-enum.ts
index 03555ea..2140635 100644
--- a/src/app/interface/piece/piece-enum.ts
+++ b/src/app/interface/piece/piece-enum.ts
@@ -1,3 +1,4 @@
+/* eslint-disable no-shadow */
export enum PieceRotation {
Deg0,
Deg90,
@@ -13,5 +14,7 @@ export enum PieceTypes {
L = 'L',
J = 'J',
Z = 'Z',
- S = 'S'
+ S = 'S',
+ None = 'None'
}
+/* eslint-enable no-shadow */
diff --git a/src/app/interface/piece/piece.ts b/src/app/interface/piece/piece.ts
index 7ae2626..9027f87 100644
--- a/src/app/interface/piece/piece.ts
+++ b/src/app/interface/piece/piece.ts
@@ -10,72 +10,77 @@ export class Piece {
shape: Shape;
next: Shape;
- private _shapes: Shapes;
- private _lastConfig: Partial;
+ private shapes: Shapes;
+ private lastConfig: Partial;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
- protected setShapes(shapes: Shapes) {
- this._shapes = shapes;
- this.shape = shapes[this.rotation];
- }
-
store(): Piece {
- this._lastConfig = {
+ this.lastConfig = {
x: this.x,
y: this.y,
rotation: this.rotation,
shape: this.shape
};
- return this._newPiece();
+ return this.newPiece();
}
clearStore(): Piece {
- this._lastConfig = null;
- return this._newPiece();
+ this.lastConfig = null;
+ return this.newPiece();
}
revert(): Piece {
- if (this._lastConfig) {
- for (const key in this._lastConfig) {
- if (this._lastConfig.hasOwnProperty(key)) {
- this[key] = this._lastConfig[key];
+ if (this.lastConfig) {
+ for (const key in this.lastConfig) {
+ if (this.lastConfig.hasOwnProperty(key)) {
+ this[key] = this.lastConfig[key];
}
}
- this._lastConfig = null;
+ this.lastConfig = null;
}
- return this._newPiece();
+ return this.newPiece();
}
rotate(): Piece {
- const keys = Object.keys(this._shapes);
- let idx = keys.indexOf(this.rotation.toString());
- let isTurnOver = idx >= keys.length - 1;
+ const keys = Object.keys(this.shapes);
+ const idx = keys.indexOf(this.rotation.toString());
+ const isTurnOver = idx >= keys.length - 1;
this.rotation = Number(isTurnOver ? keys[0] : keys[idx + 1]);
- this.shape = this._shapes[this.rotation];
- return this._newPiece();
+ this.shape = this.shapes[this.rotation];
+ return this.newPiece();
+ }
+
+ reset(): Piece {
+ this.rotation = PieceRotation.Deg0;
+ this.shape = this.shapes[this.rotation];
+ return this.newPiece();
}
moveDown(step = 1): Piece {
this.y = this.y + step;
- return this._newPiece();
+ return this.newPiece();
}
moveRight(): Piece {
this.x++;
- return this._newPiece();
+ return this.newPiece();
}
moveLeft(): Piece {
this.x--;
- return this._newPiece();
+ return this.newPiece();
+ }
+
+ isNone(): boolean {
+ return this.type === PieceTypes.None;
}
get positionOnGrid(): number[] {
- let positions = [];
+ const positions = [];
for (let row = 0; row < 4; row++) {
for (let col = 0; col < 4; col++) {
if (this.shape[row][col]) {
@@ -109,13 +114,18 @@ export class Piece {
return this.x;
}
- private _newPiece(): Piece {
- let piece = new Piece(this.x, this.y);
+ protected setShapes(shapes: Shapes) {
+ this.shapes = shapes;
+ this.shape = shapes[this.rotation];
+ }
+
+ private newPiece(): Piece {
+ const piece = new Piece(this.x, this.y);
piece.rotation = this.rotation;
piece.type = this.type;
piece.next = this.next;
- piece.setShapes(this._shapes);
- piece._lastConfig = this._lastConfig;
+ piece.setShapes(this.shapes);
+ piece.lastConfig = this.lastConfig;
return piece;
}
}
diff --git a/src/app/interface/piece/shape.ts b/src/app/interface/piece/shape.ts
index d503e88..3c02857 100644
--- a/src/app/interface/piece/shape.ts
+++ b/src/app/interface/piece/shape.ts
@@ -1,4 +1,3 @@
-
export type Shape = number[][];
export type Shapes = Shape[];
diff --git a/src/app/interface/tile/tile.ts b/src/app/interface/tile/tile.ts
index 0928a27..9b3dcdd 100644
--- a/src/app/interface/tile/tile.ts
+++ b/src/app/interface/tile/tile.ts
@@ -1,16 +1,17 @@
export type TileValue = 0 | 1 | 2;
export class Tile {
- private _value: TileValue;
- isSolid: boolean;
+ public isSolid: boolean;
+ private value: TileValue;
constructor(val: TileValue) {
- this._value = val;
+ this.value = val;
}
+
get isFilled(): boolean {
- return this._value === 1;
+ return this.value === 1;
}
get isAnimated(): boolean {
- return this._value === 2;
+ return this.value === 2;
}
}
diff --git a/src/app/interface/ui-model/arrow-button.ts b/src/app/interface/ui-model/arrow-button.ts
index 24c8561..ce76821 100644
--- a/src/app/interface/ui-model/arrow-button.ts
+++ b/src/app/interface/ui-model/arrow-button.ts
@@ -1,13 +1,13 @@
-export enum ArrowButton {
+export enum ArrowButton { // eslint-disable-line no-shadow
UP = 'UP',
DOWN = 'DOWN',
LEFT = 'LEFT',
- RIGHT = 'RIGHT',
+ RIGHT = 'RIGHT'
}
export const ArrowButtonTransform = {
[ArrowButton.UP]: 'translate(0px, 63px) scale(1, 2)',
[ArrowButton.DOWN]: 'translate(0,-71px) rotate(180deg) scale(1, 2)',
[ArrowButton.LEFT]: 'translate(60px, -12px) rotate(270deg) scale(1, 2)',
- [ArrowButton.RIGHT]: 'translate(-60px, -12px) rotate(90deg) scale(1, 2)',
+ [ArrowButton.RIGHT]: 'translate(-60px, -12px) rotate(90deg) scale(1, 2)'
};
diff --git a/src/app/interface/utils/matrix.ts b/src/app/interface/utils/matrix.ts
index c23a9b4..1b0dd79 100644
--- a/src/app/interface/utils/matrix.ts
+++ b/src/app/interface/utils/matrix.ts
@@ -2,15 +2,19 @@ import { EmptyTile } from '../tile/empty-tile';
import { Tile } from '../tile/tile';
import { FilledTile } from '../tile/filled-tile';
+/* eslint-disable @typescript-eslint/naming-convention */
export class MatrixUtil {
static readonly Width = 10;
static readonly Height = 20;
+ static Points = [100, 300, 700, 1500];
+ static MaxPoint = 999999;
+ static SpeedDelay = [700, 600, 450, 320, 240, 160];
static getStartBoard(startLines: number = 0): Tile[] {
if (startLines === 0) {
return new Array(this.Width * this.Height).fill(new EmptyTile());
}
- let startMatrix: Tile[] = [];
+ const startMatrix: Tile[] = [];
for (let i = 0; i < startLines; i++) {
if (i <= 2) {
@@ -51,11 +55,8 @@ export class MatrixUtil {
return new Array(this.Width).fill(new FilledTile());
}
- static Points = [100, 300, 700, 1500];
- static MaxPoint = 999999;
- static SpeedDelay = [700, 600, 450, 320, 240, 160];
-
static getSpeedDelay(speed: number) {
return this.SpeedDelay[speed - 1] ?? this.SpeedDelay[0];
}
}
+/* eslint-enable @typescript-eslint/naming-convention */
diff --git a/src/app/services/google-analytics.service.ts b/src/app/services/google-analytics.service.ts
index 25d1f88..f5d7105 100644
--- a/src/app/services/google-analytics.service.ts
+++ b/src/app/services/google-analytics.service.ts
@@ -1,12 +1,11 @@
import { Injectable } from '@angular/core';
-declare var gtag: any;
+declare let gtag: any;
const GOOGLE_ANALYTICS_ID = 'UA-80363801-4';
@Injectable({
providedIn: 'root'
})
export class GoogleAnalyticsService {
- constructor() {
- }
+ constructor() {}
public sendEvent(
eventName: string,
@@ -17,17 +16,19 @@ export class GoogleAnalyticsService {
if (!gtag) {
return;
}
+ /* eslint-disable @typescript-eslint/naming-convention */
gtag('event', eventName, {
- 'event_category': eventCategory,
- 'event_label': eventLabel,
- 'value': eventValue
+ event_category: eventCategory,
+ event_label: eventLabel,
+ value: eventValue
});
+ /* eslint-enable @typescript-eslint/naming-convention */
}
public sendPageView(url: string) {
if (!gtag) {
return;
}
- gtag('config', GOOGLE_ANALYTICS_ID, { page_path: url });
+ gtag('config', GOOGLE_ANALYTICS_ID, { page_path: url }); // eslint-disable-line @typescript-eslint/naming-convention
}
}
diff --git a/src/app/services/local-storage.service.ts b/src/app/services/local-storage.service.ts
index 61bcf1c..7a67b3a 100644
--- a/src/app/services/local-storage.service.ts
+++ b/src/app/services/local-storage.service.ts
@@ -7,7 +7,7 @@ export class LocalStorageService {
}
static get maxPoint(): number {
- let max = parseInt(localStorage.getItem(ANGULAR_TETRIS_STORAGE_KEY));
+ const max = parseInt(localStorage.getItem(ANGULAR_TETRIS_STORAGE_KEY));
return Number.isInteger(max) ? max : 0;
}
}
diff --git a/src/app/services/sound-manager.service.ts b/src/app/services/sound-manager.service.ts
index 0fc7be2..86bcfc0 100644
--- a/src/app/services/sound-manager.service.ts
+++ b/src/app/services/sound-manager.service.ts
@@ -1,80 +1,71 @@
-import { Injectable } from '@angular/core';
-import { TetrisQuery } from '@trungk18/state/tetris/tetris.query';
+import { TetrisStateService } from '@angular-tetris/state/tetris/tetris.state';
+import { Injectable, inject } from '@angular/core';
-const SoundFilePath = '/assets/tetris-sound.mp3';
+const SOUND_FILE_PATH = '/assets/tetris-sound.mp3';
@Injectable({
providedIn: 'root'
})
export class SoundManagerService {
- private _context: AudioContext;
- private _buffer: AudioBuffer;
-
- constructor(private _query: TetrisQuery) {
-
- }
-
- private get _hasWebAudioAPI(): boolean {
- return !!AudioContext && location.protocol.indexOf('http') !== -1;
- }
+ private context: AudioContext;
+ private buffer: AudioBuffer;
+ private tetrisState = inject(TetrisStateService);
start() {
- this._playMusic(0, 3.7202, 3.6224);
+ this.playMusic(0, 3.7202, 3.6224);
}
clear() {
- this._playMusic(0, 0, 0.7675);
+ this.playMusic(0, 0, 0.7675);
}
fall() {
- this._playMusic(0, 1.2558, 0.3546);
+ this.playMusic(0, 1.2558, 0.3546);
}
gameOver() {
- this._playMusic(0, 8.1276, 1.1437);
+ this.playMusic(0, 8.1276, 1.1437);
}
rotate() {
- this._playMusic(0, 2.2471, 0.0807);
+ this.playMusic(0, 2.2471, 0.0807);
}
move() {
- this._playMusic(0, 2.9088, 0.1437);
+ this.playMusic(0, 2.9088, 0.1437);
}
- private _playMusic(when: number, offset: number, duration: number) {
- if (!this._query.isEnableSound) {
+ private playMusic(when: number, offset: number, duration: number) {
+ if (!this.tetrisState.isEnableSound()) {
return;
}
- this._loadSound().then((source) => {
- source && source.start(when, offset, duration);
+ this.loadSound().then((source) => {
+ if (source) {
+ source.start(when, offset, duration);
+ }
});
}
- private _loadSound(): Promise {
+ private loadSound(): Promise {
return new Promise((resolve, reject) => {
- if (!this._hasWebAudioAPI) {
- resolve(null);
- return;
- }
- if (this._context && this._buffer) {
- resolve(this._getSource(this._context, this._buffer));
+ if (this.context && this.buffer) {
+ resolve(this.getSource(this.context, this.buffer));
return;
}
const context = new AudioContext();
const req = new XMLHttpRequest();
- req.open('GET', SoundFilePath, true);
+ req.open('GET', SOUND_FILE_PATH, true);
req.responseType = 'arraybuffer';
req.onload = () => {
context.decodeAudioData(
req.response,
(buffer) => {
- this._context = context;
- this._buffer = buffer;
- resolve(this._getSource(context, buffer));
+ this.context = context;
+ this.buffer = buffer;
+ resolve(this.getSource(context, buffer));
},
() => {
- let msg = 'Sorry lah, cannot play sound. But I hope you still enjoy Angular Tetris!!';
+ const msg = 'Sorry lah, cannot play sound. But I hope you still enjoy Angular Tetris!!';
alert(msg);
reject(msg);
}
@@ -84,8 +75,8 @@ export class SoundManagerService {
});
}
- private _getSource(context: AudioContext, buffer: AudioBuffer): AudioBufferSourceNode {
- let source = context.createBufferSource();
+ private getSource(context: AudioContext, buffer: AudioBuffer): AudioBufferSourceNode {
+ const source = context.createBufferSource();
source.buffer = buffer;
source.connect(context.destination);
return source;
diff --git a/src/app/state/keyboard/keyboard.query.ts b/src/app/state/keyboard/keyboard.query.ts
deleted file mode 100644
index 5de211a..0000000
--- a/src/app/state/keyboard/keyboard.query.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { Injectable } from '@angular/core';
-import { Query } from '@datorama/akita';
-import { KeyboardStore, KeyboardState } from './keyboard.store';
-
-@Injectable({ providedIn: 'root' })
-export class KeyboardQuery extends Query {
- constructor(protected store: KeyboardStore) {
- super(store);
- }
-
- up$ = this.select('up');
- down$ = this.select('down');
- left$ = this.select('left');
- right$ = this.select('right');
- drop$ = this.select('drop');
- pause$ = this.select('pause');
- sound$ = this.select('sound');
- reset$ = this.select('reset');
-}
diff --git a/src/app/state/keyboard/keyboard.service.ts b/src/app/state/keyboard/keyboard.service.ts
index 7da3ce5..efe7d6e 100644
--- a/src/app/state/keyboard/keyboard.service.ts
+++ b/src/app/state/keyboard/keyboard.service.ts
@@ -1,13 +1,46 @@
-import { Injectable } from '@angular/core';
-import { KeyboardStore, KeyboardState } from './keyboard.store';
+import { Injectable, computed, signal } from '@angular/core';
+
+export interface KeyboardState {
+ up: boolean;
+ down: boolean;
+ left: boolean;
+ right: boolean;
+ pause: boolean;
+ sound: boolean;
+ reset: boolean;
+ drop: boolean;
+ hold: boolean;
+}
@Injectable({
providedIn: 'root'
})
export class KeyboardService {
- constructor(private _store: KeyboardStore) {}
+ private keyboardState = signal({
+ up: false,
+ down: false,
+ left: false,
+ right: false,
+ pause: false,
+ sound: false,
+ reset: false,
+ drop: false,
+ hold: false
+ });
+
+ // computed does memorized value so it won't emit if
+ // value doesn't change
+ up = computed(() => this.keyboardState().up);
+ down = computed(() => this.keyboardState().down);
+ left = computed(() => this.keyboardState().left);
+ right = computed(() => this.keyboardState().right);
+ drop = computed(() => this.keyboardState().drop);
+ pause = computed(() => this.keyboardState().pause);
+ sound = computed(() => this.keyboardState().sound);
+ reset = computed(() => this.keyboardState().reset);
+ hold = computed(() => this.keyboardState().hold);
setKeỵ(keyState: Partial) {
- this._store.update(keyState);
+ this.keyboardState.update((currentKeyState) => ({ ...currentKeyState, ...keyState }));
}
}
diff --git a/src/app/state/keyboard/keyboard.store.ts b/src/app/state/keyboard/keyboard.store.ts
deleted file mode 100644
index 45e652a..0000000
--- a/src/app/state/keyboard/keyboard.store.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import { Injectable } from '@angular/core';
-import { Store, StoreConfig } from '@datorama/akita';
-import { TetrisKeyboard } from '@trungk18/interface/keyboard';
-
-export interface KeyboardState {
- up: boolean;
- down: boolean;
- left: boolean;
- right: boolean;
- pause: boolean;
- sound: boolean;
- reset: boolean;
- drop: boolean;
-}
-
-export function createInitialState(): KeyboardState {
- return {
- up: false,
- down: false,
- left: false,
- right: false,
- pause: false,
- sound: false,
- reset: false,
- drop: false
- };
-}
-
-@Injectable({ providedIn: 'root' })
-@StoreConfig({ name: 'AngularTetrisKeyboard' })
-export class KeyboardStore extends Store {
- constructor() {
- super(createInitialState());
- }
-}
diff --git a/src/app/state/tetris/tetris.query.ts b/src/app/state/tetris/tetris.query.ts
deleted file mode 100644
index 7099bce..0000000
--- a/src/app/state/tetris/tetris.query.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-import { Injectable } from '@angular/core';
-import { Query } from '@datorama/akita';
-import { TetrisStore, TetrisState } from './tetris.store';
-import { GameState } from '@trungk18/interface/game-state';
-import { map, delay, switchMap } from 'rxjs/operators';
-import { combineLatest, of } from 'rxjs';
-
-@Injectable({ providedIn: 'root' })
-export class TetrisQuery extends Query {
- constructor(protected store: TetrisStore) {
- super(store);
- }
-
- get raw(): TetrisState {
- return this.getValue();
- }
-
- get locked(): boolean {
- return this.raw.locked;
- }
-
- get current() {
- return this.raw.current;
- }
-
- get next() {
- return this.raw.next;
- }
-
- get matrix() {
- return this.raw.matrix;
- }
-
- get canStartGame() {
- return this.raw.gameState !== GameState.Started;
- }
-
- get isPlaying() {
- return this.raw.gameState === GameState.Started;
- }
-
- get isEnableSound(): boolean {
- return !!this.raw.sound;
- }
-
- next$ = this.select('next');
- matrix$ = this.select('matrix');
- sound$ = this.select('sound');
- gameState$ = this.select('gameState');
- hasCurrent$ = this.select('current').pipe(map((x) => !!x));
- points$ = this.select('points');
- clearedLines$ = this.select('clearedLines');
- initLine$ = this.select('initLine');
- speed$ = this.select('speed');
- initSpeed$ = this.select('initSpeed');
- max$ = this.select('max');
-
- isShowLogo$ = combineLatest(this.gameState$, this.select('current')).pipe(
- switchMap(([state, current]) => {
- let isLoadingOrOver = state === GameState.Loading || state === GameState.Over;
- let isRenderingLogo$ = of(isLoadingOrOver && !current);
- return isLoadingOrOver ? isRenderingLogo$.pipe(delay(1800)) : isRenderingLogo$;
- })
- );
-}
diff --git a/src/app/state/tetris/tetris.service.ts b/src/app/state/tetris/tetris.service.ts
index e2e89fb..34e9393 100644
--- a/src/app/state/tetris/tetris.service.ts
+++ b/src/app/state/tetris/tetris.service.ts
@@ -1,380 +1,406 @@
-import { Injectable } from '@angular/core';
-import { PieceFactory } from '@trungk18/factory/piece-factory';
-import { CallBack } from '@trungk18/interface/callback';
-import { GameState } from '@trungk18/interface/game-state';
-import { Piece } from '@trungk18/interface/piece/piece';
-import { EmptyTile } from '@trungk18/interface/tile/empty-tile';
-import { FilledTile } from '@trungk18/interface/tile/filled-tile';
-import { Tile } from '@trungk18/interface/tile/tile';
-import { MatrixUtil } from '@trungk18/interface/utils/matrix';
-import { Subscription, timer } from 'rxjs';
-import { TetrisQuery } from './tetris.query';
-import { createInitialState, TetrisStore } from './tetris.store';
-import { Speed } from '@trungk18/interface/speed';
-import { SoundManagerService } from '@trungk18/services/sound-manager.service';
-import { LocalStorageService } from '@trungk18/services/local-storage.service';
+import {
+ PieceFactory,
+ SPAWN_POSITION_X,
+ SPAWN_POSITION_Y
+} from '@angular-tetris/factory/piece-factory';
+import { CallBack } from '@angular-tetris/interface/callback';
+import { GameState } from '@angular-tetris/interface/game-state';
+import { Piece } from '@angular-tetris/interface/piece/piece';
+import { Speed } from '@angular-tetris/interface/speed';
+import { EmptyTile } from '@angular-tetris/interface/tile/empty-tile';
+import { FilledTile } from '@angular-tetris/interface/tile/filled-tile';
+import { Tile } from '@angular-tetris/interface/tile/tile';
+import { MatrixUtil } from '@angular-tetris/interface/utils/matrix';
+import { LocalStorageService } from '@angular-tetris/services/local-storage.service';
+import { SoundManagerService } from '@angular-tetris/services/sound-manager.service';
+import { Injectable, inject } from '@angular/core';
+import { TetrisStateService } from './tetris.state';
@Injectable({ providedIn: 'root' })
export class TetrisService {
- _gameInterval: Subscription;
+ gameInterval: number | null;
- constructor(
- private _store: TetrisStore,
- private _query: TetrisQuery,
- private _soundManager: SoundManagerService,
- private _pieceFactory: PieceFactory
- ) {}
-
- private get _locked(): boolean {
- return this._query.locked;
- }
-
- private get _current() {
- return this._query.current;
- }
-
- private get _next() {
- return this._query.next;
- }
-
- private get _matrix() {
- return this._query.matrix;
- }
+ private soundManager = inject(SoundManagerService);
+ private pieceFactory = inject(PieceFactory);
+ private tetrisState = inject(TetrisStateService);
start() {
- if (!this._current) {
- this._setCurrentPiece(this._next);
- this._setNext();
+ if (!this.tetrisState.hasCurrent()) {
+ this.setCurrentPiece(this.tetrisState.next());
+ this.setNext();
}
- let { initLine, initSpeed } = this._query.raw;
- this._store.update({
+
+ this.tetrisState.updateState({
points: 0,
gameState: GameState.Started,
- matrix: MatrixUtil.getStartBoard(initLine),
- speed: initSpeed
+ matrix: MatrixUtil.getStartBoard(this.tetrisState.initLine()),
+ speed: this.tetrisState.initSpeed()
});
- this._unsubscribe();
- this.auto(MatrixUtil.getSpeedDelay(initSpeed));
- this._setLocked(false);
+ this.stopGameInterval();
+ this.auto(MatrixUtil.getSpeedDelay(this.tetrisState.initSpeed()));
+ this.setLocked(false);
}
auto(delay: number) {
- this._gameInterval = timer(0, delay).subscribe(() => {
- this._update();
- });
+ this.update();
+
+ this.gameInterval = setInterval(() => {
+ this.update();
+ }, delay);
}
resume() {
- let { speed } = this._query.raw;
- this._store.update({
+ if (!this.tetrisState.isPause()) {
+ return;
+ }
+
+ this.tetrisState.updateState({
locked: false,
gameState: GameState.Started
});
- this.auto(MatrixUtil.getSpeedDelay(speed));
+ this.auto(MatrixUtil.getSpeedDelay(this.tetrisState.speed()));
}
pause() {
- this._store.update({
+ if (!this.tetrisState.isPlaying()) {
+ return;
+ }
+ this.tetrisState.updateState({
locked: true,
gameState: GameState.Paused
});
- this._unsubscribe();
+ this.stopGameInterval();
}
reset() {
- this._store.update(createInitialState(this._pieceFactory));
+ this.tetrisState.resetState({
+ sound: this.tetrisState.isEnableSound()
+ });
}
moveLeft() {
- if (this._locked) {
+ if (this.tetrisState.locked()) {
return;
}
- this._clearPiece();
- this._setCurrentPiece(this._current.store());
- this._setCurrentPiece(this._current.moveLeft());
- if (this._isCollidesLeft) {
- this._setCurrentPiece(this._current.revert());
+ this.clearPiece();
+ this.setCurrentPiece(this.tetrisState.current().store());
+ this.setCurrentPiece(this.tetrisState.current().moveLeft());
+ if (this.isCollidesLeft) {
+ this.setCurrentPiece(this.tetrisState.current().revert());
}
- this._drawPiece();
+ this.drawPiece();
}
moveRight() {
- if (this._locked) {
+ if (this.tetrisState.locked()) {
return;
}
- this._clearPiece();
- this._setCurrentPiece(this._current.store());
- this._setCurrentPiece(this._current.moveRight());
- if (this._isCollidesRight) {
- this._setCurrentPiece(this._current.revert());
+ this.clearPiece();
+ this.setCurrentPiece(this.tetrisState.current().store());
+ this.setCurrentPiece(this.tetrisState.current().moveRight());
+ if (this.isCollidesRight) {
+ this.setCurrentPiece(this.tetrisState.current().revert());
}
- this._drawPiece();
+ this.drawPiece();
}
rotate() {
- if (this._locked) {
+ if (this.tetrisState.locked()) {
return;
}
- this._clearPiece();
- this._setCurrentPiece(this._current.store());
- this._setCurrentPiece(this._current.rotate());
- while (this._isCollidesRight) {
- this._setCurrentPiece(this._current.moveLeft());
- if (this._isCollidesLeft) {
- this._setCurrentPiece(this._current.revert());
+ this.clearPiece();
+ this.setCurrentPiece(this.tetrisState.current().store());
+ this.setCurrentPiece(this.tetrisState.current().rotate());
+ while (this.isCollidesRight) {
+ this.setCurrentPiece(this.tetrisState.current().moveLeft());
+ if (this.isCollidesLeft) {
+ this.setCurrentPiece(this.tetrisState.current().revert());
break;
}
}
- this._drawPiece();
+ this.drawPiece();
}
moveDown() {
- this._update();
+ this.update();
}
drop() {
- if (this._locked) {
+ if (this.tetrisState.locked()) {
return;
}
- while (!this._isCollidesBottom) {
- this._clearPiece();
- this._setCurrentPiece(this._current.store());
- this._setCurrentPiece(this._current.moveDown());
+ while (!this.isCollidesBottom) {
+ this.clearPiece();
+ this.setCurrentPiece(this.tetrisState.current().store());
+ this.setCurrentPiece(this.tetrisState.current().moveDown());
}
- this._setCurrentPiece(this._current.revert());
- this._drawPiece();
+ this.setCurrentPiece(this.tetrisState.current().revert());
+ this.drawPiece();
+ this.setCanHold(true);
}
- setSound() {
- let sound = this._query.raw.sound;
- this._store.update({
- sound: !sound
+ holdPiece(): void {
+ if (this.tetrisState.locked() || !this.tetrisState.canHold()) {
+ return;
+ }
+ this.clearPiece();
+ const isHoldNonePiece = this.tetrisState.hold().isNone();
+ const newCurrent = isHoldNonePiece ? this.tetrisState.next() : this.tetrisState.hold();
+ if (isHoldNonePiece) {
+ this.setNext();
+ }
+ this.setHolded(this.tetrisState.current().reset());
+ this.setCurrentPiece(newCurrent);
+ this.resetPosition(this.tetrisState.hold());
+ this.setCanHold(false);
+ }
+
+ toggleSound() {
+ this.tetrisState.updateState({
+ sound: !this.tetrisState.isEnableSound()
});
}
decreaseLevel() {
- let { initSpeed } = this._query.raw;
- let newSpeed = (initSpeed - 1 < 1 ? 6 : initSpeed - 1) as Speed;
- this._store.update({
+ const initSpeed = this.tetrisState.initSpeed();
+ const newSpeed = (initSpeed - 1 < 1 ? 6 : initSpeed - 1) as Speed;
+ this.tetrisState.updateState({
initSpeed: newSpeed
});
}
increaseLevel() {
- let { initSpeed } = this._query.raw;
- let newSpeed = (initSpeed + 1 > 6 ? 1 : initSpeed + 1) as Speed;
- this._store.update({
+ const initSpeed = this.tetrisState.initSpeed();
+ const newSpeed = (initSpeed + 1 > 6 ? 1 : initSpeed + 1) as Speed;
+ this.tetrisState.updateState({
initSpeed: newSpeed
});
}
increaseStartLine() {
- let { initLine } = this._query.raw;
- let startLine = initLine + 1 > 10 ? 1 : initLine + 1;
- this._store.update({
+ const initLine = this.tetrisState.initLine();
+ const startLine = initLine + 1 > 10 ? 1 : initLine + 1;
+ this.tetrisState.updateState({
initLine: startLine
});
}
decreaseStartLine() {
- let { initLine } = this._query.raw;
- let startLine = initLine - 1 < 1 ? 10 : initLine - 1;
- this._store.update({
+ const initLine = this.tetrisState.initLine();
+ const startLine = initLine - 1 < 1 ? 10 : initLine - 1;
+ this.tetrisState.updateState({
initLine: startLine
});
}
- private _update() {
- if (this._locked) {
+ private update() {
+ if (this.tetrisState.locked()) {
return;
}
- this._setLocked(true);
- this._setCurrentPiece(this._current.revert());
- this._clearPiece();
- this._setCurrentPiece(this._current.store());
- this._setCurrentPiece(this._current.moveDown());
-
- if (this._isCollidesBottom) {
- this._setCurrentPiece(this._current.revert());
- this._markAsSolid();
- this._drawPiece();
- this._clearFullLines();
- this._setCurrentPiece(this._next);
- this._setNext();
- if (this._isGameOver) {
- this._onGameOver();
+ this.setLocked(true);
+ this.setCurrentPiece(this.tetrisState.current().revert());
+ this.clearPiece();
+ this.setCurrentPiece(this.tetrisState.current().store());
+ this.setCurrentPiece(this.tetrisState.current().moveDown());
+
+ if (this.isCollidesBottom) {
+ this.setCurrentPiece(this.tetrisState.current().revert());
+ this.markAsSolid();
+ this.drawPiece();
+ this.clearFullLines();
+ this.setCurrentPiece(this.tetrisState.next());
+ this.setNext();
+ this.setCanHold(true);
+ if (this.isGameOver) {
+ this.onGameOver();
return;
}
}
- this._drawPiece();
- this._setLocked(false);
+ this.drawPiece();
+ this.setLocked(false);
}
- private _clearFullLines() {
+ private clearFullLines() {
let numberOfClearedLines = 0;
- let newMatrix = [...this._matrix];
+ const newMatrix = [...this.tetrisState.matrix()];
for (let row = MatrixUtil.Height - 1; row >= 0; row--) {
- let pos = row * MatrixUtil.Width;
- let fullRowTiles = newMatrix.slice(pos, pos + MatrixUtil.Width);
- let isFullRow = fullRowTiles.every((x) => x.isSolid);
+ const pos = row * MatrixUtil.Width;
+ const fullRowTiles = newMatrix.slice(pos, pos + MatrixUtil.Width);
+ const isFullRow = fullRowTiles.every((x) => x.isSolid);
if (isFullRow) {
numberOfClearedLines++;
- let topPortion = this._matrix.slice(0, row * MatrixUtil.Width);
+ const topPortion = this.tetrisState.matrix().slice(0, row * MatrixUtil.Width);
newMatrix.splice(0, ++row * MatrixUtil.Width, ...MatrixUtil.EmptyRow.concat(topPortion));
- this._setMatrix(newMatrix);
+ this.setMatrix(newMatrix);
}
}
- this._setPointsAndSpeed(numberOfClearedLines);
+ this.setPointsAndSpeed(numberOfClearedLines);
}
- private get _isGameOver() {
- this._setCurrentPiece(this._current.store());
- this._setCurrentPiece(this._current.moveDown());
- if (this._isCollidesBottom) {
+ private get isGameOver() {
+ this.setCurrentPiece(this.tetrisState.current().store());
+ this.setCurrentPiece(this.tetrisState.current().moveDown());
+ if (this.isCollidesBottom) {
return true;
}
- this._setCurrentPiece(this._current.revert());
+ this.setCurrentPiece(this.tetrisState.current().revert());
return false;
}
- private _onGameOver() {
+ private onGameOver() {
this.pause();
- this._soundManager.gameOver();
- let { points, max } = this._query.raw;
- let maxPoint = Math.max(points, max);
- this._store.update({
- gameState: GameState.Over,
- current: null,
+ this.soundManager.gameOver();
+ const maxPoint = Math.max(this.tetrisState.points(), this.tetrisState.max());
+ LocalStorageService.setMaxPoint(maxPoint);
+ this.tetrisState.resetState({
max: maxPoint,
- points: 0
+ gameState: GameState.Over,
+ sound: this.tetrisState.isEnableSound()
});
- LocalStorageService.setMaxPoint(maxPoint);
}
- private get _isCollidesBottom(): boolean {
- if (this._current.bottomRow >= MatrixUtil.Height) {
+ private get isCollidesBottom(): boolean {
+ if (this.tetrisState.current().bottomRow >= MatrixUtil.Height) {
return true;
}
- return this._collides();
+ return this.collides();
}
- private get _isCollidesLeft(): boolean {
- if (this._current.leftCol < 0) {
+ private get isCollidesLeft(): boolean {
+ if (this.tetrisState.current().leftCol < 0) {
return true;
}
- return this._collides();
+ return this.collides();
}
- private get _isCollidesRight(): boolean {
- if (this._current.rightCol >= MatrixUtil.Width) {
+ private get isCollidesRight(): boolean {
+ if (this.tetrisState.current().rightCol >= MatrixUtil.Width) {
return true;
}
- return this._collides();
+ return this.collides();
}
- private _collides(): boolean {
- return this._current.positionOnGrid.some((pos) => {
- if (this._matrix[pos].isSolid) {
+ private collides(): boolean {
+ return this.tetrisState.current().positionOnGrid.some((pos) => {
+ if (this.tetrisState.matrix()[pos].isSolid) {
return true;
}
return false;
});
}
- private _drawPiece() {
- this._setCurrentPiece(this._current.clearStore());
- this._loopThroughPiecePosition((position) => {
- let isSolid = this._matrix[position].isSolid;
- this._updateMatrix(position, new FilledTile(isSolid));
+ private drawPiece() {
+ this.setCurrentPiece(this.tetrisState.current().clearStore());
+ this.loopThroughPiecePosition((position) => {
+ const isSolid = this.tetrisState.matrix()[position].isSolid;
+ this.updateMatrix(position, new FilledTile(isSolid));
});
}
- private _markAsSolid() {
- this._loopThroughPiecePosition((position) => {
- this._updateMatrix(position, new FilledTile(true));
+ private markAsSolid() {
+ this.loopThroughPiecePosition((position) => {
+ this.updateMatrix(position, new FilledTile(true));
});
}
- private _clearPiece() {
- this._loopThroughPiecePosition((position) => {
- this._updateMatrix(position, new EmptyTile());
+ private clearPiece() {
+ this.loopThroughPiecePosition((position) => {
+ this.updateMatrix(position, new EmptyTile());
});
}
- private _loopThroughPiecePosition(callback: CallBack) {
- this._current.positionOnGrid.forEach((position) => {
+ private loopThroughPiecePosition(callback: CallBack) {
+ this.tetrisState.current().positionOnGrid.forEach((position) => {
callback(position);
});
}
- private _setPointsAndSpeed(numberOfClearedLines: number) {
+ private setPointsAndSpeed(numberOfClearedLines: number) {
if (!numberOfClearedLines) {
return;
}
- this._soundManager.clear();
- let { points, clearedLines, speed, initSpeed } = this._query.raw;
- let newLines = clearedLines + numberOfClearedLines;
- let newPoints = this._getPoints(numberOfClearedLines, points);
- let newSpeed = this._getSpeed(newLines, initSpeed);
+ this.soundManager.clear();
+ const newLines = this.tetrisState.clearedLines() + numberOfClearedLines;
+ const newPoints = this.getPoints(numberOfClearedLines, this.tetrisState.points());
+ const newSpeed = this.getSpeed(newLines, this.tetrisState.initSpeed());
- this._store.update({
+ this.tetrisState.updateState({
points: newPoints,
clearedLines: newLines,
speed: newSpeed
});
- if (newSpeed !== speed) {
- this._unsubscribe();
+ if (newSpeed !== this.tetrisState.speed()) {
+ this.stopGameInterval();
this.auto(MatrixUtil.getSpeedDelay(newSpeed));
}
}
- private _getSpeed(totalLines: number, initSpeed: number): Speed {
- let addedSpeed = Math.floor(totalLines / MatrixUtil.Height);
- let newSpeed = (initSpeed + addedSpeed);
+ private getSpeed(totalLines: number, initSpeed: number): Speed {
+ const addedSpeed = Math.floor(totalLines / MatrixUtil.Height);
+ let newSpeed = (initSpeed + addedSpeed) as Speed;
newSpeed = newSpeed > 6 ? 6 : newSpeed;
return newSpeed;
}
- private _getPoints(numberOfClearedLines: number, points: number): number {
- let addedPoints = MatrixUtil.Points[numberOfClearedLines - 1];
- let newPoints = points + addedPoints;
+ private getPoints(numberOfClearedLines: number, points: number): number {
+ const addedPoints = MatrixUtil.Points[numberOfClearedLines - 1];
+ const newPoints = points + addedPoints;
return newPoints > MatrixUtil.MaxPoint ? MatrixUtil.MaxPoint : newPoints;
}
- private _updateMatrix(pos: number, tile: Tile) {
- let newMatrix = [...this._matrix];
+ private updateMatrix(pos: number, tile: Tile) {
+ const newMatrix = [...this.tetrisState.matrix()];
newMatrix[pos] = tile;
- this._setMatrix(newMatrix);
+ this.setMatrix(newMatrix);
}
- private _setNext() {
- this._store.update({
- next: this._pieceFactory.getRandomPiece()
+ private setNext() {
+ this.tetrisState.updateState({
+ next: this.pieceFactory.getRandomPiece()
});
}
- private _setCurrentPiece(piece: Piece) {
- this._store.update({
+ private setCurrentPiece(piece: Piece) {
+ this.tetrisState.updateState({
current: piece
});
}
- private _setMatrix(matrix: Tile[]) {
- this._store.update({
+ private setMatrix(matrix: Tile[]) {
+ this.tetrisState.updateState({
matrix
});
}
- private _setLocked(locked: boolean) {
- this._store.update({
+ private setLocked(locked: boolean) {
+ this.tetrisState.updateState({
locked
});
}
- private _unsubscribe() {
- this._gameInterval && this._gameInterval.unsubscribe();
+ private setHolded(piece: Piece): void {
+ this.tetrisState.updateState({
+ hold: piece
+ });
+ }
+
+ private setCanHold(canHoldPiece: boolean) {
+ this.tetrisState.updateState({
+ canHold: canHoldPiece
+ });
+ }
+
+ private stopGameInterval() {
+ if (this.gameInterval) {
+ clearInterval(this.gameInterval);
+ }
+ }
+
+ private resetPosition(piece: Piece) {
+ piece.x = SPAWN_POSITION_X;
+ piece.y = SPAWN_POSITION_Y;
}
}
diff --git a/src/app/state/tetris/tetris.state.ts b/src/app/state/tetris/tetris.state.ts
new file mode 100644
index 0000000..77cdf05
--- /dev/null
+++ b/src/app/state/tetris/tetris.state.ts
@@ -0,0 +1,107 @@
+import { PieceFactory } from '@angular-tetris/factory/piece-factory';
+import { GameState } from '@angular-tetris/interface/game-state';
+import { Piece } from '@angular-tetris/interface/piece/piece';
+import { Speed } from '@angular-tetris/interface/speed';
+import { Tile } from '@angular-tetris/interface/tile/tile';
+import { MatrixUtil } from '@angular-tetris/interface/utils/matrix';
+import { LocalStorageService } from '@angular-tetris/services/local-storage.service';
+import { Injectable, computed, inject, signal } from '@angular/core';
+import { toObservable } from '@angular/core/rxjs-interop';
+import { combineLatest, delay, of, switchMap } from 'rxjs';
+
+export interface TetrisState {
+ matrix: Tile[];
+ current: Piece | null;
+ next: Piece;
+ hold: Piece;
+ canHold: boolean;
+ points: number;
+ locked: boolean;
+ sound: boolean;
+ initSpeed: Speed;
+ speed: Speed;
+ initLine: number;
+ clearedLines: number;
+ gameState: GameState;
+ saved: TetrisState;
+ max: number;
+}
+
+const createInitialState = (pieceFactory: PieceFactory): TetrisState => ({
+ matrix: MatrixUtil.getStartBoard(),
+ current: null,
+ next: pieceFactory.getRandomPiece(),
+ hold: pieceFactory.getNonePiece(),
+ canHold: true,
+ points: 0,
+ locked: true,
+ sound: true,
+ initLine: 0,
+ clearedLines: 0,
+ initSpeed: 1,
+ speed: 1,
+ gameState: GameState.Loading,
+ saved: null,
+ max: LocalStorageService.maxPoint
+});
+
+const isObjectShallowEqual = (a: any, b: any) => a === b;
+
+@Injectable({ providedIn: 'root' })
+export class TetrisStateService {
+ private pieceFactory = inject(PieceFactory);
+ private tetrisState = signal(createInitialState(this.pieceFactory));
+
+ next = computed(() => this.tetrisState().next, {
+ equal: isObjectShallowEqual
+ });
+ hold = computed(() => this.tetrisState().hold, {
+ equal: isObjectShallowEqual
+ });
+ matrix = computed(() => this.tetrisState().matrix, {
+ equal: isObjectShallowEqual
+ });
+ current = computed(() => this.tetrisState().current, {
+ equal: isObjectShallowEqual
+ });
+ isEnableSound = computed(() => this.tetrisState().sound);
+ gameState = computed(() => this.tetrisState().gameState);
+ hasCurrent = computed(() => !!this.current());
+ points = computed(() => this.tetrisState().points);
+ clearedLines = computed(() => this.tetrisState().clearedLines);
+ initLine = computed(() => this.tetrisState().initLine);
+ speed = computed(() => this.tetrisState().speed);
+ initSpeed = computed(() => this.tetrisState().initSpeed);
+ max = computed(() => this.tetrisState().max);
+ canStartGame = computed(() => this.gameState() !== GameState.Started);
+ isPlaying = computed(() => this.gameState() === GameState.Started);
+ isPause = computed(() => this.gameState() === GameState.Paused);
+ locked = computed(() => this.tetrisState().locked);
+ canHold = computed(() => this.tetrisState().canHold);
+
+ gameState$ = toObservable(this.gameState);
+ matrix$ = toObservable(this.matrix);
+ current$ = toObservable(this.current);
+
+ isShowLogo$ = combineLatest([this.gameState$, this.current$]).pipe(
+ switchMap(([state, current]) => {
+ const isLoadingOrOver = state === GameState.Loading || state === GameState.Over;
+ const isRenderingLogo$ = of(isLoadingOrOver && !current);
+ return isLoadingOrOver ? isRenderingLogo$.pipe(delay(1800)) : isRenderingLogo$;
+ })
+ );
+
+ updateState(updatedState: Partial) {
+ this.tetrisState.update((currentState) => ({
+ ...currentState,
+ ...updatedState
+ }));
+ }
+
+ resetState(updatedState: Partial) {
+ this.tetrisState.set({
+ ...createInitialState(this.pieceFactory),
+ ...updatedState
+ });
+ }
+}
diff --git a/src/app/state/tetris/tetris.store.ts b/src/app/state/tetris/tetris.store.ts
deleted file mode 100644
index 3e7a098..0000000
--- a/src/app/state/tetris/tetris.store.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-import { Injectable } from '@angular/core';
-import { Store, StoreConfig } from '@datorama/akita';
-import { PieceFactory } from '@trungk18/factory/piece-factory';
-import { GameState } from '@trungk18/interface/game-state';
-import { Piece } from '@trungk18/interface/piece/piece';
-import { Tile } from '@trungk18/interface/tile/tile';
-import { MatrixUtil } from '@trungk18/interface/utils/matrix';
-import { Speed } from '@trungk18/interface/speed';
-import { LocalStorageService } from '@trungk18/services/local-storage.service';
-
-export interface TetrisState {
- matrix: Tile[];
- current: Piece;
- next: Piece;
- points: number;
- locked: boolean;
- sound: boolean;
- initSpeed: Speed;
- speed: Speed;
- initLine: number;
- clearedLines: number;
- gameState: GameState;
- saved: TetrisState;
- max: number;
-}
-
-export function createInitialState(pieceFactory: PieceFactory): TetrisState {
- return {
- matrix: MatrixUtil.getStartBoard(),
- current: null,
- next: pieceFactory.getRandomPiece(),
- points: 0,
- locked: true,
- sound: true,
- initLine: 0,
- clearedLines: 0,
- initSpeed: 1,
- speed: 1,
- gameState: GameState.Loading,
- saved: null,
- max: LocalStorageService.maxPoint
- };
-}
-
-@Injectable({ providedIn: 'root' })
-@StoreConfig({ name: 'AngularTetris' })
-export class TetrisStore extends Store {
- constructor(_pieceFactory: PieceFactory) {
- super(createInitialState(_pieceFactory));
- }
-}
diff --git a/src/app/styles/_reset.scss b/src/app/styles/_reset.scss
index 953bb39..ba2d0f2 100644
--- a/src/app/styles/_reset.scss
+++ b/src/app/styles/_reset.scss
@@ -29,8 +29,8 @@ body {
right: 15px;
p {
- line-height: 47px;
- height: 57px;
+ line-height: 43px;
+ height: 50px;
padding: 10px 0 0;
white-space: nowrap;
clear: both;
@@ -39,7 +39,7 @@ body {
.last-row {
position: absolute;
width: 114px;
- top: 426px;
+ top: 430px;
left: 0;
}
}
diff --git a/src/assets/js/AudioContextMonkeyPatch.js b/src/assets/js/AudioContextMonkeyPatch.js
new file mode 100644
index 0000000..8e5ed0c
--- /dev/null
+++ b/src/assets/js/AudioContextMonkeyPatch.js
@@ -0,0 +1,182 @@
+/* Copyright 2013 Chris Wilson
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+/*
+
+This monkeypatch library is intended to be included in projects that are
+written to the proper AudioContext spec (instead of webkitAudioContext),
+and that use the new naming and proper bits of the Web Audio API (e.g.
+using BufferSourceNode.start() instead of BufferSourceNode.noteOn()), but may
+have to run on systems that only support the deprecated bits.
+
+This library should be harmless to include if the browser supports
+unprefixed "AudioContext", and/or if it supports the new names.
+
+The patches this library handles:
+if window.AudioContext is unsupported, it will be aliased to webkitAudioContext().
+if AudioBufferSourceNode.start() is unimplemented, it will be routed to noteOn() or
+noteGrainOn(), depending on parameters.
+
+The following aliases only take effect if the new names are not already in place:
+
+AudioBufferSourceNode.stop() is aliased to noteOff()
+AudioContext.createGain() is aliased to createGainNode()
+AudioContext.createDelay() is aliased to createDelayNode()
+AudioContext.createScriptProcessor() is aliased to createJavaScriptNode()
+AudioContext.createPeriodicWave() is aliased to createWaveTable()
+OscillatorNode.start() is aliased to noteOn()
+OscillatorNode.stop() is aliased to noteOff()
+OscillatorNode.setPeriodicWave() is aliased to setWaveTable()
+AudioParam.setTargetAtTime() is aliased to setTargetValueAtTime()
+
+This library does NOT patch the enumerated type changes, as it is
+recommended in the specification that implementations support both integer
+and string types for AudioPannerNode.panningModel, AudioPannerNode.distanceModel
+BiquadFilterNode.type and OscillatorNode.type.
+
+*/
+(function (global, exports, perf) {
+ 'use strict';
+
+ function fixSetTarget(param) {
+ if (!param) // if NYI, just return
+ return;
+ if (!param.setTargetAtTime)
+ param.setTargetAtTime = param.setTargetValueAtTime;
+ }
+
+ if (window.hasOwnProperty('webkitAudioContext') &&
+ !window.hasOwnProperty('AudioContext')) {
+ window.AudioContext = webkitAudioContext;
+
+ if (!AudioContext.prototype.hasOwnProperty('createGain'))
+ AudioContext.prototype.createGain = AudioContext.prototype.createGainNode;
+ if (!AudioContext.prototype.hasOwnProperty('createDelay'))
+ AudioContext.prototype.createDelay = AudioContext.prototype.createDelayNode;
+ if (!AudioContext.prototype.hasOwnProperty('createScriptProcessor'))
+ AudioContext.prototype.createScriptProcessor = AudioContext.prototype.createJavaScriptNode;
+ if (!AudioContext.prototype.hasOwnProperty('createPeriodicWave'))
+ AudioContext.prototype.createPeriodicWave = AudioContext.prototype.createWaveTable;
+
+
+ AudioContext.prototype.internal_createGain = AudioContext.prototype.createGain;
+ AudioContext.prototype.createGain = function() {
+ var node = this.internal_createGain();
+ fixSetTarget(node.gain);
+ return node;
+ };
+
+ AudioContext.prototype.internal_createDelay = AudioContext.prototype.createDelay;
+ AudioContext.prototype.createDelay = function(maxDelayTime) {
+ var node = maxDelayTime ? this.internal_createDelay(maxDelayTime) : this.internal_createDelay();
+ fixSetTarget(node.delayTime);
+ return node;
+ };
+
+ AudioContext.prototype.internal_createBufferSource = AudioContext.prototype.createBufferSource;
+ AudioContext.prototype.createBufferSource = function() {
+ var node = this.internal_createBufferSource();
+ if (!node.start) {
+ node.start = function ( when, offset, duration ) {
+ if ( offset || duration )
+ this.noteGrainOn( when || 0, offset, duration );
+ else
+ this.noteOn( when || 0 );
+ };
+ } else {
+ node.internal_start = node.start;
+ node.start = function( when, offset, duration ) {
+ if( typeof duration !== 'undefined' )
+ node.internal_start( when || 0, offset, duration );
+ else
+ node.internal_start( when || 0, offset || 0 );
+ };
+ }
+ if (!node.stop) {
+ node.stop = function ( when ) {
+ this.noteOff( when || 0 );
+ };
+ } else {
+ node.internal_stop = node.stop;
+ node.stop = function( when ) {
+ node.internal_stop( when || 0 );
+ };
+ }
+ fixSetTarget(node.playbackRate);
+ return node;
+ };
+
+ AudioContext.prototype.internal_createDynamicsCompressor = AudioContext.prototype.createDynamicsCompressor;
+ AudioContext.prototype.createDynamicsCompressor = function() {
+ var node = this.internal_createDynamicsCompressor();
+ fixSetTarget(node.threshold);
+ fixSetTarget(node.knee);
+ fixSetTarget(node.ratio);
+ fixSetTarget(node.reduction);
+ fixSetTarget(node.attack);
+ fixSetTarget(node.release);
+ return node;
+ };
+
+ AudioContext.prototype.internal_createBiquadFilter = AudioContext.prototype.createBiquadFilter;
+ AudioContext.prototype.createBiquadFilter = function() {
+ var node = this.internal_createBiquadFilter();
+ fixSetTarget(node.frequency);
+ fixSetTarget(node.detune);
+ fixSetTarget(node.Q);
+ fixSetTarget(node.gain);
+ return node;
+ };
+
+ if (AudioContext.prototype.hasOwnProperty( 'createOscillator' )) {
+ AudioContext.prototype.internal_createOscillator = AudioContext.prototype.createOscillator;
+ AudioContext.prototype.createOscillator = function() {
+ var node = this.internal_createOscillator();
+ if (!node.start) {
+ node.start = function ( when ) {
+ this.noteOn( when || 0 );
+ };
+ } else {
+ node.internal_start = node.start;
+ node.start = function ( when ) {
+ node.internal_start( when || 0);
+ };
+ }
+ if (!node.stop) {
+ node.stop = function ( when ) {
+ this.noteOff( when || 0 );
+ };
+ } else {
+ node.internal_stop = node.stop;
+ node.stop = function( when ) {
+ node.internal_stop( when || 0 );
+ };
+ }
+ if (!node.setPeriodicWave)
+ node.setPeriodicWave = node.setWaveTable;
+ fixSetTarget(node.frequency);
+ fixSetTarget(node.detune);
+ return node;
+ };
+ }
+ }
+
+ if (window.hasOwnProperty('webkitOfflineAudioContext') &&
+ !window.hasOwnProperty('OfflineAudioContext')) {
+ window.OfflineAudioContext = webkitOfflineAudioContext;
+ }
+
+}(window));
+
diff --git a/src/assets/readme/angular-tetris-iphonex.gif b/src/assets/readme/angular-tetris-iphonex.gif
index e149464..b52c17c 100644
Binary files a/src/assets/readme/angular-tetris-iphonex.gif and b/src/assets/readme/angular-tetris-iphonex.gif differ
diff --git a/src/assets/readme/tech-stack.png b/src/assets/readme/tech-stack.png
index 2f19ce9..bf2aa84 100644
Binary files a/src/assets/readme/tech-stack.png and b/src/assets/readme/tech-stack.png differ
diff --git a/src/assets/readme/tetris-social-cover.png b/src/assets/readme/tetris-social-cover.png
new file mode 100644
index 0000000..3f738b5
Binary files /dev/null and b/src/assets/readme/tetris-social-cover.png differ
diff --git a/src/index.html b/src/index.html
index e28c3f9..b7f246f 100644
--- a/src/index.html
+++ b/src/index.html
@@ -16,7 +16,6 @@
- Angular Tetris built with Angular and Akita - by trungk18
@@ -25,14 +24,14 @@
-
+
-
-
-
-
-
+
+
+
+
+
@@ -41,6 +40,7 @@
+
diff --git a/src/index.prod.html b/src/index.prod.html
index 024ddbb..3abac37 100644
--- a/src/index.prod.html
+++ b/src/index.prod.html
@@ -16,7 +16,6 @@
- Angular Tetris built with Angular and Akita - by trungk18
@@ -25,14 +24,14 @@
-
+
-
-
-
-
-
+
+
+
+
+
@@ -41,7 +40,7 @@
-
+