How to Make Tetris in JavaScript: A Step-by-Step Guide

 

How to Make Tetris in JavaScript: A Step-by-Step Guide

Introduction to Tetris and JavaScript

Tetris, the iconic puzzle game, has captured the hearts of millions worldwide since its creation in 1984. With its simple yet addictive gameplay, it remains a favorite among gamers of all ages. In this tutorial, we’ll explore how to create your own Tetris game using JavaScript, a popular programming language for web development. By the end of this guide, you’ll have a fully functional Tetris game that you can customize and share with others.

Setting Up the Development Environment

Before diving into the coding process, let’s ensure we have the necessary tools in place. To develop our Tetris game, we’ll need a text editor and a web browser. You can choose any text editor of your preference, such as Visual Studio Code, Sublime Text, or Atom. Additionally, ensure you have a modern web browser like Chrome or Firefox for testing and running the game.

Creating the Game Board and Tetriminos

To start building our Tetris game, we’ll first create the game board and the Tetriminos—the geometric shapes that fall down the board. We’ll utilize HTML5 canvas, a powerful element for drawing graphics on web pages. By using JavaScript, we can manipulate the canvas to render the game elements dynamically.

Implementing the Game Logic

Next, we’ll delve into implementing the game logic. This involves handling user input, moving the Tetriminos, and detecting collisions. JavaScript provides event listeners to capture user input, such as arrow key presses, which we’ll utilize to control the movement of the Tetriminos. By employing algorithms and data structures, we can manage the game’s mechanics, scoring, and level progression.

Styling and Enhancing the Game

Now that we have the core functionality of our Tetris game working, let’s focus on enhancing its appearance and user experience. With CSS, we can style the game elements, including the game board, Tetriminos, and background. You can unleash your creativity here by designing a visually appealing game interface. Additionally, consider adding sound effects or animations to make the game more engaging.

Conclusion and Next Steps

Congratulations! You’ve successfully created your own Tetris game using JavaScript. You’ve learned how to set up the development environment, build the game board and Tetriminos, implement the game logic, and enhance the game’s visual appeal. This tutorial serves as a starting point for further exploration and customization. You can experiment with different features, such as a high-score leaderboard, multiplayer functionality, or responsive design. Remember, the possibilities are endless when it comes to game development and JavaScript.

Keyword Clusters:

Cluster 1: Tetris, JavaScript, game development Cluster 2: HTML5 canvas, game elements, graphics Cluster 3: User input, movement, collisions, event listeners Cluster 4: Styling, CSS, game interface, visual appeal Cluster 5: Game logic, scoring, level progression, algorithms Cluster 6: Tetriminos, game board, mechanics, data structures Cluster 7: Sound effects, animations, user experience Cluster 8: Development environment, text editor, web browser

Index.html

				
					<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="Tetris Javascript | An ode to gaming nostalgia, falling tetriminos and that catchy theme tune.">
    <meta name="keywords" content="tetris, javascript, tetriminos, game, gaming, nostalgia">

    <!-- custom style link -->
    <link rel="stylesheet" href="style.css">
    <title>Welcome to Tetris JS</title>
</head>

<body class="main-bg">

    <!-- main title welcoming the user to the site -->
    <section class="main">
        <div class="title">
            <h2>welcome to</h2>
            <h1>tetris js</h1>
        </div>
    </section>
    
    <!-- with no nav bar on the landing page, the buttons are the users main source of navigation -->
    <section class="navButtons">
        <h2 hidden>secondary navigation buttons</h2>
        <div>
            <div>
                <a href="tetris.html" class="buttons btn">Play Tetris</a>
            </div>
            <div>
                <a href="instructions.html" class="buttons btn">Instructions</a>
            </div>
        </div>
    </section>

<!-- footer section for social links, copyright information -->
<footer>

    <a class="arrow-top" href="#top" aria-label="return to the top of the page">
        <i class="fas fa-arrow-alt-circle-up"></i>
    </a>
    <p>Copyright <span>&copy;</span> 2022 | Dailywebdesign | privacy Policy | Terms & Conditions</p>
</footer>

    <!-- scripts for js and fontawesome -->
    <script src="main.js"></script>
    <script src="https://kit.fontawesome.com/0fb38816a1.js" crossorigin="anonymous"></script>
</body>
</html>
				
			

