Add files via upload
This commit is contained in:
commit
9800dcfb8d
10
README.md
Normal file
10
README.md
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
## 🐦 Flappy game
|
||||||
|
|
||||||
|
* A game developed with JavaScript and CSS;
|
||||||
|
* This is a replica of the original flappy bird game developed for the sole purpose of learning.
|
||||||
|
* <strong>Instructions:</strong>
|
||||||
|
* Click on the screen, or use your spacebar to get started.
|
||||||
|
* Fly the bird as far as you can without hitting a pipe.
|
||||||
|
* As the score increases, the difficulty also increases.
|
||||||
|
* There is also a version in my CodePen page: [https://codepen.io/jguarato/full/mdKoaYR](https://codepen.io/jguarato/full/mdKoaYR).
|
||||||
|
|
267
src/flappy.js
Normal file
267
src/flappy.js
Normal file
@ -0,0 +1,267 @@
|
|||||||
|
function newElement(tagName, className) {
|
||||||
|
const element = document.createElement(tagName);
|
||||||
|
element.className = className;
|
||||||
|
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ScoreBoard() {
|
||||||
|
|
||||||
|
this.HTMLElement = newElement('div', 'score');
|
||||||
|
|
||||||
|
this.updateScore = score => this.HTMLElement.innerHTML = score;
|
||||||
|
|
||||||
|
this.updateScore(0);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function Bird() {
|
||||||
|
|
||||||
|
let isFlying = false;
|
||||||
|
|
||||||
|
this.HTMLElement = newElement('img', 'bird');
|
||||||
|
|
||||||
|
this.getSource = () => parseInt(this.HTMLElement.src.split('bird')[1].split('.png')[0]);
|
||||||
|
this.setSource = number => this.HTMLElement.src = `./imgs/bird${number}.png`;
|
||||||
|
|
||||||
|
this.getAltitude = () => parseFloat(this.HTMLElement.style.bottom.split('%')[0]);
|
||||||
|
this.setAltitude = altitude => this.HTMLElement.style.bottom = `${altitude}%`;
|
||||||
|
|
||||||
|
const flying = () => isFlying = true;
|
||||||
|
const falling = () => isFlying = false;
|
||||||
|
|
||||||
|
this.resetEvents = () => {
|
||||||
|
|
||||||
|
window.onmousedown = () => flying();
|
||||||
|
window.onmouseup = () => falling();
|
||||||
|
|
||||||
|
window.onkeydown = () => flying();
|
||||||
|
window.onkeyup = () => falling();
|
||||||
|
|
||||||
|
window.ontouchstart = () => flying();
|
||||||
|
window.ontouchend = () => falling();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.fly = () => {
|
||||||
|
|
||||||
|
let y = this.getAltitude();
|
||||||
|
let src = this.getSource();
|
||||||
|
|
||||||
|
y += isFlying ? 1 : -0.6;
|
||||||
|
src = isFlying ?
|
||||||
|
(src === 0 ? 2 : 0) : 1;
|
||||||
|
|
||||||
|
this.setAltitude(y);
|
||||||
|
this.setSource(src);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.resetEvents();
|
||||||
|
this.setSource(1);
|
||||||
|
this.setAltitude(50);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function Barrier(reverse = false) {
|
||||||
|
|
||||||
|
this.HTMLElement = newElement('div', 'barrier');
|
||||||
|
|
||||||
|
const pipe = newElement('div', 'pipe');
|
||||||
|
this.HTMLElement.appendChild(pipe);
|
||||||
|
|
||||||
|
const border = newElement('div', 'border');
|
||||||
|
this.HTMLElement.appendChild(border);
|
||||||
|
|
||||||
|
this.HTMLElement.style.flexDirection = reverse ? 'column-reverse' : 'column';
|
||||||
|
|
||||||
|
this.setHeight = height => this.HTMLElement.style.height = `${height}%`;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function BarrierPair(initPosition) {
|
||||||
|
|
||||||
|
this.HTMLElement = newElement('div', 'barrier-pair');
|
||||||
|
|
||||||
|
this.crossedMiddle = false;
|
||||||
|
|
||||||
|
this.top = new Barrier();
|
||||||
|
this.HTMLElement.appendChild(this.top.HTMLElement);
|
||||||
|
|
||||||
|
this.bottom = new Barrier(true);
|
||||||
|
this.HTMLElement.appendChild(this.bottom.HTMLElement);
|
||||||
|
|
||||||
|
this.sortOpening = () => {
|
||||||
|
|
||||||
|
const minOpening = 25;
|
||||||
|
const maxOpening = 40;
|
||||||
|
const minHeight = 10;
|
||||||
|
|
||||||
|
const opening = Math.random() * (maxOpening - minOpening) + minOpening;
|
||||||
|
|
||||||
|
const maxHeight = 100 - (2 * minHeight + opening);
|
||||||
|
const heightTop = Math.random() * (maxHeight - minHeight) + minHeight;
|
||||||
|
const heightBottom = 100 - opening - heightTop;
|
||||||
|
|
||||||
|
this.top.setHeight(heightTop);
|
||||||
|
this.bottom.setHeight(heightBottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.getPosition = () => parseFloat(this.HTMLElement.style.right.split('%')[0]);
|
||||||
|
this.setPosition = position => this.HTMLElement.style.right = `${position}%`;
|
||||||
|
|
||||||
|
this.move = displacement => this.setPosition(this.getPosition() + displacement);
|
||||||
|
|
||||||
|
this.sortOpening();
|
||||||
|
this.setPosition(initPosition);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function Obstacles(addScore) {
|
||||||
|
|
||||||
|
this.displacement = 1;
|
||||||
|
|
||||||
|
this.pairs = [
|
||||||
|
new BarrierPair(-25),
|
||||||
|
new BarrierPair(-100)
|
||||||
|
];
|
||||||
|
|
||||||
|
this.animation = () => {
|
||||||
|
this.pairs.forEach(pair => {
|
||||||
|
|
||||||
|
const position = pair.getPosition();
|
||||||
|
pair.move(this.displacement);
|
||||||
|
|
||||||
|
if (position >= 100) {
|
||||||
|
pair.setPosition(-50);
|
||||||
|
pair.sortOpening();
|
||||||
|
pair.crossedMiddle = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const crossedMiddle = position >= 50 &&
|
||||||
|
position < 50 + this.displacement;
|
||||||
|
|
||||||
|
if (crossedMiddle && !pair.crossedMiddle) {
|
||||||
|
addScore();
|
||||||
|
pair.crossedMiddle = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkOverlappingElements(elem1, elem2) {
|
||||||
|
|
||||||
|
const e1 = elem1.getBoundingClientRect();
|
||||||
|
const e2 = elem2.getBoundingClientRect();
|
||||||
|
|
||||||
|
const horizontal = e1.right >= e2.left
|
||||||
|
&& e1.left <= e2.right;
|
||||||
|
|
||||||
|
const vertical = e1.top <= e2.bottom
|
||||||
|
&& e1.bottom >= e2.top;
|
||||||
|
|
||||||
|
return horizontal && vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkCollision(bird, barriers) {
|
||||||
|
|
||||||
|
let collisionHappened = false;
|
||||||
|
|
||||||
|
barriers.forEach(pair => {
|
||||||
|
|
||||||
|
if (!collisionHappened) {
|
||||||
|
|
||||||
|
const birdElem = bird.HTMLElement;
|
||||||
|
const topBarrier = pair.top.HTMLElement;
|
||||||
|
const bottomBarrier = pair.bottom.HTMLElement;
|
||||||
|
|
||||||
|
collisionHappened = checkOverlappingElements(birdElem, topBarrier)
|
||||||
|
|| checkOverlappingElements(birdElem, bottomBarrier);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
return collisionHappened;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetEventFunctions(clear = true, callBackFunc) {
|
||||||
|
|
||||||
|
if (clear) {
|
||||||
|
window.onclick = () => {};
|
||||||
|
window.onkeydown = () => {};
|
||||||
|
window.ontouchstart = () => {};
|
||||||
|
} else {
|
||||||
|
window.onclick = callBackFunc;
|
||||||
|
window.onkeydown = callBackFunc;
|
||||||
|
window.ontouchstart = callBackFunc;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function FlappyGame() {
|
||||||
|
|
||||||
|
let loop;
|
||||||
|
let score = 0;
|
||||||
|
|
||||||
|
const gameScene = document.querySelector('[flappy-game]');
|
||||||
|
|
||||||
|
const scoreBoard = new ScoreBoard();
|
||||||
|
gameScene.appendChild(scoreBoard.HTMLElement);
|
||||||
|
|
||||||
|
const bird = new Bird();
|
||||||
|
gameScene.appendChild(bird.HTMLElement);
|
||||||
|
|
||||||
|
const obstacles = new Obstacles(() => {
|
||||||
|
|
||||||
|
scoreBoard.updateScore(++score);
|
||||||
|
if (score % 10 === 0 && score > 0) obstacles.displacement += 0.2;
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
obstacles.pairs.forEach(pair => gameScene.appendChild(pair.HTMLElement));
|
||||||
|
|
||||||
|
this.start = () => {
|
||||||
|
|
||||||
|
resetEventFunctions();
|
||||||
|
bird.resetEvents();
|
||||||
|
|
||||||
|
loop = setInterval(() => {
|
||||||
|
|
||||||
|
bird.fly();
|
||||||
|
|
||||||
|
obstacles.animation();
|
||||||
|
|
||||||
|
if (checkCollision(bird, obstacles.pairs)) this.finish();
|
||||||
|
|
||||||
|
}, 20);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
this.startWithDelay = (miliseconds = 3000) => {
|
||||||
|
|
||||||
|
setTimeout(() => this.start(), miliseconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.stop = () => clearInterval(loop);
|
||||||
|
|
||||||
|
this.finish = () => {
|
||||||
|
|
||||||
|
this.stop();
|
||||||
|
|
||||||
|
const userResponse = confirm('GAME OVER! \n\nRestart game?');
|
||||||
|
|
||||||
|
if (userResponse) {
|
||||||
|
window.location.reload();
|
||||||
|
} else {
|
||||||
|
const message = () => {
|
||||||
|
alert('GAME OVER! \n\nReload page to restart (F5).');
|
||||||
|
resetEventFunctions();
|
||||||
|
}
|
||||||
|
resetEventFunctions(false, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const newGame = new FlappyGame();
|
||||||
|
// newGame.startWithDelay();
|
||||||
|
resetEventFunctions(false, newGame.start);
|
BIN
src/imgs/bird0.png
Normal file
BIN
src/imgs/bird0.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 91 KiB |
BIN
src/imgs/bird1.png
Normal file
BIN
src/imgs/bird1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 91 KiB |
BIN
src/imgs/bird2.png
Normal file
BIN
src/imgs/bird2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 91 KiB |
31
src/index.html
Normal file
31
src/index.html
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Flappy Game</title>
|
||||||
|
<link rel="stylesheet" href="./style.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div flappy-game></div>
|
||||||
|
<div class="instructions">
|
||||||
|
<p>
|
||||||
|
This is a replica of the original flappy bird game developed for the sole purpose of learning.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Click on the screen, or use your spacebar to get started.
|
||||||
|
Fly the bird as far as you can without hitting a pipe.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<footer>
|
||||||
|
<p>
|
||||||
|
Coded by <strong>jguarato</strong> •
|
||||||
|
<a href="https://codepen.io/jguarato" target="_blank">Check out my other Pens</a> •
|
||||||
|
<a href="https://github.com/jguarato" target="_blank">Follow me on GitHub</a>
|
||||||
|
</p>
|
||||||
|
</footer>
|
||||||
|
<script src="./flappy.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
149
src/style.css
Normal file
149
src/style.css
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&family=Poppins&display=swap');
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
html, body {
|
||||||
|
width: 100vw;
|
||||||
|
min-height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content:space-between;
|
||||||
|
align-items: center;
|
||||||
|
font-family: Poppins;
|
||||||
|
background-color: #232936;
|
||||||
|
}
|
||||||
|
|
||||||
|
[flappy-game] {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
overflow: hidden;
|
||||||
|
margin: 50px;
|
||||||
|
height: 640px;
|
||||||
|
width: 480px;
|
||||||
|
background-color: rgb(35, 177, 182);
|
||||||
|
}
|
||||||
|
|
||||||
|
.score {
|
||||||
|
position: absolute;
|
||||||
|
margin: 20px;
|
||||||
|
z-index: 1;
|
||||||
|
font-size: 40px;
|
||||||
|
font-family: "Press Start 2P";
|
||||||
|
}
|
||||||
|
|
||||||
|
.bird {
|
||||||
|
position: absolute;
|
||||||
|
align-self: center;
|
||||||
|
z-index: 2;
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.barrier-pair {
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
height: 100%;
|
||||||
|
width: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.barrier {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pipe {
|
||||||
|
height: calc(100% - 20px);
|
||||||
|
width: 85%;
|
||||||
|
background: linear-gradient(to right, #8bb747, #dcf582 20%, #528021 80%);
|
||||||
|
box-shadow: 0.5px 1px 10px 0px #272e1aad;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border {
|
||||||
|
height: 20px;
|
||||||
|
width: 100%;
|
||||||
|
background: linear-gradient(to right, #8bb747 ,#dcf582 20%, #528021 80%);
|
||||||
|
box-shadow: 0.5px 1px 10px 0px #272e1aad;
|
||||||
|
}
|
||||||
|
|
||||||
|
.instructions {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 3;
|
||||||
|
right: 0;
|
||||||
|
top: 40px;
|
||||||
|
width: 300px;
|
||||||
|
padding: 15px;
|
||||||
|
text-align: center;
|
||||||
|
background-color: #384155;
|
||||||
|
}
|
||||||
|
|
||||||
|
.instructions p {
|
||||||
|
color: #d5dbe7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.instructions::before {
|
||||||
|
content: "?";
|
||||||
|
font-size: 30px;
|
||||||
|
padding: 0 15px;
|
||||||
|
border-radius: 100%;
|
||||||
|
background-color: #d5dbe7;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
height: 60px;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0px 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #e7edf8;
|
||||||
|
text-align: center;
|
||||||
|
background-color: #384155;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer a:link {
|
||||||
|
color: #af8ae0;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer a:visited {
|
||||||
|
color: #eeff00;
|
||||||
|
}
|
||||||
|
footer a:hover {
|
||||||
|
color: #eb8919;
|
||||||
|
}
|
||||||
|
footer a:active {
|
||||||
|
color: #eb8919;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer strong {
|
||||||
|
color: #91d4d0;
|
||||||
|
text-decoration: none;
|
||||||
|
background: linear-gradient(to top, #eeff00 40%, #3a1866 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
0% { opacity: 0; }
|
||||||
|
100% { opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1112px) {
|
||||||
|
.instructions { visibility: hidden; }
|
||||||
|
.instructions:hover { animation: 1s fadeIn; visibility: visible; }
|
||||||
|
.instructions::before { visibility: visible; cursor: pointer; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
body { overflow-x: hidden; }
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user