Skip to content

Commit bbe70bf

Browse files
authored
Merge pull request trungvose#4 from trungk18/feature/publishing
Feature/publishing
2 parents 9ed7c15 + 232f805 commit bbe70bf

23 files changed

+258
-72
lines changed

README.md

Lines changed: 43 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Angular Tetris
22

3-
A child-hood memory Tetris game built with Angular 10 and Akita.
3+
A childhood memory Tetris game built with Angular 10 and Akita.
44

55
## Working Game
66

@@ -21,47 +21,47 @@ Thanks a bunch for stopping by and supporting me!
2121

2222
## Why?
2323

24-
Tetris was the first game that my dad bought to me and It costed about 1$ US at that time. It didn't sound a lot today. But 20 years ago, 1$ can feed my family for at least few days. Put it that way, with 1\$ you can buy 2 dozens of eggs.
25-
This is the only one gaming "machine" that I ever had until my first computer arrived. I have never had a SNES or PS1 at home.
24+
Tetris was the first game that my dad bought for me and It cost about 1$ US at that time. It didn't sound a lot today. But 20 years ago, 1$ can feed my family for at least a few days. Put it that way, with 1\$ you can buy 2 dozens eggs.
25+
This is the only gaming "machine" that I ever had until my first computer arrived. I have never had a SNES or PS1 at home.
2626

2727
My Tetris was exactly in the same yellow color and it was so big, running on 2 AA battery. It is how it looks.
2828

2929
![Retro Tetris][tetris]
3030

3131
After showing my wife the [Tetris game built with Vue][vue]. She told me why didn't I build the same <u>Tetris with Angular</u>? And here you go.
3232

33-
> I designed the game to hold maximum score of 999999 (one million minus one 😂) and I have never reached to that very end. Please [tweet][tweetmax] your screenshot together with hashtag `#angulartetris` and tag my name as well `@tuantrungvo`.
33+
> I designed the game to hold a maximum score of 999999 (one million minus one 😂) and I have never reached that very end. Please [tweet][tweetmax] your screenshot together with hashtag `#angulartetris` and tag my name as well `@tuantrungvo`.
3434
>
35-
> The **first five** amazing gamer that reached to 999999 points will received a <u>free gift</u>
35+
> The **first five** amazing gamer that reached to 999999 points will receive a <u>free gift</u>
3636
3737
[tweetmax]: https://twitter.com/intent/tweet?url=https%3A%2F%2Fgithub.com%2Ftrungk18%2Fangular-tetris&text=Woo-hoo!%20I%20got%20a%20999999%20points%20on%20Angular%20Tetris%20%40tuantrungvo.%20Wanna%20join%20the%20party%3F%20&hashtags=angular,angulartetris,akita,typescript
3838

3939
## Who is this for?
4040

4141
I built this game dedicated to:
4242

43-
- For anyone that grew up with Tetris as a part of your memory. It was definitely my childhood memory and I hope you enjoy the game as well.
44-
- For Angular developer community, I have never really see a game that built with Angular and that's my answer. Using Akita as the underlying state management really helps me to see all of the data flow, it is great for debugging. I wanted to see more Angular game from you guys 💪💪💪
43+
- For anyone that grew up with Tetris as a part of your memory. It was my childhood memory and I hope you enjoy the game as well.
44+
- For the Angular developer community, I have never really seen a game that built with Angular and that's my answer. Using Akita as the underlying state management helps me to see all of the data flow, it is great for debugging. I wanted to see more Angular game from you guys 💪💪💪
4545

4646
## How to play
4747

4848
### Before playing
4949

5050
- You can use both keyboard and mouse to play. But prefer to use <u>keyboard</u>
5151
- Press arrow left and right to change the speed of the game **(1 - 6)**. The higher the number, the faster the piece will fall
52-
- Press arrow up and down to change how many of lines has been filled before starting the game **(1 - 10)**
52+
- Press arrow up and down to change how many of lines have been filled before starting the game **(1 - 10)**
5353
- Press `Space` to start the game
5454
- Press `P` for pause/resume game
55-
- Press `R` for reset the game
56-
- Press `S` for the turn on/off music
55+
- Press `R` for resetting the game
56+
- Press `S` for the turn on/off the sounds
5757

5858
### Playing game
5959