Tetris.html

				
					<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="Tetris Javascript | An ode to gaming nostalgia, falling tetriminos and that catchy theme tune.">
    <meta name="keywords" content="tetris, javascript, tetriminos, game, gaming, nostalgia">

    <!-- custom style link -->
    <link rel="stylesheet" href="style.css">
    <title>Tetris JS - Tetris</title>
</head>


<body >
    <!-- navigation elements for User to access instruction, rules and scores-->
    <nav class="navbar">
        <div class="brand-logo">
            <a href="index.html" aria-label="returns the user to the top of the home page">
                <h2>Tetris JS</h2>
            </a>
        </div>
        <a href="#" class="toggle-btn">
            <span class="line"></span>
            <span class="line"></span>
            <span class="line"></span>
        </a>
        <div class="navbar-links">
            <ul>
                <li> <a href="index.html">Home</a></li>
                <li> <a href="instructions.html">Instructions</a></li>
                <li> <a class="active" href="tetris.html">Play Tetris</a></li>
            </ul>
        </div>
    </nav>

<section class="gameArea">
    <h2 hidden>main game grid area</h2>
    <div class="panels">
        <div class="box sidebox1">
            <div class="box-content">
                <div class="score un-btn">Score : <span id="score">0</span> </div>
                <div class="score un-btn">Lines : <span id="lines">0</span> </div>
                <div class="score un-btn">Level : <span id="levels">0</span> </div>
                <button id="play" class=" score btn">Music Off </button>
            </div>
        </div>

        <div class="box">
            <div class="box-content">
                <!-- manually creating tetris grid -->
                <div class="grid">
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                    <div class="taken"></div>
                    <div class="taken"></div>
                    <div class="taken"></div>
                    <div class="taken"></div>
                    <div class="taken"></div>
                    <div class="taken"></div>
                    <div class="taken"></div>
                    <div class="taken"></div>
                    <div class="taken"></div>
                    <div class="taken"></div>
                </div>
            </div>
        </div>

        <div class="box sidebox2">
            <div class="box-content">
                <div class="controls">

                    <span class="keys"><button id="rotate" class="control-btn "
                            aria-label="this button will Rotate the Tetrimino by 90 degrees">Rotate</button></span>


                    <span class="keys"><button id="left" class="control-btn "
                            aria-label="this button will move the Tetrimino to the Left">Left</button></span>
                    <span class="keys"><button id="right" class="control-btn "
                            aria-label="this button will move the Tetrimino to the Right">Right</button></span>


                    <span class="keys"><button id="down" class="control-btn "
                            aria-label="this button is will move the Tetrimino Down the game grid">Down</button></span>
                </div>
                <button id="start-button" class="btn " aria-label="this button will start and pause the game">Start
                    Game</button>
            </div>
        </div>
    </div>
</section>

<!-- footer section for social links, copyright information -->
<footer>

    <a class="arrow-top" href="#top" aria-label="return to the top of the page">
        <i class="fas fa-arrow-alt-circle-up"></i>
    </a>
    <p>Copyright <span>&copy;</span> 2022 | Dailywebdesign | privacy Policy | Terms & Conditions</p>
</footer>

<!-- audio files -->
<audio src="audio/Tetris-99-Main-Theme.mp3" id="music" aria-label="theme song for the classic game boy Tetris game">
    If you are seeing this, it is because your brower does not support this audio element
</audio>

    <!-- scripts for js and fontawesome -->
    <script src="main.js"></script>
    <script src="https://kit.fontawesome.com/0fb38816a1.js" crossorigin="anonymous"></script>
</body>
</html>
				
			

instructions.html

				
					<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="Tetris Javascript | An ode to gaming nostalgia, falling tetriminos and that catchy theme tune.">
    <meta name="keywords" content="tetris, javascript, tetriminos, game, gaming, nostalgia">

    <!-- custom style link -->
    <link rel="stylesheet" href="style.css">
    <title>Tetris JS -About Tetris</title>
</head>

<body class="about-bg">
    <!-- navigation elements for User to access instruction, rules and scores-->
    <nav class="navbar">
        <div class="brand-logo">
            <a href="index.html" aria-label="returns the user to the top of the home page">
                <h2>Tetris JS</h2>
            </a>
        </div>
        <a href="#" class="toggle-btn">
            <span class="line"></span>
            <span class="line"></span>
            <span class="line"></span>
        </a>
        <div class="navbar-links">
            <ul>
                <li> <a href="index.html">Home</a></li>
                <li> <a class="active" href="instructions.html">Instructions</a></li>
                <li> <a href="tetris.html">Play Tetris</a></li>
            </ul>
        </div>
    </nav>

