├── .gitattributes ├── media ├── mixkit-game-ball-tap-2073.wav ├── mixkit-game-level-music-689.wav ├── mixkit-small-hit-in-a-game-2072.wav ├── mixkit-winning-notification-2018.wav ├── mixkit-winning-an-extra-bonus-2060.wav ├── mixkit-arcade-retro-changing-tab-206.wav ├── mixkit-neutral-bot-pinbal-tone-3137.wav └── mixkit-thin-metal-card-deck-shuffle-3175.wav ├── Paddle.js ├── README.md ├── Ball.js ├── index.html ├── style.css └── script.js /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /media/mixkit-game-ball-tap-2073.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BriteBloomSolutions/Bit-Pong/HEAD/media/mixkit-game-ball-tap-2073.wav -------------------------------------------------------------------------------- /media/mixkit-game-level-music-689.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BriteBloomSolutions/Bit-Pong/HEAD/media/mixkit-game-level-music-689.wav -------------------------------------------------------------------------------- /media/mixkit-small-hit-in-a-game-2072.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BriteBloomSolutions/Bit-Pong/HEAD/media/mixkit-small-hit-in-a-game-2072.wav -------------------------------------------------------------------------------- /media/mixkit-winning-notification-2018.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BriteBloomSolutions/Bit-Pong/HEAD/media/mixkit-winning-notification-2018.wav -------------------------------------------------------------------------------- /media/mixkit-winning-an-extra-bonus-2060.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BriteBloomSolutions/Bit-Pong/HEAD/media/mixkit-winning-an-extra-bonus-2060.wav -------------------------------------------------------------------------------- /media/mixkit-arcade-retro-changing-tab-206.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BriteBloomSolutions/Bit-Pong/HEAD/media/mixkit-arcade-retro-changing-tab-206.wav -------------------------------------------------------------------------------- /media/mixkit-neutral-bot-pinbal-tone-3137.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BriteBloomSolutions/Bit-Pong/HEAD/media/mixkit-neutral-bot-pinbal-tone-3137.wav -------------------------------------------------------------------------------- /media/mixkit-thin-metal-card-deck-shuffle-3175.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BriteBloomSolutions/Bit-Pong/HEAD/media/mixkit-thin-metal-card-deck-shuffle-3175.wav -------------------------------------------------------------------------------- /Paddle.js: -------------------------------------------------------------------------------- 1 | const SPEED = 0.02; 2 | 3 | export default class Paddle { 4 | constructor(paddleEl) { 5 | this.paddleEl = paddleEl; 6 | this.reset(); 7 | } 8 | 9 | get position() { 10 | return parseFloat( 11 | getComputedStyle(this.paddleEl).getPropertyValue("--position") 12 | ); 13 | } 14 | 15 | set position(value) { 16 | this.paddleEl.style.setProperty("--position", value); 17 | } 18 | 19 | rect() { 20 | return this.paddleEl.getBoundingClientRect(); 21 | } 22 | 23 | reset() { 24 | this.position = 50; 25 | } 26 | 27 | update(delta, ballHeight) { 28 | this.position += SPEED * delta * (ballHeight - this.position); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bit-Pong 2 | 3 | ## Recommended Specs 4 | 5 | * Operating Systems 6 | - Windows 10 7 | - Windows 8, 8.1 8 | - Windows 7 9 | - Windows vista 10 | - Mac 11 | * Hardware Environment 12 | - Processor: x86 or x64 13 | - RAM : 512 MB (minimum), 1 GB (recommended) 14 | - Hard disc: up to 1 GB of free space may be required 15 | * Web browsers 16 | - Internet Explorer 8+ 17 | - Microsoft Edge Latest 18 | - Mozilla Firefox Latest 19 | - Chrome 20+ 20 | - Opera 17+ 21 | - Safari 12+ 22 | 23 | 24 | ## The inspiration 25 | 26 | - Color Changing Paddle 35 | 36 | ## Tech Stack 37 | * Javascript 38 | * HTML 39 | * CSS 40 | 41 | ## Upcoming Features 42 | -multi-ball play 43 | -additional difficulty levels 44 | -multi-player 45 | 46 | ## Sources 47 | -https://www.youtube.com/watch?v=LXTnfp7nU_0 48 | -https://www.youtube.com/watch?v=nl0KXCa5pJk 49 | -https://www.youtube.com/watch?v=PeY6lXPrPaA&t=1909s 50 | 51 | 52 | -------------------------------------------------------------------------------- /Ball.js: -------------------------------------------------------------------------------- 1 | let INITIAL_VELOCITY = 0.015; 2 | let VELOCITY_INCREASE = 0.000001; 3 | 4 | export default class Ball { 5 | constructor(ballElem) { 6 | this.ballElem = ballElem; 7 | this.reset(); 8 | } 9 | 10 | get x() { 11 | return parseFloat(getComputedStyle(this.ballElem).getPropertyValue("--x")); 12 | } 13 | 14 | set x(value) { 15 | this.ballElem.style.setProperty("--x", value); 16 | } 17 | 18 | get y() { 19 | return parseFloat(getComputedStyle(this.ballElem).getPropertyValue("--y")); 20 | } 21 | 22 | set y(value) { 23 | this.ballElem.style.setProperty("--y", value); 24 | } 25 | 26 | rect() { 27 | return this.ballElem.getBoundingClientRect(); 28 | } 29 | 30 | reset() { 31 | this.x = 50; 32 | this.y = 50; 33 | this.direction = { x: 0 }; 34 | while ( 35 | Math.abs(this.direction.x) <= 0.25 || 36 | Math.abs(this.direction.x) >= 0.85 37 | ) { 38 | const heading = randomNumberBetween(0, 1.75 * Math.PI); 39 | this.direction = { x: Math.cos(heading), y: Math.sin(heading) }; 40 | } 41 | this.velocity = INITIAL_VELOCITY; 42 | } 43 | 44 | update(delta, paddleRects) { 45 | this.x += this.direction.x * this.velocity * delta; 46 | this.y += this.direction.y * this.velocity * delta; 47 | this.velocity += VELOCITY_INCREASE * delta; 48 | const rect = this.rect(); 49 | 50 | //collision occurrances 51 | if (rect.bottom >= window.innerHeight || rect.top <= 0) { 52 | this.direction.y *= -1; 53 | } 54 | 55 | if (paddleRects.some((r) => isCollision(r, rect))) { 56 | this.direction.x *= -1; 57 | } 58 | } 59 | } 60 | 61 | function randomNumberBetween(min, max) { 62 | return Math.random() * (max - min) + min; 63 | } 64 | 65 | function isCollision(rect1, rect2) { 66 | return ( 67 | rect1.left <= rect2.right && 68 | rect1.right >= rect2.left && 69 | rect1.top <= rect2.bottom && 70 | rect1.bottom >= rect2.top 71 | ); 72 | } 73 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Bit Pong JS 10 | 11 |
12 |

13 |
14 | 15 | //audio files 16 | 21 | 26 | 30 | 34 | 38 | 42 | 43 | 50 |
51 |
0
52 |
0
53 |
54 |
0
55 | 56 |
57 |
58 | 59 | 63 | 64 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | *, 2 | *::after, 3 | *::before { 4 | box-sizing: border-box; 5 | } 6 | :root { 7 | --hue: 100; 8 | --saturation: 70%; 9 | --foreground-color: hsl(var(--hue), var(--saturation), 75%); 10 | --background-color: hsl(var(--hue), var(--saturation), 10%); 11 | --logo-color: hsl(var(--hue), var(--saturation), 85%); 12 | font-family:Roboto, monospace, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif 13 | 14 | } 15 | .game-heading{ 16 | display:flex; 17 | /* row-gap:inherit */ 18 | font-weight: bold; 19 | font-size: xxxxxx-large; 20 | color: var(--foreground-color); 21 | padding-top:50 vh; 22 | margin-bottom: 0%; 23 | margin-left: 46%; 24 | } 25 | header { 26 | margin: 0vh 0vw; 27 | width:100vw; 28 | height: 20vh; 29 | padding: 1vh 20vw ; 30 | } 31 | 32 | #winner-alert{ 33 | background-color: var(--foreground-color); 34 | color: #fff; 35 | font-size:x-large; 36 | text-align: center; 37 | } 38 | 39 | .sub-title{ 40 | font-weight:lighter; 41 | font-size: 2vh; 42 | color: var(--logo-color); 43 | margin-bottom: 90%; 44 | margin-left: 75%; 45 | } 46 | 47 | .body { 48 | margin: 0; 49 | background-color: var(--background-color); 50 | perspective: 40%; 51 | } 52 | 53 | .paddle { 54 | --position: 50; 55 | 56 | position: absolute; 57 | background-color: var(--foreground-color); 58 | top: calc(var(--position) * 1vh); 59 | transform: translateY(-50%); 60 | width: 1vh; 61 | height: 10vh; 62 | } 63 | 64 | .paddle.left { 65 | left: 1vw; 66 | } 67 | 68 | .paddle.right { 69 | right: 1vw; 70 | } 71 | 72 | .ball{ 73 | --x: 100; 74 | --y: 100; 75 | 76 | position: absolute; 77 | color: var(--foreground-color); 78 | left: calc(var(--x) * 1vw); 79 | top: calc(var(--y) * 1vh); 80 | border-radius: 50%; 81 | transform: translateY(-50%, -50%); 82 | width: 30 vh; 83 | height: 30 vh; 84 | background-color: hsl(var(--hue), var(50%), 75%); 85 | text-align: center; 86 | font-weight:bolder; 87 | font-size: 5vh; 88 | } 89 | 90 | .score { 91 | display: flex; 92 | justify-content:center; 93 | font-weight: bold; 94 | font-size: 10vh; 95 | color: var(--foreground-color); 96 | margin-top: 10%; 97 | align-items: center; 98 | row-gap:inherit; 99 | } 100 | 101 | .score > * { 102 | flex-grow: 1; 103 | flex-basis: 0; 104 | padding: 0 1vh; 105 | margin: 2vh 0; 106 | opacity: .35; 107 | } 108 | 109 | .score > :first-child { 110 | text-align: right; 111 | border-right: 1vh solid var(--foreground-color); 112 | 113 | } 114 | .menu{ 115 | display: flex; 116 | align-content: center; 117 | } 118 | .button { 119 | position:inherit; 120 | top:10%; 121 | background-color: var(--foreground-color); 122 | color: white; 123 | font-size:larger; 124 | border:none; 125 | border-radius:10px; 126 | 127 | 128 | } 129 | 130 | ul{ 131 | list-style-type: none; 132 | display:flex; 133 | margin:auto; 134 | 135 | } -------------------------------------------------------------------------------- /script.js: -------------------------------------------------------------------------------- 1 | import Ball from "./Ball.js"; 2 | import Paddle from "./Paddle.js"; 3 | 4 | const ball = new Ball(document.getElementById("ball")); 5 | const playerPaddle = new Paddle(document.getElementById("player-paddle")); 6 | const computerPaddle = new Paddle(document.getElementById("computer-paddle")); 7 | const playerScoreElem = document.getElementById("player-score"); 8 | const computerScoreElem = document.getElementById("computer-score"); 9 | const newBtn = document.getElementById("new-button"); 10 | const modeBtn = document.getElementById("mode-button"); 11 | const quitBtn = document.getElementById("quit-button"); 12 | const winnerAlert = document.getElementById("winner-alert"); 13 | let myAudio = document.querySelector("#audio"); 14 | let myFastAudio = document.querySelector(`#fast-audio`); 15 | myFastAudio.playbackRate=1.25; 16 | let playerWinSound = document.querySelector(`#player-sound`); 17 | let computerWinSound = document.querySelector(`#computer-sound`); 18 | let lostBallSound = document.querySelector(`#lost-sound`); 19 | let collisionSound = document.querySelector(`#collision-sound`); 20 | 21 | function settime(audioName, endTime) { 22 | audioName.currentTime; 23 | audioName.play(); 24 | if (audioName.currentTime > endTime) { 25 | audioName.stop(); 26 | } 27 | } 28 | myAudio.play(); 29 | let winner; 30 | let chooseWinner = () => { 31 | if ( 32 | Number(playerScoreElem.textContent) > Number(computerScoreElem.textContent) 33 | ) { 34 | winner = "Player"; 35 | } 36 | if ( 37 | Number(playerScoreElem.textContent) < Number(computerScoreElem.textContent) 38 | ) { 39 | winner = "Computer"; 40 | } else { 41 | winner = "Tied. No one "; 42 | } 43 | }; 44 | let prevTime; 45 | let gameOn = true; 46 | function update(time) { 47 | if (prevTime != null && gameOn) { 48 | const delta = time - prevTime; 49 | ball.update(delta, [playerPaddle.rect(), computerPaddle.rect()]); 50 | 51 | computerPaddle.update(delta, ball.y); 52 | const hue = parseFloat( 53 | getComputedStyle(document.documentElement).getPropertyValue("--hue") 54 | ); 55 | 56 | document.documentElement.style.setProperty("--hue", hue + delta * 0.01); 57 | if (isLost()) handleLoss(); 58 | } else if (gameOn == false) { 59 | const delta = 0; 60 | ball.update(delta, [playerPaddle.rect(), computerPaddle.rect()]); 61 | computerPaddle.update(delta, ball.y); 62 | chooseWinner(); 63 | winnerAlert.textContent = `${winner} wins!`; 64 | myAudio.pause(); 65 | if (winner == "Player") { 66 | //player win sound 67 | settime(playerWinSound, .25); 68 | } else { 69 | //computer win sound 70 | settime(computerWinSound, .6); 71 | } 72 | } 73 | 74 | prevTime = time; 75 | window.requestAnimationFrame(update); 76 | } 77 | 78 | function isLost() { 79 | const rect = ball.rect(); 80 | return rect.right >= window.innerWidth || rect.left <= 0; 81 | } 82 | 83 | function handleLoss() { 84 | const rect = ball.rect(); 85 | if (rect.right >= window.innerWidth) { 86 | playerScoreElem.textContent = parseInt(playerScoreElem.textContent) + 1; 87 | settime(lostBallSound, .25); 88 | 89 | } else { 90 | computerScoreElem.textContent = parseInt(computerScoreElem.textContent) + 1; 91 | settime(lostBallSound, .25); 92 | } 93 | ball.reset(); 94 | computerPaddle.reset(); 95 | } 96 | 97 | const reload = () => { 98 | window.location.reload();} 99 | 100 | const changeMode = () => { 101 | console.log("changing mode"); 102 | const initialText = "Easy"; 103 | if (modeBtn.textContent.toLowerCase().includes(initialText.toLowerCase())) { 104 | settime(collisionSound, .2); 105 | modeBtn.innerHTML = 'Hard'; 106 | //harder 107 | INITIAL_VELOCITY = 0.035; 108 | VELOCITY_INCREASE = 0.00001; 109 | // console.log(`ball x: ${ball.x}`); 110 | // console.log(`ball y: ${ball.y}`); 111 | } else { 112 | settime(collisionSound, .2); 113 | modeBtn.innerHTML = 'Easy'; 114 | //easier 115 | INITIAL_VELOCITY = 0.015; 116 | VELOCITY_INCREASE = 0.00003; 117 | // console.log(`ball x: ${ball.x}`); 118 | // console.log(`ball y: ${ball.y}`); 119 | } 120 | }; 121 | 122 | const endGame = () => { 123 | console.log("ending game"); 124 | gameOn = false; 125 | }; 126 | 127 | document.addEventListener("mousemove", (e) => { 128 | playerPaddle.position = (e.y / window.innerHeight) * 100; 129 | }); 130 | newBtn.addEventListener("click", reload); 131 | modeBtn.addEventListener("click", changeMode); 132 | quitBtn.addEventListener("click", endGame); 133 | window.requestAnimationFrame(update); 134 | --------------------------------------------------------------------------------