├── .github
└── workflows
│ └── main.yml
├── .gitignore
├── Dockerfile
├── Eks-terraform
├── backend.tf
├── main.tf
└── provider.tf
├── Jenkins-CICD
├── Jenkinsfile
├── backend.tf
├── error.html
├── index.html
├── main.tf
├── outputs.tf
├── provider.tf
├── s3.tf
├── script.js
├── style.css
├── variables.tf
└── website.sh
├── Jenkins-terraform
├── Main.tf
├── backend.tf
├── install_jenkins.sh
└── provider.tf
├── README.md
├── deployment-service.yml
├── images
└── game.jpg
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
└── src
├── components
├── Cell.js
├── Cell.module.css
├── Game.js
├── Game.module.css
├── Game.test.js
├── Level.js
├── Next.js
├── Next.module.css
└── Score.js
├── helpers
├── Generators.js
└── Helpers.js
├── index.css
├── index.js
├── reportWebVitals.js
├── setupTests.js
├── tetris
├── Colors.js
├── Piece.js
├── State.js
├── Tetris.js
└── Tetromino.js
└── webpack.config.js
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: Deploy to EKS
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 |
8 | jobs:
9 | deploy:
10 | runs-on: [self-hosted]
11 |
12 | steps:
13 | - name: Checkout code
14 | uses: actions/checkout@v2
15 |
16 | - name: Update kubeconfig
17 | run: aws eks --region ap-south-1 update-kubeconfig --name EKS_CLOUD
18 |
19 | - name: Deploy to EKS
20 | run: kubectl delete -f deployment-service.yml
21 |
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | #ide
2 | #intellij
3 | .idea/
4 | # Logs
5 | logs
6 | *.log
7 | npm-debug.log*
8 | yarn-debug.log*
9 | yarn-error.log*
10 | lerna-debug.log*
11 |
12 | # Diagnostic reports (https://nodejs.org/api/report.html)
13 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
14 |
15 | # Runtime data
16 | pids
17 | *.pid
18 | *.seed
19 | *.pid.lock
20 |
21 | # Directory for instrumented libs generated by jscoverage/JSCover
22 | lib-cov
23 |
24 | # Coverage directory used by tools like istanbul
25 | coverage
26 | *.lcov
27 |
28 | # nyc test coverage
29 | .nyc_output
30 |
31 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
32 | .grunt
33 |
34 | # Bower dependency directory (https://bower.io/)
35 | bower_components
36 |
37 | # node-waf configuration
38 | .lock-wscript
39 |
40 | # Compiled binary addons (https://nodejs.org/api/addons.html)
41 | build/Release
42 |
43 |
44 | #build directory
45 | build
46 |
47 | # Dependency directories
48 | node_modules/
49 | jspm_packages/
50 |
51 | # TypeScript v1 declaration files
52 | typings/
53 |
54 | # TypeScript cache
55 | *.tsbuildinfo
56 |
57 | # Optional npm cache directory
58 | .npm
59 |
60 | # Optional eslint cache
61 | .eslintcache
62 |
63 | # Microbundle cache
64 | .rpt2_cache/
65 | .rts2_cache_cjs/
66 | .rts2_cache_es/
67 | .rts2_cache_umd/
68 |
69 | # Optional REPL history
70 | .node_repl_history
71 |
72 | # Output of 'npm pack'
73 | *.tgz
74 |
75 | # Yarn Integrity file
76 | .yarn-integrity
77 |
78 | # dotenv environment variables file
79 | .env
80 | .env.test
81 |
82 | # parcel-bundler cache (https://parceljs.org/)
83 | .cache
84 |
85 | # Next.js build output
86 | .next
87 |
88 | # Nuxt.js build / generate output
89 | .nuxt
90 | dist
91 |
92 | # Gatsby files
93 | .cache/
94 | # Comment in the public line in if your project uses Gatsby and *not* Next.js
95 | # https://nextjs.org/blog/next-9-1#public-directory-support
96 | # public
97 |
98 | # vuepress build output
99 | .vuepress/dist
100 |
101 | # Serverless directories
102 | .serverless/
103 |
104 | # FuseBox cache
105 | .fusebox/
106 |
107 | # DynamoDB Local files
108 | .dynamodb/
109 |
110 | # TernJS port file
111 | .tern-port
112 |
113 | #Mac
114 | .DS_Store
115 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # Use the official Node.js 16 image as the base image
2 | FROM node:16
3 |
4 | # Set the working directory inside the container
5 | WORKDIR /app
6 |
7 | # Copy package.json and package-lock.json (or yarn.lock) to the container
8 | COPY package*.json ./
9 |
10 | # Install project dependencies
11 | RUN npm install
12 |
13 | # Copy the rest of the application code to the container
14 | COPY . .
15 |
16 | # Build the React app
17 | RUN npm run build
18 |
19 | # Expose the port that the app will run on (usually 3000 by default)
20 | EXPOSE 3000
21 |
22 | # Start the React app when the container starts
23 | CMD [ "npm", "start" ]
24 |
--------------------------------------------------------------------------------
/Eks-terraform/backend.tf:
--------------------------------------------------------------------------------
1 | terraform {
2 | backend "s3" {
3 | bucket = "mario123bucket" # Replace with your actual S3 bucket name
4 | key = "Jenkins/terraform.tfstate"
5 | region = "ap-south-1"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/Eks-terraform/main.tf:
--------------------------------------------------------------------------------
1 | data "aws_iam_policy_document" "assume_role" {
2 | statement {
3 | effect = "Allow"
4 |
5 | principals {
6 | type = "Service"
7 | identifiers = ["eks.amazonaws.com"]
8 | }
9 |
10 | actions = ["sts:AssumeRole"]
11 | }
12 | }
13 |
14 | resource "aws_iam_role" "example" {
15 | name = "eks-cluster-cloud"
16 | assume_role_policy = data.aws_iam_policy_document.assume_role.json
17 | }
18 |
19 | resource "aws_iam_role_policy_attachment" "example-AmazonEKSClusterPolicy" {
20 | policy_arn = "arn:aws:iam::aws:policy/AmazonEKSClusterPolicy"
21 | role = aws_iam_role.example.name
22 | }
23 |
24 | #get vpc data
25 | data "aws_vpc" "default" {
26 | default = true
27 | }
28 | #get public subnets for cluster
29 | data "aws_subnets" "public" {
30 | filter {
31 | name = "vpc-id"
32 | values = [data.aws_vpc.default.id]
33 | }
34 | }
35 | #cluster provision
36 | resource "aws_eks_cluster" "example" {
37 | name = "EKS_CLOUD"
38 | role_arn = aws_iam_role.example.arn
39 |
40 | vpc_config {
41 | subnet_ids = data.aws_subnets.public.ids
42 | }
43 |
44 | # Ensure that IAM Role permissions are created before and deleted after EKS Cluster handling.
45 | # Otherwise, EKS will not be able to properly delete EKS managed EC2 infrastructure such as Security Groups.
46 | depends_on = [
47 | aws_iam_role_policy_attachment.example-AmazonEKSClusterPolicy,
48 | ]
49 | }
50 |
51 | resource "aws_iam_role" "example1" {
52 | name = "eks-node-group-cloud"
53 |
54 | assume_role_policy = jsonencode({
55 | Statement = [{
56 | Action = "sts:AssumeRole"
57 | Effect = "Allow"
58 | Principal = {
59 | Service = "ec2.amazonaws.com"
60 | }
61 | }]
62 | Version = "2012-10-17"
63 | })
64 | }
65 |
66 | resource "aws_iam_role_policy_attachment" "example-AmazonEKSWorkerNodePolicy" {
67 | policy_arn = "arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy"
68 | role = aws_iam_role.example1.name
69 | }
70 |
71 | resource "aws_iam_role_policy_attachment" "example-AmazonEKS_CNI_Policy" {
72 | policy_arn = "arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy"
73 | role = aws_iam_role.example1.name
74 | }
75 |
76 | resource "aws_iam_role_policy_attachment" "example-AmazonEC2ContainerRegistryReadOnly" {
77 | policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly"
78 | role = aws_iam_role.example1.name
79 | }
80 |
81 | #create node group
82 | resource "aws_eks_node_group" "example" {
83 | cluster_name = aws_eks_cluster.example.name
84 | node_group_name = "Node-cloud"
85 | node_role_arn = aws_iam_role.example1.arn
86 | subnet_ids = data.aws_subnets.public.ids
87 |
88 | scaling_config {
89 | desired_size = 1
90 | max_size = 2
91 | min_size = 1
92 | }
93 | instance_types = ["t2.medium"]
94 |
95 | # Ensure that IAM Role permissions are created before and deleted after EKS Node Group handling.
96 | # Otherwise, EKS will not be able to properly delete EC2 Instances and Elastic Network Interfaces.
97 | depends_on = [
98 | aws_iam_role_policy_attachment.example-AmazonEKSWorkerNodePolicy,
99 | aws_iam_role_policy_attachment.example-AmazonEKS_CNI_Policy,
100 | aws_iam_role_policy_attachment.example-AmazonEC2ContainerRegistryReadOnly,
101 | ]
102 | }
103 |
--------------------------------------------------------------------------------
/Eks-terraform/provider.tf:
--------------------------------------------------------------------------------
1 | terraform {
2 | required_providers {
3 | aws = {
4 | source = "hashicorp/aws"
5 | version = "~> 5.0"
6 | }
7 | }
8 | }
9 |
10 | # Configure the AWS Provider
11 | provider "aws" {
12 | region = "ap-south-1"
13 | }
14 |
--------------------------------------------------------------------------------
/Jenkins-CICD/Jenkinsfile:
--------------------------------------------------------------------------------
1 | pipeline{
2 | agent any
3 | tools{
4 | jdk 'jdk17'
5 | terraform 'terraform'
6 | }
7 | stages{
8 | stage('clean Workspace'){
9 | steps{
10 | cleanWs()
11 | }
12 | }
13 | stage('checkout from Git'){
14 | steps{
15 | git branch: 'main', url: 'https://github.com/Aj7Ay/TERRAFORM-JENKINS-CICD.git'
16 | }
17 | }
18 | stage('Terraform version'){
19 | steps{
20 | sh 'terraform --version'
21 | }
22 | }
23 | stage("Sonarqube Analysis "){
24 | steps{
25 | withSonarQubeEnv('sonar-server') {
26 | sh ''' $SCANNER_HOME/bin/sonar-scanner -Dsonar.projectName=Terraform \
27 | -Dsonar.projectKey=Terraform '''
28 | }
29 | }
30 | }
31 | stage("quality gate"){
32 | steps {
33 | script {
34 | waitForQualityGate abortPipeline: false, credentialsId: 'Sonar-token'
35 | }
36 | }
37 | }
38 | stage('TRIVY FS SCAN') {
39 | steps {
40 | sh "trivy fs . > trivyfs.txt"
41 | }
42 | }
43 | stage('Excutable permission to userdata'){
44 | steps{
45 | sh 'chmod 777 website.sh'
46 | }
47 | }
48 | stage('Terraform init'){
49 | steps{
50 | sh 'terraform init'
51 | }
52 | }
53 | stage('Terraform plan'){
54 | steps{
55 | sh 'terraform plan'
56 | }
57 | }
58 | stage('Terraform apply'){
59 | steps{
60 | sh 'terraform ${action} --auto approve'
61 | }
62 | }
63 | }
64 | }
--------------------------------------------------------------------------------
/Jenkins-CICD/backend.tf:
--------------------------------------------------------------------------------
1 | terraform {
2 | backend "s3" {
3 | bucket = var.bucket_name
4 | key = "my-terraform-environment/main"
5 | region = var.aws_region
6 | dynamodb_table = var.dynamodb_table
7 | }
8 | }
--------------------------------------------------------------------------------
/Jenkins-CICD/error.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
14 |
15 |
16 |
17 |
25 |
31 |
32 |
35 |
36 |
41 |
42 |
43 |
73 |
85 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/Jenkins-CICD/main.tf:
--------------------------------------------------------------------------------
1 | resource "aws_instance" "ec2_instance" {
2 | ami = var.ami_id
3 | instance_type = var.instance_type
4 | key_name = var.key_name
5 | vpc_security_group_ids = [aws_security_group.ec2_security_group.id]
6 | user_data = base64encode(file("website.sh"))
7 | tags = {
8 | Name = "aws-ec2-instance"
9 | }
10 | }
11 |
12 | resource "aws_security_group" "ec2_security_group" {
13 | name = "ec2 security group"
14 | description = "allow access on ports 80 and 22 and 443"
15 |
16 | ingress = [
17 | for port in [22, 80, 443] : {
18 | description = "TLS from VPC"
19 | from_port = port
20 | to_port = port
21 | protocol = "tcp"
22 | cidr_blocks = ["0.0.0.0/0"]
23 | ipv6_cidr_blocks = []
24 | prefix_list_ids = []
25 | security_groups = []
26 | self = false
27 | }
28 | ]
29 |
30 | egress {
31 | from_port = 0
32 | to_port = 0
33 | protocol = "-1"
34 | cidr_blocks = ["0.0.0.0/0"]
35 | ipv6_cidr_blocks = ["::/0"]
36 | }
37 |
38 | tags = {
39 | Name = "ec2-sg"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Jenkins-CICD/outputs.tf:
--------------------------------------------------------------------------------
1 | output "websiteendpoint" {
2 | value = aws_s3_bucket.s3storage.website_endpoint
3 | }
4 |
5 | output "public_ip" {
6 | value = aws_instance.ec2_instance.public_ip
7 | }
--------------------------------------------------------------------------------
/Jenkins-CICD/provider.tf:
--------------------------------------------------------------------------------
1 | terraform {
2 | required_providers {
3 | aws = {
4 | source = "hashicorp/aws"
5 | version = "~> 4.16"
6 | }
7 | }
8 | required_version = ">= 1.2.0"
9 | }
10 |
11 | provider "aws" {
12 | region = var.aws_region
13 | }
--------------------------------------------------------------------------------
/Jenkins-CICD/s3.tf:
--------------------------------------------------------------------------------
1 | resource "aws_s3_bucket" "s3storage" {
2 | bucket = var.bucket_name
3 | }
4 | resource "aws_s3_bucket_ownership_controls" "s3storage" {
5 | bucket = aws_s3_bucket.s3storage.id
6 | rule {
7 | object_ownership = "BucketOwnerPreferred"
8 | }
9 | }
10 |
11 | resource "aws_s3_bucket_public_access_block" "s3storage" {
12 | bucket = aws_s3_bucket.s3storage.id
13 | block_public_acls = false
14 | block_public_policy = false
15 | ignore_public_acls = false
16 | restrict_public_buckets = false
17 | }
18 |
19 | resource "aws_s3_bucket_acl" "s3storage" {
20 | depends_on = [
21 | aws_s3_bucket_ownership_controls.s3storage,
22 | aws_s3_bucket_public_access_block.s3storage,
23 | ]
24 | bucket = aws_s3_bucket.s3storage.id
25 | acl = "public-read"
26 | }
27 |
28 | resource "aws_s3_object" "index" {
29 | bucket = aws_s3_bucket.s3storage
30 | key = "index.html"
31 | source = "index.html"
32 | acl = "public-read"
33 | content_type = "text/html"
34 | }
35 |
36 |
37 | resource "aws_s3_object" "error" {
38 | bucket = aws_s3_bucket.s3storage
39 | key = "error.html"
40 | source = "error.html"
41 | acl = "public-read"
42 | content_type = "text/html"
43 | }
44 |
45 |
46 | resource "aws_s3_object" "style" {
47 | bucket = aws_s3_bucket.s3storage.id
48 | key = "style.css"
49 | source = "style.css"
50 | acl = "public-read"
51 | content_type = "text/css"
52 | }
53 |
54 |
55 | resource "aws_s3_object" "script" {
56 | bucket = aws_s3_bucket.s3storage.id
57 | key = "script.js"
58 | source = "script.js"
59 | acl = "public-read"
60 | content_type = "text/javascript"
61 | }
62 |
63 |
64 | resource "aws_s3_bucket_website_configuration" "website" {
65 | bucket = aws_s3_bucket.s3storage.id
66 | index_document {
67 | suffix = "index.html"
68 | }
69 |
70 | error_document {
71 | key = "error.html"
72 | }
73 |
74 | depends_on = [aws_s3_bucket_acl.s3storage.id]
75 | }
76 |
77 |
--------------------------------------------------------------------------------
/Jenkins-CICD/script.js:
--------------------------------------------------------------------------------
1 | let usernameInput = document.querySelector('.username');
2 | let passwordInput = document.querySelector('.password');
3 | let showPasswordButton = document.querySelector('.password-button');
4 | let face = document.querySelector('.face');
5 |
6 | passwordInput.addEventListener('focus', event => {
7 | document.querySelectorAll('.hand').forEach(hand => {
8 | hand.classList.add('hide');
9 | });
10 | document.querySelector('.tongue').classList.remove('breath');
11 | });
12 |
13 | passwordInput.addEventListener('blur', event => {
14 | document.querySelectorAll('.hand').forEach(hand => {
15 | hand.classList.remove('hide');
16 | hand.classList.remove('peek');
17 | });
18 | document.querySelector('.tongue').classList.add('breath');
19 | });
20 |
21 | usernameInput.addEventListener('focus', event => {
22 | let length = Math.min(usernameInput.value.length - 16, 19);
23 | document.querySelectorAll('.hand').forEach(hand => {
24 | hand.classList.remove('hide');
25 | hand.classList.remove('peek');
26 | });
27 |
28 | face.style.setProperty('--rotate-head', `${-length}deg`);
29 | });
30 |
31 | usernameInput.addEventListener('blur', event => {
32 | face.style.setProperty('--rotate-head', '0deg');
33 | });
34 |
35 | usernameInput.addEventListener('input', _.throttle(event => {
36 | let length = Math.min(event.target.value.length - 16, 19);
37 |
38 | face.style.setProperty('--rotate-head', `${-length}deg`);
39 | }, 100));
40 |
41 | showPasswordButton.addEventListener('click', event => {
42 | if (passwordInput.type === 'text') {
43 | passwordInput.type = 'password';
44 | document.querySelectorAll('.hand').forEach(hand => {
45 | hand.classList.remove('peek');
46 | hand.classList.add('hide');
47 | });
48 | } else {
49 | passwordInput.type = 'text';
50 | document.querySelectorAll('.hand').forEach(hand => {
51 | hand.classList.remove('hide');
52 | hand.classList.add('peek');
53 | });
54 | }
55 | });
56 |
--------------------------------------------------------------------------------
/Jenkins-CICD/style.css:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | }
4 | body {
5 | width: 100vw;
6 | height: 100vh;
7 | background-color: rgb(41, 0, 75);
8 | overflow: hidden;
9 | font-size: 12px;
10 | }
11 | .inspiration {
12 | position: fixed;
13 | bottom: 0;
14 | right: 0;
15 | padding: 10px;
16 | text-align: center;
17 | text-decoration: none;
18 | font-family: 'Gill Sans', sans-serif;
19 | font-size: 12px;
20 | color: #969696;
21 | }
22 | .inspiration img {
23 | width: 60px;
24 | }
25 | .center {
26 | position: relative;
27 | top: 50%;
28 | left: 50%;
29 | display: inline-block;
30 | width: 275px;
31 | height: 490px;
32 | border-radius: 3px;
33 | transform: translate(-50%, -50%);
34 | overflow: hidden;
35 | background-image: linear-gradient(to top right, rgb(0 168 255), rgb(249 95 230));
36 | }
37 | @media screen and (max-height: 500px) {
38 | .center {
39 | transition: transform 0.5s;
40 | transform: translate(-50%, -50%) scale(0.8);
41 | }
42 | }
43 | .center .ear {
44 | position: absolute;
45 | top: -110px;
46 | width: 200px;
47 | height: 200px;
48 | border-radius: 50%;
49 | background-color: rgb(50 22 22);
50 | }
51 | .center .ear.ear--left {
52 | left: -135px;
53 | }
54 | .center .ear.ear--right {
55 | right: -135px;
56 | }
57 | .center .face {
58 | display: flex;
59 | flex-direction: column;
60 | align-items: center;
61 | width: 200px;
62 | height: 150px;
63 | margin: 80px auto 10px;
64 | --rotate-head: 0deg;
65 | transform: rotate(var(--rotate-head));
66 | transition: transform 0.2s;
67 | transform-origin: center 20px;
68 | }
69 | .center .eye {
70 | display: inline-block;
71 | width: 25px;
72 | height: 25px;
73 | border-radius: 50%;
74 | background-color: #243946;
75 | }
76 | .center .eye.eye--left {
77 | margin-right: 40px;
78 | }
79 | .center .eye.eye--right {
80 | margin-left: 40px;
81 | }
82 | .center .eye .glow {
83 | position: relative;
84 | top: 3px;
85 | right: -12px;
86 | width: 12px;
87 | height: 6px;
88 | border-radius: 50%;
89 | background-color: #fff;
90 | transform: rotate(38deg);
91 | }
92 | .center .nose {
93 | position: relative;
94 | top: 30px;
95 | transform: scale(1.1);
96 | }
97 | .center .nose .glow {
98 | position: absolute;
99 | top: 3px;
100 | left: 32%;
101 | width: 15px;
102 | height: 8px;
103 | border-radius: 50%;
104 | background-color: #476375;
105 | }
106 | .center .mouth {
107 | position: relative;
108 | margin-top: 45px;
109 | }
110 | .center svg.smile {
111 | position: absolute;
112 | left: -28px;
113 | top: -19px;
114 | transform: scaleX(1.1);
115 | stroke: #243946;
116 | }
117 | .center .mouth-hole {
118 | position: absolute;
119 | top: 0;
120 | left: -50%;
121 | width: 60px;
122 | height: 15px;
123 | border-radius: 50%/100% 100% 0% 0;
124 | transform: rotate(180deg);
125 | background-color: #243946;
126 | z-index: -1;
127 | }
128 | .center .tongue {
129 | position: relative;
130 | top: 5px;
131 | width: 30px;
132 | height: 20px;
133 | background-color: #ffd7dd;
134 | transform-origin: top;
135 | transform: rotateX(60deg);
136 | }
137 | .center .tongue.breath {
138 | -webkit-animation: breath 0.3s infinite linear;
139 | animation: breath 0.3s infinite linear;
140 | }
141 | .center .tongue-top {
142 | position: absolute;
143 | bottom: -15px;
144 | width: 30px;
145 | height: 30px;
146 | border-radius: 15px;
147 | background-color: #ffd7dd;
148 | }
149 | .center .line {
150 | position: absolute;
151 | top: 0;
152 | width: 30px;
153 | height: 5px;
154 | background-color: #fcb7bf;
155 | }
156 | .center .median {
157 | position: absolute;
158 | top: 0;
159 | left: 50%;
160 | transform: translateX(-50%);
161 | width: 4px;
162 | height: 25px;
163 | border-radius: 5px;
164 | background-color: #fcb7bf;
165 | }
166 | .center .hands {
167 | position: relative;
168 | }
169 | .center .hands .hand {
170 | position: absolute;
171 | top: -6px;
172 | display: flex;
173 | transition: transform 0.5s ease-in-out;
174 | z-index: 1;
175 | }
176 | .center .hands .hand--left {
177 | left: 50px;
178 | }
179 | .center .hands .hand--left.hide {
180 | transform: translate(2px, -155px) rotate(-160deg);
181 | }
182 | .center .hands .hand--left.peek {
183 | transform: translate(0px, -120px) rotate(-160deg);
184 | }
185 | .center .hands .hand--right {
186 | left: 170px;
187 | }
188 | .center .hands .hand--right.hide {
189 | transform: translate(-6px, -155px) rotate(160deg);
190 | }
191 | .center .hands .hand--right.peek {
192 | transform: translate(-4px, -120px) rotate(160deg);
193 | }
194 | .center .hands .finger {
195 | position: relative;
196 | z-index: 0;
197 | }
198 | .center .hands .finger .bone {
199 | width: 20px;
200 | height: 20px;
201 | border: 2px solid #243946;
202 | border-bottom: none;
203 | border-top: none;
204 | background-color: rgb(255 211 11);
205 | }
206 | .center .hands .finger .nail {
207 | position: absolute;
208 | left: 0;
209 | top: 10px;
210 | width: 20px;
211 | height: 18px;
212 | border-radius: 50%;
213 | border: 2px solid #243946;
214 | background-color: #fac555;
215 | z-index: -1;
216 | }
217 | .center .hands .finger:nth-child(1),
218 | .center .hands .finger:nth-child(3) {
219 | left: 4px;
220 | z-index: 1;
221 | }
222 | .center .hands .finger:nth-child(1) .bone,
223 | .center .hands .finger:nth-child(3) .bone {
224 | height: 10px;
225 | }
226 | .center .hands .finger:nth-child(3) {
227 | left: -4px;
228 | }
229 | .center .hands .finger:nth-child(2) {
230 | top: -5px;
231 | z-index: 2;
232 | }
233 | .center .hands .finger:nth-child(1) .nail,
234 | .center .hands .finger:nth-child(3) .nail {
235 | top: 0px;
236 | }
237 | .center .login {
238 | position: relative;
239 | display: flex;
240 | flex-direction: column;
241 | }
242 | .center .login label {
243 | position: relative;
244 | padding: 0 20px;
245 | }
246 | .center .login label .fa {
247 | position: absolute;
248 | top: 40%;
249 | left: 35px;
250 | color: #bbb;
251 | }
252 | .center .login label .fa:before {
253 | position: relative;
254 | left: 1px;
255 | }
256 | .center .login input,
257 | .center .login .login-button {
258 | width: 100%;
259 | height: 35px;
260 | border: none;
261 | border-radius: 30px;
262 | }
263 | .center .login input {
264 | padding: 0 20px 0 40px;
265 | margin: 5px 0;
266 | box-shadow: none;
267 | outline: none;
268 | }
269 | .center .login input::-moz-placeholder {
270 | color: #ccc;
271 | }
272 | .center .login input:-ms-input-placeholder {
273 | color: #ccc;
274 | }
275 | .center .login input::placeholder {
276 | color: #ccc;
277 | }
278 | .center .login input.password {
279 | padding: 0 90px 0 40px;
280 | }
281 | .center .login .password-button {
282 | position: absolute;
283 | top: 9px;
284 | right: 25px;
285 | display: flex;
286 | justify-content: center;
287 | align-items: center;
288 | width: 80px;
289 | height: 27px;
290 | border-radius: 30px;
291 | border: none;
292 | outline: none;
293 | background-color: #243946;
294 | color: #fff;
295 | }
296 | .center .login .password-button:active {
297 | transform: scale(0.95);
298 | }
299 | .center .login .login-button {
300 | width: calc(100% - 40px);
301 | margin: 20px 20px 0;
302 | outline: none;
303 | background-color: #243946;
304 | color: #fff;
305 | transition: transform 0.1s;
306 | }
307 | .center .login .login-button:active {
308 | transform: scale(0.95);
309 | }
310 | .center .social-buttons {
311 | display: flex;
312 | justify-content: center;
313 | margin-top: 25px;
314 | }
315 | .center .social-buttons .social {
316 | display: flex;
317 | justify-content: center;
318 | align-items: center;
319 | width: 35px;
320 | height: 35px;
321 | margin: 0 10px;
322 | border-radius: 50%;
323 | background-color: #243946;
324 | color: #fff;
325 | font-size: 18px;
326 | }
327 | .center .social-buttons .social:active {
328 | transform: scale(0.95);
329 | }
330 | .center .footer {
331 | text-align: center;
332 | margin-top: 15px;
333 | }
334 | @-webkit-keyframes breath {
335 | 0%, 100% {
336 | transform: rotateX(0deg);
337 | }
338 | 50% {
339 | transform: rotateX(60deg);
340 | }
341 | }
342 | @keyframes breath {
343 | 0%, 100% {
344 | transform: rotateX(0deg);
345 | }
346 | 50% {
347 | transform: rotateX(60deg);
348 | }
349 | }
--------------------------------------------------------------------------------
/Jenkins-CICD/variables.tf:
--------------------------------------------------------------------------------
1 | variable "aws_region" {
2 | description = "The AWS region to create things in."
3 | default = "ap-southeast-2"
4 | }
5 | variable "key_name" {
6 | description = " SSH keys to connect to ec2 instance"
7 | default = "purplehaze"
8 | }
9 | variable "instance_type" {
10 | description = "instance type for ec2"
11 | default = "t2.medium"
12 | }
13 | variable "ami_id" {
14 | description = "AMI for Ubuntu Ec2 instance"
15 | default = "ami-0df4b2961410d4cff"
16 | }
17 | variable "bucket_name" {
18 | description = "The name of the S3 bucket to create"
19 | type = string
20 | default = "purplehaze777"
21 | }
22 |
23 | variable "dynamodb_table" {
24 | description = "The name of the dynamodb table"
25 | type = string
26 | default = "purplehaze777-dynamodb-table"
27 | }
28 |
29 | variable "acl" {
30 | description = "The ACL (Access Control List) for the S3 bucket"
31 | type = string
32 | default = "private"
33 | }
--------------------------------------------------------------------------------
/Jenkins-CICD/website.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Update the package manager and install Docker
4 | sudo apt-get update -y
5 | sudo apt-get install -y docker.io
6 |
7 | # Start the Docker service
8 | sudo systemctl start docker
9 |
10 | # Enable Docker to start on boot
11 | sudo systemctl enable docker
12 |
13 | # Pull and run a simple Nginx web server container
14 | sudo docker run -d --name zomato -p 3000:3000 sevenajay/zomato:latest
15 | sudo docker run -d --name netflix -p 8081:80 sevenajay/netflix:latest
--------------------------------------------------------------------------------
/Jenkins-terraform/Main.tf:
--------------------------------------------------------------------------------
1 | resource "aws_iam_role" "example_role" {
2 | name = "Jenkins-terraform"
3 | assume_role_policy = <
/dev/null
9 | echo deb [signed-by=/usr/share/keyrings/jenkins-keyring.asc] https://pkg.jenkins.io/debian-stable binary/ | sudo tee /etc/apt/sources.list.d/jenkins.list > /dev/null
10 | sudo apt-get update -y
11 | sudo apt-get install jenkins -y
12 | sudo systemctl start jenkins
13 | sudo systemctl status jenkins
14 |
15 | #install docker
16 | sudo apt-get update
17 | sudo apt-get install docker.io -y
18 | sudo usermod -aG docker ubuntu
19 | sudo usermod -aG docker jenkins
20 | newgrp docker
21 | sudo chmod 777 /var/run/docker.sock
22 | docker run -d --name sonar -p 9000:9000 sonarqube:lts-community
23 |
24 | # install trivy
25 | sudo apt-get install wget apt-transport-https gnupg lsb-release -y
26 | wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | gpg --dearmor | sudo tee /usr/share/keyrings/trivy.gpg > /dev/null
27 | echo "deb [signed-by=/usr/share/keyrings/trivy.gpg] https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main" | sudo tee -a /etc/apt/sources.list.d/trivy.list
28 | sudo apt-get update
29 | sudo apt-get install trivy -y
30 |
31 | #install terraform
32 | sudo apt install wget -y
33 | wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
34 | echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
35 | sudo apt update && sudo apt install terraform
36 |
37 | #install Kubectl on Jenkins
38 | sudo apt update
39 | sudo apt install curl -y
40 | curl -LO https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl
41 | sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
42 | kubectl version --client
43 |
44 | #install Aws cli
45 | curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
46 | sudo apt-get install unzip -y
47 | unzip awscliv2.zip
48 | sudo ./aws/install
49 |
50 |
--------------------------------------------------------------------------------
/Jenkins-terraform/provider.tf:
--------------------------------------------------------------------------------
1 | terraform {
2 | required_providers {
3 | aws = {
4 | source = "hashicorp/aws"
5 | version = "~> 5.0"
6 | }
7 | }
8 | }
9 |
10 | # Configure the AWS Provider
11 | provider "aws" {
12 | region = "ap-south-1"
13 | }
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React Tetris V1
2 |
3 | Tetris game built with React
4 |
5 |
6 |
7 |
8 |
9 |
10 | Use Sonarqube block
11 | ```
12 | environment {
13 | SCANNER_HOME=tool 'sonar-scanner'
14 | }
15 |
16 | stage("Sonarqube Analysis "){
17 | steps{
18 | withSonarQubeEnv('sonar-server') {
19 | sh ''' $SCANNER_HOME/bin/sonar-scanner -Dsonar.projectName=Amazon \
20 | -Dsonar.projectKey=Amazon '''
21 | }
22 | }
23 | }
24 | ```
25 |
26 | Owasp block
27 | ```
28 | stage('OWASP FS SCAN') {
29 | steps {
30 | dependencyCheck additionalArguments: '--scan ./ --disableYarnAudit --disableNodeAudit', odcInstallation: 'DP-Check'
31 | dependencyCheckPublisher pattern: '**/dependency-check-report.xml'
32 | }
33 | }
34 | ```
35 |
36 | # ARGO CD SETUP
37 | https://archive.eksworkshop.com/intermediate/290_argocd/install/
38 |
39 | # Image updater stage
40 | ```
41 | environment {
42 | GIT_REPO_NAME = "Tetris-deployment-file"
43 | GIT_USER_NAME = "Aakibgithuber"
44 | }
45 | stage('Checkout Code') {
46 | steps {
47 | git branch: 'main', url: 'https://github.com/Aakibgithuber/Tetris-gamev1.git'
48 | }
49 | }
50 |
51 | stage('Update Deployment File') {
52 | steps {
53 | script {
54 | withCredentials([string(credentialsId: 'github', variable: 'GITHUB_TOKEN')]) {
55 | // Determine the image name dynamically based on your versioning strategy
56 | NEW_IMAGE_NAME = "aakibkhan1212/tetrisv1:latest"
57 |
58 | // Replace the image name in the deployment.yaml file
59 | sh "sed -i 's|image: .*|image: $NEW_IMAGE_NAME|' deployment.yml"
60 |
61 | // Git commands to stage, commit, and push the changes
62 | sh 'git add deployment.yml'
63 | sh "git commit -m 'Update deployment image to $NEW_IMAGE_NAME'"
64 | sh "git push https://${GITHUB_TOKEN}@github.com/${GIT_USER_NAME}/${GIT_REPO_NAME} HEAD:main"
65 | }
66 | }
67 | }
68 | }
69 |
70 | ```
71 |
--------------------------------------------------------------------------------
/deployment-service.yml:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: apps/v1
3 | kind: Deployment
4 | metadata:
5 | name: tetris
6 | spec:
7 | replicas: 3
8 | selector:
9 | matchLabels:
10 | app: tetris
11 | template:
12 | metadata:
13 | labels:
14 | app: tetris
15 | spec:
16 | containers:
17 | - name: tetris
18 | image: sevenajay/tetrisv2:latest
19 | ports:
20 | - containerPort: 3000 # Use port 3000
21 |
22 | ---
23 | apiVersion: v1
24 | kind: Service
25 | metadata:
26 | name: tetris-service
27 | spec:
28 | selector:
29 | app: tetris
30 | ports:
31 | - protocol: TCP
32 | port: 80 # Expose port 80
33 | targetPort: 3000
34 | type: LoadBalancer
35 |
--------------------------------------------------------------------------------
/images/game.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Aakibgithuber/Tetris-gamev1/50fdded4a58ed49b22cdc9d41051107ab22f893d/images/game.jpg
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tetris-react-js",
3 | "version": "0.1.0",
4 | "homepage": "./",
5 | "private": true,
6 | "dependencies": {
7 | "@testing-library/jest-dom": "^5.11.10",
8 | "@testing-library/react": "^11.2.6",
9 | "@testing-library/user-event": "^12.8.3",
10 | "react": "^17.0.2",
11 | "react-dom": "^17.0.2",
12 | "react-scripts": "4.0.3",
13 | "web-vitals": "^1.1.1"
14 | },
15 | "scripts": {
16 | "start": "react-scripts start",
17 | "build": "react-scripts build",
18 | "test": "react-scripts test",
19 | "eject": "react-scripts eject"
20 | },
21 | "eslintConfig": {
22 | "extends": [
23 | "react-app",
24 | "react-app/jest"
25 | ]
26 | },
27 | "browserslist": {
28 | "production": [
29 | ">0.2%",
30 | "not dead",
31 | "not op_mini all"
32 | ],
33 | "development": [
34 | "last 1 chrome version",
35 | "last 1 firefox version",
36 | "last 1 safari version"
37 | ]
38 | },
39 | "devDependencies": {
40 | "css-loader": "^4.3.0"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Aakibgithuber/Tetris-gamev1/50fdded4a58ed49b22cdc9d41051107ab22f893d/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Aakibgithuber/Tetris-gamev1/50fdded4a58ed49b22cdc9d41051107ab22f893d/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Aakibgithuber/Tetris-gamev1/50fdded4a58ed49b22cdc9d41051107ab22f893d/public/logo512.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/src/components/Cell.js:
--------------------------------------------------------------------------------
1 | import styles from "./Cell.module.css";
2 | import React, { useEffect, useState } from 'react';
3 | import {tetromino2Class} from "../helpers/Helpers";
4 | export const Cell = (props) => {
5 | const [tetromino, setTetromino] = useState(props.tetromino);
6 |
7 | useEffect(() => {
8 | setTetromino(props.tetromino);
9 | }, [props.tetromino])
10 |
11 | return (
12 |
13 | )
14 | }
15 |
16 | export default Cell;
17 |
--------------------------------------------------------------------------------
/src/components/Cell.module.css:
--------------------------------------------------------------------------------
1 | .tetromino {
2 | float: left; /* fix for buggy browsers */
3 | display: table-column;
4 | width: 25px;
5 | height: 25px;
6 | /* border: 1px solid; */
7 | margin: 1px;
8 | }
9 | .empty {
10 | background-color: lightgray;
11 | }
12 |
13 | .red {
14 | background-color: red;
15 | }
16 |
17 | .green {
18 | background-color: green;
19 | }
20 |
21 | .blue {
22 | background-color: dodgerblue;
23 | }
24 |
25 | .yellow {
26 | background-color: #ffbc31;
27 | }
--------------------------------------------------------------------------------
/src/components/Game.js:
--------------------------------------------------------------------------------
1 | import Next from "./Next.js";
2 | import Score from "./Score.js";
3 | import Level from "./Level.js";
4 | import {createMatrix} from "../helpers/Helpers";
5 | import React, {useState, useEffect} from 'react';
6 | import styles from "./Game.module.css";
7 | import {emtpyPiece} from "../tetris/Piece";
8 |
9 | export const Game = (props) => {
10 | const tetris = props.tetris;
11 | const [state, setState] = useState(props.tetris.state);
12 |
13 | const onStateChange = (_state) => {
14 | setState(_state);
15 | };
16 |
17 | useEffect(() => {
18 | tetris.onStateChange(onStateChange);
19 | document.title ="React Tetris";
20 | }, [tetris]);
21 |
22 |
23 | let resumePauseButton;
24 | if (state.isStarted()) {
25 | if (state.isRunning()) {
26 | resumePauseButton =
27 |
28 | } else {
29 | resumePauseButton =
30 |
31 | }
32 | }
33 |
34 |
35 | const matrix = createMatrix(state.visibleMatrix(), styles);
36 |
37 | const nextPiece = state.nextPiece();
38 | const visibleNextPiece = nextPiece ? nextPiece : emtpyPiece();
39 |
40 | const gameOver = state.isGameOver() ?
41 |
44 | :
45 | '';
46 |
47 | return (
48 |
49 |
React Tetris
50 |
51 |
52 | {matrix}
53 | {gameOver}
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | {resumePauseButton}
71 |
72 |
73 |
74 |
75 | Start/Pause/Resume: Space
76 | Rotate: Arrow Up
77 | Left: Arrow Left
78 | Right: Arrow Right
79 | Soft Drop: Arrow Down
80 |
81 |
82 |
83 |
84 | );
85 |
86 | }
87 |
88 | export default Game;
89 |
90 |
--------------------------------------------------------------------------------
/src/components/Game.module.css:
--------------------------------------------------------------------------------
1 | .content {
2 | position: absolute;
3 | top: 50%;
4 | left: 50%;
5 | transform: translate(-50%, -50%);
6 | display: grid;
7 | grid-template-columns:
8 | [left] auto
9 | [right] 180px;
10 | }
11 |
12 | .header {
13 | grid-column: left / right;
14 | font-size: 32px;
15 | color: dimgray;
16 | }
17 |
18 | .matrix {
19 | grid-column: left;
20 | width: auto;
21 | background-color: #eee;
22 | padding: 1px;
23 | display: table;
24 | /*float: left;*/
25 | border: 1px black solid;
26 | }
27 |
28 | .controls {
29 | /*float: left;*/
30 | display: grid;
31 | grid-template-rows:
32 | [next] 160px
33 | [level] 70px
34 | [score] 70px
35 | [buttons] 120px
36 | [info ] 60px;
37 | padding-left: 15px;
38 | padding-right: 15px;
39 | background-color: #cbf0cc;
40 | grid-column: right;
41 | grid-row: 2 / 2;
42 | background-image: linear-gradient(to top, #cfd9df 0%, #e2ebf0 100%);
43 | border: 1px black solid;
44 | border-left: 0;
45 | }
46 |
47 | .controlsNext {
48 | grid-row: next;
49 | margin-top: 5px;
50 | }
51 |
52 | .controlsScore {
53 | grid-row: score;
54 | }
55 |
56 | .controlsLevel {
57 | grid-row: level;
58 | }
59 |
60 | .controlsButtons {
61 | grid-row: buttons;
62 | }
63 |
64 | .controlsInfo {
65 | grid-row: info;
66 | }
67 |
68 | .btn{
69 | border: none;
70 | color: white;
71 | padding: 12px 25px;
72 | text-align: center;
73 | text-decoration: none;
74 | display: inline-block;
75 | font-size: 16px;
76 | border-radius: 5px;
77 | margin-top: 10px;
78 | width: 100%;
79 | }
80 |
81 | .btnNew {
82 | background-color: #4CAF50;
83 | }
84 |
85 | .btnPause {
86 | background-color: #f44336;
87 | }
88 |
89 | .gameOver {
90 | z-index: 5;
91 | font-size: 24px;
92 | width: 180px;
93 | height: 80px;
94 | position: absolute;
95 | background-color: #f44336;
96 | color: #eeeeee;
97 | border-radius: 15px;
98 | top: 50%;
99 | left: 10%;
100 | align-content: center;
101 | -webkit-box-shadow: inset 0 10px 10px -10px black;
102 | -moz-box-shadow: inset 0 10px 10px -10px black;
103 | box-shadow: inset 0 10px 10px -10px black;
104 | display:table;
105 | vertical-align:middle;
106 | text-align:center;
107 | }
108 |
--------------------------------------------------------------------------------
/src/components/Game.test.js:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import Game from './Game';
3 |
4 | test('renders learn react link', () => {
5 | render();
6 | const linkElement = screen.getByText(/learn react/i);
7 | expect(linkElement).toBeInTheDocument();
8 | });
9 |
--------------------------------------------------------------------------------
/src/components/Level.js:
--------------------------------------------------------------------------------
1 | import {useEffect, useState} from "react";
2 | export const Level = (props) => {
3 | const [level, setLevel] = useState(props.level);
4 |
5 | useEffect(() => {
6 | setLevel(props.level);
7 | }, [props.level])
8 |
9 | return (
10 |
11 |
Level
12 |
{level}
13 |
14 | )
15 | }
16 |
17 | export default Level;
18 |
--------------------------------------------------------------------------------
/src/components/Next.js:
--------------------------------------------------------------------------------
1 | import {useEffect, useState} from "react";
2 | import styles from "./Next.module.css";
3 | import {createMatrix} from "../helpers/Helpers";
4 |
5 | export const Next = (props) => {
6 | const [next, setNext] = useState(props.next);
7 |
8 | useEffect(() => {
9 | setNext(props.next);
10 | }, [props.next])
11 |
12 | const tetrominos = next?.tetrominos();
13 | const matrix = tetrominos ? createMatrix(tetrominos) : "";
14 |
15 | return (
16 |
17 |
18 | Next
19 |
20 |
21 | {matrix}
22 |
23 |
24 | )
25 | }
26 |
27 | export default Next;
28 |
29 |
--------------------------------------------------------------------------------
/src/components/Next.module.css:
--------------------------------------------------------------------------------
1 | .matrix {
2 | display: table;
3 | background-color: #ffffff;
4 | padding: 1px;
5 | float: left;
6 | }
7 |
8 | .row {
9 | display: table-row;
10 | width: auto;
11 | clear: both;
12 | }
--------------------------------------------------------------------------------
/src/components/Score.js:
--------------------------------------------------------------------------------
1 | import {useEffect, useState} from "react";
2 |
3 | export const Score = (props) => {
4 | const [score, setScore] = useState(props.score);
5 |
6 | useEffect(() => {
7 | setScore(props.score);
8 | }, [props.score])
9 |
10 | return (
11 |
12 |
Score
13 |
{score}
14 |
15 | )
16 | }
17 |
18 | export default Score;
19 |
--------------------------------------------------------------------------------
/src/helpers/Generators.js:
--------------------------------------------------------------------------------
1 | export function sequence(max) {
2 | var items = [];
3 | for(var i = 0; i < max; i ++){
4 | items.push(i);
5 | }
6 | return items;
7 | }
--------------------------------------------------------------------------------
/src/helpers/Helpers.js:
--------------------------------------------------------------------------------
1 | import Colors from "../tetris/Colors";
2 | import Cell from "../components/Cell";
3 | const tetromino2Class = (tetromino, styles) => {
4 | let clazz = styles.empty;
5 | if(tetromino == null){
6 | return clazz;
7 | } else if(tetromino.isFilled()){
8 | if(Colors.BLUE === tetromino.color){
9 | return styles.blue;
10 | } else if(Colors.RED === tetromino.color){
11 | return styles.red;
12 | } else if(Colors.GREEN === tetromino.color){
13 | return styles.green;
14 | } else if(Colors.YELLOW === tetromino.color){
15 | return styles.yellow;
16 | } else {
17 | return styles.empty;
18 | }
19 | } else {
20 | return styles.empty;
21 | }
22 | };
23 |
24 | const createTetrominoRow = (tetraminos, rowIndex, styles) => {
25 | const cols = tetraminos.map( (tetramino, colIndex) =>
26 |
27 | );
28 | const rowComponent = {cols} ;
29 | return rowComponent;
30 | };
31 |
32 | const createMatrix = (state, styles) => {
33 | if(state == null){
34 | return null;
35 | }
36 | const matrix = state.map( (tetraminos , rowIndex) => {
37 | return createTetrominoRow(tetraminos, rowIndex, state, styles);
38 | });
39 | return matrix;
40 | }
41 |
42 | export {tetromino2Class, createMatrix}
43 |
44 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
15 | .title {
16 | font-size: 28px;
17 | color: dimgray;
18 | }
19 |
20 | .gameText {
21 | font-size: 18px;
22 | color: #0a07bc;
23 | margin: 10px;
24 | }
25 |
26 | .infoText {
27 | font-size: 12px;
28 | }
29 |
30 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import Game from './components/Game';
5 | import {createTetris} from "./tetris/Tetris";
6 |
7 | const tetris = createTetris();
8 | tetris.start();
9 | document.addEventListener("keydown", (event) => {
10 | const keyName = event.key;
11 | if (tetris.isRunning()) {
12 | if (keyName === "ArrowUp") {
13 | tetris.rotateCurrentPiece();
14 | } else if (keyName === "ArrowDown") {
15 | tetris.tick();
16 | }
17 | else if (keyName === "ArrowLeft") {
18 | tetris.moveLeft();
19 | } else if (keyName === "ArrowRight") {
20 | tetris.moveRight();
21 | } else if (keyName === " ") {
22 | tetris.pause();
23 | }
24 | } else {
25 | if(keyName === " ") {
26 | if (tetris.isPaused()) {
27 | tetris.resume();
28 | } else {
29 | tetris.start();
30 | }
31 | }
32 | }
33 | });
34 | ReactDOM.render(
35 |
36 |
37 | ,
38 | document.getElementById('root')
39 | );
40 |
--------------------------------------------------------------------------------
/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = onPerfEntry => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/src/tetris/Colors.js:
--------------------------------------------------------------------------------
1 | const Colors = {
2 | RED: 1,
3 | BLUE: 2,
4 | YELLOW: 3,
5 | GREEN: 4,
6 | };
7 |
8 | const randomColor = () => {
9 | const rand = Math.floor(Math.random() * Object.keys(Colors).length);
10 | const randColorValue = Colors[Object.keys(Colors)[rand]];
11 | return randColorValue;
12 | }
13 |
14 | export default Colors;
15 | export {randomColor}
16 |
--------------------------------------------------------------------------------
/src/tetris/Piece.js:
--------------------------------------------------------------------------------
1 | import {Tetromino, TetrominoStates} from "./Tetromino";
2 | import {randomColor} from "./Colors";
3 |
4 | const Piece = ({x = 0, y = 0, color = randomColor(), state}) => {
5 | const FILLED = 1;
6 | let _x = x;
7 | let _y = y;
8 | let _color = color;
9 | let _state = state;
10 | let width = _state[0].length;
11 | let height = _state.length;
12 | const rotate = (matrixState) => {
13 | let newState = [];
14 | for(let i = 0; i < width; i ++){
15 | newState.push([]);
16 | for(let j = 0; j < height; j ++){
17 | newState[i].push(_state[height - j - 1][i]);
18 | }
19 | }
20 |
21 | const nextPieceState = Piece({x: _x, y: _y, color: _color, state: newState});
22 | const leftMost = Math.min(...nextPieceState.matrixCoordinates().map(coordinate => coordinate.x));
23 | let rightMost = Math.max(...nextPieceState.matrixCoordinates().map(coordinate => coordinate.x));
24 |
25 | let newX = _x;
26 | let newY = _y;
27 |
28 | if(leftMost < 0){
29 | newX = _x + Math.abs(leftMost);
30 | }
31 |
32 | if(rightMost >= matrixState.width - 1){
33 | newX = _x - (rightMost - (matrixState.width - 1));
34 | }
35 |
36 | return Piece({x: newX, y: newY, color: _color, state: newState});
37 |
38 | };
39 |
40 | const move = (x, y) => {
41 | return Piece(x, y, _color, _state);
42 | };
43 |
44 | const moveInitCenter = (matrixWidth) => {
45 | const x = Math.floor((matrixWidth - width) / 2);
46 | const upMostPoint = Math.min(...matrixCoordinates().map(coordinate => coordinate.y));
47 | const y = 0 - upMostPoint;
48 | return Piece({x: x, y: y, color: _color, state: _state})
49 | }
50 |
51 | const moveLeft = () => {
52 | let newX = _x - 1;
53 | let newY = _y;
54 |
55 | const leftMost = Math.min(...matrixCoordinates().map(coordinate => coordinate.x));
56 | //if piece out of matrix after rotation, auto align coordinates.
57 | if(leftMost <= 0){
58 | newX = _x;
59 | }
60 |
61 | return Piece({x: newX, y: newY, color: _color, state:_state});
62 | };
63 |
64 | const moveRight = (matrixState) => {
65 | let newX = _x + 1;
66 | let newY = _y;
67 |
68 | let rightMost = Math.max(...matrixCoordinates().map(coordinate => coordinate.x));
69 | if(rightMost >= matrixState.width - 1){
70 | newX = _x;
71 | }
72 | return Piece({x: newX, y: newY, color: _color, state:_state});
73 | };
74 |
75 | const matrixCoordinates = () => {
76 | const coordinates = [];
77 | for(let i = 0; i < height; i ++){
78 | for(let j = 0; j < width; j++){
79 | if(_state[i][j] === FILLED){
80 | const x = j + _x;
81 | const y = i + _y;
82 | //if piece is rotated at initial position (especially for I piece),
83 | // some cells can go to out of matrix
84 | if(x < 0 || y < 0){
85 | continue;
86 | }
87 | coordinates.push({x: x, y: y});
88 | }
89 | }
90 | }
91 | return coordinates;
92 | };
93 |
94 | const moveDown = (gameState) => {
95 | const newY = _y + 1;
96 | const piece = Piece({x: _x, y: newY, color, state});
97 | const matrixCoordinates = piece.matrixCoordinates();
98 | const canMoveDown = matrixCoordinates.every(coordinate => {
99 | const canMove = (coordinate.y < gameState.height) && !gameState.isFilled(coordinate);
100 | return canMove;
101 | });
102 |
103 | if(canMoveDown){
104 | return piece;
105 | } else {
106 | const samePiece = Piece({x: _x, y: _y, color, state});
107 | return samePiece;
108 | }
109 | };
110 |
111 | const isOnSamePosition = (piece) =>{
112 | return _x === piece.x && _y === piece.y;
113 | };
114 |
115 | const toTetrominos = (state, color) => {
116 | const tetrominos = [];
117 | for(let i = 0; i < state.length; i ++){
118 | const row = [];
119 | for(let j = 0; j < state[0].length; j ++){
120 | let tetromino = Tetromino();
121 | if(state[i][j] === FILLED){
122 | tetromino = Tetromino(TetrominoStates.FILLED, color);
123 | }
124 | row.push(tetromino);
125 | }
126 | tetrominos.push(row);
127 | }
128 | return tetrominos;
129 | };
130 |
131 | const tetrominos = () => {
132 | return toTetrominos(_state, _color);
133 | };
134 |
135 | return {
136 | rotate,
137 | move,
138 | moveLeft,
139 | moveRight,
140 | moveDown,
141 | x: _x,
142 | y: _y,
143 | isOnSamePosition,
144 | matrixCoordinates,
145 | color: _color,
146 | tetrominos,
147 | moveInitCenter
148 | };
149 | }
150 |
151 | const I_Piece = () => {
152 | let initalState = [
153 | [0,0,0,0],
154 | [1,1,1,1],
155 | [0,0,0,0],
156 | [0,0,0,0],
157 | ];
158 |
159 | const piece = Piece({state: initalState});
160 | return piece;
161 | }
162 |
163 | const O_Piece = () => {
164 | let initalState = [
165 | [1,1],
166 | [1,1],
167 |
168 | ];
169 |
170 | const piece = Piece({state: initalState});
171 | return piece;
172 | }
173 |
174 | const Z_Piece = () => {
175 | let initalState = [
176 | [1,1,0],
177 | [0,1,1],
178 | [0,0,0]
179 | ];
180 |
181 | const piece = Piece({state: initalState});
182 | return piece;
183 | }
184 |
185 | const S_Piece = () => {
186 | let initalState = [
187 | [0,1,1],
188 | [1,1,0],
189 | [0,0,0]
190 | ];
191 |
192 | const piece = Piece({state: initalState});
193 | return piece;
194 | }
195 |
196 | const J_Piece = () => {
197 | let initalState = [
198 | [1,0,0],
199 | [1,1,1],
200 | [0,0,0],
201 |
202 | ];
203 |
204 | const piece = Piece({state: initalState});
205 | return piece;
206 | }
207 |
208 | const L_Piece = () => {
209 | let initalState = [
210 | [0,0,1],
211 | [1,1,1],
212 | [0,0,0],
213 |
214 | ];
215 |
216 | const piece = Piece({state: initalState});
217 | return piece;
218 | }
219 |
220 | const M_Piece = () => {
221 | let initalState = [
222 | [1,1,1],
223 | [0,1,0],
224 | [0,0,0],
225 |
226 | ];
227 |
228 | const piece = Piece({state: initalState});
229 | return piece;
230 | }
231 |
232 | const X_Piece = () => {
233 | let initalState = [
234 | [1,0,1],
235 | [0,1,0],
236 | [1,0,1],
237 |
238 | ];
239 |
240 | const piece = Piece({state: initalState});
241 | return piece;
242 | }
243 |
244 |
245 | const empty_Piece = () => {
246 | let initalState = [
247 | [0,0,0],
248 | [0,0,0],
249 | [0,0,0],
250 |
251 | ];
252 |
253 | const piece = Piece({state: initalState});
254 | return piece;
255 | }
256 |
257 | const randomPiece = () => {
258 | const i = I_Piece();
259 | const o = O_Piece();
260 | const z = Z_Piece();
261 | const s = S_Piece();
262 | const j = J_Piece();
263 | const l = L_Piece();
264 | const m = M_Piece();
265 |
266 | //eslint-disable-next-line no-unused-vars
267 | const x = X_Piece();
268 | const pieces = [
269 | i,
270 | o,
271 | z,
272 | s,
273 | j,
274 | l,
275 | m,
276 | //x --> experimental X piece.
277 | ];
278 |
279 | const rnd = Math.floor(Math.random() * pieces.length);
280 | const randomPiece = pieces[rnd];
281 | return randomPiece;
282 | }
283 |
284 | const emtpyPiece = () => {
285 | return empty_Piece();
286 | }
287 |
288 | export {randomPiece, emtpyPiece};
--------------------------------------------------------------------------------
/src/tetris/State.js:
--------------------------------------------------------------------------------
1 | import {Tetromino, TetrominoStates} from "./Tetromino";
2 | import {randomPiece} from "./Piece";
3 |
4 | const MAX_ROW_CLEAR_PER_LEVEL = 10;
5 |
6 | const createState = (height, width) => {
7 | let _height = height;
8 | let _width = width;
9 | let paused = false;
10 | let started = false;
11 | let gameOver = false;
12 | let _level = 1;
13 | let _score = 0;
14 | let _nextPiece = null;
15 | let _currentPiece = null;
16 | let totalNumberOfClearedRows = 0;
17 | let onLevelChangeAction = null;
18 |
19 | const isRunning = () => {
20 | return started && !paused;
21 | };
22 |
23 | const isStarted = () => {
24 | return started;
25 | };
26 |
27 | const isGameOver = () => {
28 | return gameOver;
29 | };
30 |
31 | const isPaused = () => {
32 | return paused;
33 | };
34 |
35 | const initMatrix = (height, width) => {
36 | let _matrix = [];
37 | for (let i = 0; i < height; i++) {
38 | _matrix[i] = [];
39 | for (let j = 0; j < width; j++) {
40 | _matrix[i][j] = Tetromino();
41 | }
42 | }
43 | return _matrix;
44 | };
45 |
46 | let matrix = initMatrix(height, width);
47 | let _visibleMatrix = [...matrix];
48 |
49 |
50 | const start = () => {
51 | paused = false;
52 | started = true;
53 | gameOver = false;
54 | _level = 1;
55 | _score = 0;
56 | matrix = initMatrix(_height, _width);
57 | sendNextPiece();
58 | _visibleMatrix = createVisibleMatrix(matrix);
59 | };
60 |
61 | const pause = () => {
62 | paused = true;
63 | }
64 |
65 | const resume = () => {
66 | paused = false;
67 | }
68 |
69 | const sendNextPiece = () => {
70 | if(_nextPiece == null){
71 | _nextPiece = randomPiece();
72 | }
73 | _currentPiece = _nextPiece;
74 | _nextPiece = randomPiece();
75 | _currentPiece = _currentPiece.moveInitCenter(width);
76 | }
77 |
78 | const isFilled = (coordinate) => {
79 | return matrix[coordinate.y][coordinate.x].isFilled();
80 | };
81 |
82 | const increaseLevel = () => {
83 | if (totalNumberOfClearedRows > MAX_ROW_CLEAR_PER_LEVEL) {
84 | _level++;
85 | totalNumberOfClearedRows = 0;
86 | notifyLevelChangeListener(_level);
87 | }
88 | }
89 |
90 | const moveCurrentPieceDown = () => {
91 | if(_currentPiece == null){
92 | return;
93 | }
94 |
95 | if(overlaps(_currentPiece)){
96 | setGameOver();
97 | return;
98 | }
99 | const newPosition = _currentPiece.moveDown({isFilled, height});
100 | //piece cannot move down.
101 | //merge piece into matrix and start a new piece.
102 | if(newPosition.isOnSamePosition(_currentPiece)){
103 | mergePieceIntoMatrix(newPosition);
104 | sendNextPiece();
105 | } else {
106 | _currentPiece = newPosition;
107 | }
108 | const numberOfClearedRows = clearRows();
109 | totalNumberOfClearedRows+=numberOfClearedRows;
110 | increaseLevel();
111 | _score = calculateScore(_score, numberOfClearedRows, _level);
112 | _visibleMatrix = createVisibleMatrix(matrix);
113 | };
114 |
115 | const calculateScore = (_score, numberOfClearedRows, _level) => {
116 | return _score + Math.pow(numberOfClearedRows, 2) * _level
117 | }
118 |
119 | const createVisibleMatrix = (matrix) => {
120 | const visibleMatrix = matrix.map(row => row.slice());
121 | if(_currentPiece != null){
122 | _currentPiece.matrixCoordinates().forEach(coordinate => {
123 | const tetromino = Tetromino(TetrominoStates.FILLED, _currentPiece.color);
124 | visibleMatrix[coordinate.y][coordinate.x] = tetromino;
125 | })
126 | }
127 | return visibleMatrix;
128 | };
129 |
130 | const overlaps = (piece) => {
131 | const overlap = piece.matrixCoordinates().some(coordinate => {
132 | return matrix[coordinate.y][coordinate.x].isFilled()
133 | });
134 |
135 | return overlap;
136 | };
137 |
138 | const setGameOver = () => {
139 | started = false;
140 | paused = false;
141 | gameOver = true;
142 | };
143 |
144 | const mergePieceIntoMatrix = (piece) => {
145 | piece.matrixCoordinates().forEach( coordinate => {
146 | matrix[coordinate.y][coordinate.x] = Tetromino(TetrominoStates.FILLED, piece.color)
147 | });
148 | };
149 |
150 | const rotateCurrentPiece = () => {
151 | move(() => _currentPiece.rotate({width: _width}));
152 | }
153 |
154 | const moveLeft = () => {
155 | move(() => _currentPiece.moveLeft());
156 | }
157 |
158 | const moveRight = () => {
159 | move(() => _currentPiece.moveRight({width: _width}));
160 | }
161 |
162 | const move = (action) => {
163 | if(_currentPiece == null){
164 | return;
165 | }
166 | let nextPosition = action();
167 | if(nextPosition != null && !overlaps(nextPosition)){
168 | _currentPiece = nextPosition;
169 | _visibleMatrix = createVisibleMatrix(matrix);
170 | }
171 | }
172 |
173 | const clearRows = () => {
174 | let numberOfClearedRows = 0;
175 | for(let i = height - 1; i >= 0 ; ){
176 | const allEmpty = matrix[i].every(cell => cell.isEmpty());
177 | if(allEmpty){
178 | break;
179 | }
180 |
181 | const allFilled = matrix[i].every(cell => cell.isFilled());
182 | if(allFilled){
183 | matrix.splice(i, 1);
184 | const emtyRow = [];
185 | for(let j = 0; j < width; j++){
186 | emtyRow.push(Tetromino());
187 | }
188 | matrix.unshift(emtyRow);
189 | numberOfClearedRows++;
190 | } else {
191 | i--;
192 | }
193 | }
194 | return numberOfClearedRows;
195 | };
196 |
197 | const visibleMatrix = () => {
198 | return _visibleMatrix;
199 | }
200 |
201 | const score = () => {
202 | return _score;
203 | };
204 |
205 | const level = () => {
206 | return _level;
207 | };
208 |
209 | const nextPiece = () => {
210 | return _nextPiece;
211 | };
212 |
213 | const onLevelChange = (action) => {
214 | onLevelChangeAction = action;
215 | };
216 |
217 | const notifyLevelChangeListener = (level) => {
218 | if(onLevelChangeAction){
219 | onLevelChangeAction(level);
220 | }
221 | };
222 |
223 | return {
224 | isRunning,
225 | isStarted,
226 | isPaused,
227 | start,
228 | pause,
229 | resume,
230 | level,
231 | score,
232 | isFilled,
233 | isGameOver,
234 | moveCurrentPieceDown,
235 | visibleMatrix,
236 | rotateCurrentPiece,
237 | moveLeft,
238 | moveRight,
239 | nextPiece,
240 | onLevelChange
241 | };
242 | };
243 |
244 | export {createState};
245 |
--------------------------------------------------------------------------------
/src/tetris/Tetris.js:
--------------------------------------------------------------------------------
1 | import {createState} from "./State.js";
2 |
3 |
4 | const createTetris = ({height, width} = {height: 20, width:10}) => {
5 | const _height = height;
6 | const _width = width;
7 | const state = createState(_height, _width);
8 | let timer = null;
9 | const stateChangeListeners = [];
10 |
11 | state.onLevelChange((level) => startTimer(timer, calculateSpeedForCurrentLevel(level)))
12 |
13 | const startTimer = (timer, delay) => {
14 | if (timer != null) {
15 | clearInterval(timer);
16 | }
17 |
18 | const newTimer = setInterval(tick, delay)
19 | return newTimer;
20 | };
21 |
22 | const stopTimer = (timer) => {
23 | if (timer != null) {
24 | clearInterval(timer);
25 | }
26 | };
27 |
28 | const calculateSpeedForCurrentLevel = (level) => {
29 | return Math.floor(900 * (Math.pow(0.9, level - 1)));
30 | };
31 |
32 |
33 | //eslint-disable-next-line no-unused-vars
34 | const toString = () => {
35 | let str = state.visibleMatrix().map(row => {
36 | return row.map(tetromino => {
37 | if (tetromino.isFilled()) {
38 | return tetromino.color;
39 | } else {
40 | return tetromino.state;
41 | }
42 | }).join(",");
43 | }).join("\n");
44 |
45 | return str;
46 | };
47 |
48 | const tick = () => {
49 | if (!state.isRunning()) {
50 | return;
51 | }
52 |
53 | state.moveCurrentPieceDown();
54 | if(state.isGameOver()){
55 | stopTimer(timer);
56 | }
57 | notifyOnStateChangeListeners();
58 | };
59 |
60 | const notifyOnStateChangeListeners = () => {
61 | stateChangeListeners.forEach(fn => fn({...state}));
62 | };
63 |
64 | const resume = () => {
65 | state.resume();
66 | notifyOnStateChangeListeners();
67 | timer = startTimer(timer, calculateSpeedForCurrentLevel(state.level()));
68 |
69 | };
70 |
71 | const start = () => {
72 | if (state.isStarted()) {
73 | return;
74 | }
75 | state.start();
76 | notifyOnStateChangeListeners();
77 | timer = startTimer(timer, calculateSpeedForCurrentLevel(state.level()));
78 | };
79 |
80 | const pause = () => {
81 | if (state.isPaused()) {
82 | return;
83 | }
84 | if (timer != null) {
85 | clearInterval(timer);
86 | }
87 | state.pause();
88 | stopTimer(timer);
89 | notifyOnStateChangeListeners();
90 | };
91 |
92 | const isPaused = () => {
93 | return state.isPaused();
94 | }
95 |
96 | const moveLeft = () => {
97 | state.moveLeft();
98 | notifyOnStateChangeListeners();
99 | };
100 |
101 | const moveRight = () => {
102 | state.moveRight();
103 | notifyOnStateChangeListeners();
104 | }
105 |
106 | const rotateCurrentPiece = () => {
107 | state.rotateCurrentPiece();
108 | notifyOnStateChangeListeners();
109 | }
110 |
111 |
112 |
113 | return {
114 | onStateChange(fn) {
115 | stateChangeListeners.push(fn);
116 | },
117 |
118 | isGameOver() {
119 | return state.gameOver;
120 | },
121 |
122 | isRunning() {
123 | return state.isRunning();
124 | },
125 |
126 | isStarted() {
127 | return state.isStarted();
128 | },
129 | state,
130 | rotateCurrentPiece,
131 | moveLeft,
132 | moveRight,
133 | start,
134 | pause,
135 | isPaused,
136 | resume,
137 | tick,
138 | height: _height,
139 | width: _width
140 | };
141 |
142 | };
143 |
144 | export {createTetris}
145 |
--------------------------------------------------------------------------------
/src/tetris/Tetromino.js:
--------------------------------------------------------------------------------
1 | const TetrominoStates = {
2 | EMPTY: 0,
3 | FILLED: 1,
4 | SHADOW: 2
5 | };
6 | const Tetromino = (state, color) => {
7 |
8 | let _state = state == null ? TetrominoStates.EMPTY : state;
9 | let _color = color;
10 |
11 | return {
12 | isEmpty() {
13 | return _state === TetrominoStates.EMPTY || _state === TetrominoStates.SHADOW;
14 | },
15 | isShadow() {
16 | return _state === TetrominoStates.SHADOW;
17 | },
18 | isFilled() {
19 | return _state === TetrominoStates.FILLED;
20 | },
21 | color: _color,
22 | state: _state
23 |
24 | }
25 | }
26 |
27 | export {Tetromino, TetrominoStates};
28 |
--------------------------------------------------------------------------------
/src/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | module: {
3 | rules: [
4 | {
5 | test: /\.css$/i,
6 | use: ["style-loader", "css-loader"],
7 | options: {
8 | modules: true,
9 | },
10 | },
11 | ],
12 | },
13 | };
--------------------------------------------------------------------------------
|