<div class="title">
    <h2>How to Tetris</h2>
    <h3>A Brief History</h3>
    <br>
    <p class="heading">Gameplay</p>
    <p>Tetris is composed of a grid of play in which pieces of different geometric forms, called
        "tetriminos", fall from the top of the grid.While falling, the player can move the pieces
        laterally and rotate them until they touch the bottom of the grid or land on a piece that had been placed
        before it. The objective of the game is to use the pieces to create as many horizontal lines of
        blocks as possible. When a line is completed, it disappears, and the blocks placed above fall one rank.
        Completing lines grants points, and accumulating a certain number of points moves the player up a level,
        which increases the number of points granted per completed line.</p>
        <br>
    <p class="heading">Tetriminos</p>
    <p>The pieces on which the game of Tetris is based around are called "tetriminos". Pajitnov's original version
        for the Electronika 60 computer used green brackets to represent the blocks that make up tetriminos.
        Versions of Tetris on the original Game Boy/Game Boy Color and on most dedicated handheld games use
        monochrome or grayscale graphics, but most popular versions use a separate color for each distinct shape.
        Prior to The Tetris Company's standardization in the early 2000s, those colors varied widely from
        implementation to implementation.</p>    
        <br>
    <p class="heading">Scoring</p>
    <p>The scoring formula for the majority of Tetris products is built on the idea that more difficult line clears
        should be awarded more points. For example, a single line clear in Tetris Zone is worth 100 points, clearing
        four lines at once (known as a Tetris) is worth 800, while each subsequent back-to-back Tetris is worth
        1,200. In conjunction, players can be awarded combos that exist in certain games which reward multiple
        line clears in quick succession. The exact conditions for triggering combos, and the amount of importance
        assigned to them, vary from game to game.

        Nearly all Tetris games allow the player to press a button to increase the speed of the current piece's
        descent or cause the piece to drop and lock into place immediately, known as a "soft drop" and a "hard
        drop", respectively. While performing a soft drop, the player can also stop the piece's increased speed by
        releasing the button before the piece settles into place. Some games only allow either soft drop or hard
        drop; others have separate buttons for both. Many games award a number of points based on the height that
        the piece fell before locking, so using the hard drop generally awards more points.</p>    

    <div class="links">
        <a href="http://en.wikipedia.org/wiki/Tetris" target="_blank" rel="noopener"
        aria-label="visit our Tetris wikipedia article (opens a new browser)">If you want to find out
        more, head
        on
        over to this great Wikipedia article</a>
    </div>    
    <br>
    <p class="heading">How to Play</p>
    <p>You start a new game by pressing the <strong>Start Game</strong> button. Tetriminos will begin to fall from the top of the
        grid and begin to pile up at the bottom. You, the player, must rotate the Tetriminos to best fit together
        and complete lines across the width of the grid. Each successful clear will result in the line being removed
        from the grid and you awarded 15 points. For every 5 lines cleared you will gain a level. The game continues
        till the Tetriminos reach the top of the grid, at which point the game is over.</p>
    <br>
    <p class="heading">Controls</p>
    <p>Left Arrow Key/ Left Button - <strong>Move Left</strong></p>    
    <p>Right Arrow Key/ Right Button - <strong>Move Right</strong></p>    
    <p>Up Arrow Key/ Rotate Button - <strong>Rotate 90 degees</strong></p>    
    <p>Down Arrow Key/ Down  - <strong>Move Down</strong></p>    
    <p>Music  can be Played or Paused by clicking the - <strong>Music off</strong> button</p>   
    <br> 
</div>

<div class="title">
    <a href="tetris.html" class="btn">Want to play Tetris?</a>
</div>


<!-- footer section for social links, copyright information -->
<footer>

    <a class="arrow-top" href="#top" aria-label="return to the top of the page">
        <i class="fas fa-arrow-alt-circle-up"></i>
    </a>
    <p>Copyright <span>&copy;</span> 2022 | Dailywebdesign | privacy Policy | Terms & Conditions</p>
