Skip to content

Commit

Permalink
File cleanups + documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
mohanamisra committed Jun 28, 2024
1 parent 56fc58d commit fe76bfe
Show file tree
Hide file tree
Showing 11 changed files with 116 additions and 28 deletions.
37 changes: 19 additions & 18 deletions .firebase/hosting.ZGlzdA.cache
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
index.html,1719501861992,6c0d7a7c7b99cee592230168c0fd4fd6f232fefa9ff7577a8574bb20ecd2845a
assets/skull-4aD260e8.webp,1719501861992,a0455c53b48ea85405948295f0cf7d15aaafec7f45b28f5994101d3df5ca605e
assets/scoreboard-C4un5giP.webp,1719501861991,ebbf61adeecb7609764907312305f64ad0b20d988f236339b1e748cde9e7bec1
assets/vampire-mPAYI4wv.webp,1719501861991,145a9ec22ac87737a918eda21976c720072c9ff911249ab6fa152b4b47f70fb1
assets/pixelify-sans-latin-ext-wght-normal-DEDzhmUG.woff2,1719501861992,c6277cd196d476ffc30a787acd0e38824a13ed166d16dda2a762872aa448bd7d
assets/pixelify-sans-cyrillic-wght-normal-CPPz0Qvd.woff2,1719501861992,e5f836538ee35dfa62c9e1690c005d8e14fe9b26850bf6f69ee8432570e1dcfb
assets/pixelify-sans-latin-wght-normal-vdc2vUDH.woff2,1719501861992,c066c80d2f859f8733dcfc1b86a970ef829c99884c11b5be87491a5bd98bca20
assets/index-BROHwali.css,1719501861992,01c42bae31932964edc95c32c3c8d1d92f08f257e0479ba5bb543e405d989dd8
assets/googleIcon-B82wVp1S.webp,1719501861992,ef6bc8746a5b091b21b2a54762d9e2c6b97e6d877ac055af39c6c601e4270adc
assets/ghost-CIU8fCIk.webp,1719501861991,83babc01a29b5b53de1527b8155b6ab0b5a70e825daee505f253a2fc3c41ead6
assets/goblin-DJRMrO0b.webp,1719501861991,ad4cc627dcb081641832d374b37695353c33376c3b06931094a6f6ec83ab425a
assets/audioon-uTUd5m9x.webp,1719501861991,b7383d87bc77f7d452b239cc820a619443f3b2699f1c7419f322576e1ca54620
assets/audiooff-CKrKWU2a.webp,1719501861991,78d6a453312b17c4690615c9beb41814472b239e084024e65f674a6fddeab4be
assets/candy-CTybcJ4Y.webp,1719501861992,013c4baee432d614adde0b83e5a932e08e72f1020a0e551dcae40b0cc5220559
assets/background-UArc6RgK.webp,1719501861991,bc5d933faa2d838b2e3a39bc648336c26b543c9501a1ca15daa147d46046165c
assets/a-BZyeQR8F.webp,1719501861992,2a38602b97232ea63f2a18a341cdc8ace716d5e2836bf99ae0bc94e332cc2fed
assets/index-BU3-UhWA.js,1719501861993,11e916556c8c9960f64730d1e23d79a75f7363305bb09bef0ffc5e3d4e98c2a2
assets/bgmusic-CSIQiEVP.mp3,1719501861995,dd0452663450fd5a6b66e7864c84e55acc49defe2e0d1c2cd60c06184c8b25ea
index.html,1719502170421,8f098e62e9536b1a0a2b78d31efd458d779ca9fa7015d40b55192229a80c00ac
assets/skull-4aD260e8.webp,1719502170420,a0455c53b48ea85405948295f0cf7d15aaafec7f45b28f5994101d3df5ca605e
assets/scoreboard-C4un5giP.webp,1719502170420,ebbf61adeecb7609764907312305f64ad0b20d988f236339b1e748cde9e7bec1
assets/pixelify-sans-latin-ext-wght-normal-DEDzhmUG.woff2,1719502170420,c6277cd196d476ffc30a787acd0e38824a13ed166d16dda2a762872aa448bd7d
assets/vampire-mPAYI4wv.webp,1719502170420,145a9ec22ac87737a918eda21976c720072c9ff911249ab6fa152b4b47f70fb1
assets/pixelify-sans-cyrillic-wght-normal-CPPz0Qvd.woff2,1719502170420,e5f836538ee35dfa62c9e1690c005d8e14fe9b26850bf6f69ee8432570e1dcfb
assets/pixelify-sans-latin-wght-normal-vdc2vUDH.woff2,1719502170420,c066c80d2f859f8733dcfc1b86a970ef829c99884c11b5be87491a5bd98bca20
assets/index-BROHwali.css,1719502170421,01c42bae31932964edc95c32c3c8d1d92f08f257e0479ba5bb543e405d989dd8
assets/hit-Cvtlgl9C.mp3,1719502170420,66f78108cb3a1a3576ea493db240af18cdc0b2a2bcab25ed9e8bc56e2fb5cdda
assets/googleIcon-B82wVp1S.webp,1719502170420,ef6bc8746a5b091b21b2a54762d9e2c6b97e6d877ac055af39c6c601e4270adc
assets/ghost-CIU8fCIk.webp,1719502170420,83babc01a29b5b53de1527b8155b6ab0b5a70e825daee505f253a2fc3c41ead6
assets/goblin-DJRMrO0b.webp,1719502170420,ad4cc627dcb081641832d374b37695353c33376c3b06931094a6f6ec83ab425a
assets/audiooff-CKrKWU2a.webp,1719502170420,78d6a453312b17c4690615c9beb41814472b239e084024e65f674a6fddeab4be
assets/audioon-uTUd5m9x.webp,1719502170420,b7383d87bc77f7d452b239cc820a619443f3b2699f1c7419f322576e1ca54620
assets/candy-CTybcJ4Y.webp,1719502170420,013c4baee432d614adde0b83e5a932e08e72f1020a0e551dcae40b0cc5220559
assets/background-UArc6RgK.webp,1719502170421,bc5d933faa2d838b2e3a39bc648336c26b543c9501a1ca15daa147d46046165c
assets/a-BZyeQR8F.webp,1719502170420,2a38602b97232ea63f2a18a341cdc8ace716d5e2836bf99ae0bc94e332cc2fed
assets/index-CzvPyDa7.js,1719502170422,dd137921d08caf3099843b204e445b8691cd75155610211c3d0c8d7eb47c70ab
assets/bgmusic-CSIQiEVP.mp3,1719502170425,dd0452663450fd5a6b66e7864c84e55acc49defe2e0d1c2cd60c06184c8b25ea
5 changes: 4 additions & 1 deletion src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import Game from './pages/Game.jsx';
import Login from './pages/Login.jsx';
import GameOver from './pages/GameOver.jsx';

