Radu Mariescu-Istodor developed this course. Radu has a PhD in Computer Science and creates really creative programming tutorials.
This is a really unique tutorial. The project is fun to build and fun to play.
Here are the topics covered in this course:
- Accessing the camera
- Cropping the image
- Drag and drop
- Gameplay elements
- Logo design
- MySQL database
- PHP web server
- Advanced cropping
- Advanced hit-testing
Watch the full course below or on the freeCodeCamp.org YouTube channel (2-hour watch).
To create a puzzle game that uses the video feed from a camera, you will also learn a bit of HTML and sequel.
Raju has a unique and interesting style that makes learning the concepts really fun.
Hi, I'm Raju, and today I'm going to teach you how to make a puzzle game that uses the camera.
This one here is done using PHP and MySQL to save and load scores from the database.
The game works on desktop computers, but also on mobile devices like phones and tablets.
By watching this, you'll learn many useful things that you can later apply in other projects.
Even though some of these things are quite advanced.
I'll teach them as simple as I can.
And I'll leave the two most complicated things at the end.
Good news is that they're also optional, meaning that you can get a fully working game without them.
So don't be scared.
It'll be fun, I promise.
Also, special thanks to Beau from Free Code Camp for allowing me to share this with you guys.
I hope you'll like it.
Then if you have any questions do contact me via my YouTube channel.
Now let's begin.
We begin by writing the HTML page.
In the header, we give the page a title.
This will change what we see here in the browser tab.
These files are just empty for now.
Then the body part of our document will be really simple.
For now, we just run a function called main when the page loads and add the canvas element here, which is what we'll primarily use to build the game, it will be a Canvas app.
If we refresh the page, now we see the tab shows the correct title.
And we also get an error here in the console.
If you don't see the console in your browser, try pressing F 12.
It's the shortcut for the developer tools in most browsers.
I'm using Google Chrome, I think it has the most useful tools.
But it's really just a personal preference.
Any one of them will work just fine.
Now the error says that this main function is missing.
For now, I'll just make it output the string main into the console to test if it works.
And it does.
It's very good to debug like this whenever you make a change.
Otherwise, errors pile up, and it can be very confusing to deal with many of them at once.
But now let's see how to access the camera.
We'll use a promise to get access to our media devices.
We're only interested in the video coming from the camera, and we specify that here.
What happens now is that the browser will ask the player for permission to use the camera.
When they give it the code will go here, where we define a callback function in which we have access to the camera signal.
If the player doesn't allow the camera or there is some other error, we will catch it and show it on screen.
When everything works out, we initialize the video object.
We'll use a global variable for this, and I will write these in capital letters.
To make it clear when a variable is global or not in the code, we next create the video element.
initialize it to the signal coming from the camera and play it when video data is available, we can start updating it on the canvas.
This update Canvas function is what we will implement next.
But first let's also define the canvas here globally and add the reference to the canvas context object as well.
We initialize the canvas with the one defined in the HTML page previously.
This to the context of the Canvas provides all drawing methods we will need to build the game.
Our Canvas will feel the entire window now the update Canvas function needs to draw the video onto the canvas.
So we will use the draw image method Have the canvas context to do just that.
We also need to specify here that we want to start drawing from the top left corner, which has 00 coordinates.
Now when I refresh, something happens, it shows the image coming from my camera.
My camera is just pointed at the wall here at the moment.
But it doesn't pick up any movement just yet.
We need to update the canvas many times per second to see this happen.
And we'll use the request animation frame methods to make it work.
This method will call the function recursively many times per second, and it will try to update 60 times per second if the computer is fast enough.
Now we get the live image.
Let me just put Mr chibi son here to make it less boring.
Now you'll notice a white border here.
It's a bit strange, because we said we want to draw the video at 00.
But the body element has some default margin.
And that's what we're actually seeing.
We'll remove that by going to our CSS file, and changing the style of the body element by setting the margin to zero.
Better now, but we still get the scroll bars here, which are not really necessary.
So I'll remove them by setting overflow to hidden.
Now the scroll bars are gone, but the video actually goes outside the screen.
We don't want that.
And we need to keep in mind that in general, there are many webcams or phone cameras out there with different resolutions, aspect ratios and so on.
So we need a way to resize the video so that it fits in the middle of the screen, preferably with some space around it so that we have enough room to move the pieces later.
We'll use this scaler to specify how much of the screen space will be used by the image.
And they will also keep track of other related information in this size variable.
We update these values here when metadata about the video is available.
First we use a helper variable to find out the minimum ratio between the screen size and the video size.
And then we set the size attributes accordingly.
Note that here one of these things will simplify.
So we properly preserve the aspect ratio and nothing gets stretched.
For the x&y coordinates we start off in the middle of the screen and go just half the width towards the left and half the height towards the top.
Then to actually affect the image, we must update the draw image method here.
This method can be called with a different number of arguments before we just specified the top left corner.
But now we also add the width and the height.
Okay, it works, we now see the entire image coming from the camera before it was slightly cropped on the right.
We also get this safety percent margin.
And if we change the value here, we get the different size margin instead.
We can also check to see how the application looks on different size screens by pressing this button.
We can even select some specific device from this list.
But every time we change something, we need to refresh the page to see it happen.
How about in landscape mode, need to refresh again.
We could actually make this resizing happen automatically by using an event listener for the window resize.
We then need to move this code in the callback function and also need to call this function here so that the code executes as before.
And I should also move these lines here too.
In this way the Canvas will resize as well not just the camera stream.
Okay, let's see if we change the orientation.
Yes, it works.
No matter what the screen size, we will get the proper fitting this time.
But even more important is that it will work regardless of the size and aspect ratio your camera outputs.
I'll demonstrate this by forcing my webcam to output a specific size.
Note that not all webcam supports resizing in this way, and it may not work for you, but you can try, you can see it works just fine with the square aspect ratio.
Let's try a vertical aspect ratio next.
Okay, works just fine.
I will remove these extra parameters.
Now, because we want to see the entire video coming from the camera.
This was just an experiment.
And I will actually comment out this event listener.
Because we don't really need this functionality inside the app.
Users are not expected to resize the screen like this.
I made it to demonstrate better how things work and the void refreshing all the time.
Plus, you may need it in one of your projects.
We now define the piece class and specify a row and column index in the constructor.
We will have an array of these pieces and define it globally here.
We also need to specify how many rows and columns there are.
And I will store these here.
Now to initialize the pieces, we begin with an empty array and iterate through the rows using the I variable and through the columns using the j variable and add the new piece defined using these two indices into the array.
To be able to draw the pieces, we are going to implement the draw method that takes the context as a parameter.
Let's first just draw a simple rectangle here to see if what we did so far is working.
Now we need to figure out the location and the size of these pieces based on the number of rows, columns, and the size of the video on the screen.
I will enter these placeholders here and calculate the exact values in the constructor.
The width and height of each piece is just the width and height of the area divided by the number of columns and rows respectively.
I set here the x and y to be so that each piece is defined to be at the correct location at first.
Even though this is a puzzle game where the player needs to find out what the correct locations are, it helps to start with the soft configuration when developing the game.
I realized now that x and y could actually be defined after we have the width and the height, and then define them using the width and height like this.
The code is clear in this way.
We now need a way to draw the pieces.
We do this here by just iterating through all of them and calling the draw method using the global context.
Okay, I think we're ready to debug now.
Oh, I forgot to call the stroke method here.
Without it, nothing will be drawn.
Let's see, I'll refresh and nothing is different just yet.
It's because the pieces array is empty, we need to call the initialize pieces function.
And we'll do this in the console for now.
Okay, now something happens and it looks good.
Three rows and three columns.
But it's not ready yet.
These are just empty rectangles drawn on top of the video.
Look, if I comment out drawing the video image here you can just see the grid.
Each piece needs to crop a specific part of the video and show it we do that by adding a call to the draw image method here.
We use another version of the draw image method a more complicated one that accepts nine arguments.
After we specify the video, we need to tell the left part where the cropping happens.
Then the top part then the width and the height these values are relative to the video.
And whatever the resolution and aspect ratio it has Now after we specify where to take the image data from, we need to say where to draw it.
This is easy.
It's just that the piece x&y location, and we need to use its width and height okay, it now looks the same as before, but each piece is showing the part of the video is responsible for we can actually parameterize this function here to support any number of rows and columns and we just call it here with default values in the beginning we can test in the console by using different sized grids let's randomize the location of the pieces.
Next, we'll just write a function where we iterate through all of them and generate a random location for each piece.
We set here the x and y respectively we call this in the console.
And it's not what we expect.
The random values are between zero and one, meaning that they are all pretty much in the top left corner here.
I'm not going to go into how half pixels work.
So let's just move on and scale these by the canvas width and height.
Okay, much better.
But you can see that some pieces go outside the screen here.
We can prevent that by subtracting the piece width and height here when scaling.
Now the reason why we see duplicate pieces, once randomly distributed and others that are behind them in the correct locations, is that we are not clearing the canvas before redrawing each frame.
If we do this here and randomize the pieces again, we get what we expect.
Let's test different grid sizes as well.
Neat, I actually want to have an idea of where the pieces need to go, it will make debugging easier.
And maybe we can keep it as a easy mode or or something like that.
So I'll move this clear rect call to the top set the 50% transparency, and uncomment drawing the video we had earlier.
Then I reset the transparency so that only the video is semi transparent, but the pieces are drawn normally after that.
In this way, the video on the background shows where I need to move the pieces, but it's faded so it won't be confused with the actual pieces now to implement the drag and drop functionality for the pieces, we need to add some event listeners to the canvas.
We implement the function for it here and begin by adding an event listener for mouse down with a callback function that we will need to define.
Then two more event listeners for mouse move and mouse up will also be needed.
To handle the mouse down event, we first marked down what is the selected piece by getting the one the user presses in the interface.
We will define this function soon.
But let's get the top level logic down first.
We'll store the selected piece inside the global variable which we initialized with normal in the beginning or if no piece is pressed.
Then if a piece is selected, we calculate what is the offset to the top left corner of the piece like this we will use this offset while dragging so that the piece doesn't just snap to the mouse location like this.
It will make for a smooth interaction.
Then on mouse move, if a piece is selected, we update the location to the new mouse location and consider the offset as well.
The mouse up event will just leave empty for now.
So how do we find out if the player presses a piece or not? Well, all we need to do is iterate through all the pieces and check to see if the click location is within the bounds of any of them.
We need to check if x is greater than the pieces X and less than that plus the pieces with.
And if the y is greater than the pieces why and less than that, plus the pieces height if so, we just returned this piece.
If nothing matches these conditions, we just returned No, which means that nothing was pressed.
Okay, let's test I move the mouse over the bottom left corner piece, I click and drag.
Great, but now I'm stuck like this, I need to implement the mouse up event so that I can actually let go of the piece.
Now the obvious thing that we need to do is set the selected piece to know.
But before that, we need to do one more thing.
We check if the piece is close to the correct location where it's supposed to go.
And if so we snap it in place.
We will need to implement these new methods.
But first, let's discuss why we're doing this.
The first reason is that it gives proper feedback.
When a player lets go of the piece, if it jumps into place, it means that the move was correct.
And the player can move on to the next one.
The second reason is that later, we will need to evaluate if the pieces are in the correct position or not.
And it's very unlikely that the player will be able to drop all pieces in a pixel perfect way.
So this snapping will help with that as well.
final reason is that I think it makes the game more interesting and fun to play.
So to implement this functionality, we go back to the piece class and add the two new methods.
First, we check if the piece is close to the correct location.
We do that by simply calculating the distance to the correct location.
And checking if it's under a threshold.
Or use a threshold that is proportional to the size of the piece here, about 33%.
I just did some trial and error and I think it feels good like that.
If the distance is less, then it's considered close.
Otherwise, it's not the correct locations should be stored somewhere.
And I'll do that here in the constructor because we already agreed that the pieces will be initialized at the correct location.
To measure the distance, we just use a consequence of the Pythagorean Theorem.
I'm pretty sure you know it already.
But if you don't or want to learn something more about it, check out the recent video I made about it.
I think I did a pretty good job explaining it proving it and giving all kinds of applications.
What do you guys think? The snap method is really easy.
It just puts the piece in the correct location and that's it.
Let's debug now I can go and click on a piece, drag it and release.
I can release it anywhere I want.
And if I drop it close to the correct location, it will snap into place.
And this works for every piece.
But one issue is that the selected piece is sometimes drawn underneath the others.
This is not good.
It depends on the order we are drawing the pieces.
Since the top left one is the first in the array.
All others will be drawn on top of it.
But the bottom right one doesn't have this problem.
It's always on top.
We need To fix this by moving the selected piece at the last index in the array, we first find out the index it's currently at.
Then even though it should not be possible for it to be missing, I check here just to be sure, I then remove it using the splice method and add it again at the end of the array using the push method.
Now, no matter which piece I select, it will be drawn on top of everything as expected.
But there still one problem.
When I click here where multiple pieces overlap, it selects the bottom most piece.
We fix this by iterating the pieces array in reverse order.
In this way, we stop at the topmost piece that the user is clicking on.
Now everything works well.
But one final thing we need to consider is mobile devices.
What we did so far actually doesn't work on those yet.
See, even if I debug using this mode, and I try dragging pieces around, nothing happens.
This is because mobile uses touch events instead of mouse events.
So we need to support those as well.
We go back to the function where we add event listeners copy all three of them and rewrite what is necessary.
The touchstart touchmove and touch end callback functions will need to be defined next.
But they are easy to do because we will reuse the mouse event callback functions from earlier, we just need to get the location here in a little different way.
And then we can call the other callback like this we copy this callback function and implement the other two.
Now when we debug it works, but an error happens on touch.
And that's because there's no location available then.
I mean, if you're not touching, where are you not touching.
So we actually omit these arguments here.
Now everything works when I'm debugging here.
But some mobile devices have a built in function that refreshes the page when dragging down.
We don't want this to happen every time a player drags a piece downwards.
So we disable it in the CSS file by setting over scroll behavior to none.
That's it for the drag and drop.
Let's just see if it also works with different sized pieces.
Next, we add gameplay elements like the menu and showing the elapsed time, we need to define some more structure in the HTML page, a div that will contain all these new elements.
A div for the menu and the div for showing the time.
The menu will contain a select component for setting the difficulty there will be four different options.
We also need the button here to start the game.
Next, we add two new global variables to keep track of the start and end time.
We then implement the set difficulty function first, we get the value of the select component we defined in HTML.
Based on that we call the initialized pieces function with a different number of rows and columns.
I'm using a switch case structure here to avoid many if else statements.
It's a common mistake my students make.
The values I use here are just guesses pretty much.
But for the insane variant, I wanted to have exactly 1000 pieces.
So I use 40 and 25, which has the most square aspect ratio, then the restart function will set the start time variable to the current timestamp.
And the end time is set to No because we just started playing, we also randomize the pieces at this stage.
If I refresh, we don't see the menu anywhere.
Actually, it flashes rapidly before the page loads.
But where does it go, we can use the developer tools to inspect the elements and see that they are pushed below the bottom of the page by the canvas, we can actually try out different styles here to get it to work.
Setting an absolute positioning with the Xero top will do the trick for now.
So I will add this to our CSS file.
Refreshing now it works.
I can press start and it will randomize the pieces each time.
And if I change the difficulty level, we see the different sized grids coming up.
I thought the insane mode would be too processor intensive at first, but it works fine on my computer at least.
I'm sure some devices will struggle though.
Next, we display the time, I will define a function called Update time that first takes the current time from the system.
Then it takes the div with defined earlier to hold this value and sets the inner HTML to the difference between now and the start time.
We need to call this function somewhere.
So I will go to the update Canvas function that is called on every frame and enter it here.
We also noticed that this name is not proper anymore, because now we update more than just the canvas here.
So I will refactor it a bit when I refresh now I press start and it shows the time here but it's in milliseconds and we should format it properly I'll first converted to seconds by keeping the integer part of the milliseconds when dividing by 1,000th then I want to consider this format.
So we need to know the seconds part between zero and 59 and we will get it like this.
The minute part also between zero and 59 can be obtained like this.
And the Our part is between zero and 23.
And we get it like this, I doubt anybody will spend more than a day playing this.
Then to format them into the final string.
I take each component and do a left padding with zero if necessary.
I use a column between each part then we return this final value and that's it.
Now we can use it here to format the difference starts to feel like a real game already, don't you think? But nothing happens when we're done.
We need to detect somehow that all pieces are in the correct location.
We do that by adding an attribute here that will save the location is correct or not.
Initially, it should be because the pieces are defined at the correct location.
But then when we randomize, we immediately set it to false.
Then, when a piece is snapped to the correct location, we set it to true again.
It's possible that someone grabs a piece from the correct location.
So we need to set it back to false here on mouse down.
Then to check if all the pieces are correct, we write the function like this.
It goes through all pieces.
And if even one of them is not correct, the function returns false.
Otherwise, we return true.
We check this in the mouse app callback function.
Because the game can only be completed when the player releases a piece, the last piece that goes in the correct location.
Here, we also need to check if the end time is no because in principle, the player can continue to move pieces around after the game is over.
And we don't want to update the time anymore.
After that, we just set the end time here to the current time.
We also need to change the display here so that the front end time is available, the difference to that is shown and not the difference to the current time.
Okay, let's see now, timer starts counting same as before we played the game.
And now the timer stopped.
Now we'll make this part look better.
First, let's put the button below like this.
Now we can move to the CSS file and position these in the center of the screen.
We say that the items should be 50% from the left and 50% from the top.
Now if we leave it like this, the items will move here.
So their top left corner is in the center screen.
But we will like them to be here.
So we need to add transform translate minus 50% minus 50%.
And now we get this.
Now the text isn't clearly readable as such, let's add the semi transparent background here like this.
Better now we can align the items to center like this we can give the items some spacing around by setting a value for the padding.
I'm using a value of five min here, which means that the spacing will be 5% of the minimum between the width and height of the window.
I also set the black semi transparent border here as well.
Now we can make the button look better.
We'll give it the larger font size and the blueish background color and white text.
We can make the button more user friendly by changing its properties when mouse is hovering it.
I'll make it have an orange background and black text in this case.
Okay, now when I hover it changes style.
You can't see my mouse here now for some reason, but it's there and it looks like the default arrow to make it look like a hand cursor.
We can change it here and CSS.
Nowadays there are many fancy features you can choose when styling elements like you can easily add animations with the transition property.
By setting this 0.3 second duration here, we will get the short animation from one style to the other on hover.
I think it's really nice.
I don't like this border here.
So I'll remove it and that will make the button corners round by setting a small radius the font size apart from this button is rather small.
So we'll work on that next.
First I change it to Arial I think that the Sarah stone look very good in this case.
Okay, now I'll make it bigger.
Same as the font size on the button.
I think it's okay now Let's see how it would look like on mobiles as well.
It's definitely read the ball, I think it could be a bit bigger.
But this will also do for now, the text on the select component is not affected yet.
So we need to specifically add the font size there as well.
Okay, switching back to full screen now, I'll change the mouse to look like a pointer when hovering this select component as well, and style this element to be more in line with the button.
So no border, a small radius, and we need to remove the outline here as well.
Okay, let's try testing the game.
I press start, and it's not what we want, really, this menu should disappear at this stage, and only the timer should remain visible.
So I will go back to the HTML and take out this element from here.
Now I refresh, press the start button and the menu is gone.
But the timer is gone as well.
It should be there.
I switch to the Elements panel.
And it is there.
It is just below the canvas.
So we need to change its style and CSS.
I want it on the top of the screen.
So I will change positioning to absolute.
Set the zero top and let's have it center screen.
So I set left to 50%.
And I use the Transform translate method again, but only on the left side.
Now I refresh, press start and the timer is there.
Next I'll show you how to draw a logo using PowerPoint.
You can use a number of different software to do this.
I'm using PowerPoint just because it's installed on most student computers.
So hopefully you have it too.
I use the rounded rectangle tool to start drawing a camera, I can adjust the corner radius like this.
Now I make a copy of the item by holding down the Ctrl key and dragging it to a new location I will do a similar thing with circles now.
I will change the colors to just black and white for the circles.
And I will use the align tools to make sure they are just the way I want them to be.
I changed the color of the rectangles drawn earlier to black and move the circles on top.
newer features of PowerPoint have these helper lines appearing and they are useful, I think, but if you don't have them and want to have proper alignment, just use the align tools we used earlier.
Now I'm happy with how this looks.
So I will copy it and paste it as an image, we will need to use it later in this format.
But now I want to draw a small puzzle piece.
So I start off with the rectangle about this size.
And I draw an oval here as well.
I will shape this oval to look more like the connecting part of puzzle pieces.
And I do that by editing the points of this shape.
Right click Edit Points.
It's useful if you understand how Bezier curves work at this stage.
Okay, I'm happy with this.
I copy it, rotate it on its side and merge these three items into a single shape by going to merge shapes and union.
Now I'll place this on top of the image copy of our camera, align it a bit better and duplicate both objects on the right.
For the first copy, we select first the camera image.
Then holding Shift, press on the puzzle piece as well go to merge shapes and subtract.
For the second copy.
Also select the camera image first.
Then holding Shift press on the puzzle piece as well go to merge shapes and intersect.
I now move this piece into place so that it looks like we're just completing the puzzle.
Now select these two objects Copy and Paste his image.
At this point, we don't need all the other items anymore.
We just keep this one as the icon, and we'll use it to make a title.
Next, I just create a text box and right puzzle cam here, in all caps.
I set the font to Arial Black to be similar to that in the HTML page and increase the size of the font.
I then make the P larger, and I want it to be the same size as the camera.
So a bit of trial and error goes here.
And when we're happy with it, apply the same size to let her see and we're almost done.
We can now copy everything and paste as an image once again.
But you'll see that the image has some weird transparent margins here.
It's how PowerPoint works when text boxes are involved, unfortunately, but we can crop that out using the cropping tool.
Resize the image to the size we want.
Right click and save as picture.
Then I name it this title dot png and add it to the same directory as our code.
Speaking of which, we can now add the new image to the HTML file like this.
And I'll set its width to 90% inline here, because there's no other style I will add to this, then refreshing makes the image appear.
I think it will look better if we do one small change in CSS, I will make the whole menu wider like this.
In this way the title image is significantly larger than the other text and the drop down for the difficulty moved on the same line as the label.
I think it's better like this let's add some sounds next.
First, I'll define one using an mp3 file I recorded earlier like this I'll set the volume quite low.
Because this will play whenever we drop a piece in the correct place.
It would be really annoying if it plays at maximum volume, I think.
So let's now add it here in the snap method and test.
I refresh the page press start and yay, it works.
But there is no sound when completing the game.
And there definitely needs to be something there.
Instead of simply adding another mp3 file here, which would make this tutorial really short.
To generate the sounds we will need to define an audio context and doing it like this will make it work on most browsers.
Then I want to create a simple melody consisting of three notes played on piano keys.
Here I'm writing their frequencies we need to create a function to play a single note, it will have a specified key and a given duration, how long the key should be pressed.
We next define an oscillator.
This will be responsible for generating the sound with a given frequency.
We set the frequency to the value coming as a parameter here.
We start the oscillator like this at the current time.
And we can also tell it to stop after the specified duration.
It is good to also disconnect the oscillator at the same time.
Otherwise, some hardly noticeable background noise can still be heard on some browsers.
I'm using set timeout for that.
And I specified the duration here as well.
Now there's a problem because set timeout expects the duration in milliseconds, but the stop method expects it in seconds.
So I decided it to be in milliseconds and need to divide here by 1001.
more thing we need to do before we test is to connect the oscillator to the destination, which in this case is the default speakers on your device.
Okay, Let's test.
I do that by calling the play note here in the console.
And we get an error looks like I don't know how to spell oscillator.
Let me just fix that real quick.
And you can hear a simple sound for one second.
Now we begin to shape this to sound more like a piano, I will use an envelope to control the game, you can think of it as the volume in essence, I connect the envelope now to the destination.
And I changed the oscillator here to be connected to this envelope instead forming a kind of chain, then the piano sound has a powerful attack, which means that it needs to go from zero to maximum gain really quickly.
Here, it happens in point one of a second, I set the maximum here to be 0.5 instead of one so that it's not too loud, then the game will gradually decrease back to zero for the given duration, again, specified here in seconds.
Now let's refresh and test another error.
I sure do many of those today, it seems the problem is that I connect the oscillator here to the envelope before I even define it.
So moving it down here should work.
You can now hear the sound gradually fading during the course of one second.
And you can get an even nicer sound by changing the wave type from the default sign to triangle.
If you're having trouble understanding these things, do check out my visual web development course.
There I use a much slower pace and explain all the different techniques I needed to make my augmented reality piano.
Now let's compose the melody itself.
It will start with a simple note that first played for 300 milliseconds, then I'll play a different note after 300 milliseconds.
And another one 300 milliseconds after that, or 600 milliseconds from the starting point.
And what we get is this.
Now this could work as the melody but you're free to make it sound any way you like.
For example, it could sound like this now to play it when finishing the puzzle, we just call this function here.
It sounds a bit bad because it overlaps with the final popping noise when the piece snaps into place.
I fix this by adding a small delay here to the function call.
To store the scores, we will need to use some kind of database I recommend going to the following link and downloading XAMPP it comes with MySQL and PHP myadmin, which is a nice user interface for managing database contents.
Sample also includes the Apache server, which we will need to write the backend PHP code.
After installing sample we go to the Control Panel and start these two services.
Then we head on to the admin page.
From here we can open PHP myadmin and begin to construct our database to hold the scores.
We create the new database by pressing the New button on the left and we give our data basename now we need to create a table for columns are just fine.
And let's call it scores.
Now every MySQL table should have an ID field, we leave it as an integer type and mark it as a primary key.
And tick this checkbox right here so that it will auto increment.
This means that every time we'll add a new score, we don't have to worry about its ID anymore, it will automatically be decided by MySQL.
Next, we'll need to store the name of the player here, it will be a string, so we'll use var char as the datatype.
And put the maximum length of 255 characters here might be a bit too much, but never can know what names people come up with.
Next will stored the time, it can be an integer because I plan to store the number of seconds.
I can also add the comment here to clarify the format so I don't need to remember it later.
And finally, let's store the difficulty level so that we can later group the scores based on that.
Now we press Save, and we can see the table structure right here.
Let's add some sample data to work with.
It's gonna be some fake placeholder data for now, we go to insert and start entering values in every field.
But as I said earlier, we don't need to worry about the ID, it will be automatically generated by my SQL.
I'll just add my name here.
And let's say it took me 50 seconds to complete the puzzle on Easy difficulty.
Then I'll add Rado again and 100 seconds while playing a medium and again with 200 seconds on hard.
And finally 4000 seconds on insane mode.
So now it looks like I've played the game on all difficulty levels.
Let's add a few more people here like John with 20 seconds on easy and 1000 seconds on insane Diana with 100 seconds on hard than 400 seconds also on heart.
Michael with 400 seconds on medium, and Leo with 10 seconds on medium.
Now we press go and all these 10 entries were added into the table, we can click on the Browse tab to inspect it.
Now we can actually see here the SQL query that outputs this entire table.
It says select everything from scores.
We will later need to write similar queries in PHP to communicate between our front end game and the database.
This query right here could be useful to load the scores for example, but it's not very nice as such, it mixes all difficulty levels and is not sorted in any way.
Hard to see who the winners are like this.
So let's head on over to search and say that we want only those entries with easy difficulty.
We can see how the SQL query here is updated as well.
I think this PHP myadmin tool is very useful for learning SQL syntax.
You can also practice writing the syntax by yourself by pressing on edit.
Here we could for example, add an ORDER BY clause so that the results are sorted by time.
Now it's really easy to see that John is the winner when playing on easy mode, we can similarly obtain the results for the other difficulty levels.
We just need to change the difficulty here each time.
We'll use this query later in BHB to get the high scores.
Now to add the new result, we use the Insert keyword.
We specify the table name and the columns that will receive the data and then the actual values that go into it.
Note here that the name and difficulty are strings.
So they need to be specified using quotes otherwise you'll get an error.
Then we press go and get feedback that one row was inserted.
If we browse the table again, we'll see a new entry for our do an insane difficulty.
Now we'll practice these queries using PHP.
And because this is the first time I teach it, I will take it slow.
I've moved all project files of the game built so far in the HT docs directory of SAP.
Now I'll create an empty file called server dot php.
We next open this file in our text editor and in the browser.
But note here, the address is local host slash puzzle cam slash server dot php, you need to write it like that, because then the PHP code is executed by the Apache server, I remind you that you need to have samp installed and both Apache and MySQL running at this stage.
If you want to print something here, you use the keyword echo, followed by what you want to print.
If you refresh the page, you get exactly that.
Now I'll teach you how to connect the MySQL database we created earlier.
Because it's also running locally on our computer, we'll use localhost as the host.
The default user is root and the password is empty.
Of course, if you're going to put this online somewhere, you'll want to add some better security, but I won't focus on that in this video.
Then to connect to the database, we use the MySQL y connect function and provided this information.
If the link was not established, we weren't that the connection failed.
The DI function terminates the PHP script as well.
If the code passes the if statement, it will go here where we just echo that connection was successful.
I'll refresh and connection was successful.
But if I specify a different host, PHP will produce some warning messages, and the program terminates after this dies statement.
We also get an error message if we use the wrong user or password.
Now with a successful connection, we can move on and select the database like this.
And we can use the or keyword to call the DI function in case of failure.
If the code reaches here, it will output the success message, we can test and see that it was a success because the puzzle cam database really exists.
If we mess up here and use a different name, for example, the code will terminate with an error.
Let's now define an SQL query as a simple string.
I'll use the query we figured out earlier using PHP myadmin to select those entries from the Scores Table with easy difficulty.
We can execute this query using the MySQL I query function.
Now this function doesn't actually return the result.
But an object that we can use to iterate through the results, I'll show you, we first initialize an empty array.
Then if the result contains any rows, we will use a while loop to fetch each row one by one using the MySQL I fetch Asok function on the RS object.
We add each row to the results array using the array push function, and then print the array using the print our function like this.
And we get an error.
I forgot an eye here, it should be my SQL I num rows.
Now we have the array printed out here, but it's not sorted with respect to the score, or in this case playing time.
As we saw earlier, we can do that using the ORDER BY clause.
I'll also format this query a bit better so it doesn't go outside the screen.
Now, because we'll reuse this code to get results for other difficulty levels, it makes sense to write this as a function that returns the results array.
We can then call it with easy as a parameter and get the same result.
Let me just replace here the difficulty to use this argument and almost forgot need to specify the link here as well.
Now we refresh and get the same result as previously.
But we can get other results as well by calling the function again.
Let me quickly add the other difficulty levels as well.
Now let's wrap all this into one function to get all the scores.
We don't need these print statements anymore, we can just return an associative array with each of these results.
Now we call this function and print the result like this.
Or to be more familiar, you can use Echo and convert this result array into a JSON string like this.
It's great that PHP has support for all these without needing to install any libraries.
I'll comment this out and focus on creating the other function, we'll need to add the new score to the database.
I'll pass the information as the first argument.
For now I'll write the query as a simple string with hard coded values here, and then replace each value one by one with the elements from the info.
If you write your query like this, it's less likely to make mistakes.
Otherwise, it's quite easy to mess up and forget the single quote or a double quote somewhere.
Now we call my SQL like query again, if we don't succeed, for some reason, we will return false.
Otherwise, we return true to signify success.
Now, we can test the function like this by defining some info and calling the Add score function using it.
We can wrap it in an if statement like this, to see if the insert was a success or not.
And it claims to have worked.
Let me now bring back this function call to get all the scores and see if it really worked.
And it did.
Now we're going to change this PHP server to support both functionalities to add the score and to read all the scores.
Let me remove this testing code first.
Now I'll use a get parameter named info to send some additional information to the page.
Let's first read it here and see how it works.
So I need to set here info equal to something.
Let's try hello for now.
And you can see it is indeed printed here.
I'll change info now to contain information necessary to add the score.
And I'll write it as a JSON string.
Now to parse this string I call Jason decode and get info is an object here, which I can pass to our ad score function we wrote previously, and handled the outcome as before.
Now we can insert the score by just visiting this address with some parameters.
I'll now make it so that if we don't send any info, it will list all the scores instead.
In this way, we support both reading and writing to the database with this single script.
Now, let me address the elephant in the room.
Yes, this code is not very secure.
Anyone can hack my scores database by going to this link and sending some data there.
Even worse, this data is not escaped in any way.
So they can even do some damage through SQL injections here.
All these issues are not easy to fix.
And if I spend time on it now it will deviate too much from the project.
We are building a puzzle game not making the most secure thing in the world.
But because many of you are interested in security, here's what you should do.
First, escape the information here or even better use of PreparedStatement change get here into post, it's more secure.
I didn't do it myself because I would need something like postman to test.
And I didn't want to install more tools here.
Post is also the common practice when sending data, so you definitely should use it here.
Then you'd want to add some authentication here so that not everyone can make a request such as this, you will need to ask people to register and confirm their account by sending them a confirmation email.
I don't usually do this for my small projects, I feel I would be asking too much from those visiting my website.
And they find hacked.
So what I don't have any sensitive information here, and I kind of like the attention.
All these steps don't make the system bulletproof.
But each of them make it a little bit harder to hack.
You could go even further like adding a CAPTCHA because someone could automate the authentication if they really want and flood your database with a bunch of fake accounts and fake scores.
Captures make it harder to do but not impossible.
You could design some kind of validity checking system here, maybe record the start time and time and individual moves.
If the result doesn't contain pieces moving one by one, at reasonable time intervals, it's a sign of foul play.
Of course, one could hack that too, by figuring out how the validity checker works and generating fake data that looks real.
But that's all I got.
So let me know if you have better tips on security.
I'm really interested to know what you guys think.
But now let's continue with the front end part.
Let me put Mr chibi son here again.
In HTML, I'll now create an end screen, it will show the score and allow the user to enter his name in this input field.
I'll also add here a button to show the scores with another function will need to define and another button to go back to the menu.
I will also create the scores screen.
Now I'll copy this title image in both these screens so that they look consistent.
And speaking about looks, I'll define a CSS class called menu and apply it to all these items.
This class will be the same style we previously applied to the div with ID menu items.
So we just change this here in CSS.
Now we refresh and we can see all three screens overlapping each other, we need to hide the end screen and the score screen in the beginning of course.
First, they want to show the end screen, I will simply use the playing time as the score and I'll place it inside the element with ID score value.
We also need to compute this time as the end time minus the start time, we need to make sure the end screen element becomes visible to like this.
Then the function to show the menu will begin by closing the end screen and then making the menu up here.
Now if I refresh and press start, the menu disappears as before.
But if I call the show and Screen function here in the console, the end screen appears and it looks good.
But the score is now something strange because we didn't actually complete the puzzle to get the true value for the end time.
It will be okay when playing the game for real we don't need to worry about this.
Now pressing the menu doesn't work.
Oh, I forgot the parent thesis here.
We need to call this function here on click.
Sorry about that.
Let's fix this and refresh start call show and Screen function in the console.
Press menu and yes it works now let's move on to show the scores next.
I will copy this line so that the end screen goes away and let's make the score screen appear instead.
Now I'll make the scores container initially showed the text loading because it can take some time to load the score.
And we want to let the player know something's happening.
We then get the scores with the function we'll implement next.
This get scores function uses fetch to get data from server dot php, which I remind is located in the same directory here, fetch returns a promise.
When the response is available, we convert it into JSON.
This returns another promise.
So we need to write then again.
And here we have access to the actual data.
Let's log it to the console to test, I refresh, press start, call show and screen in the console, and press Show scores.
And now our data is here.
It still shows loading, because now we have to format the scores and place them here.
Instead, the format scores function will generate the table in HTML.
I won't explain this too much.
I feel that this tutorial becomes too long if I do that.
And if you were able to follow up to here, I'm sure you'll have no trouble understanding what this is.
I will say, however, that now when I have the results formatted for easy, I will extract this functionality into its own function.
It's a really bad habit to write similar code instead of using functions.
And many of my students have this problem.
So I thought I pointed out here as well.
I now call this function for each difficulty level to show all results one after the other.
The final list is just too long.
I'll set the fixed height here and overflow to auto so that the scroll bar appears.
This is better, I think.
Now I press back, and I guess it's time to define the close scores function.
This is easy to do, it will make the N screen visible and hide the score screen.
Now I refresh and everything seems to work well, except for the save feature that is not implemented yet.
The Save score function is going to calculate the score as the time it took to play the game and get the player name from the input field.
If no name is entered, we give a warning and stop the function right here.
Otherwise, we get the difficulty from the select element and do a fetch to server dot PHP using the info parameter where we pass the name playing time and the difficulty level.
To give feedback to the user, I'll change the text on the Save button to say okay, during the fetch, I disable this button so that players can press it multiple times by mistake.
I also reset the text on the Save button when opening the end screen again, in case the player plays a second time, and they need to re enable the button as well.
Let's add the gray style for the disabled button and make the cursor a default arrow indicating that it can be clicked.
Now let's see an error.
Of course, looks like I forgot the closing parenthesis.
Before I test let's also call the show End Screen function here when the puzzle is complete and test for real this time.
Okay, works pretty well.
But my score here is in milliseconds instead of seconds.
But on second thought, I think it's better to keep these in milliseconds so that we can better distinguish between people playing in almost the same time, I all format the score to show the number of seconds, but I keep milliseconds in the database so that the ordering of the ranked list comes from that.
Okay, now the score appears to be the number of seconds.
And the score stable looks good to those to have zero because we now consider they played in 20 and 50 milliseconds.
But when we clear the database and people actually start playing this, this problem will go away.
One thing that still bothers me is that the save button is on the next line, but then it becomes smaller when it says okay, and it jumps up.
I'll fix this by making the input smaller so that the button always fits on the same line.
I'll also style it to be more consistent with the other items.
Now we're going to do some fine tuning and work on styling the pieces to look like actual puzzle pieces.
To do that, we go to initialize pieces function and iterate through the grid of pieces like this, the pieces are stored in a list in this top down and left to right order.
So we can get the index of each piece by simply counting here like this.
Now we take each piece one by one, and i n j will point to the row and column where it belongs.
We now need to add information about these things to each piece, let's call them tabs.
For each tab, we need to know if it's an inner tab or an outer tab, we'll decide that randomly with equal probability like this.
So the sign becomes either a minus one or a plus one, telling if it's going to be an inner or an outer tab.
The second thing we need to decide is where will the tab be located on the edge.
If we consider this to start at zero and end at 100%, let's allow the tab to be anywhere between 30% and 70%.
Moving too close to the corners is not a good idea.
I'll now set this bottom attribute to encode the information for the tab on the bottom.
It will be a single number between 0.3 and 0.7.
If it's an outer tab, and between minus 0.7 and minus 0.3.
If it's an inner tab, note how we store both pieces of information with this simple trick.
If we are on the last row, we don't have any bottom tabs here.
So we'll set this to No.
We now do the same thing for the tab on the right and make sure I don't add tabs on the rightmost column.
The top tabs are not going to be random anymore, because they need to connect with the pieces from above, we need to set top to be minus the bottom value of the piece directly on top of it.
If we're on the first row, we don't have a tab on the top we now handle the left tab in a similar way, we use the value of minus the previous pieces right that tribute.
Now let's refresh make this console bigger and check here what the pieces array looks like.
So the first piece has a positive value here, meaning it should be an outer tab.
The right value is negative, meaning it's an inner tab.
We have no four left and top.
So looking good so far.
For the second piece, we need to check if the left value is this number but flipped.
So let's see.
Yes, it looks good.
Let's check again with the next piece.
And now we get right equal to no because we are on the rightmost column.
Now the next piece is on the next row.
So the top is not normal anymore.
And it should be the opposite of the bottom of the first piece.
Which it is good.
Let's see the last piece now two.
It has no values for the bottom and right and values for the left and top look okay.
Yeah, I think we're good.
Let's now draw new shapes for the pieces using this extra information.
We won't use the rect method here anymore, but for now I'll just reimplemented using move two and four line two commands.
Let me just add comments here to explain where each of these lines is going.
Now refreshing doesn't change anything.
Pieces are still rectangle shaped.
But now we have more control over this drawing.
I'm going to need to know the minimum length of the piece and use it to define some visual characteristics of the tab relative to it like the neck, tab width and height.
Now let's first make a detour on our way to top right like this.
The location of the tab is given by the absolute value of the top argument.
We move on the vertical axis the tab height in the direction given by the sign The top value.
Let's refresh now and see what happens.
So bases look a bit like houses with some kind of roof.
For some, their roof goes inward like this.
Let me disable drawing of the video for now.
It's confusing, I think.
So now you just see the shape of the pieces, but you can actually tell that those with the flat roof belong on top.
So we start to see that the shape of the pieces really helps when playing the game and makes the overall experience nicer.
I'll add similar detours for the other sides as well.
We refresh and look at that.
We get an interesting division now, I think this could actually be another version of the game if so wanted, is quite unique looking, I think.
What an error, where did that come from? Ah, I see.
Looks like it happens when I click outside the piece.
This here is considered to be outside the piece because we're still using the rectangle shape for hit detection.
But the error appears every time I click out here as well.
Can't believe I missed that.
We need to make sure the selected piece is not no before checking if it's close or not.
Okay, no more error.
Also notice how every time we refresh, we get different looking pieces.
This is really nice, I think.
But now let's create the tabs.
I'll make two more detours one before this point and one after it.
And we use the value of the neck we defined earlier.
Let me now quickly write this for the other sides as well.
i Now refresh and it looks okay.
But you see some of the pieces have some weird lines sticking out.
Not all PCs have it.
This one is okay because it has tabs on all sides.
The problem is that we add these new detours.
Even when an edge doesn't have a tab, we need to check if we need to draw the tab.
Otherwise, we just lined to the end of the edge.
And that's it.
I'll quickly add this to the other sides as well.
And now things look quite good.
And notice that because we work with values relative to the size of the pieces, it will work as expected on every difficulty level when this size changes.
No need to do anything special here at all.
Now this shape is still not perfect, we need to add some curves will replace this line here with the Bezier curve.
Instead, I'll define it with the help of two control points.
And this same endpoint from earlier Looks like something's not right here.
I think I'm missing this closing parenthesis.
But let me arrange this code better anyway.
Refreshing and now you can see what happened here.
It makes a curve instead of a straight line.
I'll do the same thing on the right side.
I just copy these lines in reverse order and change this minus into a plus here.
We basically make it symmetrical.
Let's test and it looks okay.
I think if you want it to look differently, you can change these values here.
Like you can make the neck narrower.
Or you can make the tab wider for example.
You could actually even have different properties for each piece if you want.
It's not too much trouble, but I think it's overkill and I'm happy with this being hard coded here.
Notice that this also affected the other tabs now.
They are thinner and longer.
I will make them all curved the next by adding similar code in each of these sections.
This is where I would usually say that we should extract the function like Draw tab or something, but I couldn't think of good parameter names, and it would actually make some things more complicated inside the function.
So I decided to leave it like that.
But the advice still stands just because I can't figure out how to do it nicely doesn't mean it can't be done.
Okay, let's see here.
And again, it works on any size pieces now all we need to do is add the video back in.
I'll move the code dry here.
And before it I call the clip method of the context.
And I save the state before the clip and restore it after the call to draw image so it won't affect the following pieces in a weird way.
Now, some of the pieces look great like this one, but those that have an outer tab are missing the video inside it.
We fix this by taking the video with some padding on each side, a padding equal to the tab height is enough.
Because the size of the puzzle and the size of the video don't match, we need to scale this tab height for the first four parameters like this note that they actually divide here by the size.
So essentially, I'm simplifying here, it would have been better to store the percent values only.
But by notice this just when editing the video, so now it's too late.
Anyway, this works or not.
Looks like I have an extra parenthesis here.
It's supposed to close after the main method here.
Now it works and it looks great.
Only thing remaining is to handle when user clicks inside the tabs.
The outer tabs are not clickable, and the inner tab select the piece even when it shouldn't, you would think that it's not the big issue.
But if pieces overlap like this, you won't get the piece below it.
And it's very annoying.
You don't want to annoy your players now do you want to fix this, we will reimplement the way we select the pieces, I will use a different strategy for this.
Bear with me for a while as I implement some things that may seem unrelated.
First, let's make a function to generate a random color, it will first generate the random red component between zero and 255.
And we make it into an integer using floor.
We do the same for the green and blue values and return it as a string using the RGB syntax like this.
Now we'll generate a random color for each piece.
We really want the colors to be unique, and it's very unlikely for duplicates to happen, especially when the game has only a few pieces.
But an insane mode item no better safe than sorry and check if a color was used before and regenerated in that case.
We'll pass this color to each piece and now in the piece class, we store it as an attribute.
Then in the draw method, we'll specify a second argument that tells whether to use the camera or not default set to true.
If true, the code here stays the same, but if not, it will draw the pieces using their color instead like this.
I use the Fill rect method here and add a padding of tab height so that the outer tabs are colored as well.
Now let's test by setting use cam to false everything still works.
But now the pieces are more colorful and the camera input is not used anymore.
You can actually play the game like this based on the shape alone.
It's quite easy on easy mode at least.
Now we did this change because I want to use the click Color to detect if I'm clicking on a piece or not.
Like here it should not detect the click because there's nothing there.
And here it should activate because I'm pressing on a pink color.
The piece with the color I'm clicking on should start dragging.
We implement this by going to the onMouseDown callback function.
Getting the color information from where we clicked on the canvas, we get the image data at the event dot x and event dot y is an array with four elements.
First is for red, second is for green for this blue.
And fourth is the transparency, I check if the fourth element is zero, so transparent, and return in this case, we're not clicking on anything, then this is not white, actually, it's transparent black.
But if we pass this part, we format the color in the RGB string format, and check to see where it belongs inside the piece.
We need to implement a new function for that, but it's really easy.
I'll use this other function as a reference and pass the color here as a second argument.
Now we look through these pieces as before.
And if the color matches, we return that piece.
Simple as that.
Now, if you want to make the code more efficient, you could store the pieces in a dictionary with keys equal to the color values, and then do this lookup in constant time instead of linear.
But this method here works just fine.
We don't have that many pieces even on insane mode.
Okay, let's refresh and test.
But now you're gonna say but Rado.
So what if it works, we want to see the webcam image here and not some random colors.
And to that I say multiple canvases.
We'll add another canvas here.
I'll call it helper canvas.
We then set its width and height to be the same as well.
Now in Update game, I'll also cleared the helper canvas like this, and then draw the pieces normally with the camera they down the main canvas, but using the colors on the helper canvas.
And the trick is that now on mouse down, we don't take the color data from the main Canvas, we take it from the helper Canvas instead, let me just refresh and you'll get it in a sec.
Or a couple of seconds.
It doesn't work.
I forgot to change the ID here to help her canvas.
Okay, now my elements showed the helper canvas somewhere here below.
Let me just move it into screen like this.
I'll make it smaller so it doesn't overlap as much, and I'll put it in the corner here.
Now when I click somewhere on the main canvas, the corresponding color value comes from this helper Canvas.
So selecting the piece now works as expected.
And both canvases update at the same time making the helper canvas a reliable hit detector for our pieces.
This helper Canvas doesn't need to be visible.
Actually, it doesn't even need to be added to the DOM.
But it can be there as well.
It's useful for debugging I think I'll just set the display here in line to non and that's it.
Let's test one final time.
That's it I hope it was worth it then that you learned something from this.
And if you did, please like and share this video with anyone you think is interested.
You can download the source code from my website.
It's split into parts so you can take it step by step if you want.
Also, let me know if you can think of a good strategy to play the game.
It took me one and a half hours to solve the insane mode with 1000 pieces.
There must be a smarter way of doing this.
Anyway, thanks for watching and see you guys.