</footer>

    <!-- scripts for js and fontawesome -->
    <script src="main.js"></script>
    <script src="https://kit.fontawesome.com/0fb38816a1.js" crossorigin="anonymous"></script>
</body>
</html>
				
			

style.css

				
					@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&family=Assistant:wght@300;400;500;600;700;800&family=Rammetto+One&display=swap');


* {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
    border: none;
    scroll-behavior: smooth;
}



/* navigation bar */
.navbar {
    font-family: 'Assistant', sans-serif;
    display: flex;
    justify-content: space-between;
    align-items: center;
    background: url("images/blue-tetrimino.png");
    background-repeat: no-repeat;
    background-position: center;
    background-size: cover;
    color: white;
    padding-right: 10px;
}


.brand-logo {
    font-family: 'Rammetto One', sans-serif;
    font-size: 1.25rem;
    margin: 0;
    padding: .55rem;
    text-align: center;
    color: #1f0322;
}

/* create an anchor on the brand logo that return the user back to home on click*/
.brand-logo a {
    text-decoration: none;
    color: #1f0322;
}

.navbar-links ul {
    margin: 0;
    padding: 0;
    display: flex;
}

.navbar-links li {
    list-style: none;
}

.navbar-links li a {
    text-decoration: none;
    color: #fff;
    padding: 1rem;
    display: block;
}

.navbar-links li:hover {
    background: url('images/blue-tetrimino.png') no-repeat;
    border-radius: 10px;

}

.navbar-links .active {
    border: 2px solid white;
    border-radius: 10px;
}

/* create a hamburger menu for mobile displays*/
.toggle-btn {
    position: absolute;
    top: 1.2rem;
    right: 1rem;
    display: none;
    flex-direction: column;
    justify-content: space-between;
    width: 40px;
    height: 30px;
}

.toggle-btn .line {
    width: 100%;
    height: 5px;
    background-color: white;
    border-radius: 10px;
}


/* media query to change the flow of the navbar on mobile devices*/
@media screen and (max-width: 400px) {

    .toggle-btn {
        display: flex;
    }

    .navbar-links {
        display: none;
        width: 100%;
    }

    .navbar {
        flex-direction: column;
        align-items: flex-start;
        padding: 0;
    }

    .navbar-links ul {
        flex-direction: column;
        width: 100%;
    }

    .navbar-links li {
        text-align: center;
    }

    .navbar-links li a {
        padding: .5rem 1rem;
    }

    .navbar-links.active {
        display: flex;
    }
}

/* create background image for the landing page */
.main-bg {
    background: url("images/orange-tetrimino.png");
    height: 100vh;
    background-repeat: no-repeat;
    background-position: center;
    background-size: cover;
}

/* create background image for the instructions page */
.about-bg {
    background: url("images/pink-tetrimino.jpg");
    height: 100%;
    background-repeat: no-repeat;
    background-position: center;
    background-size: cover;
}

/* create background image for the scores page */
.scores-bg {
    background: url("images/blue-tetrimino.png");
    height: 100%;
    background-repeat: no-repeat;
    background-position: center;
    background-size: cover;
}

/* universal styling for the headings across all pages */
.title{
    font-family: "Rammetto One", sans-serif;
    margin: 20px auto;
    text-align: center;
}

.title h1{
    font-size: 2.5rem;
    text-transform: uppercase;
    color: #1f0322;
}

.title h2::after{
    content: " ";
    display: block;
    height:2px;
    width: 10rem;
    margin: .25em auto;
    background-color: blueviolet;
    box-shadow: 0px 2px 8px 0px red;
}

.title p{
    font-family: "Assistant", sans-serif;
    font-size: 1.25rem;
    margin: 0 auto;
    padding: .5rem;
}

.heading{
    font-weight: bold;
}

.links a{
    font-family: "Assistant", sans-serif;
    font-size: 1rem;
    text-decoration: none;
    margin-bottom: 1rem;
    padding: .5rem;
    text-align: center;
    justify-content: center;
    display: block;
}

/* game page panels */

.panels{
    margin: 0 1rem;
    width: 95%;
    display:grid;
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    grid-gap:12px;
    justify-content:center;
    align-items:center;
}

/* main panel for the game grid and start button */

.box{
    height:460px;
    border: none;
    box-shadow: 0 0.1rem 1.4rem 0 rgba(86, 185, 235, 0.5);
    position:relative;
    border-radius: 5px;
    background-color:rgb(255, 127, 148);
}

