How to build 2048 in React (with animations)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hello everyone welcome to my youtube channel i'm matt and today we are going to build 2048 the game we are going to use react css transitions custom hooks the video will be different than my other ones since we are not going to do the live coding we will do only the code review i hope you like this form please let me know in the comment section please hit the like button subscribe and let's get started what are the rules of the game so the players should combine tiles until they reach the tile of the number 2048. there are some constraints the tile can only contain integer values starting from the number 2 and being the power of two what does it mean being the power of two it means 2 4 8 16 32 64 128 and so on board is restricted to the size four tiles by four tiles so it means there are only 16 possible tiles to be on the board in the very same time ideally the player should reach the finite tile within the smallest number of steps and if the board is full and there is no possible move to make the game shall be over unfortunately i took some shortcuts to focus on the game mechanics and animation so i didn't implement scoring i simplified the type generation and you cannot finish the game like you cannot complete it so the winning situation and the loose situation is not handled by my code if you think this is unacceptable you can fork my repository and implement it on your own if you want me i can review your code just add my username into your pull request and i will look into it so now let's get started now i'm going to introduce you to the project structure our application will contain four very important elements the first one is the board component responsible for rendering the tile and grid the second one is the grid component which renders the grid four by four tiles the third one is the tile component responsible for rendering the tile and also all animations related to itself the gaming component is the last one and it combines all those elements together it also includes the use game hook which will be responsible for enforcing the game rules and all of its constraints since we want to focus more animation in this tutorial i will start the story from the title component in the end this component is responsible for all animations in the game so there are two fairly simple animations in our game such as tile highlighting and the second one is sliding the tile across the board and then merging it which is actually the tile highlighting right so we can handle those animations with simple css transition by declaring the following styles as i mentioned before we are going to use css transition to achieve animations our application so css transitions allows us to change the property value smoothly over a given period of time so what does it mean let's look into the code we have two different properties related to transitions the first one is translation property and we define following values left top and transform and transition duration which we have three other values right so what is the transition property so left and top tells us that we are going to transition the position of the tail so we wanted to animate the slides and we are going to do this by defining this property what about the transform transform actually allows us to scale up and down the tile so we'll indicate this highlight by using this property as you can see the animation of slides will last 250 milliseconds and the scale up and scale down will be 100 milliseconds now we are ready to look into the tile component itself so as you can see the component accepts only three properties such as a value which is an actual number within the tile such as 2 4 8 16 and so on the second one is a position of the tile on the board since our board is two-dimensional it has x and y value we are going to pass the position as an array of two elements the first element will be responsible for the x value and the second one for the y value instead of using pixels we are going to pass the indexes of those properties what does it mean let's look at our board as you can see the row represents the x value and it can take the following indexes 0 1 2 and 3. the column is very similar but it represents the y value it can take the same indexes as the row since the board has the shape of square the last property is that index which allows us to define the order of tiles if they are stacked on each other we are going to use it later on to handle the tile merges now you know all properties required to use this component so we can deep dive into how it's spelled let's get started from the line 18. i'm calling a hook that returns properties of the board such as the length of the container which are the width and height it is a square and the amount of tires per row and column i'm going to need those properties to calculate the position of the tile you can check out the complete code of the hook on my github it is a very standard implementation of react contacts api and hooks so i will skip that if you want to learn more about it you can check out my video on feature toggles find the link on the top right corner of this video in line 20 i defined the scale state i'm gonna use it to animate the highlighting of the tile so we will scale it up and down whenever it is needed to indicate the change of the tile from the line 23 to 30 we are going to determine if we want to animate the highlighting of the tile the highlighting shall be triggered when the new tile is created or whenever the existing tile changed its value not the position just the value just the number within the tile the use effect hook in the line 33 will be handling the animation for us it will rescale the size of the tile to 110 percent and after 100 milliseconds it will change it back to 100 percent so the tile will have this some sort of bump effect you might have noticed that i'm using a custom hook called use breath props it helps me to track the previous values of the component properties the props so i can indicate if the value of the tile has changed and then i can react to this you can find the code of this hook on my github as well now let's look how the position of tile is converted into pixels in line 43 i created a function called position to pixels the responsibility of this function is converting the index on the board into the actual position in pixels the function accepts only one argument which is the index of tile on the board as i said before the board has a shape of square so that we need only one function to convert both coordinates x and y so how are we going to use this function we'll simply pass the new position into the style property of the component just like that now you know how the tile component works so we are ready to go through the board component the board component will be displaying our tiles and populating the properties of the board to the context api this component accepts only two properties and only the first one is mandatory the first one is the tiles which contains the list of tiles available in the game and the moment i will explain what includes the tile meta type later in the current moment we only need to know that it includes tile value and its position on the board the second one is the tile count per row it tells the board how many tiles are in the row and column by default it equals four but if you wish you can change it so now we know all properties of this component so we can review its code i'm gonna jump around the components code since it does two different things and the business logic is a bit mixed up but those two things sort of overlap so i didn't decide to split this component into two let's look into the tile rendering first in line 20-22 we will create an array of tile components which will be rendered in the tile container what is the tile container the tile container is a simple div that has only one custom css property the property responsible for positioning called position and we need to set its value to relative why we want to position every tile in a relative way to the board so we reach one directional way of control from the parent to the children not other way around now let's look into the second role of this component which is populating all properties to the context api in line 17 and 15 i'm calculating the width of the tile container and its board wrapper i decided to calculate those properties on my own instead of using references to the dom elements it was just easier to do it since i know all tile sizes and intermargins look the container width equals the width of the tile with all margins multiplied by the amount of tiles and now the second one the board width equals the container width plus margins it is simple by following this approach we made the code easier to read comparing to references but please don't take it as an argument against references the references are super important but in this case are not needed okay let's get deeper in line 28 and 27 we are injecting the container with variable and tile count per row property into the board provider what is the board provider the board provider is a provider coming from the context api i don't want to dig deeper into context api since i explained it in my previous video on feature toggles you can find the link to this video in the top right corner now the most important thing you need to know about contacts api is that enables us to use declared properties in all children of this component without passing them as props we are using it extensively in the tile component do you remember the position to pixels function it uses board properties to set the right coordinates on the board now you know how the board works so we are ready to go through the game component the game component is responsible for connecting all pieces together it will render the board and link keyboard events with the game engine the component doesn't accept any properties so that will skip this part in the very first line of this component you can see a strange hook called use game this hook returns the list of tiles and exposes four functions responsible for navigation such as move left right up and down the list of tiles must be passed down to the board component so it can display them but the most important role of this component is linking the right keystrokes with actions in line 10 we have the handle key down function which maps navigation of keyboard arrows but you might say hey matt but this game will not work on my iphone and you are right if you want it to work on your iphone you need to fork this repository and implement it on your own it is a nice exercise that will let you dig deeper into the code and understand it better but before you do so smash the like button click subscribe and notification bell in line 31 i throttled our function to be sure the player will not be able to float the game with tons of ski strokes our game has beautiful animation and we want our players to admire them and also our logic of the game is built based on red users so we really need to have all events completed because otherwise the game will be broken do you see this use effect in line 37 i have bound our function into the window evenly standard so it can listen to keyboard events otherwise it will not be possible to do this as you can see this function returns the callback that removes this event it is required to prevent memory links this callback will be called when the component will be destroyed when it can happen when you click the reset button for example now we are reaching the core of our game the use game hook as i said the use game hook will be dealing with the game logic i split it into two parts the reducer and the hook we're gonna start from the red user since it will be storing and manipulating the game data the reducer requires two arguments the first one is the state and the second one is actions let's dig deeper into the state first as you can see it contains the following fields details it is a hash table responsible for storing the tiles the hash table makes it easy to find entries by keys it is the best match for us since we want to find our tiles by ids now it is a good time to explain what the tile meta type actually contains it has an id which is a unique identifier we'll need ids to merge tiles with each other but they have also one another important role they help react them to detect changes in the right tiles rather than re-render all of them from scratch the second one is the position it is exactly the same position we discussed along with the tile component simply the row and the column index on the board so for our size of the board it might have values from 0 to 3. the third one is the tile value it can have values from 2 up to 2048 the last one is the merge with property if this property is present the tile will be merged into another tile the property can be undefined or it must contain the id of the tile which is going to absorb the current style so we needed to extract this property since we want to run two animation one after another so the last one needs to be delayed how it works first we want to let the tile slide onto another style and once the animation is finished we want to destroy the tile and update the destination tile now you know what the tile meta contains so we can continue with the state definition the second property of the state is by id's array this array contains all ids of the test in the expected order it means ascending it will help react dom to update only the tasks that have been changed the third property is the hash change flag it keeps track on the tile changes as you know on every move we want to add a new tile to the board so this property tells us if any type change their value or position if they don't the new tie will not be added the last property is the inmotion flag it determines if tests are still moving if they are moving the new tail will not be generated until the animation is completed now we are done with the state so it is a due time to discuss the actions we will support 5 different actions in this reducer the first one is create tile it creates a new tail in the hash table and sets the hash change flag to false to indicate that the new tile was already generated so there is no need to create a new one the second one is update tile it updates an existing tile and sets the hash change flux to draw to notify that we want to still create a new tag after animations are completed the third one is merge style it merges this hole style into the destination tile the source type will be removed from the hash table and by id's ri and also it changes the hast change property to true the next one is start move and it tells reducer it should expect multiple updates and it must wait until all of them are completed to keep the consistency of the animations the last one is and move it till the animations are completed so we can safely add a new tile into a hash table now you know how the reducer handles actions so we can dig deep into the hook the hook will be mostly dealing with navigation so i'm gonna explain now how i implemented the move function first the move function will perform the board transformations such as tile moves and merges it accepts two arguments the first one is the retrieve tile ids per row or columned callback which supposed to return all tile ids within the row or column it depends if we transform the board horizontally or vertically the second one is the calculate first free index callback it's supposed to return the first three index in the row or column we needed to calculate the new indexes of tails after board transformation it might sound a bit vague but hopefully it will get clear once i explain the logic of this function in the very first line we can see i dispatch the start move action to lock the board so none utile can be created until it is released as you can see i'm gonna dispatch the end move action at the end of this function so i will delay this dispatch by the duration of animations so animations have time to complete in line 111 i declared the max index in row or column i will need it to calculate the first free position of the tile in our case max index equals 3 since the board is 4 by 4 tiles and all indexes start from 0. in line 114 i'm gonna iterate through every row or column to merge and update tiles in line 123 we'll use the first argument of the move function to retrieve all tiles in the row or column once we have our tile ids in line 131 we gonna iterate and transform them in the line 132 we will create a reference to the current style which we are going to use to create a new tile object in line 156 as you can see in line 156 we are going to use the second callback we injected into the function i skip explaining the parameter of this for now we'll return to it very soon though in line 172 we check if the title was moved and if so we are going to update it but you might say hey matt but you skip some parts of the code here you are right let's return to line 126. in line 126 i declared the previous style variable which i'm gonna use to detect merges in line 128 i defined the variable to store how many merges happen in this row or column so i can correct the position of the next test to fill the gaps in line 135 of this for each function we'll check if the previous style has the same value as the current one and if it does we're gonna schedule to merge those styles together as you can see the merge style action is throttled so it waits until the moves are finished and then it performs the merge in line 148 we clear the value of the previous tail variable so we allow for only one merge per tile per move in line 150 we'll increase the merge style counter by 1 and then we'll update the tile finally we are reaching the very last function of this game we will focus only on one move function since all the other ones are almost the same the only differences are if they are dealing with the columns instead of rows or from which directions they calculate their indexes let's get started as you can see i declared the move left factory function which will create a move left function for us in the very first line i create a helper function that will return tiles in the row it accepts only one argument that says from which row index we want to get tiles we are using rows since this is a horizontal transformation if we will create move up or down factory function we will operate on columns rather than rows in line 197 i declared the calculate first free index helper that will calculate a new position of the tile we just need to pass the tile index tile index in row the amount of merges in this row and the max index isn't really required in this case because the move left function doesn't calculate stuff from left to right obviously this helper will be different for other directions but i want to focus on one only to simplify the review process and in the very last line of this function we are binding those helpers into move function this technique is called inversion of control what it does it allows a developer to pass a custom code into the function to manipulate its execution it is a very important design pattern so you should learn it so if you want go through the code on your own as well you can find it on my github now let's look at the bottom of our hook as you can see we initialize all moves to their factory functions and return them as the output of the hook and that's it for today i hope you enjoyed the video let me know in the comment section please hit the like button subscribe and stay tuned for the next episodes
Info
Channel: Matt Sokola
Views: 3,512
Rating: undefined out of 5
Keywords: 2048-game, 2048, 2048 in react, animations, css animations, css transitions, react animations, typescript, javascript, react hooks, how to build 2048 in javascript, howto, tutorial
Id: vI0QArPnkUc
Channel Id: undefined
Length: 24min 31sec (1471 seconds)
Published: Mon Sep 06 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.