{"id":13406733,"url":"https://github.com/trungvose/angular-tetris","last_synced_at":"2025-04-05T01:03:28.426Z","repository":{"id":38425540,"uuid":"277126275","full_name":"trungvose/angular-tetris","owner":"trungvose","description":"Tetris game built with Angular and Akita 🎮","archived":false,"fork":false,"pushed_at":"2024-01-10T09:54:25.000Z","size":7374,"stargazers_count":536,"open_issues_count":13,"forks_count":103,"subscribers_count":23,"default_branch":"master","last_synced_at":"2025-01-30T11:03:14.067Z","etag":null,"topics":["akita","angular","typescript"],"latest_commit_sha":null,"homepage":"https://tetris.trungk18.com","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/trungvose.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["trungk18"],"patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":["https://www.buymeacoffee.com/trungvose"]}},"created_at":"2020-07-04T14:32:20.000Z","updated_at":"2025-01-22T16:19:39.000Z","dependencies_parsed_at":"2023-02-15T14:01:33.738Z","dependency_job_id":"7072d935-0992-4893-807c-a66155f3553b","html_url":"https://github.com/trungvose/angular-tetris","commit_stats":null,"previous_names":["trungvose/angular-tetris","trungk18/angular-tetris"],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/trungvose%2Fangular-tetris","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/trungvose%2Fangular-tetris/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/trungvose%2Fangular-tetris/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/trungvose%2Fangular-tetris/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/trungvose","download_url":"https://codeload.github.com/trungvose/angular-tetris/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247271514,"owners_count":20911587,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["akita","angular","typescript"],"created_at":"2024-07-30T19:02:37.848Z","updated_at":"2025-04-05T01:03:28.389Z","avatar_url":"https://github.com/trungvose.png","language":"TypeScript","readme":"# Angular Tetris\n\nA childhood memory Tetris game built with Angular and Akita.\n\n## Working Game\n\nCheck out the **working game** -\u003e https://tetris.trungk18.com\n\nThe game has sounds, wear your 🎧 or turn on your 🔊 for a better experience.\n\n![A childhood memory Tetris game built with Angular and Akita][demo]\n\n\u003e Please tweet and tag me @trungvose for any issues that you are currently facing!\n\u003e Thanks for your understanding. Stay tuned!\n\n![A childhood memory Tetris game built with Angular and Akita][iphonex]\n\n## Support\n\nIf you like my work, feel free to:\n\n- ⭐ this repository. And we will be happy together :)\n- [![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)][tweet] about Angular Tetris\n- \u003ca title=\"Thanks for your support!\" href=\"https://www.buymeacoffee.com/trungvose\" target=\"_blank\"\u003e\u003cimg src=\"https://res.cloudinary.com/dvujyxh7e/image/upload/c_thumb,w_140,g_face/v1596378474/default-orange_uthxgz.jpg\" alt=\"Buy Me A Coffee\"\u003e\u003c/a\u003e\n\nThanks a bunch for stopping by and supporting me!\n\n[tweet]: https://twitter.com/intent/tweet?url=https%3A%2F%2Fgithub.com%2Ftrungk18%2Fangular-tetris\u0026text=Awesome%20Tetris%20game%20built%20with%20Angular%2010%20and%20Akita%2C%20can%20you%20get%20999999%20points%3F\u0026hashtags=angular,angulartetris,akita,typescript\n\n## Why?\n\nTetris 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 two dozens eggs.\nThis is the only gaming \"machine\" that I ever had until my first computer arrived. I have never had a SNES or PS1 at home.\n\nMy Tetris was exactly in the same yellow color and it was so big, running on 2 AA battery. It is how it looks.\n\n![Retro Tetris][tetris]\n\nAfter showing my wife the [Tetris game built with Vue][vue]. She asked me why I didn't build the same \u003cu\u003eTetris with Angular\u003c/u\u003e? And here you go.\n\n\u003e The game can hold up to a maximum score of 999999 (one million minus one 😂) and I have never reached that very end.\n\u003e\n\u003e Please [tweet][tweetmax] the screenshot with your highest score, together with hashtag `#angulartetris` and my name tagged as well `@trungvose`. I will send **a free gift** to the one having the highest score of the day, from now till \u003cu\u003e1 Aug 2020\u003c/u\u003e.\n\n[tweetmax]: https://twitter.com/intent/tweet?url=https%3A%2F%2Fgithub.com%2Ftrungk18%2Fangular-tetris\u0026text=Woo-hoo!%20I%20got%20a%20999999%20points%20on%20Angular%20Tetris%20%40trungvose.%20Wanna%20join%20the%20party%3F%20\u0026hashtags=angular,angulartetris,akita,typescript\n\n## Who is this for?\n\nI built this game dedicated to:\n\n- 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.\n- 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 💪💪💪\n\n## How to play\n\n### Before playing\n\n- You can use both keyboard and mouse to play. But prefer to use \u003cu\u003ekeyboard\u003c/u\u003e\n- Press arrow left and right to change the speed of the game **(1 - 6)**. The higher the number, the faster the piece will fall\n- Press arrow up and down to change how many of lines have been filled before starting the game **(1 - 10)**\n- Press `Space` to start the game\n- Press `P` for pause/resume game\n- Press `R` for resetting the game\n- Press `S` for the turn on/off the sounds\n\n### Playing game\n\n- Press `Space` make the piece drop quickly\n- Press `Arrow left` and `right` for moving left and right\n- Press `Arrow up` to rotate the piece\n- Press `Arrow down` to move a piece faster\n- 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\n- The drop speed of the pieces increases with the number of rows eliminated (one level up for every 20 lines cleared)\n\n## Techstack\n\nI built it barely with Angular and Akita, no additional UI framework/library was required.\n\n![Angular Tetris][techstack]\n\n## Development Challenge\n\nI 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 on, I realized a few catches:\n\n- 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.\n- 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.\n- The brain of the game also used `setTimeout` for the game loop. It was not a problem, but I was having a \u003cu\u003ehard time\u003c/u\u003e 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].\n\n### Tetris Core\n\nIt 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.\n\nTake 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.\n\n![Angular Tetris][compare01]\n\nI 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.\n\n\u003e “ Code is like humor. When you have to explain it, it’s bad.” – Cory House\n\nAnd 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.\n\n### Akita state management + dev tool support\n\nAlthough you don't dispatch any action, Akita will still do it undo the hood as the Update action. And you still can see the data with [Redux DevTools][redux-devtool]. Remember to put that option into your `AppModule`\n\n```ts\nimports: [environment.production ? [] : AkitaNgDevtools.forRoot()];\n```\n\nI turn it on all the time on [tetris.trungk18.com][angular-tetris], you can open the DevTools and start seeing the data flow.\n\n![Angular Tetris][akita-devtool]\n\n\u003e Note: opening the DevTools could reduce the performance of the game significantly. I recommended you turn it off when you want to archive a high score 🤓\n\n### Customizing Piece\n\nI defined a base [Piece class][piece-class] for a piece. And for each type of piece, it will extend from the same base class to inherit the same capability\n\n[piece-class]: src/app/interface/piece/piece.ts\n\n```ts\nexport class Piece {\n  x: number;\n  y: number;\n  rotation = PieceRotation.Deg0;\n  type: PieceTypes;\n  shape: Shape;\n  next: Shape;\n\n  private shapes: Shapes;\n  private lastConfig: Partial\u003cPiece\u003e;\n\n  constructor(x: number, y: number) {\n    this.x = x;\n    this.y = y;\n  }\n\n  store(): Piece {\n    this.lastConfig = {\n      x: this.x,\n      y: this.y,\n      rotation: this.rotation,\n      shape: this.shape\n    };\n    return this.newPiece();\n  }\n\n  //code removed for brevity\n}\n```\n\nFor example, I have a piece L. I create a new class name [PieceL][piecel]. I will contain the shape of L in four different rotation so that I don't have to mess up with the math to do minus plus on the XY axis. And I think defining in that way makes the code self-express better. If you see 1, it means on the matrix it will be filled, 0 mean empty tile.\n\nIf my team member needs to maintain the code, I hope he will understand what I was trying to write immediately. Or maybe not 🤣\n\nOne import property of the Piece is the `next` property to display the piece shape on the decoration box for the upcoming piece.\n\n[piecel]: src/app/interface/piece/L.ts\n\n```ts\nconst ShapesL: Shapes = [];\nShapesL[PieceRotation.Deg0] = [\n  [0, 0, 0, 0],\n  [1, 0, 0, 0],\n  [1, 0, 0, 0],\n  [1, 1, 0, 0]\n];\n\nShapesL[PieceRotation.Deg90] = [\n  [0, 0, 0, 0],\n  [0, 0, 0, 0],\n  [1, 1, 1, 0],\n  [1, 0, 0, 0]\n];\n//code removed for brevity\n\nexport class PieceL extends Piece {\n  constructor(x: number, y: number) {\n    super(x, y);\n    this.type = PieceTypes.L;\n    this.next = [\n      [0, 0, 1, 0],\n      [1, 1, 1, 0]\n    ];\n    this.setShapes(ShapesL);\n  }\n}\n```\n\nNow is the interesting part, you create a custom piece by yourself. Simply create a new class that extends from `Piece` with different rotations.\n\nFor instance, I will define a new piece call F with class name [`PieceF`][piecef]. That is how it should look like.\n\n[piecef]: https://github.com/trungk18/angular-tetris/blob/feature/pieceF/src/app/interface/piece/F.ts\n\n```ts\nconst ShapesF: Shapes = [];\nShapesF[PieceRotation.Deg0] = [\n  [1, 0, 0, 0],\n  [1, 1, 0, 0],\n  [1, 0, 0, 0],\n  [1, 1, 0, 0]\n];\n\nexport class PieceF extends Piece {\n  constructor(x, y) {\n    super(x, y);\n    this.type = PieceTypes.F;\n    this.next = [\n      [1, 0, 1, 0],\n      [1, 1, 1, 1]\n    ];\n    this.setShapes(ShapesF);\n  }\n}\n```\n\nAnd the last step, go to [PieceFactory][piecefactory] to add the new PieceF into the available pieces.\n\n[piecefactory]: src/app/factory/piece-factory.ts\n\n```ts\nexport class PieceFactory {\n  private available: typeof Piece[] = [];\n\n  constructor() {\n    //code removed for brevity\n    this.available.push(PieceF);\n  }\n}\n```\n\nAnd you're all set, this is the result. See how easy it is to understand the code and add a custom piece that you like.\n\nThe source code for that custom piece F, you can see at [feature/pieceF][feature/piecef] branch.\n\n![Angular Tetris Piece F][piecef-demo]\n\n[feature/piecef]: https://github.com/trungk18/angular-tetris/tree/feature/pieceF\n[piecef-demo]: src/assets/readme/piecef-demo.gif\n\n### Animation\n\nI rewrote the animation with RxJS. See the comparison below for the simple dinosaurs running animation at the beginning of the game.\n\nYou 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.\n\n![Angular Tetris][compare02]\n\nThe actual result doesn't look very identical but it is good enough in my standard.\n\n![Angular Tetris][compare02-result]\n\n### Web Audio API\n\nThere 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].\n\nI don't have much experience working with audio before but the Web Audio API looks very promising. You could do more with it.\n\n- See the [official documentation][webaudio]\n- See how I load the mp3 file and store it in [sound-manager.service.ts][sound-manager]\n- [Writing Web Audio API code that works in every browser][web_audio_api_cross_browser]\n\n### Keyboard handling\n\nI planned to use [@ngneat/hotkeys][hotkeys] but I decided to use `@HostListener` instead. A simple implementation could look like:\n\n```typescript\n@HostListener(`${KeyDown}.${TetrisKeyboard.Left}`)\nkeyDownLeft() {\n  this.soundManager.move();\n  this.keyboardService.setKeỵ({\n    left: true\n  });\n  if (this.hasCurrent) {\n    this.tetrisService.moveLeft();\n  } else {\n    this.tetrisService.decreaseLevel();\n  }\n}\n```\n\nSee more at [containers/angular-tetris/angular-tetris.component.ts][hotkeys-implementation]\n\n## Features and Roadmap\n\n### Phase 1 - Angular Tetris basic functionality\n\n\u003e July 10 - 23, 2020\n\n- [x] Proven, scalable, and easy to understand project structure\n- [x] Basic Tetris functionality\n- [x] Six levels\n- [x] Local storage high score\n- [x] Sounds effects\n- [x] Limited mobile support\n\n### Phase 2 - Firebase high score, service worker, more sounds effect, more animation\n\n\u003e TBD\n\n- [ ] Fully mobile support\n- [ ] Offline mode (play without internet connection)\n- [ ] Firebase high score\n- [ ] More sound effects\n- [ ] More animations\n\n## Time spending\n\nI 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.\n\nAccording 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 😩\n\n![Angular Tetris][timespending]\n\nThe 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.\n\n## Setting up development environment 🛠\n\n- `git clone https://github.com/trungk18/angular-tetris.git`\n- `cd angular-tetris`\n- `npm start`\n- The app should run on `http://localhost:4200/`\n\n## Author: Trung Vo ✍️\n\n- A young and passionate front-end engineer. Working with Angular and TypeScript. Like photography, running, cooking, and reading books.\n- Author of Angular Jira clone -\u003e [jira.trungk18.com][jira-clone]\n- Personal blog: https://trungk18.com/\n- Say hello: trungk18 [et] gmail [dot] com\n\n## Credits and references\n\n| Resource                                      | Description                                                                                                                       |\n| --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |\n| [@Binaryify/vue-tetris][vue]                  | Vue Tetris, I reused part of HTML, CSS and static assets from that project                                                        |\n| [@chrum/ngx-tetris][ngx-tetris]               | A comprehensive core Tetris written with Angular, I reused part of that for the brain of the game.                                |\n| [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 that |\n| [Super Rotation System][srs]                  | A standard for how the piece behaves. I didn't follow everything but it is good to know as wells                                  |\n\n## Contributing\n\nIf you have any ideas, just [open an issue][issues] and tell me what you think.\n\nIf you'd like to contribute, please fork the repository and make changes as you'd like. [Pull requests][pull] are warmly welcome.\n\n## License\n\nFeel free to use my code on your project. It would be great if you put a reference to this repository.\n\n[MIT](https://opensource.org/licenses/MIT)\n\n[issues]: https://github.com/trungk18/angular-tetris/issues/new/choose\n[pull]: https://github.com/trungk18/angular-tetris/pulls\n[angular-tetris]: https://tetris.trungk18.com\n[medium]: https://medium.com/angular-in-depth/game-development-tetris-in-angular-64ef96ce56f7\n[srs]: https://tetris.fandom.com/wiki/SRS\n[vue]: https://github.com/Binaryify/vue-tetris\n[tetris]: src/assets/readme/retro-tetris.jpg\n[demo]: src/assets/readme/angular-tetris-demo.gif\n[iphonex]: src/assets/readme/angular-tetris-iphonex.gif\n[ngx-tetris]: https://github.com/chrum/ngx-tetris\n[techstack]: src/assets/readme/tech-stack.png\n[compare01]: src/assets/readme/compare01.png\n[compare02]: src/assets/readme/compare02.png\n[compare02-result]: src/assets/readme/compare02-result.gif\n[timespending]: src/assets/readme/time-spending.png\n[akita-devtool]: src/assets/readme/akita-devtool.gif\n[sounds]: src/assets/tetris-sound.mp3\n[sound-manager]: src/app/services/sound-manager.service.ts\n[webaudio]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API\n[redux-devtool]: https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?hl=en\n[hotkeys]: https://github.com/ngneat/hotkeys\n[hotkeys-implementation]: src/app/containers/angular-tetris/angular-tetris.component.ts\n[chautran]: https://github.com/nartc\n[jira-clone]: https://github.com/trungk18/jira-clone-angular\n[marathon]: https://www.strava.com/activities/2902245728\n[todolist]: https://www.notion.so/trungk18/Phase-1-be1ae0fbbf2c4c2fb92887e2218413db\n[web_audio_api_cross_browser]: https://developer.mozilla.org/en-US/docs/Web/Guide/Audio_and_video_delivery/Web_Audio_API_cross_browser\n","funding_links":["https://github.com/sponsors/trungk18","https://www.buymeacoffee.com/trungvose"],"categories":["TypeScript"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftrungvose%2Fangular-tetris","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftrungvose%2Fangular-tetris","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftrungvose%2Fangular-tetris/lists"}