Trung Vo
Trung Vo, web expert with 10 years of experience, Google Developer Expert in Angular, fosters web dev communities and speaks globally, based in Singapore.
Frontend Engineer
Angular Kenya - 05 Feb 2021
Hi, My name is Trung š
What is Tetris?
What and why Angular Tetris?
Techstack
Development Challenge
Tetris Game Loop
Piece/Tetrominos
Board
Animation/Timer
Keyboard
Sounds
š® tetris.trungk18.com
Ā
It is the most important part of the game
ā Code is like humor. When you have to explain it, itās bad.āĀ - Cory House
I ended up using @chrum/ngx-tetris
Ā
I did write some additional functionality
_gameInterval: Subscription;
auto(delay: number) {
this._gameInterval = timer(0, delay).subscribe(() => {
this._update();
});
}
export class Piece {
x: number;
y: number;
rotation = PieceRotation.Deg0;
type: PieceTypes;
shape: Shape;
next: Shape;
private _shapes: Shapes;
private _lastConfig: Partial<Piece>;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
protected setShapes(shapes: Shapes) {
this._shapes = shapes;
this.shape = shapes[this.rotation];
}
}
const ShapesL: Shapes = [];
ShapesL[PieceRotation.Deg0] = [
[0, 0, 0, 0],
[1, 0, 0, 0],
[1, 0, 0, 0],
[1, 1, 0, 0]
];
ShapesL[PieceRotation.Deg90] = [
[0, 0, 0, 0],
[0, 0, 0, 0],
[1, 1, 1, 0],
[1, 0, 0, 0]
];
export class PieceL extends Piece {
constructor(x: number, y: number) {
super(x, y);
this.type = PieceTypes.L;
this.next = [
[0, 0, 1, 0],
[1, 1, 1, 0]
];
this.setShapes(ShapesL);
}
}
const ShapesF: Shapes = [];
ShapesF[PieceRotation.Deg0] = [
[1, 0, 0, 0],
[1, 1, 0, 0],
[1, 0, 0, 0],
[1, 1, 0, 0]
];
export class PieceF extends Piece {
constructor(x, y) {
super(x, y);
this.type = PieceTypes.F;
this.next = [
[1, 0, 1, 0],
[1, 1, 1, 1]
];
this.setShapes(ShapesF);
}
}
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 |
0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 |
0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
I rewrote the animation with RxJS
div.b {
transform: scale(-1, 1);
}
div.cube {
width: 150px;
height: 80px;
background-color: yellow;
}
div.a {
transform: scale(1, 1);
}
.dragon {
width: 80px;
height: 86px;
margin: 0 auto;
background-position: 0 -100px;
&.l1 {
background-position: 0 -100px;
}
&.l1
transform: scale(-1, 1);
}
}
.dragon {
width: 80px;
height: 86px;
margin: 0 auto;
background-position: 0 -100px;
&.r1 {
background-position: 0 -100px;
}
}
eyes() {
return timer(0, 500).pipe(
startWith(0),
map((x) => x + 1),
takeWhile((x) => x < 6),
tap((x) => {
let state = x % 2 === 0 ? 1 : 2;
this.className = `l${ state }`;
})
);
}
run() {
let side = 'r';
return timer(0, 100).pipe(
startWith(0),
map((x) => x + 1),
takeWhile((x) => x <= 40),
tap((x) => {
if (x === 10 || x === 20 || x === 30) {
side = side === 'r' ? 'l' : 'r';
}
let state = x % 2 === 0 ? 3 : 4;
this.className = `${ side }${ state }`;
}),
finalize(() => {
this.className = `${ side }1`;
})
);
}
r 1s -> l 1s -> r1s -> l 1s -> end with {side}1 ~ l1
The ConcatĀ operator concatenates the output of multiple ObservablesĀ so that they act like a single Observable, with all of the items emitted by the first ObservableĀ being emitted before any of the items emitted by the second Observable
ngOnInit(): void {
concat(this.run(), this.eyes())
.pipe(
delay(5000),
repeat(1000),
untilDestroyed(this)
)
.subscribe();
}
The actual result doesn't look very identical but it is good enough in my standard.
export enum TetrisKeyboard {
Up = 'arrowup',
Down = 'arrowdown',
Left = 'arrowleft',
Right = 'arrowright',
Space = 'space',
P = 'p',
R = 'r',
S = 's'
}
@HostListener(`${KeyDown}.${TetrisKeyboard.Left}`)
keyDownLeft() {
this._soundManager.move();
this._keyboardService.setKeyĢ£({
left: true
});
if (this.hasCurrent) {
this._tetrisService.moveLeft();
} else {
this._tetrisService.decreaseLevel();
}
}
See more ā”ļø @HostListener
Some browsers use deprecated properties and method names that are not present in standards-compliant browsers
By Trung Vo
Deck for Angular APAC Dec 2020. https://www.angularnation.net/
Trung Vo, web expert with 10 years of experience, Google Developer Expert in Angular, fosters web dev communities and speaks globally, based in Singapore.