6060
- Press `Space` make the piece drop quickly
6161
- Press `Arrow left` and `right` for moving left and right
6262
- Press `Arrow up` to rotate the piece
6363
- Press `Arrow down` to move a piece faster
64-
- When clearing lines, you will received a point - 100 points for 1 line, 300 points for 2 lines, 700 points for 3 lines, 1500 points for 4 lines
64+
- When clearing lines, you will receive a point - 100 points for 1 line, 300 points for 2 lines, 700 points for 3 lines, 1500 points for 4 lines
6565
- The drop speed of the pieces increases with the number of rows eliminated (one level up for every 20 lines cleared)
6666

6767
## Techstack
@@ -72,25 +72,25 @@ I built it barely with Angular and Akita, no additional UI framework/library was
7272

7373
## Development Challenge
7474

75-
I got the inspiration from the same same but different [Tetris game built with Vue][vue]. To not reinvented the wheel, I started to look at Vue code and thought it would be very identical to Angular. But later one, I realized a few catches:
75+
I got the inspiration from the same but different [Tetris game built with Vue][vue]. To not reinvented the wheel, I started to look at Vue code and thought it would be very identical to Angular. But later one, I realized a few catches:
7676

77-
- The Vue source code was written few years ago with pure JS. I could find several problems that the complier didn't tell you. Such as giving `parseInt` a number. It is still working though, but I don't like it.
78-
- There was an extensively uses of `setTimeout` and `setInterval` for making animations. I rewrote all of the animation logic using RxJS. You will see the detail below.
79-
- The brain of the game also used `setTimeout` for the game loop. It was not a problem, but I was having a <u>hard time</u> understanding the code on some essential elements: how to render the piece to the UI, how the calculation make senses with xy axis. In the end, I changed all of the logic to a proper OOP way using TypeScript class, based on [@chrum/ngx-tetris][ngx-tetris].
77+
- The Vue source code was written a few years ago with pure JS. I could find several problems that the compiler didn't tell you. Such as giving `parseInt` a number. It is still working though, but I don't like it.
78+
- There was extensive use of `setTimeout` and `setInterval` for making animations. I rewrote all of the animation logic using RxJS. You will see the detail below.
79+
- The brain of the game also used `setTimeout` for the game loop. It was not a problem, but I was having a <u>hard time</u> understanding the code on some essential elements: how to render the piece to the UI, how the calculation makes sense with XY axis. In the end, I changed all of the logic to a proper OOP way using TypeScript class, based on [@chrum/ngx-tetris][ngx-tetris].
8080

8181
### Tetris Core
8282

83-
It is the most important part of the game. As I am following the Vue source code, It is getting harder to understand what was the developer's intention. The Vue version gave me the inspiration but I think I have to write the core tetris differently.
83+
It is the most important part of the game. As I am following the Vue source code, It is getting harder to understand what was the developer's intention. The Vue version inspired me but I think I have to write the core Tetris differently.
8484

85-
Take a look at the two block of code below which do the same rendering piece on the screen and you will understand what I am talking about. The left side was rewritten with Angular and TypeScript and the right side was the JS version.
85+
Take a look at the two blocks of code below which do the same rendering piece on the screen and you will understand what I am talking about. The left side was rewritten with Angular and TypeScript and the right side was the JS version.
8686

8787
![Angular Tetris][compare01]
8888

89-
I always think that your code must be written as you talk to people, without explaining a word. Otherwise when someone come in and read your code and maintain it, they will be struggling.
89+
I always think that your code must be written as you talk to people, without explaining a word. Otherwise, when someone comes in and reads your code and maintains it, they will be struggling.
9090

9191
> “ Code is like humor. When you have to explain it, it’s bad.” – Cory House
9292
93-
And let me emphasize it again, I didn't write the brain of the game from the scratch. I adapted the well-written source by [@chrum/ngx-tetris][ngx-tetris] for tetris core. I did refactor some parts to support Akita and wrote some new functionality as well.
93+
And let me emphasize it again, I didn't write the brain of the game from scratch. I adapted the well-written source by [@chrum/ngx-tetris][ngx-tetris] for Tetris core. I did refactor some parts to support Akita and wrote some new functionality as well.
9494

9595
### Akita state management + dev tool support
9696