import {Routes, Route, Navigate} from "react-router-dom";
// The App component essentialy just tells the web application where to go if
// certain things happen, are clicked, certain urls are accessed, etc

import {Routes, Route} from "react-router-dom";

const App = () => {
return (
Expand Down
Binary file removed src/assets/images/1.webp
Binary file not shown.
Binary file removed src/assets/images/2.webp
Binary file not shown.
Binary file removed src/assets/images/generalBackground.webp
Binary file not shown.
Binary file removed src/assets/images/night-12128.gif
Binary file not shown.
4 changes: 2 additions & 2 deletions src/main.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import {BrowserRouter} from "react-router-dom";

// The BrowserRouter component wraps the main App component and enables App to implement routes.

ReactDOM.createRoot(document.getElementById('root')).render(
// <React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
// </React.StrictMode>,
)
62 changes: 60 additions & 2 deletions src/pages/Game.jsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
// THIS IS WHERE EVERYTHING HAPPENS!
// The first section right below imports all the assets required (which are a lot!)
// This section can grow in size easily for the simplest games. I am still looking for a way to making this entire
// importing thing cleaner.

import React, { useRef, useEffect, useState } from 'react';
import { useNavigate, useLocation } from 'react-router-dom';
import p5 from 'p5';
Expand All @@ -20,34 +25,62 @@ import bgMusic from "../assets/music/bgmusic.mp3";
import hitMusic from "../assets/music/hit.mp3"
import '@fontsource-variable/pixelify-sans';

// Now we declare the variables we need.
// In general, global variables aren't preferred because they can easily cause namespace collisions,
// and get manipulated ANYWHERE. That makes it hard to write maintainable and reusable code.
// One of the reasons I have excused myself here is because this was my first attempt at embedding p5 into React
// without any wrapper.
// Definitely a priority to avoid this in future projects!

let Engine = Matter.Engine;
let World = Matter.Composite;
let Mouse = Matter.Mouse;
let MouseConstraint = Matter.MouseConstraint;
let Collision = Matter.Collision;

// The above just helps me write code. It's easier to write Engine.create() rather than Matter.Engine.create(), for example.
// The below are the "true" global variables that I am going to use to make the game.
// They don't just make writing code easy. They are essential.

let ground, ball, world, engine, mCon, leftWall, rightWall, scoreBoard, audioButton;
let boxes = [];
let score = 0;
let timer = 42;
let bgImage, ballImage, scoreboardImage, audioOnImage, audioOffImage;
let monsterImages = [];
let imgIndex = 0;
let constraint, constraintOptions;
let gameOver = false;

// Constants! The below constants specifically tell us the collision category of bodies.
// In Matter JS, whether two bodies collide or not depends on their collision category and collision mask.
// Refer to the docs for more info!

const BALL_CATEGORY = 0b0001;
const MOUSE_CATEGORY = 0b0010;

// Behold, the functional component wrapping everything!
// NOTE: Initially I had tried to put the p5 stuff outside the functional component
// but that made a lot of React hooks useless because they'd be declared after the lines that were going to use them.
// I couldn't sacrifice the need for hooks, so here we are, all refactored inside the Game() component.

function Game() {
// navigate() and location() help in, well, navigation, specifically to the game over screen.
const navigate = useNavigate();
const location = useLocation();

// p5 and audio playing code.
const p5Container = useRef();
const audioRef = useRef(new Audio(bgMusic));
const hitAudioRef = useRef(new Audio(hitMusic));
// it was too loud otherwise. Would recommend resolving it in some audio manipulation software instead of writing
// more code.
hitAudioRef.current.volume = 0.2;
const [playing, setPlaying] = useState(false);

// This is a p5 function. It wraps up all of the p5 "stuff".
function sketch(p) {

// Preloading image assets makes it so much better at rendering images faster.
p.preload = async function () {
bgImage = p.loadImage(bg);
ballImage = p.loadImage(candy);
Expand All @@ -61,13 +94,18 @@ function Game() {
audioOffImage = p.loadImage(audioOff);
}

// The world setup. Includes a ton of Matter JS code to setup the game world.
p.setup = function () {
const canvas = p.createCanvas(p.windowWidth, p.windowHeight);
engine = Engine.create();
world = engine.world;
const canvasMouse = Mouse.create(canvas.elt);
// If your mouse doesn't know your screen's pixel density, it will lowkey suck identifying
// where exactly the mouse even is.
// For all you know, what p5 thinks is a pixel, is actually 4 pixels on your device!
canvasMouse.pixelRatio = p.pixelDensity();

// audio playing logic.
audioButton = p.createImg(audioRef.current.paused ? audioOff : audioOn, "audio button");
audioButton.position(20, p.height - 50);
audioButton.size(30, 30);
Expand All @@ -83,6 +121,7 @@ function Game() {
}, 0);
});

// Creating the components from the classes defined in "src/components/" folder.
ground = new Boundary(p.width / 2, p.height - 10, p.width, 20, world, BALL_CATEGORY);
leftWall = new Boundary(10, p.height / 2, 20, p.height, world, BALL_CATEGORY);
rightWall = new Boundary(p.width - 10, p.height / 2, 20, p.height, world, BALL_CATEGORY);
Expand All @@ -100,6 +139,8 @@ function Game() {

World.add(world, [mCon]);

// This prevents the ball from rising above the specified area.
// Note the if conditionals. They detect loads of things such as is the player even dragging the ball to begin with.
Matter.Events.on(engine, "afterUpdate", () => {
if (p.mouseIsPressed && p.mouseY < 0.55 * p.height && ball.body.position.y < 0.55 * p.height) {
Matter.Body.applyForce(ball.body, { x: ball.body.position.x, y: ball.body.position.y }, { x: 0, y: -150 });
Expand All @@ -108,6 +149,11 @@ function Game() {
});
}

// My favourite part of this file. The spawnBoxes() function, in-charge of spawning boxes (monsters in the final implementation)!
// NOTE: I made the interval shorter because the random() function often generated locations that were overlapping.
// There must be more elegant ways of resolving an overlap, but since I just ignore those values and make
// random() regenerate x and y coords, I need a shorter interval.
// Otherwise spawning boxes would just take forever.
setInterval(spawnBoxes, 500);

function spawnBoxes() {
Expand Down Expand Up @@ -151,12 +197,17 @@ function Game() {
}
}

// This makes the canvas responsive.
p.windowResized = function () {
p.resizeCanvas(p.windowWidth, p.windowHeight);
}

// The code executed every frame, that is drawing on your screen typically 60 times a second.
p.draw = function () {
p.background(bgImage);

// The below is to avoid someone from just adding "/game" to the base url and appearing at the game page.
// That would break the leaderboard functionality!
if (location.state.loggedIn === true) {
Engine.update(engine);

Expand All @@ -168,21 +219,23 @@ function Game() {
timer--;
if (timer === 0) {
gameOver = true;
console.log("game over");
navigate("/leaderboard", { state: { score: score } });
}
}
scoreBoard.show(p, score, timer);

// It's one thing to spawn boxes...another thing to actually render them on the screen!
for (let i = 0; i < boxes.length; i++) {
boxes[i].show(p);
}

// Ball off-screen reset.
if (ball.body.position.y < 0 || ball.body.position.y > p.height + 50 || ball.body.position.x < 0 || ball.body.position.x > p.width + 50) {
ball.hide();
ball.reset(p);
}

// Collision logic
for (let i = 0; i < boxes.length; i++) {
if (Collision.collides(ball.body, boxes[i].body)) {
ball.hide();
Expand All @@ -198,8 +251,13 @@ function Game() {
}

useEffect(() => {
// Create a new p5 instance when this Game() component mounts.
// This is how all the p5 functions are accessible to us.
const p5Instance = new p5(sketch, p5Container.current);

// But you also have to remove it when Game() component unmounts(), as a precaution to remove any
// potential data leaks.
// Also, just clean code practice. Remove what you don't need.
return () => {
p5Instance.remove();
}
Expand Down
7 changes: 6 additions & 1 deletion src/pages/GameOver.jsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
// Basic component. Just accesses all kinds of data.
// It gets your current score from the previous screen's state.
// It receives your highscore and the leaderboard from Firebase.
// The only tricky part was to update everything in time for your current score to reflect in
// your highscore and even the leaderboard, *if* it was greater than your previous highscore.

import React, {useEffect, useState} from 'react';
import {useLocation, useNavigate} from 'react-router-dom';
import {auth, db} from "../config.jsx"
Expand Down Expand Up @@ -42,7 +48,6 @@ const GameOver = () => {
newLeaderboard.push(doc.data());
})
setLeaderboard(newLeaderboard);
console.log(newLeaderboard);
}

useEffect(() => {
Expand Down
21 changes: 17 additions & 4 deletions src/pages/Login.jsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
// This is a typical React login page.
// Beyond just UI stuff which differs from project to project,
// I have implemented only login via google.
// That is secure, delegated to Firebase to manage, and takes away a lot of validation requirements from me.

import React, {useEffect, useState, useRef} from 'react';
import {auth, provider, db} from "../config.jsx";
import {signInWithPopup} from "firebase/auth";
Expand All @@ -22,20 +27,23 @@ const Login = () => {
const handleLogin = () => {
signInWithPopup(auth, provider)
.then(async(data) => {
console.log(data.user.uid);
if(data.user) {
let player = await getDoc(doc(db, "users", data.user.uid));

// if the above player document doesn't exist in the database, that means this is the first time the player has ever logged in!
// So we need them to enter a username, the component for which needs to be visible at this point.
if(!player._document) {
setUserID(data.user.uid);
console.log("player data not detected");
setVisible(true);
}
}
setValue(data.user.email);
// So that the device remembers you have already logged in once!
localStorage.setItem("email", data.user.email);
});
}

// Basic logout functionality.
const handleLogout = () => {
localStorage.clear();
window.location.reload();
Expand All @@ -45,26 +53,30 @@ const Login = () => {
navigate("/game", {state: {loggedIn: true}});
}

// newUsername because in React, you do not manipulate the state directly.
const handleInputChange = (e) => {
const newUsername = e.target.value;
setUsername(newUsername);
}

const handleSetUsername = async() => {
console.log(userID);
console.log(username);
if(username === "") {
// Error message
alert("Invalid username!");
}
else {
await setDoc(doc(db, "users", userID), {
username: username,
highscore: 0,
});

// Done entering username? Cool.
// We now remove the component that accepted your username by setting its visibility to false.
setVisible(false);
}
}

// Audio playing logic. Was surprisingly more finnicky than I expected, across the project.
const handleAudioClick = () => {
if (audioRef.current) {
if (!playing) {
Expand Down Expand Up @@ -92,6 +104,7 @@ const Login = () => {
};
}, [visible]);

// Tons of conditional rendering below.
return (
<div className="login-container">
{visible?
Expand Down
8 changes: 8 additions & 0 deletions src/pages/Pages.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
/*I typically do not endorse throwing in all your css in one file.
However here, I didn't see the need for extensive css files for every page/component. A lot of the complex
visual rendering was taken care of by p5.js, so I only needed to really style the typical react web app elements.
That to me, didn't call for separate css files for everything.
Typically though, you want to separate things into modules, functions, classes, files, etc whenever possible.
Modular code is so much easier to maintain, and read!*/

/*LOGIN PAGE STYLING*/
.login-container {
background-image: url("../assets/images/a.webp");
Expand Down

0 comments on commit fe76bfe

Please sign in to comment.