/* create smaller side panels foe the buttons and game information */
.sidebox1{
    height:320px;
}

.sidebox2{
    height: 250px;
}

.box-content{
    margin: 0;
    position: absolute;
    left: 50%;
    top: 50%;
    transform:translate(-50%, -50%);
}

.box-content h2{
    color: #c24cf6;
    visibility:hidden;
}

/* button used on the landing page and game screens */
.buttons{
    text-align:center;
    margin: 0 auto;
    display:block;
}

/* styling for the buttons that so same for of action on the site */

.btn{
    width: 20ch;
    font-family: 'Assistant', sans-serif;
    font-size: 1.12rem;
    font-weight:bold;
    background-color: #03011e;
    border:2px solid #847655;
    color: #fc6e22;
    padding:1rem;
    margin: .5rem auto;
    border-radius: 10px;
    justify-content: space-between;
    transition: all 200ms ease-in-out;
    text-decoration: none;
}

/* div to arrange the control buttons */
.controls{
    text-align: center;
    margin: 0 auto;
}

/* styling for the control keys left, rotate, right and down */
.control-btn{
    max-width: 40ch;
    font-family: 'Assistant', sans-serif;
    font-size: 1.12rem;
    font-weight: bold;
    display: inline-block;
    background-color: #03011e;
    border:2px solid #847655;
    color:#fc6e22;
    padding:.5rem;
    margin: .25rem 1rem;
    border-radius: 10px;
    transition: all 200ms ease-in-out;
    text-decoration: none;
}

/* styling for the game information blocks */
.un-btn{
    width: 20ch;
    font-family: 'Assistant', sans-serif;
    font-size: 1.12rem;
    font-weight: bold;
    background-color: #03011e;
    border:2px solid #847655;
    color: #fc6e22;
    padding: 1rem;
    margin:.5rem auto;
    border-radius: 10px;
    justify-content:space-between;
    transition: all 200ms ease-in-out;
    text-decoration: none;
}

.btn:hover{
    background: #847655;
    color: #fff;
    cursor: pointer;

}

/* remove btn functionality during gameOver() and score form submissinon till requirements are met */
.btn[disabled]:hover{
    cursor: not-allowed;
    transform:none;
}

.control-btn:hover{
    background: #847655;
    color: #fff;
    cursor: pointer;
}

.music{
    padding:1rem;
    margin:.5rem auto;
    justify-content:space-between;
}

/* the grid created as the play area for housing the tetriminos */
.grid{
    width: 200px;
    height:400px;
    border-radius: 5px;
    background-color:lightgrey;
    display:flex;
    flex-wrap: wrap;
}

.grid div{
    width:20px;
    height:20px;
}

/* fallback color for tetrimino to display with the grid */
.tetrimino{
    background-color:violet;
}

/* scores Page */

.high-score-wrapper{
    margin: 0 auto;
    padding:2rem;

}

.high-score{
    text-align:center;
    text-transform:uppercase;
}

.score{
    text-align:center;
    text-transform:uppercase;
}

/* styling for the score form */

form{
    width:100%;
    display:flex;
    flex-direction:column;
    align-items:center;
}

/* input for the player to enter their name */

input{
    font-size: 1rem;
    max-width: 20ch;
    text-align: center;
    margin-bottom: 1rem;
    padding:1.5rem;
    border:none;
    box-shadow: 0 0.1rem 1.4rem 0 rgba(86, 185, 235, 0.5);
}

input::placeholder{
    color:#ff1793;
}

/* footer section */

footer{
    height:150px;
}

footer p{
    font-family: 'Assistant', sans-serif;
    font-size: .8rem;
    padding:.25rem;
    text-align: center;
}

.social-network{
    text-align: center;
}

.social-network>li{
    display:inline;
}

.social-network i{
    font-size: 2.5rem;
    margin: 1rem;
    padding:.25rem;
    color: #cea716;
    justify-content:space-between;
}

/* an arrow fixed to scroll with the screen, so that a user can quickly return to the top of the page */
.arrow-top{
    position:fixed;
    right: .5rem;
    bottom:.25rem;
    font-family: 'Rammetto One', sans-serif;
    font-size: 2rem;
    color: #cea716;
}
				
			