@@ -108,7 +108,7 @@ I turn it on all the time on [tetris.trungk18.com][angular-tetris], you can open
108108
109109
### Web Audio API
110110

111-
There are many sound effects in the game such as when you press space, or left, right. In reality, all of the sound was reference to a single file [assets/tetris-sound.mp3][sounds].
111+
There are many sound effects in the game such as when you press space, or left, right. In reality, all of the sounds were a reference to a single file [assets/tetris-sound.mp3][sounds].
112112

113113
I don't have much experience working with audio before but the Web Audio API looks very promising. You could do more with it.
114114

@@ -127,9 +127,21 @@ The actual result doesn't look very identical but it is good enough in my standa
127127

128128
![Angular Tetris][compare02-result]
129129

130+
### Animation
131+
132+
I rewrote the animation with RxJS. See the comparison below for the simple dinosaurs running animation at the beginning of the game.
133+
134+
You could do a lot of stuff if you know RxJS well enough :) I think I need to strengthen my RxJS knowledge soon enough as well. Super powerful.
135+
136+
![Angular Tetris][compare02]
137+
138+
The actual result doesn't look very identical but it is good enough in my standard.
139+
140+
![Angular Tetris][compare02-result]
141+
130142
### Keyboard handling
131143

132-
I planned to use [@ngneat/hotkeys][hotkeys] but I decided to use `@HostListener` instead. A simple implementation could looks like:
144+
I planned to use [@ngneat/hotkeys][hotkeys] but I decided to use `@HostListener` instead. A simple implementation could look like:
133145

134146
```typescript
135147
@HostListener(`${KeyDown}.${TetrisKeyboard.Left}`)
@@ -170,12 +182,14 @@ See more at [containers/angular-tetris/angular-tetris.component.ts][hotkeys-impl
170182

171183
## Time spending
172184

173-
I was actually still working with [Chau Tran][chautran] on phase two of [Angular Jira clone][jira-clone] and I saw a Tetris game. My wife really want to have a version that I built so that I decided to finish the Angular Tetris first before completing Jira clone phase two.
185+
I was still working with [Chau Tran][chautran] on phase two of [Angular Jira clone][jira-clone] when I saw that Tetris game built with Vue. My wife wanted to have a version that I built so that I decided to finish the Angular Tetris first before completing Jira clone phase two.
174186

175187
According to waka time report, I have spent about 30 hours working on this project. Which is equal to [run a marathon five times][marathon] at my current speed 😩
176188

177189
![Angular Tetris][timespending]
178190

191+
The flow was easy. I designed a simple [to do list][todolist], then start reading the code in Vue. And start working on the Angular at the same time. Halfway, I start to read [@chrum/ngx-tetris][ngx-tetris] instead of the Vue source. And keep building until I have the final result. 30 hours was a good number. It would take me longer, or lesser. But I enjoyed the experience working on the first-ever game I have built.
192+
179193
## Setting up development environment 🛠
180194

181195
- `git clone https://github.com/trungk18/angular-tetris.git`
@@ -196,8 +210,8 @@ According to waka time report, I have spent about 30 hours working on this proje
196210
| --------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
197211
| [@Binaryify/vue-tetris][vue] | Vue Tetris, I reused part of HTML, CSS and static assets from that project |
198212
| [@chrum/ngx-tetris][ngx-tetris] | A comprehensive core Tetris written with Angular, I reused part of that for the brain of the game. |
199-
| [Game Development: Tetris in Angular][medium] | A detailed excellent article about how to built a complete Tetris game. I didn't check the code but I learned much more from the article |
200-
| [Super Rotation System][srs] | A standard for how the piece behave. I didn't follow everything but it is good to know as wells |
213+
| [Game Development: Tetris in Angular][medium] | A detailed excellent article about how to build a complete Tetris game. I didn't check the code but I learned much more from the article |
214+
| [Super Rotation System][srs] | A standard for how the piece behaves. I didn't follow everything but it is good to know as wells |
201215

202216
## Contributing
203217

@@ -211,19 +225,22 @@ Feel free to use my code on your project. It would be great if you put a referen
211225

