Created
August 5, 2020 22:48
-
-
Save micmmakarov/ed915724459ac0ffb7550ca5857ccb65 to your computer and use it in GitHub Desktop.
Comedy game with flying
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Declaration of constants | |
levels = [ | |
{ | |
title: "1: You make your coworkers laugh", | |
score: 0, | |
max: 30, | |
emoji: { | |
bomb: 9, | |
drink: 30, | |
nobombing: 1000 | |
} | |
}, | |
{ | |
title: "2: You can do an open mic in Ohio", | |
score: 20, | |
max: 40, | |
emoji: { | |
bomb: 9, | |
drink: 30, | |
nobombing: 2000 | |
} | |
}, | |
{ | |
title: "3: An open mic with real audience", | |
score: 80, | |
max: 50, | |
emoji: { | |
bomb: 8, | |
drink: 30, | |
nobombing: 100 | |
} | |
}, | |
{ | |
title: "4: Killed it at an amature show in a bar", | |
score: 160, | |
emoji: { | |
bomb: 7, | |
drink: 30, | |
nobombing: 700 | |
} | |
}, | |
{ | |
title: "5: A set in The Setup San Francisco", | |
score: 260, | |
emoji: { | |
bomb: 6, | |
drink: 25, | |
nobombing: 1000 | |
} | |
}, | |
{ | |
title: "6: Destroyed it at the Punchline SF", | |
score: 380, | |
emoji: { | |
bomb: 5, | |
drink: 40, | |
nobombing: 1000 | |
} | |
}, | |
{ | |
title: "7: Murdered again at the Punchline SF", | |
score: 420, | |
emoji: { | |
bomb: 4, | |
drink: 30, | |
nobombing: 100 | |
} | |
}, | |
{ | |
title: "8: Sleep at the Comedy Store LA", | |
score: 600, | |
emoji: { | |
bomb: 3, | |
drink: 30, | |
nobombing: 2000 | |
} | |
}, | |
{ | |
title: "9: Comedy Cellar NYC might call you", | |
score: 800, | |
emoji: { | |
bomb: 2, | |
drink: 35, | |
nobombing: 100 | |
} | |
}, | |
{ | |
name: "10: Scheduled a Netflix Special", | |
score: 1200, | |
emoji: { | |
bomb: 1, | |
drink: 35, | |
nobombing: 700 | |
} | |
}, | |
{ | |
name: "11: You're the comedy god'", | |
score: 1500, | |
emoji: { | |
bomb: 0, | |
drink: 30, | |
nobombing: 400 | |
} | |
} | |
] | |
const laughFiles = [ | |
'laugh1.m4a', | |
'laugh2.m4a', | |
'laugh3.mp3', | |
'laugh4.mp3', | |
] | |
const booSound = new Audio('/static/sounds/boo.m4a'); | |
const drinkSound = new Audio('/static/sounds/drink.mp3'); | |
drinkSound.volume = 0.5; | |
const laugh = () => { | |
const laughFile = laughFiles[rand(laughFiles.length)]; | |
const audio = new Audio('/static/sounds/' + laughFile); | |
audio.volume = 0.1 * (rand(4) + 1); | |
audio.play(); | |
} | |
const emoji = { | |
bomb: "💣", | |
drink: "🍺", | |
nobombing: "🍹", | |
laugh: "😂", | |
rolling: "🤣" | |
} | |
// Class of the single emoji | |
class Emoji { | |
constructor(x, y, house, type = null) { | |
house.total++; | |
this.x = x; | |
this.y_ = y; | |
this.createdTime = new Date(); | |
this.lastTime = new Date(); | |
this.size_ = 20 + rand(80); | |
this.speed = rand(30) | |
this.rotate_ = 0; | |
this.house = house; | |
this.score = 1; | |
if (!type) { | |
if (this.condition(0, 10)) type = 'rolling'; | |
if (this.condition(100, levels[this.house.levelIndex].emoji.nobombing, !this.house.noBombing)) type = 'nobombing'; | |
if (this.condition(50, levels[this.house.levelIndex].emoji.drink, !this.house.noBombing)) type = 'drink'; | |
if (this.condition(20, levels[this.house.levelIndex].emoji.bomb, !this.house.noBombing)) type = 'bomb'; | |
} | |
if (!type) type = 'laugh'; | |
this.type_ = type; | |
this.emoji = emoji[type]; | |
} | |
condition(min, chance, condition) { | |
if (this.house.total > min) { | |
if (rand(chance) === 0 && condition) { | |
return true; | |
} | |
} | |
return false; | |
} | |
get type() { | |
return { | |
[this.type_]: true | |
} | |
} | |
get fontSize() { | |
if (this.exploded_time_) { | |
const mil = new Date - this.exploded_time_; | |
if (this.disappear_) { | |
const unit = (mil / 100) < 1 ? 1 : mil / 100 | |
const fontSize = Math.round((this.size_ / (unit))); | |
return fontSize + 'px serif'; | |
} else { | |
return this.size_ + Math.round(mil / this.speed * 10) + 'px serif'; | |
} | |
} | |
return this.size_ + 'px serif'; | |
} | |
get y() { | |
const mil = new Date - this.lastTime; | |
if (!this.house.gameOver) this.y_ = this.exploded_time_ ? this.exploded_y_ : (this.y_ - Math.round(mil / this.speed)); | |
this.lastTime = new Date; | |
return this.y_; | |
//return this.y_ - Math.round(mil / this.speed); | |
} | |
get alpha() { | |
if (this.type.bomb && !this.disappear_) { | |
return 1 | |
} | |
if (this.exploded_time_) { | |
const mil = new Date - this.exploded_time_; | |
const a = 1 - mil / 1000; | |
return a < 0 ? 0 : a; | |
} | |
return 1; | |
} | |
render(ctx) { | |
const { x, y } = this; | |
ctx.save(); | |
ctx.globalAlpha = this.alpha; | |
ctx.textBaseline = 'middle'; | |
ctx.textAlign = "center"; | |
ctx.font = this.fontSize; | |
ctx.fillText((this.house.gameOver && !this.type.bomb) ? "👿" : this.emoji, this.x, this.y); | |
ctx.restore(); | |
if (y < -this.size_) { | |
this.destroy(); | |
this.house.missed++; | |
} | |
} | |
disappear() { | |
this.exploded_y_ = this.y; | |
this.exploded_time_ = new Date(); | |
this.disappear_ = true; | |
setTimeout(() => this.destroy(), 2000); | |
} | |
explode() { | |
if (this.type.bomb) { | |
this.house.gameOver = true; | |
booSound.play(); | |
} | |
this.exploded_y_ = this.y; | |
this.exploded_time_ = new Date(); | |
if (this.type.bomb) { | |
setTimeout(() => { | |
this.house.updateScore(); | |
this.destroy() | |
this.house.gameOver = false; | |
}, 3000); | |
this.house.score -= 10; | |
this.house.loseLife(); | |
return; | |
} | |
if (this.type.drink) { | |
this.house.setMode('drink'); | |
drinkSound.play(); | |
setTimeout(() => this.destroy(), 1000); | |
return; | |
} | |
if (this.type.nobombing) { | |
this.house.setMode('nobombing'); | |
setTimeout(() => this.destroy(), 1000); | |
return; | |
} | |
this.emoji = "💥"; | |
laugh(); | |
setTimeout(() => this.destroy(), 1000); | |
this.house.score += this.score; | |
} | |
checkCollision(mx, my) { | |
if (this.exploded_time_) return; | |
const { x, y } = this; | |
const half = this.size_ / 2 + 20; | |
if ((mx > x - half) && (mx < x + half) && (my > y - half) && (my < y + half)) { | |
this.explode(); | |
} | |
} | |
destroy() { | |
const index = this.house.emojiList.indexOf(this); | |
this.house.emojiList.splice(index, 1); | |
} | |
} | |
// Class of the game | |
class Game { | |
constructor(elPath, scorePath, embedded = false) { | |
window.game = this; | |
this.embedded = embedded; | |
this.canvas = document.querySelector(elPath); | |
this.scoreboard = document.querySelector(scorePath); | |
} | |
reset() { | |
this.gameOver = false; | |
this.endGame = false; | |
} | |
end() { | |
this.gameContainer.classList.remove('game-show'); | |
} | |
start() { | |
this.gameContainer = document.querySelector(".game-container"); | |
this.gameContainer.classList.add('game-show'); | |
const game = document.querySelector("#game"); | |
game.width = window.innerWidth; | |
game.height = window.innerHeight; | |
this.gameOver = false; | |
this.total = 0; | |
this.score_ = 0; | |
this.missed_ = 0; | |
this.scoreList = []; | |
this.interval = 2000; | |
this.levelIndex = 0; | |
this.totalLifes = 4; | |
this.lifes_ = this.totalLifes; | |
this.gameOverElement = document.querySelector("#gameover"); | |
this.currentLevelEl = document.querySelector(".your-level"); | |
this.mx = 0; | |
this.my = 0; | |
const section = this.embedded ? this.canvas.parentElement : this.canvas; | |
this.ctx = this.canvas.getContext('2d'); | |
this.canvas.setAttribute('width', this.canvas.clientWidth); | |
this.canvas.setAttribute('height', this.canvas.clientHeight); | |
this.emojiList = []; | |
this.objectList = []; | |
this.isTouchDevice = isTouchDevice(); | |
if (this.isTouchDevice) { | |
setTimeout(() => { | |
const rect = section.getBoundingClientRect() | |
section.addEventListener('touchstart', (e) => { | |
this.touchStarted = true; | |
this.mx = e.touches[0].clientX; | |
this.my = e.touches[0].clientY; | |
this.emojiList.forEach(e => e.checkCollision(this.mx, this.my)); | |
}) | |
section.addEventListener('touchmove', (e) => { | |
this.mx = e.touches[0].clientX; | |
this.my = e.touches[0].clientY; | |
this.emojiList.forEach(e => e.checkCollision(this.mx, this.my)); | |
}) | |
section.addEventListener('touchend', (e) => { | |
this.touchStarted = false; | |
this.mx = -1000; | |
this.my = -1000; | |
}) | |
section.addEventListener('touchcancel', (e) => { | |
this.touchStarted = false; | |
this.mx = -1000; | |
this.my = -1000; | |
}) | |
}, 1000); | |
} else { | |
section.addEventListener('mousemove', (e) => { | |
this.mx = e.clientX; | |
this.my = e.clientY; | |
}) | |
} | |
this.level = levels[this.levelIndex].title; | |
this.createdTime = new Date(); | |
requestAnimationFrame(this.draw.bind(this)); | |
this.speed = 1000; | |
this.tick(); | |
this.updateScore(); | |
} | |
get speed() { | |
return this.speed_; | |
} | |
set speed(value) { | |
this.speed_ = value; | |
} | |
setMode(mode) { | |
switch (mode) { | |
case 'nobombing': | |
if(this.noBombing) clearTimeout(this.noBombing); | |
this.noBombing = setTimeout(() => { | |
this.noBombing = null; | |
}, 10000); | |
this.emojiList.forEach(e => { | |
if (e.type.bomb) e.disappear(); | |
}); | |
this.sendMessage("🚫💣"); | |
break; | |
case 'drink': | |
this.emojiList.forEach(e => { | |
if (e.type.bomb) e.disappear(); | |
}); | |
break; | |
default: | |
} | |
} | |
sendMessage(message) { | |
} | |
get livesLeft() { | |
if (this.lifes_ < 0 || this.totalLifes - this.lifes_ < 0) return; | |
const life = "❤️" | |
const bomb = "🖤" | |
return life.repeat(this.lifes_) + bomb.repeat(this.totalLifes - this.lifes_); | |
} | |
announceGameOver() { | |
this.gameOverElement.classList.add('gameover-show') | |
} | |
loseLife() { | |
this.lifes_--; | |
if (this.lifes_ === 0) { | |
// Game over; | |
this.gameOver = true; | |
this.endGame = true; | |
this.announceGameOver(); | |
} | |
} | |
tick() { | |
if (this.timeout) clearTimeout(this.timeout); | |
this.getScoreLastSeconds(5); | |
const interval = this.noBombing ? 50 : this.scoreLastSeconds > 3 ? this.speed / (this.scoreLastSeconds / 3) : this.speed; | |
if (this.speed !== 0) { | |
this.timeout = setTimeout(() => { | |
if (!this.gameOver) this.addEmoji(); | |
this.tick(); | |
}, interval / 2 + rand(interval)) | |
} | |
} | |
addEmoji() { | |
if (this.emojiList.length > (levels[this.levelIndex].max || 50)) return; | |
if (this.gameOver) return; | |
if (this.endGame) return; | |
const e = new Emoji(Math.floor(Math.random() * this.canvas.width), this.canvas.height, this) | |
this.emojiList.push(e) | |
} | |
get score() { | |
return this.score_; | |
} | |
set score(v) { | |
if (levels[this.levelIndex + 1].score <= v) { | |
this.currentLevelEl.classList.remove("blink"); | |
setTimeout(() => this.currentLevelEl.classList.add("blink"), 50); | |
this.levelIndex++; | |
this.level = levels[this.levelIndex].title; | |
} | |
this.scoreList.push({ | |
date: Date.now(), | |
value: v - this.score_ | |
}) | |
this.score_ = v; | |
this.updateScore(); | |
} | |
get missed() { | |
return this.missed_; | |
} | |
getScoreLastSeconds(seconds) { | |
const cut = Date.now() - seconds * 1000; | |
let nobread = true; | |
for (let i = 0; i < this.scoreList.length; i++) { | |
if (this.scoreList[i].date > cut) { | |
nobread = false; | |
if (i > 0) this.scoreList.splice(0, i - 1); | |
break; | |
} | |
} | |
if (nobread) this.scoreList = []; | |
this.scoreLastSeconds = this.scoreList.length | |
return this.scoreLastSeconds; | |
} | |
set missed(v) { | |
this.missed_ = v; | |
this.updateScore(); | |
} | |
updateScore() { | |
if (!this.scoreboard) return; | |
if (!this.scoreboardElements) { | |
this.scoreboardElements = {}; | |
['score', 'missed', 'livesLeft', 'level'].forEach(param => { | |
this.scoreboardElements[param] = document.querySelector(`#score-${param}`) | |
}); | |
} | |
['score', 'missed', 'livesLeft', 'level'].forEach(param => { | |
this.scoreboardElements[param].innerText = this[param]; | |
}); | |
//this.scoreboardElements.level.className = 'level-' + this.levelIndex; | |
} | |
// Main game loop | |
draw() { | |
const time = Date.now(); | |
if (!this.lastTime) this.lastTime = time; | |
if ((this.lastTime + this.interval) < time) { | |
this.lastTime = time; | |
} | |
if (!this.isTouchDevice) this.emojiList.forEach(e => e.checkCollision(this.mx, this.my)); | |
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); | |
this.emojiList.forEach(e => e.render(this.ctx)); | |
this.objectList.forEach(e => e.render(this.ctx)); | |
if (!this.isTouchDevice || this.touchStarted) { | |
this.ctx.textBaseline = 'middle'; | |
this.ctx.textAlign = "center"; | |
this.ctx.font = '70px serif';; | |
this.ctx.fillText(" 🎤", this.mx, this.my); | |
} | |
requestAnimationFrame(this.draw.bind(this)); | |
} | |
} | |
// Game init | |
const theGame = new Game('#game', "#score"); | |
theGame.start(); | |
// Helper functions | |
function rand(i) { | |
return Math.floor(Math.random() * i); | |
} | |
function isTouchDevice() { | |
try { | |
document.createEvent("TouchEvent"); | |
return true; | |
} catch (e) { | |
return false; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment