You will be making a Falling Letter game in pure JavaScript (without any frontend frameworks) according to this assignment. The project will be in the form of a serious
Create a new empty folder for your project. Then, open VS Code and go to File
=> Open Folder
and open your folder within
VS Code.
By hitting Ctrl + ~
(or going into Terminal
=> New Terminal
) you'll be able to open up a Linux shell within
your VS Code environment. This shell's working directory is automatically set to your project's folder.
You will use this in-built VS Code terminal to execute shell commands.
Try running node -v
. Your Node version should be printed out (if you have Node installed).
You should also initialize an empty git repository by running git init
.
Node is a runtime to let JavaScript run outside of the browser, on the operating system. It's usually used for backend applications like servers, or CLI scripts. You might be asking why we're bringing Node in here if we're making a web app.
The thing is, over the years, a lot of tools were built to make web development easier. Nowadays, after you write your webpage's code, you have a tool that compiles this code into a single package, removes all spaces, newlines and comments (this process is called "minification") and automatically attaches it to your website.
Minification is done to make loading the JavaScript faster (it's much smaller) and also harder to read and reverse engineer for clients.
During development of your webapp, you usually have a "live web server" that automatically reloads the page with your changes after every save. This makes development much faster and easier.
Node also has a package manager called npm
. This package manager hosts millions of open-source useful packages for you
to use. For example, if you were building the next Facebook, you could
install humanize-duration into your project, which is a package that
contains functions to convert a number of miliseconds (like 3486468535) into a human readable format ("over a month ago").
This is why Node is used even in frontend (webpage) development.
This project will use the same setup as most modern JavaScript projects do. With a bundler, a package.json
file etc.
Use the npm init command do initialize a new project. This will allow you to install packages.
After going through the installation process, you will see a new file pop up: package.json
. Every Node
project has this file. It describes the project, its dependencies and all sorts of other info. By default
it's set up for proper Node projects that you can run on a computer so we will make two minor adjustments
to the package.json
:
- remove the
"main": "index.js",
line. This is meant to signal to Node which JavaScript file is the entrypoint of the program. However we're not going to be releasing this project as an executable, we're only using it to build a website. So this line is useless - add a line that says:
"private": true,
. This will prevent you from accidentally publishing this project as a package to thenpm
registry. We're not making a package here.
As I mentioned before, we'll need a Node package that will bundle our scripts and website together. We're going to be using Webpack which is the most popular one.
Install the webpack
, webpack-cli
, webpack-dev-server
and html-webpack-plugin
package.
webpack
contains the core webpack functionalitywebpack-cli
contains commands to easily use webpack from the terminalhtml-webpack-plugin
is a plugin for webpack that generates a HTML page for us with the bundled scripts includedwebpack-dev-server
is a plugin for webpack that allows for a live dev server with hot reload This is the command to install them all:
npm install webpack webpack-cli html-webpack-plugin webpack-dev-server --save-dev
Notice the --save-dev
flag. For packages that we're going to be using ONLY during development, we'll add this flag so they're marked as "dev only". For all other packages you might wanna ship with your application, don't use this flag.
After we've installed webpack, you'll notice our package.json
has changed. There's a new object called devDependencies
with the package names and their version. The version follow semantic versioning rules.
"devDependencies": {
"webpack": "^5.75.0",
"webpack-cli": "^5.0.1"
}
There's also a new folder present: node_modules
. This folder contains the downloaded source code of all the dependencies you have. When you open it up, you'll see that there's way more than just 2 folders. This is because webpack
also has dependencies. And those dependencies also have dependencies.
This folder can get quite big and that's why you never push node_modules
into your git repository. Everyone can always install all the packages that a project depends on by runnin npm install
(without a package name). There's no point in sending over all those dependencies when everyone can quickly and automatically download them based on the data in package.json
.
Create a new file called .gitignore
in your project's folder, open it and put in the following text:
node_modules
dist
This will cause the node_modules
folder to be ignored by git. You can see it gets greyed out in the file view when you save .gitignore
. Another folder we're hiding from git is dist
which will contain are packaged HTML and JS files. More on that later.
There's also the package-lock.json
file which includes checksums of the downloaded packages. You DO want to include this in your git. It makes sure the other people have downloaded the same version package as you did and prevents the "it only works on my machine" problem.
The Webpack package also takes a config file. You can read more about webpack config files here.
The webpack config is not a JSON file but a JavaScript file that exports exactly one object. Why? Because the developers of Webpack wanted the users to be able to use programming logic and packages in the webpack config if needed.
Create a new src
folder and inside it, create a new index.js
file. It's common practice to have all your JavaScript in one folder called src
while static resources are elsewhere.
At the root of your project folder (so not inside of src
) create a basic index.html
. This will serve as a template for our website. You don't have to import any scripts in this HTML, webpack will add the script tags by itself.
Now copy and paste this Webpack config into a new file called webpack.config.js
in the project's root folder:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js', // <== take the src/index.js file as an entrypoint
output: {
filename: 'bundle.js', // <== package it into a bundle.js file..
path: path.resolve(__dirname, 'dist'), // <== ..in the dist folder of the current project folder
},
// Set up the hot reload server
devServer: {
static: {
directory: path.join(__dirname, 'dist'),
},
compress: true,
port: 9000,
},
plugins: [
new HtmlWebpackPlugin({
template: './index.html' // <== use the index.html as a template for our exported webapp
})
]
};
This is what your folder structure should look like (if we ignore node_modules
):
.
├── index.html
├── package.json
├── package-lock.json
├── src
│ └── index.js
└── webpack.config.js
You can test out how webpack builds JavaScript yourself by running the ./node_modules/.bin/webpack
command (from the project folder root). By setting the --mode
flag we can determine if we're building for development
(no minification takes place) or production
(code gets optimized and minified).
For example, this code:
// A function that says hi to my friends!
function sayHi(person) {
console.log("Hi " + person);
}
sayHi("James")
sayHi("Alex")
gets turned into this by using ./node_modules/.bin/webpack --mode=production
. This is dist/bundle.js
:
(()=>{function o(o){console.log("Hi "+o)}o("James"),o("Alex")})();
As you can see, the comments are gone, the function name was reduced to o
and the file size is overall much smaller.
In order to make things simpler to use, we'll add the following script definitions to package.json
:
...
"scripts": {
"build": "./node_modules/.bin/webpack --mode=production",
"dev": "./node_modules/.bin/webpack serve --mode=development"
},
...
Now we can run these scripts using an alias from the terminal:
This will build a production-ready bundle of our webapp into the dist
folder:
npm run build
And this will start hosting a live development server that refreshes on every file saved:
npm run dev
The server runs on http://localhost:9000/ by default. Check the terminal output to be sure. You can configure the port in the webpack config.
Finally, we can start getting to business. Make sure to take advantage of the fact that you can have multiple JavaScript files to split up your logic:
This is one example how you can split your logic:
├── src
│ ├── game
│ │ ├── letter.js
│ │ ├── score.js
│ │ └── timer.js
│ ├── index.js
│ └── ui
│ ├── menu.js
│ └── scoreboard.js
Because JavaScript is a multi-paradigm language, it's up to you what approach you'll choose. Functional, object oriented with classes, object oriented without classes, procedural.. it's up to you.
You can also edit index.html
to add some stuff you want to have on the DOM for you to use with the game.
You absolutely should also make use of Webpack's developer server. Open the server up, open the page in your browser and start writing code. The webpage will get reloaded after every single save (of JavaScript or even the index HTML file) so you will see changes right in front of you as they happen. If you have multiple monitors, it can be useful to put the preview on one monitor and VS Code on the other.
Check out this demo to get an idea of what the game will be like. Only difference being, you won't have to implement golden letters and you'll have to add permanent score keeping.
When the game loads, you're gonna see a menu with the game's title, the author and a big button that says "Start".
Clicking on the button in the middle will cause the menu to disappear and the game to start.
Play the demo to get a feel for what the gameplay is gonna be like.
The core of the gameplay is that letters are falling from the top of the screen. The player has to type the letters that are currently visible. If the letter falls through the page without being typed, they disappear without giving the player a point. If the player types a letter that is currently on-screen, they get a point. There is a pointer counter and also a timer. After 20 seconds are over, the game ends.
This can be implemented in a few ways:
- The letters can be HTML elements that are slowly "falling down" as you modify their css properties
- The letters can be rendered using the Canvas API which allows for 2d graphics to be rendered on the page
- The letters can be rendered using a proper HTML Game Engine like Phaser 3 which builds on top of the Canvas API. However look out. If you add a game framework into your project, that's a bunch more stuff you have to deal with so I highly advise against this.
It's up to you how you implement the core gameplay.
The game over screen will have your score, your high score and a button to replay the game again. The high score should be kept permanent using the LocalStorage API. By permanent, I mean that the high score should not reset when the browser closes and opens again. Your high score should of course be the highest score you ever achieved.
Publish the source code of this project to your GitHub account.