212226
[MIT](https://opensource.org/licenses/MIT)
213227

228+
[issues]: https://github.com/trungk18/angular-tetris/issues/new/choose
229+
[pull]: https://github.com/trungk18/angular-tetris/pulls
214230
[angular-tetris]: https://tetris.trungk18.com
215231
[medium]: https://medium.com/angular-in-depth/game-development-tetris-in-angular-64ef96ce56f7
216232
[srs]: https://tetris.fandom.com/wiki/SRS
217233
[vue]: https://github.com/Binaryify/vue-tetris
218234
[tetris]: src/assets/readme/retro-tetris.jpg
235+
[demo]: src/assets/readme/angular-tetris-demo.gif
219236
[ngx-tetris]: https://github.com/chrum/ngx-tetris
220237
[techstack]: src/assets/readme/tech-stack.png
221238
[compare01]: src/assets/readme/compare01.png
222239
[compare02]: src/assets/readme/compare02.png
223240
[compare02-result]: src/assets/readme/compare02-result.gif
224241
[timespending]: src/assets/readme/time-spending.png
225242
[akita-devtool]: src/assets/readme/akita-devtool.gif
226-
[sounds]: assets/tetris-sound.mp3
243+
[sounds]: src/assets/tetris-sound.mp3
227244
[sound-manager]: src/app/services/sound-manager.service.ts
228245
[webaudio]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API
229246
[redux-devtool]: https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?hl=en
@@ -232,3 +249,4 @@ Feel free to use my code on your project. It would be great if you put a referen
232249
[chautran]: https://github.com/nartc
233250
[jira-clone]: https://github.com/trungk18/jira-clone-angular
234251
[marathon]: https://www.strava.com/activities/2902245728
252+
[todolist]: https://www.notion.so/trungk18/Phase-1-be1ae0fbbf2c4c2fb92887e2218413db

angular.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@
6262
"with": "src/environments/environment.prod.ts"
6363
}
6464
],
65+
"index": {
66+
"input": "src/index.prod.html",
67+
"output": "index.html"
68+
},
6569
"optimization": true,
6670
"outputHashing": "all",
6771
"sourceMap": false,

src/app/app.module.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ import { TwitterButtonComponent } from './components/twitter-button/twitter-butt
4343
GithubComponent,
4444
TwitterButtonComponent,
4545
],
46-
imports: [BrowserModule, environment.production ? [] : AkitaNgDevtools.forRoot()],
46+
imports: [BrowserModule, AkitaNgDevtools.forRoot()],
4747
providers: [],
4848
bootstrap: [AppComponent],
4949
})

src/app/components/github/github.component.html

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,17 @@
66
<img atl="QR Code to play on mobile"
77
src="https://res.cloudinary.com/dvujyxh7e/image/upload/v1595380432/angular-tetris-qr-code-no-text.png" />
88
</div>
9-
<div class="twitter">
10-
<t-twitter-button [tweetUrl]="tweetAngularTetrisUrl">
9+
<div class="tweet-button top"
10+
*ngIf="max$ | async as maxPoint">
11+
<t-twitter-button [tweetUrl]="getTweetMaxScoreShareUrl(maxPoint)"
12+
(click)="sendTwitterShareMaxScoreEvent()">
13+
Share Your Score - {{ maxPoint }}
14+
</t-twitter-button>
15+
</div>
16+
<div class="tweet-button">
17+
<t-twitter-button [tweetUrl]="tweetAngularTetrisUrl"
18+
(click)="sendTwitterShareEvent()">
19+
Tweet
1120
</t-twitter-button>
1221
</div>
1322
<iframe src="https://ghbtns.com/github-btn.html?user=trungk18&repo=angular-tetris&type=star&count=true&size=large"
@@ -24,10 +33,4 @@
2433
height="30"
2534
title="Angular Tetris Fork">
2635
</iframe>
27-
<iframe src="https://ghbtns.com/github-btn.html?user=trungk18&repo=angular-tetris&type=follow&count=true&size=large"
28-
frameborder="0"
29-
scrolling="0"
30-
width="230"
31-
height="30"
32-
title="GitHub"></iframe>
3336
</div>

src/app/components/github/github.component.scss

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,12 @@
4141
flex-direction: column;
4242
}
4343