main.js

				
					document.addEventListener('DOMContentLoaded', () => {
    
    const toggle = document.getElementsByClassName('toggle-btn')[0];
    const navbar = document.getElementsByClassName('navbar-links')[0];
    const grid = document.querySelector('.grid');
    let blox = Array.from(document.querySelectorAll('.grid div'));
    const width = 10;
    console.log(blox); //checking to make sure the array is correct
    const startButton = document.getElementById('start-button');
    let timerId;
    const playerScore = document.getElementById('score');
    let score = 0;
    const gameLine = document.getElementById('lines');
    let lines = 0;
    const playerLevel = document.getElementById('levels');
    let level = 0;
    const gameMusic = document.getElementById('music');
    const soundButton = document.getElementById('play');
    let nextRandom = 0;

    const colours = [
        'MidnightBlue',
        'Green',
        'Orange',
        'HotPink',
        'DarkRed',
        'Brown',
        'Magenta'
    ];

    // create a function that toggles the hamburger navigation menu
    toggle.addEventListener('click', () => {
        navbar.classList.toggle('active');
    });

    // create a function that toggles the music on or off using a button click
    soundButton.addEventListener('click', () => {
        if (gameMusic.muted == false) {
            gameMusic.muted = true;
            soundButton.innerHTML = 'Music Paused';
        } else {
            gameMusic.muted = false;
            gameMusic.play();
            gameMusic.volume = 0.3;
            gameMusic.loop = true;
            soundButton.innerHTML = 'Music Playing';
        }
    });

    /**
     * Create the arrays for each of the 7 Tetrimino shapes
     * represented by q, p, s, z, t, b and i
     */
    const pTetrimino = [
        [2, 1, width + 1, width * 2 + 1],
        [width, width + 1, width + 2, width * 2 + 2],
        [width * 2, width * 2 + 1, width + 1, 1],
        [0, width, width + 1, width + 2]
    ];

    const qTetrimino = [
        [0, 1, width + 1, width * 2 + 1],
        [width, width + 1, width + 2, 2],
        [width * 2 + 2, width * 2 + 1, width + 1, 1],
        [width * 2, width, width + 1, width + 2]
    ];

    const sTetrimino = [
        [width * 2, width * 2 + 1, width + 1, width + 2],
        [0, width, width + 1, width * 2 + 1],
        [2, 1, width + 1, width],
        [width * 2 + 2, width + 2, width + 1, 1]
    ];

    const zTetrimino = [
        [width, width + 1, width * 2 + 1, width * 2 + 2],
        [1, width + 1, width, width * 2],
        [0, 1, width + 1, width + 2],
        [width * 2 + 1, width + 1, width + 2, 2]
    ];

    const tTetrimino = [
        [1, width, width + 1, width + 2],
        [width + 2, 1, width + 1, width * 2 + 1],
        [width * 2 + 1, width, width + 1, width + 2],
        [width, 1, width + 1, width * 2 + 1]
    ];

    //square tetrimino - called b for block
    const bTetrimino = [
        [0, 1, width, width + 1],
        [1, 2, width + 1, width + 2],
        [width + 1, width + 2, width * 2 + 1, width * 2 + 2],
        [width, width + 1, width * 2, width * 2 + 1]
    ];

    const iTetrimino = [
        [1, width + 1, width * 2 + 1, width * 3 + 1],
        [width, width + 1, width + 2, width + 3],
        [2, width + 2, width * 2 + 2, width * 3 + 2],
        [width * 2, width * 2 + 1, width * 2 + 2, width * 2 + 3]
    ];

    const theTetriminos = [pTetrimino, qTetrimino, sTetrimino, zTetrimino, tTetrimino, bTetrimino, iTetrimino];
    console.log(theTetriminos[0][0]); // checking to ensure the tetriminos are output correctly

    //create an start position and rotation for the tetrimino
    let currentPosition = 4;
    let currentRotation = 0;
    console.log(currentPosition, currentRotation);

    //create a random tetrimino and its rotation
    let random = Math.floor(Math.random() * theTetriminos.length);
    let current = theTetriminos[random][currentRotation];
    console.log(current); //checking output of current, testing random and currentRotation

    //add the tetrimino to the grid with a draw function
    function draw() {
        current.forEach(index => {
            blox[currentPosition + index].classList.add('tetrimino');
            blox[currentPosition + index].style.backgroundColor = colours[random];
        });
    }
    //console.log(draw()); //testing to see if a random tetrimino appears on the grid

    //remove the tetrimino from the grid with an undraw function
    function undraw() {
        current.forEach(index => {
            blox[currentPosition + index].classList.remove('tetrimino');
            blox[currentPosition + index].style.backgroundColor = '';
        });
    }


    // start button acts as start/pause game button
    startButton.addEventListener('click', () => {
        if (timerId) {
            clearInterval(timerId);
            timerId = null;
            startButton.innerHTML = 'Game Paused'; //change text to show gameplay is paused
        } else {
            draw();
            timerId = setInterval(moveDown, 1000);
            startButton.innerHTML = 'Started'; //change text to show gameplay is running
            nextRandom = Math.floor(Math.random() * theTetriminos.length);
        }
    });

    //create a function to move the tetriminos down the grid
    function moveDown() {
        undraw();
        currentPosition += width;
        draw();
        freeze();
    }

    //create a function that freezes the tetrimino when it hits the bottom of the div
    function freeze() {
        if (current.some(index => blox[currentPosition + index + width].classList.contains('taken'))) {

            current.forEach(index => blox[currentPosition + index].classList.add('taken'));
            //introduce a new tetrimino to the grid
            random = nextRandom;
            nextRandom = Math.floor(Math.random() * theTetriminos.length);
            current = theTetriminos[random][currentRotation];
            currentPosition = 4;
            draw();
            addScore();
            gameOver();
        }
    }

    //create a function to move the tetrimino to the left until it reaches the edge of the grid
    function moveLeft() {
        undraw();
        const leftEdge = current.some(index => (currentPosition + index) % width === 0); //check to make sure the tetrimino does not exceed the left edge
        if (!leftEdge) currentPosition -= 1;
        if (current.some(index => blox[currentPosition + index].classList.contains('taken'))) {
            currentPosition += 1;
        }
        draw();
    }

    //create a function to bind directional movement to the arrow keys on the keyboard
    function control(event) {
        event.preventDefault(); // prevents default screen movement when pressing the arrow keys

        if (event.keyCode === 37) {
            moveLeft();
        } else if (event.keyCode === 39) {
            moveRight();
        } else if (event.keyCode === 40) {
            moveDown();
        } else if (event.keyCode === 38) {
            turnShape();
        }
    }

    //create an event listener to listen for keypresses and invoke the control functions
    document.addEventListener('keydown', control);

    //create event listener to listen for mouse clicks and invoke the control functions
    const leftButton = document.getElementById('left');
    const rotateButton = document.getElementById('rotate');
    const rightButton = document.getElementById('right');
    const downButton = document.getElementById('down');


    leftButton.addEventListener('click', () => {
        moveLeft();
    });
    rotateButton.addEventListener('click', () => {
        turnShape();
    });
    rightButton.addEventListener('click', () => {
        moveRight();
    });
    downButton.addEventListener('click', () => {
        moveDown();
    });

    //create a function to move the tetrimino to the right until it reaches the edge of the grid
    function moveRight() {
        undraw();
        const rightEdge = current.some(index => (currentPosition + index) % width === width - 1); //check to make sure the tetrimino does not exceed the right edge
        if (!rightEdge) currentPosition += 1;
        if (current.some(index => blox[currentPosition + index].classList.contains('taken'))) {
            currentPosition -= 1;
        }
        draw();
    }

    //create a function to turn the tetrimino around by a 90 degree rotation
    function turnShape() {
        undraw(); //undraw the current tetrimino 
        currentRotation++; //increment the rotation by 1 turn
        if (currentRotation === current.length) {
            currentRotation = 0; //set new rotation 
        }
        current = theTetriminos[random][currentRotation];
        draw(); //draw the new rotation 
    }

    /* limited coding experience resulted in this bugged code.           
                * I tried coding a display grid to show the upNext tetrimino for the player, but failed to get it to display 
                * correctly. Due to limited knowledge and experience with JS, I opted to comment this 
                * block of code out and return to it at a later date, as a future update, when I have
                * the sufficient skill level 
                * ***************************************************************************************************************
                //create a next up display grid so the player knows which tetrimino is falling next
                const nextBlox = document.querySelectorAll('.display-grid div');
                const nextWidth = 4; // define the size of the display grid 
                let nextIndex = 0;
                console.log(nextBlox); //check that the display grid is correct
            
                //create the first position of the tetrimino in the display grid so the player can see whats up next
                const nextTetrimino = [ 
                    [2, 1, nextWidth + 1, nextWidth * 2 + 1], //pTetrimino
                    [0, 1, nextWidth + 1, nextWidth * 2 + 1], //qTetrimino
                    [nextWidth * 2, nextWidth * 2 + 1, nextWidth + 1, nextWidth + 2], //sTetrimino
                    [nextWidth, nextWidth + 1, nextWidth * 2 + 1, nextWidth * 2 + 2], //zTetrimino
                    [1, nextWidth, nextWidth + 1, nextWidth + 2], //tTetrimino
                    [0, 1, nextWidth, nextWidth + 1], //bTetrimino
                    [1, nextWidth + 1, nextWidth * 2 + 1, nextWidth * 3 + 1]  //iTetrimino
                ]; 

                //display next up tetrimino in the display grid
                function nextUp() {
                    nextBlox.forEach( next => {
                        next.classList.remove('tetrimino');
                    });
                    nextTetrimino[nextRandom].forEach(index => {
                        nextBlox[nextIndex + index].classList.add('tetrimino');
                    });
                }
    */

    
    /** I tried a variety of methods to create a function whereby the players score 
    *  would be taken from the gameOver() and passed to a finalScore that could be stored locally 
   *************************************************************************************************************/
    //create a high score function to store the players highest scores locally
    const playerName = document.getElementById('playerName');
    const saveScoreBtn = document.getElementById('save-score');
    const finalScore = document.getElementById('finalScore');
    const mostRecentScore = localStorage.getItem('mostRecentScore');
    const highScores = JSON.parse(localStorage.getItem('highScores')) || [];
    finalScore.innerText = mostRecentScore;

    playerName.addEventListener('keyup', () => {
        saveScoreBtn.disabled = !playerName.value; //save button should be disabled until the player inputs their name 
    });

    saveScore = (e) => {
        e.preventDefault();

        const score = {
            score: mostRecentScore,
            name: playerName.value,
        };
        highScores.push(score);
        highScores.sort((a, b) => b.score - a.score);
        highScores.splice(3);

        localStorage.setItem('highScores', JSON.stringify(highScores));  //stores the players score in the localStorage
        window.location.assign( ' / '); 
    };

    //create function to add score to game for clearing lines
    function addScore() {
        for (let i = 0; i < 199; i += width) { // for loop to iterate through the entire grid
            const row = [i, i + 1, i + 2, i + 3, i + 4, i + 5, i + 6, i + 7, i + 8, i + 9];

            if (row.every(index => blox[index].classList.contains('taken'))) {
                score += 15; //increment the player score by 15 for every line cleared
                playerScore.innerHTML = score;
                lines += 1; //add 1 to the player dashboard to show the player how many lines they've cleared
                gameLine.innerHTML = lines; {
                    if (lines % 4 === 0 && lines < 1001) { //increment the level for every 5 lines cleared
                        level += 1;
                        score += 100; //add 100 bonus points for every level gained
                        playerScore.innerHTML = score;
                        playerLevel.innerHTML = level;
                    }
                }

                row.forEach(index => {
                    blox[index].classList.remove('taken');
                    blox[index].classList.remove('tetrimino');
                    blox[index].style.backgroundColor = '';
                });
                const bloxRemoved = blox.splice(i, width);
                blox = bloxRemoved.concat(blox);
                blox.forEach(cell => grid.appendChild(cell));
            }
        }
    }

    //create a function to check the gameOver conditions
    function gameOver() {
        if (current.some(index => blox[currentPosition + index].classList.contains('taken'))) { // check to see if a taken shape is at the original index position 
            clearInterval(timerId); // stop the moveDown() function
            startButton.innerHTML = 'Game Over';
            startButton.style.backgroundColor = 'red';
            startButton.style.color = 'white';
            startButton.disabled = true; //disable the start button so that the game cannot continue
            localStorage.setItem("mostRecentScore", score);
            //redirect player to the scores page
            return window.location.assign("index.html"); //player should be able to save score locally, navigate to other pages in the site
        }
    }

    //create a function that validates the user input in the name field of scores.html
    // TODO
    function validateForm() {
        let x = document.getElementById('PlayerName').value;
         if (x == "" ) {
            alert(' You must enter a name!');
            return false;
         }
         
    }
    validateForm();

});
				
			

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top