body {
padding: 0;
margin: 0;
background: #1a1a1c;
user-select: none;
user-select: none;
}
canvas {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
}
let player, ball, blocks, scoreDisplay, livesLostDisplay, levelDisplay, optionsMenu, scoreboard, paused, muted, gameStarted
function setup() {
const padding = 70
let h = windowHeight - padding
let w = h * 1.356
if (w > windowWidth) {
w = windowWidth - padding
h = w * 0.7375
} else if (w < 320) {
w = 500
h = w * 0.7375
}
createCanvas(w, h);
player = new Player()
ball = new Ball()
blocks = []
scoreDisplay = new NumberDisplay(3 * width / 8, height / 100, height / 18)
livesLostDisplay = new NumberDisplay(5 * width / 8, height / 100, height / 18)
levelDisplay = new NumberDisplay(7 * width / 8, height / 100, height / 18)
scoreboard = new ScoreBoard()
optionsMenu = new OptionsMenu(0, height / 100, height / 16)
paused = true
gameStarted = false
muted = false
setupBlocks()
setTimeout(() => {
ball.loading = false
playSound('high')
}, 1000);
}
function playSound(type) {
if (muted) return
const volume = 0.1
const duration = 0.1
var synth = new p5.MonoSynth();
if (type == 'low') {
synth.play('A4', volume, 0, duration);
} else if (type == 'mid') {
synth.play('A5', volume, 0, duration);
} else {
synth.play('A6', volume, 0, duration);
}
// Get rid of the MonoSynth and free up its resources / memory.
setTimeout(() => {
synth.dispose()
}, 1000);
}
function mapValue(val, minOne, maxOne, minTwo, maxTwo) {
const oneTotal = maxOne - minOne;
if (val < minOne) {
return minOne
} else if (val > maxOne) {
return maxOne
} else {
const valPercentOfTotal = val / oneTotal;
return minTwo + (valPercentOfTotal * (maxTwo - minTwo))
}
}
function getRandomAngle(min, max) {
return Math.random() * (max - min) + min;
}
class Player {
constructor() {
this.width = width / 8
this.height = height / 42
this.position = {
x: (width / 2) - (this.width / 2),
y: height - (this.height * 2)
}
this.velocity = {
x: 0,
y: 0
}
}
draw() {
noStroke()
fill('#FF5733') // vermillion (light red) color
rect(this.position.x, this.position.y, this.width, this.height)
}
update(ballHasHitTop) {
if (ballHasHitTop) {
this.width = width / 11
} else {
this.width = width / 8
}
this.position.x += this.velocity.x
if (player.position.x <= width / 16) {
player.position.x = width / 16
}
if (player.position.x + player.width >= width - (width / 16)) {
player.position.x = width - (width / 16) - player.width
}
}
}
class Ball {
constructor() {
this.width = width / 81
this.height = width / 81
this.livesLost = 0
this.speededUp = false
this.hasHitTop = false
this.loading = true
this.loadRadius = width / 20
this.position = {
x: width / 2 - this.width / 2,
y: height / 2 - this.height / 2
}
this.angle = getRandomAngle(3 * Math.PI/8, 5 * Math.PI/8)
this.speed = width / 116
this.velocity = {
x: this.speed * Math.cos(this.angle),
y: this.speed * Math.sin(this.angle)
}
}
speedUp() {
this.speededUp = true;
let xSign = 1;
let ySign = 1;
if (this.velocity.x > 0 && Math.cos(this.angle) < 0) {
xSign = -1;
} else if (this.velocity.x < 0 && Math.cos(this.angle) > 0) {
xSign = -1;
}
if (this.velocity.y > 0 && Math.sin(this.angle) < 0) {
ySign = -1;
} else if (this.velocity.y < 0 && Math.sin(this.angle) > 0) {
ySign = -1;
}
this.speed = width / 81;
this.velocity = {
x: xSign * this.speed * Math.cos(this.angle),
y: ySign * this.speed * Math.sin(this.angle)
}
}
resetBall() {
this.speededUp = false;
this.speed = width / 116;
this.hasHitTop = false;
this.velocity = {
x: this.speed * Math.cos(this.angle),
y: this.speed * Math.sin(this.angle)
}
}
draw() {
if (this.loading) {
if (this.loadRadius >= 0) {
this.loadRadius -= (width / 20) / 60
}
noFill()
stroke(255, 255, 255, `${((width / 20) - this.loadRadius)/(width / 20)})`);
rect(width / 2 - this.loadRadius / 2, height / 2 - this.loadRadius / 2, this.loadRadius, this.loadRadius)
} else {
noStroke()
if (!this.speededUp) {
fill('#FF5733') // vermillion (light red) color
} else {
let rand = Math.random()
if (rand < 0.5) {
fill('#11faa4')
} else {
fill('#ffffff')
}
}
rect(this.position.x, this.position.y, this.width, this.height)
}
}
updateAngle(angle) {
this.angle = angle;
this.velocity = {
x: this.speed * Math.cos(this.angle),
y: this.speed * Math.sin(this.angle)
}
}
update() {
if (!this.loading) {
this.position.x += this.velocity.x
this.position.y += this.velocity.y
if (this.position.y <= 2 * (width / 16)) {
this.hasHitTop = true;
this.position.y = 2 * (width / 16)
this.velocity.y *= -1
playSound('mid')
if (!this.speededUp) {
this.speedUp();
}
}
if (this.position.x <= width / 16) {
playSound('high')
this.position.x = width / 16
this.velocity.x *= -1
}
if (this.position.x + this.width >= width - (width / 16)) {
playSound('high')
this.position.x = (width - (width / 16)) - this.width
this.velocity.x *= -1
}
if (this.position.y >= height) {
this.loading = true;
this.loadRadius = width / 20;
this.livesLost += 1;
setTimeout(() => {
this.position = {
x: width / 2 - this.width / 2,
y: height / 2 - this.height / 2
}
this.angle = getRandomAngle(3 * Math.PI/8, 5 * Math.PI/8)
this.resetBall();
playSound('high')
this.loading = false;
}, 1000);
}
}
}
}
class Block {
constructor(w, x, row) {
this.width = w
this.height = height / 25
this.row = row
this.collidedWith = false
this.position = {
x: x,
y: (2 * (width / 16)) + (this.row * height / 25)
}
}
checkBlockCollision(ball) {
const ballX = ball.position.x
const ballY = ball.position.y
const blockX = this.position.x
const blockY = this.position.y
if (ballY + ball.height >= blockY && ballY <= blockY + this.height) {
if (ballX + ball.width >= blockX && ballX <= blockX + this.width) {
this.collidedWith = true
return true
}
}
return false
}
draw() {
switch(this.row) {
case 1:
fill(219, 43, 66) // red
stroke(219, 43, 66) // red
break;
case 2:
fill(212, 99, 45) // orange 1
stroke(212, 99, 45) // orange 1
break;
case 3:
fill(188, 123, 20) // orange 2
stroke(188, 123, 20) // orange 2
break;
case 4:
fill(165, 165, 0) // yellow
stroke(165, 165, 0) // yellow
break;
case 5:
fill(0, 169, 65) // green
stroke(0, 169, 65) // green
break;
case 6:
fill(73, 42, 216) // blue
stroke(73, 42, 216) // blue
break;
default:
fill('#FFFFFF')
stroke('#FFFFFF')
}
rect(this.position.x, this.position.y, this.width, this.height)
}
}
class OptionsMenu {
constructor(x, y, sideLength) {
this.position = {
x: x,
y: y
}
this.sideLength = sideLength
}
drawPausePlayButton() {
fill('#FFF')
stroke('#FFF')
rect(this.position.x, this.position.y, this.sideLength, this.sideLength)
fill('#333')
stroke('#333')
if (paused) {
triangle(this.position.x + this.sideLength / 4, this.position.y + this.sideLength / 4, this.position.x + this.sideLength / 4, this.position.y + 3 * this.sideLength / 4, this.position.x + 3 * this.sideLength / 4, this.position.y + this.sideLength / 2)
} else {
rect(this.position.x + (this.sideLength / 4), this.position.y + (this.sideLength / 4), this.sideLength / 8, (this.sideLength) / 2)
rect(this.position.x + (5 * this.sideLength / 8), this.position.y + (this.sideLength / 4), this.sideLength / 8, (this.sideLength) / 2)
}
// textSize(12)
// fill('#FFF')
// stroke('#FFF')
// let pText = paused ? "play (p)" : 'pause (p)'
// text(pText, this.position.x, this.position.y + (3 * this.sideLength)/2)
}
pointWithinPausePlay(mousePositionX, mousePositionY) {
const pausePlayLeftX = this.position.x
const pausePlayRightX = pausePlayLeftX + this.sideLength
const pausePlayTopY = this.position.y
const pausePlayBottomY = pausePlayTopY + this.sideLength
if (mousePositionX >= pausePlayLeftX &&
mousePositionX <= pausePlayRightX &&
mousePositionY >= pausePlayTopY &&
mousePositionY <= pausePlayBottomY) {
return true
}
}
drawMuteButton() {
fill('#FFF')
stroke('#FFF')
rect(this.position.x + this.sideLength * 2, this.position.y, this.sideLength, this.sideLength)
// let mText = muted ? "unmute (m)" : 'mute (m)'
// text(mText, this.position.x + this.sideLength * 2, this.position.y + (3 * this.sideLength)/2)
fill('#333')
stroke('#333')
rect(this.position.x + 9 * this.sideLength / 4, this.position.y + 3 * this.sideLength / 8, this.sideLength / 4, this.sideLength / 4)
triangle(this.position.x + 9 * this.sideLength / 4, this.position.y + this.sideLength / 2, this.position.x + 10 * this.sideLength / 4, this.position.y + this.sideLength / 4, this.position.x + 10 * this.sideLength / 4, this.position.y + 3 * this.sideLength / 4)
noFill();
arc(this.position.x + 2.575 * this.sideLength, this.position.y + this.sideLength / 2, this.sideLength / 4, this.sideLength / 4, -HALF_PI, HALF_PI)
arc(this.position.x + 2.575 * this.sideLength, this.position.y + this.sideLength / 2, this.sideLength / 2, this.sideLength / 2, -HALF_PI, HALF_PI)
if (muted) {
strokeWeight(3)
line(this.position.x + 9 * this.sideLength / 4, this.position.y + this.sideLength / 4, this.position.x + 11 * this.sideLength / 4, this.position.y + 3 * this.sideLength / 4)
}
}
pointWithinMute(mousePositionX, mousePositionY) {
const muteLeftX = this.position.x + this.sideLength * 2
const muteRightX = muteLeftX + this.sideLength
const muteTopY = this.position.y
const muteBottomY = this.position.y + this.sideLength
if (mousePositionX >= muteLeftX &&
mousePositionX <= muteRightX &&
mousePositionY >= muteTopY &&
mousePositionY <= muteBottomY) {
return true
}
}
}
class NumberDisplay {
constructor(x, y, height) {
this.position = {
x: x,
y: y
}
this.height = height
this.width = height / 2.5
this.lineWidth = this.width / 3
}
zeroPad(num, places) {
return String(num).padStart(places, '0')
}
drawNumber(number, numInSequence) {
fill('#FFF')
stroke('#FFF')
strokeWeight(1)
const numInt = parseInt(number)
const x = this.position.x + (numInSequence * (2.75 * this.width))
// top square
if ([0, 2, 3, 5, 7, 8, 9].includes(numInt)) {
// top line
rect(x, this.position.y, this.width + this.lineWidth, this.lineWidth)
}
if ([2, 3, 4, 5, 6, 8, 9].includes(numInt)) {
// bottom line
rect(x, this.position.y + this.width, this.width, this.lineWidth)
}
if ([0, 4, 5, 6, 8, 9].includes(numInt)) {
// left line
rect(x, this.position.y, this.lineWidth, this.width)
}
if ([0, 1, 2, 3, 4, 7, 8, 9].includes(numInt)) {
// right line
rect(x + this.width, this.position.y, this.lineWidth, this.width + this.lineWidth)
}
// bottom rectangle
if ([0, 2, 3, 5, 6, 8].includes(numInt)) {
// bottom line
rect(x, this.position.y + this.height, this.width + this.lineWidth, this.lineWidth)
}
if ([0, 2, 6, 8].includes(numInt)) {
// left line
rect(x, this.position.y + this.width, this.lineWidth, this.width * 1.5)
}
if ([0, 1, 3, 4, 5, 6, 7, 8, 9].includes(numInt)) {
// right line
rect(x + this.width, this.position.y + this.width, this.lineWidth, this.width * 1.5 + this.lineWidth)
}
}
draw(numberInt, numDigits) {
const numberString = this.zeroPad(numberInt, numDigits)
const numberStringArr = numberString.split("");
const that = this;
for (let i = 0; i < numberStringArr.length; i++) {
let num = numberStringArr[i]
that.drawNumber(num, i)
}
}
}
class ScoreBoard {
constructor() {
this.score = 0
this.level = 1
}
zeroPad(num, places) {
return String(num).padStart(places, '0')
}
reset() {
this.score = 0
this.level = 1
}
updateScore(inc) {
this.score += inc
}
updateLevel() {
this.level += 1
}
}
function setupBlocks() {
const rowBlocks = 14;
const rowBlocksPlusWalls = rowBlocks + 2;
for (let row = 1; row < 7; row++) {
for (let col = 0; col < rowBlocks; col++) {
const w = (width / rowBlocksPlusWalls)
const block = new Block(w, (col + 1) * w, row)
blocks.push(block)
}
}
}
function checkPaddleCollision() {
const ballX = ball.position.x
const ballY = ball.position.y
const playerX = player.position.x
const playerY = player.position.y
if (ballY + ball.height >= playerY && ballY <= playerY + player.height) {
if (ballX + ball.width >= playerX && ballX <= playerX + player.width) {
playSound('mid')
return true
}
}
return false
}
function updateScore(blockRow) {
if (blockRow === 4 || blockRow === 3) {
scoreboard.updateScore(4)
} else if (blockRow === 2 || blockRow === 1) {
scoreboard.updateScore(7)
} else {
scoreboard.updateScore(1)
}
}
function checkBlocksCollision(ball) {
let blockCollision = false
for (let b = 0; b < blocks.length; b++) {
if (!blocks[b].collidedWith) {
let didCollide = blocks[b].checkBlockCollision(ball)
if (didCollide) {
playSound('low');
blockCollision = true
updateScore(blocks[b].row)
if (blocks[b].row < 4 && !ball.speededUp) {
ball.speedUp();
}
break;
}
}
}
return blockCollision
}
function draw() {
// scoreboard area
noStroke()
fill('#1a1a1c') // very dark gray color
rect(0, 0, width, width / 16);
fill('#1a1a1c') // very dark gray color
rect(0, width / 16, width, height - width / 16)
// left wall
fill('#8d8d8d') // dark gray color
rect(0, width / 16, width / 16, height - (width / 16))
fill('#00a280') // weird teal block on the bottom left in the atari game
rect(0, height - height / 25, width / 16, height / 25)
// right wall
fill('#8d8d8d') // dark gray color
rect(width - width / 16, width / 16, width / 16, height - (width / 16))
fill('#d92f3f') // weird red block on the bottom right in the atari game
rect(width - width / 16, height - (height / 25), width / 16, height / 25)
// ceiling
fill('#8d8d8d') // dark gray color
rect(0, width/16, width, width / 16);
player.draw()
ball.draw()
if (!paused) {
player.update(ball.hasHitTop)
ball.update()
}
blocks.forEach((b) => {
if (!b.collidedWith) {
b.draw()
}
})
if (blocks.length > 0 && blocks.every(b => b.collidedWith === true)) {
if (ball.position.y > height / 2) {
scoreboard.updateLevel();
blocks.forEach((b) => {
b.collidedWith = false
})
}
}
optionsMenu.drawPausePlayButton()
const mouseWithinMute = optionsMenu.pointWithinMute(mouseX, mouseY)
const mouseWithinPausePlay = optionsMenu.pointWithinPausePlay(mouseX, mouseY)
if (mouseWithinMute || mouseWithinPausePlay) {
cursor(HAND)
} else {
cursor(ARROW)
}
optionsMenu.drawMuteButton()
scoreDisplay.draw(scoreboard.score, 3)
livesLostDisplay.draw(ball.livesLost, 1)
levelDisplay.draw(scoreboard.level, 1)
if (!gameStarted) {
fill('#FFF')
textSize(20)
textAlign(CENTER);
text("Click to start", width / 2, 5 * height / 8)
}
ballDidCollideWithPaddle = checkPaddleCollision()
ballDidCollideWithABlock = checkBlocksCollision(ball)
if (ballDidCollideWithPaddle) {
ball.position.y = player.position.y - ball.height
ball.velocity.y *= -1
const diff = ball.position.x - player.position.x
const radianValOne = 210 * (Math.PI/180);
const radianValTwo = 330 * (Math.PI/180);
const angle = mapValue(diff, 0, player.width, radianValOne, radianValTwo)
ball.updateAngle(angle);
}
if (ballDidCollideWithABlock) {
ball.velocity.y *= -1
}
if (keys.left.pressed && player.position.x > width / 16) {
player.velocity.x = -Math.abs(ball.speed)
} else if (keys.right.pressed && player.position.x + player.width < width - (width / 16)) {
player.velocity.x = Math.abs(ball.speed)
} else {
player.velocity.x = 0
}
}
const keys = {
left: {
pressed: false
},
right: {
pressed: false
}
}
function keyPressed() { // arrows and 'wasd'
if (keyCode === LEFT_ARROW || keyCode === 65) { // 65 is the 'a' key
keys.left.pressed = true
} else if (keyCode === RIGHT_ARROW || keyCode === 68) { // 68 is the 'd' key
keys.right.pressed = true
}
if (keyCode === 80) {
paused = !paused
}
if (keyCode === 77) {
muted = !muted
}
return false
}
function keyReleased() {
if (keyCode === LEFT_ARROW || keyCode === 65) {
keys.left.pressed = false
} else if (keyCode === RIGHT_ARROW || keyCode === 68) {
keys.right.pressed = false
}
return false;
}
function touchStarted() {
const mouseWithinMute = optionsMenu.pointWithinMute(mouseX, mouseY)
const mouseWithinPausePlay = optionsMenu.pointWithinPausePlay(mouseX, mouseY)
if (!(mouseWithinMute || mouseWithinPausePlay) && gameStarted) {
if (mouseX > player.position.x) {
keys.right.pressed = true
} else if (mouseX < player.position.x) {
keys.left.pressed = true
}
}
}
function mousePressed() {
const mouseWithinMute = optionsMenu.pointWithinMute(mouseX, mouseY)
const mouseWithinPausePlay = optionsMenu.pointWithinPausePlay(mouseX, mouseY)
if (!(mouseWithinMute || mouseWithinPausePlay) && gameStarted) {
if (mouseX > player.position.x) {
keys.right.pressed = true
} else if (mouseX < player.position.x) {
keys.left.pressed = true
}
}
}
function touchEnded() {
const mouseWithinMute = optionsMenu.pointWithinMute(mouseX, mouseY)
const mouseWithinPausePlay = optionsMenu.pointWithinPausePlay(mouseX, mouseY)
if (mouseWithinMute) {
muted = !muted
}
if (mouseWithinPausePlay) {
paused = !paused
}
keys.left.pressed = false
keys.right.pressed = false
if (!gameStarted) {
gameStarted = true
paused = false
}
}
function mouseReleased() {
const mouseWithinMute = optionsMenu.pointWithinMute(mouseX, mouseY)
const mouseWithinPausePlay = optionsMenu.pointWithinPausePlay(mouseX, mouseY)
if (mouseWithinMute) {
muted = !muted
}
if (mouseWithinPausePlay) {
paused = !paused
}
keys.left.pressed = false
keys.right.pressed = false
if (!gameStarted) {
gameStarted = true
paused = false
}
}
This Pen doesn't use any external CSS resources.