I'm creating a Pong clone for use in teaching JavaScript to beginners. Unfortunately, I am largely a JS beginner myself, so I probably missed something in the below code; however, I do not want to shortchange my students.
Also, the code does work as intended (with the exception of the sound-playing code, since I couldn't embed that in the Stack Snippet).
"use strict";
var canvas = document.getElementById("pongCanvas");
var context = canvas.getContext("2d");
context.fillStyle = "white";
var player1;
var player2;
var winner;
var gameOver;
var collisionSound;
var WIN_POINTS = 10;
var INITIAL_BALL_SPEED = 2;
var SCORE_OFFSET = 80;
var DIVIDER_HEIGHT = 10;
var DIVIDER_WIDTH = DIVIDER_HEIGHT / 3;
var DIVIDER_PADDING = 20;
var BALL_SIZE = 20;
var keyboardState = {
w: false,
s: false,
up: false,
down: false
};
var ball = {
width: BALL_SIZE,
height: BALL_SIZE,
reset: function() {
this.x = canvas.width / 2 - this.width / 2;
this.y = canvas.height / 2 - this.height / 2;
this.xSpeed = INITIAL_BALL_SPEED;
this.ySpeed = INITIAL_BALL_SPEED;
this.acceleration = 0.001;
},
update: function() {
this.bounceOffWalls();
this.bounceOffPlayers();
this.accelerate();
this.updatePosition();
},
draw: function() {
context.beginPath();
context.rect(this.x, this.y, this.width, this.height);
context.fill();
context.closePath();
},
bounceOffWalls: function() {
if (this.y < 0) {
this.y = 0;
this.ySpeed *= -1;
collisionSound.play();
} else if (this.y + this.height > canvas.height) {
this.y = canvas.height - this.height;
this.ySpeed *= -1;
collisionSound.play();
}
},
bounceOffPlayers: function() {
if (this.x < player1.offset + player1.width && this.y > player1.y && this.y < player1.y + player1.height) {
this.x = player1.offset + player1.width;
this.xSpeed *= -1;
collisionSound.play();
} else if (this.x + this.width > canvas.width - player2.offset - player2.width && this.y > player2.y && this.y < player2.y + player2.height) {
this.x = canvas.width - player2.offset - player2.width - this.width;
this.xSpeed *= -1;
collisionSound.play();
}
},
updatePosition: function() {
this.x += this.xSpeed;
this.y += this.ySpeed;
},
accelerate: function() {
if (this.xSpeed < 0) {
this.xSpeed -= this.acceleration;
} else {
this.xSpeed += this.acceleration;
}
if (this.ySpeed < 0) {
this.ySpeed -= this.acceleration;
} else {
this.ySpeed += this.acceleration;
}
}
};
var player = {
width: 10,
height: 75,
offset: 10,
ySpeed: INITIAL_BALL_SPEED * 2,
score: 0,
update: function() {
this.updatePosition();
this.constrainPosition();
},
draw: function() {
context.beginPath();
context.rect(this.x, this.y, this.width, this.height);
context.fill();
context.closePath();
},
updatePosition: function() {
if (keyboardState[this.upControl]) {
this.y -= this.ySpeed;
} else if (keyboardState[this.downControl]) {
this.y += this.ySpeed;
}
},
constrainPosition: function() {
if (this.y < 0) {
this.y = 0;
} else if (this.y + this.height > canvas.height) {
this.y = canvas.height - this.height;
}
}
};
player.y = canvas.height / 2 - player.height / 2;
var createSound = function(filename) {
var sound = document.createElement("audio");
sound.src = filename;
sound.setAttribute("preload", "auto");
sound.setAttribute("controls", "none");
sound.style.display = "none";
document.body.appendChild(sound);
return sound;
};
var drawDivider = function() {
for (var i = 0; i < canvas.height; i += DIVIDER_HEIGHT + DIVIDER_PADDING) {
context.beginPath();
context.rect(canvas.width / 2 - DIVIDER_WIDTH / 2, i, DIVIDER_WIDTH, DIVIDER_HEIGHT);
context.fill();
context.closePath();
}
};
var drawScores = function() {
context.font = "48px Courier New";
context.textAlign = "left";
context.fillText(player1.score.toString(), canvas.width / 2 - SCORE_OFFSET, SCORE_OFFSET);
context.textAlign = "right";
context.fillText(player2.score.toString(), canvas.width / 2 + SCORE_OFFSET, SCORE_OFFSET);
};
gameOver = false;
player1 = Object.create(player);
player1.x = player1.offset;
player1.number = 1;
player1.upControl = "w";
player1.downControl = "s";
player2 = Object.create(player);
player2.x = canvas.width - player2.width - player2.offset;
player2.number = 2;
player2.upControl = "up";
player2.downControl = "down";
collisionSound = createSound("collision.ogg");
var update = function() {
if (!gameOver) {
checkForScore();
checkForWin();
ball.update();
}
player1.update();
player2.update();
};
var draw = function() {
context.clearRect(0, 0, canvas.width, canvas.height);
if (!gameOver) {
ball.draw();
} else {
context.textAlign = "center";
context.font = "30px Courier New";
context.fillText("Game Over! Winner: Player " + winner.number, canvas.width / 2, canvas.height / 2);
}
player1.draw();
player2.draw();
drawDivider();
drawScores();
};
var checkForScore = function() {
if (ball.x > canvas.width || ball.x + ball.width < 0) {
if (ball.x > canvas.width / 2) {
++player1.score;
} else {
++player2.score;
}
ball.reset();
}
};
var checkForWin = function() {
if (player1.score == WIN_POINTS) {
gameOver = true;
winner = player1;
} else if (player2.score == WIN_POINTS) {
gameOver = true;
winner = player2;
}
};
var isArrowKey = function(keyCode) {
return keyCode >= 37 && keyCode <= 40;
};
var changeKeyboardState = function(event, status) {
if (isArrowKey(event.keyCode)) {
switch (event.keyCode) {
case 38:
keyboardState.up = status;
break;
case 40:
keyboardState.down = status;
break;
}
} else {
keyboardState[String.fromCharCode(event.keyCode).toLowerCase()] = status;
}
};
document.addEventListener("keydown", function(event) {
changeKeyboardState(event, true);
});
document.addEventListener("keyup", function(event) {
changeKeyboardState(event, false);
});
ball.reset();
var game = function() {
update();
draw();
requestAnimationFrame(game);
};
game();
* {
padding: 0;
margin: 0;
}
canvas {
background: black;
display: block;
margin: 0 auto;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Pong</title>
</head>
<body>
<canvas id="pongCanvas" width=600 height=400></canvas>
</body>
</html>