44-
.twitter {
44+
.tweet-button {
4545
cursor: pointer;
46-
margin-top: 15px;
47-
margin-bottom: 5px;
46+
margin-top: 5px;
47+
margin-bottom: 7px;
48+
49+
&.top {
50+
margin-top: 15px;
51+
}
4852
}
Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,36 @@
11
import { Component, OnInit } from '@angular/core';
2+
import { TetrisQuery } from '@trungk18/state/tetris/tetris.query';
3+
import { Observable } from 'rxjs';
4+
import { GoogleAnalyticsService } from '@trungk18/services/google-analytics.service';
5+
const HASHTAG = 'angular,angulartetris,akita,typescript';
26

37
@Component({
48
selector: 't-github',
59
templateUrl: './github.component.html',
610
styleUrls: ['./github.component.scss']
711
})
812
export class GithubComponent implements OnInit {
9-
tweetAngularTetrisUrl =
10-
'https://twitter.com/intent/tweet?url=https%3A%2F%2Fgithub.com%2Ftrungk18%2Fangular-tetris&text=Awesome%20Tetris%20game%20built%20with%20Angular%2010%20and%20Akita%2C%20can%20you%20get%20999999%20points%3F&hashtags=angular,angulartetris,akita,typescript';
13+
max$: Observable<number>;
14+
tweetAngularTetrisUrl = `https://twitter.com/intent/tweet?url=https%3A%2F%2Fgithub.com%2Ftrungk18%2Fangular-tetris&text=Awesome%20Tetris%20game%20built%20with%20Angular%2010%20and%20Akita%2C%20can%20you%20get%20999999%20points%3F&hashtags=${HASHTAG}`;
1115

12-
constructor() {}
16+
constructor(private _query: TetrisQuery, private _googleAnalytics: GoogleAnalyticsService) {}
1317

14-
ngOnInit(): void {}
18+
ngOnInit(): void {
19+
this.max$ = this._query.max$;
20+
}
21+
22+
getTweetMaxScoreShareUrl(max: number) {
23+
let text = encodeURIComponent(
24+
`Woo-hoo! I got a ${max} points on Angular Tetris @tuantrungvo. Wanna join the party?`
25+
);
26+
return `https://twitter.com/intent/tweet?url=https%3A%2F%2Fgithub.com%2Ftrungk18%2Fangular-tetris&text=${text}&hashtags=${HASHTAG}`;
27+
}
28+
29+
sendTwitterShareMaxScoreEvent() {
30+
this._googleAnalytics.sendEvent('Share Twitter High Score', 'button');
31+
}
32+
33+
sendTwitterShareEvent() {
34+
this._googleAnalytics.sendEvent('Share Twitter', 'button');
35+
}
1536
}
Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
<p>Level</p>
2-
<t-number [num]="speed$ | async"
2+
<t-number *ngIf="hasCurrent$ | async; else initSpeedTmpl"
3+
[num]="speed$ | async"
34
[length]="1">
4-
</t-number>
5+
</t-number>
6+
<ng-template #initSpeedTmpl>
7+
<t-number [num]="initSpeed$ | async"
8+
[length]="1">
9+
</t-number>
10+
</ng-template>

src/app/components/level/level.component.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,14 @@ import { TetrisQuery } from '@trungk18/state/tetris/tetris.query';
1010
})
1111
export class LevelComponent implements OnInit {
1212
speed$: Observable<Speed>;
13+
initSpeed$: Observable<Speed>;
14+
hasCurrent$: Observable<boolean>;
15+
1316
constructor(private _query: TetrisQuery) {}
1417

1518
ngOnInit(): void {
1619
this.speed$ = this._query.speed$;
20+
this.hasCurrent$ = this._query.hasCurrent$;
21+
this.initSpeed$ = this._query.initSpeed$;
1722
}
1823
}

src/app/components/logo/logo.component.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
top: 100px;
1515
left: 0;
1616
font-family: initial;
17-
letter-spacing: 6px;
17+
letter-spacing: 2px;
1818
text-shadow: 1px 1px 1px rgba(255, 255, 255, 0.35);
1919
}
2020

src/app/components/logo/logo.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export class LogoComponent implements OnInit {
1515
concat(this.run(), this.eyes())
1616
.pipe(
1717
delay(5000),
18-
repeat(10)
18+
repeat(1000)
1919
)
2020
.subscribe();
2121
}

0 commit comments

Comments
 (0)