The program has to perform as follows: text
The index.html file doesn’t contain errors:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CAA2 Starter Kit</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div class="wrapper">
<div class="track js-track">
<div class="start"></div>
<div class="car js-car">
<img class="car__inner" src="img/car.svg" alt="Car Image">
</div>
<div class="lives">
<span class="lives__label">LIVES</span>
<span class="lives__value js-lives">-</span>
</div>
</div>
</div>
<script type="module" src="/js/main.js"></script>
</body>
</html>
The style.css file doesn’t contain errors:
html,
body {
height: 100%;
}
body {
padding: 0;
margin: 0;
}
.wrapper {
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.track {
overflow: hidden;
position: relative;
width: 600px;
height: 600px;
background-color: hsl(0, 0%, 24%)
}
.start {
position: absolute;
top: 50%;
left: 50%;
width: 64px;
height: 48px;
border: 1px dashed hsl(0, 0%, 48%);
transform: translate(-50%, -50%);
}
.lives {
position: absolute;
top: 24px;
right: 24px;
display: flex;
flex-direction: column;
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
font-size: 16px;
font-weight: 400;
letter-spacing: 0.08ch;
text-align: center;
color: hsl(0, 0%, 100%);
}
.lives__label {
color: hsl(0, 0%, 56%);
}
.lives__value {
font-size: 32px;
font-weight: 700;
}
.car {
position: absolute;
left: 50%;
top: 50%;
width: 48px;
height: 48px;
transform-origin: center;
transform: translate(-50%, -50%) rotate(0);
}
.car__inner {
width: 100%;
height: 100%;
}
.car::before {
content: '';
position: absolute;
left: 50%;
top: 50%;
z-index: 2;
display: block;
width: 4px;
height: 4px;
background-color: coral;
transform: translate(-50%, -50%);
}
Errors may be found here main.js:
import { GameApp } from './modules/GameApp.js';
const trackSelector = '.js-track';
const carSelector = '.js-car';
const livesSelector = '.js-lives';
const game = new GameApp(trackSelector, carSelector, livesSelector);
Errors may be found here GameApp.js:
import { Car } from './Car.js';
import { GamePad } from './GamePad.js';
import { INIT_LIVES, FRAME_RATE, TRACK_SIZE } from '../consts/consts.js';
class GameApp {
#livesDom;
#trackDom;
#car;
#lives;
#keypad;
#tick;
constructor(trackSelector, carSelector, livesSelector) {
this.#trackDom = document.querySelector(trackSelector);
this.#car = new Car(document.querySelector(carSelector));
this.#livesDom = document.querySelector(livesSelector);
this.#init();
}
#init() {
this.#lives = INIT_LIVES;
this.#trackDom.style.width = `${TRACK_SIZE}px`;
this.#trackDom.style.height = `${TRACK_SIZE}px`;
this.#keypad = new GamePad();
this.#tick = setInterval(() => this.#render(), FRAME_RATE);
}
#render() {
const position = this.#car.update(this.#keypad);
this.#livesDom.textContent = this.#lives;
if (position.x < 0 || position.y < 0 || position.x > TRACK_SIZE || position.y > TRACK_SIZE) {
this.#crash();
}
}
#crash() {
this.#lives--;
this.#car.reset();
if (this.#lives === 0) {
this.#finish();
}
}
#finish() {
clearInterval(this.#tick);
this.#keypad.destroy();
}
}
export { GameApp };
Errors may be found here GamePad.js:
class GamePad {
#keyList = [];
constructor() {
this.init();
}
get keys() {
return this.#keyList;
}
init() {
window.addEventListener('keydown', this.#onKeyPressed.bind(this));
window.addEventListener('keyup', this.#onKeyReleased.bind(this));
}
#onKeyPressed(e) {
const key = e.key;
if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(key) && !this.#keyList.includes(key)) {
this.#keyList.push(key);
}
}
#onKeyReleased(e) {
const key = e.key;
if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(key)) {
this.#keyList = this.#keyList.filter(k => k !== key);
}
}
destroy() {
window.removeEventListener('keydown', this.#onKeyPressed.bind(this));
window.removeEventListener('keyup', this.#onKeyReleased.bind(this));
}
}
export { GamePad };
Errors may be found here Car.js:
import { Trigonometry } from '../utils/trigonometry.js';
import { SPEED_DOWN, SPEED_INERTIA, SPEED_LIMIT, SPEED_UP, TURN_AMOUNT } from '../consts/consts.js';
class Car {
#carDom;
#speed = 0;
#angle = 0;
constructor(carDom) {
this.#carDom = carDom;
}
reset() {
this.#carDom.style.top = '50%';
this.#carDom.style.left = '50%';
this.#speed = 0;
this.#angle = 0;
}
update(gamePad) {
const keyList = gamePad.keys;
if (keyList.includes('ArrowUp')) {
this.#speed = Math.min(this.#speed + SPEED_UP, SPEED_LIMIT);
} else if (keyList.includes('ArrowDown')) {
this.#speed = Math.max(this.#speed - SPEED_DOWN, -SPEED_LIMIT);
} else {
this.#speed *= SPEED_INERTIA;
}
if (keyList.includes('ArrowLeft')) {
this.#angle -= TURN_AMOUNT;
} else if (keyList.includes('ArrowRight')) {
this.#angle += TURN_AMOUNT;
}
const deltax = this.#speed * Math.cos(Trigonometry.toRadians(this.#angle));
const deltay = this.#speed * Math.sin(Trigonometry.toRadians(this.#angle));
this.#carDom.style.left = `${parseInt(this.#carDom.style.left) + deltax}px`;
this.#carDom.style.top = `${parseInt(this.#carDom.style.top) - deltay}px`;
return { x: parseInt(this.#carDom.style.left), y: parseInt(this.#carDom.style.top) };
}
destroy() {
this.gamePad.destroy();
}
}
export { Car };
There are no errors in this file consts.js:
export const INIT_LIVES = 3; // The initial lives of the car game.
export const FRAME_RATE = 1000 / 40; // The timing for the interval that is required to refresh the game
render view.
export const TRACK_SIZE = 600; // The width and height of the track.
export const SPEED_DOWN = 1; // The amount of speed to reduce when the user brakes (key down
pressed). If you hold the key down the speed could be negative
and the car goes in reverse.
export const SPEED_INERTIA = 0.8; // The amount of speed to reduce when the user is not accelerating
or braking (no key down or key up pressed)
export const SPEED_LIMIT = 12; // The maximum speed of the car. It applies to positive (ahead) and
negative (reverse) speeds.
export const SPEED_UP = 2; // The amount of speed to increase when the user accelerates (key
up pressed).
export const TURN_AMOUNT = 6; // The amount of degrees to increase / reduce to the car angle
direction (key left reduce degrees and ley right increase degrees).
There are no errors in this file Trigonometry.js:
class Trigonometry {
static toDegrees(radians) {
return radians * 180 / Math.PI;
}
static toRadians(degrees) {
return degrees * Math.PI / 180;
}
}
export { Trigonometry };
const rad = Trigonometry.toRadians(180);
const deg = Trigonometry.toDegrees(rad);
About speed updates
If the UP key is pressed, it increases the speed based on the SPEED_UP value. If the DOWN key is pressed, it decreases the speed based on the SPEED_DOWN value.
The speed never should be bigger than SPEED_LIMIT value (positive or negative).
If neither of the UP nor the DOWN keys are pressed, it decreases the speed based on the SPEED_INERTIA value.
About angle updates
If the LEFT key is pressed, it reduces the angle based on the TURN_AMOUNT value. If the RIGHT key is pressed, it increases the angle based on the TURN_AMOUNT value.
About car movement
To help you solving the task of moving the car in the right direction depending on its speed and direction, here are some tips that can help:
Because we work with a speed and angle you have to use trigonometry to decompose this force vector in horizontal and vertical factors. To do that you can use this approach:
const deltax = speed * Math.cos(angle);
const deltay = speed * Math.sin(angle);
Where deltax is the horizontal value that causes this speed at this angle, and deltay is the vertical value that causes this speed at this angle. Please note that speed and angle are generic names, they are not necessarily related to the exercise.