Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added App/src/assets/audio/collect-sound.mp3
Binary file not shown.
Binary file added App/src/assets/audio/collision-sound.mp3
Binary file not shown.
Binary file added App/src/assets/audio/gameover-sound.mp3
Binary file not shown.
Binary file added App/src/assets/audio/jump-sound.mp3
Binary file not shown.
Binary file added App/src/assets/images/banana.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added App/src/assets/images/forest.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added App/src/assets/images/monkey.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added App/src/assets/images/mushroom.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
37 changes: 37 additions & 0 deletions App/src/components/Game/Gamelist.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Link, Outlet, useLocation } from "react-router-dom";

function Gamelist() {
const location = useLocation();

// Check if user is on the base gamelist route (no game selected)
const isBaseRoute = location.pathname.endsWith("/game") || location.pathname === "/game";

return (
<div className="p-6 items-center justify-center text-center ">
{isBaseRoute ? (
<div className="bg-green-200 p-2">
<h1 className="text-2xl font-bold mb-4">Choose a Game</h1>


<Link to="crossword" className="bg-[#137f13] text-white p-3 rounded m-3 hover:shadow-[5px_10px_6px_-2px_rgba(0,_0,_0,_0.2)]">
Crossword
</Link>
<Link to="spritegame" className="bg-[#137f13] text-white p-3 rounded m-3 hover:shadow-[5px_10px_6px_-2px_rgba(0,_0,_0,_0.2)]">
Sprite Game
</Link>
<div className="text-3xl m-6">🐘 🐊</div>
</div>
) : (
<div className="mb-6">
<Link to="/game" className="bg-red-600 hover:shadow-[5px_10px_6px_-2px_rgba(0,_0,_0,_0.2)] text-white p-3 rounded">
⬅ Back
</Link>
</div>
)}

<Outlet />
</div>
);
}

export default Gamelist;
238 changes: 238 additions & 0 deletions App/src/components/Game/SpriteGame.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
import React, { useEffect, useRef } from "react";
import playerImg from "../../assets/images/monkey.png";
import hurdleImg from "../../assets/images/mushroom.png";
import fruitImg from "../../assets/images/banana.png";
import bgImg from "../../assets/images/forest.jpg";
import jumpSoundFile from "../../assets/audio/jump-sound.mp3";
import collectSoundFile from "../../assets/audio/collect-sound.mp3";
import gameOverSoundFile from "../../assets/audio/gameover-sound.mp3";

function SpriteGame() {
const canvasRef = useRef(null);
let frameCount=0;
let highScore = parseInt(localStorage.getItem("highScore")) || 0;

useEffect(() => {
const canvas = canvasRef.current;
const parent = canvas.parentElement;
canvas.width = parent.clientWidth*0.98;
canvas.height = parent.clientHeight;
const ctx = canvas.getContext("2d");
const w = canvas.width;
const h = canvas.height;

const monkeyHeight = h * 0.15;
const monkeyWidth = w * 0.04;

// images and sounds
const playerSprite = new Image();
playerSprite.src = playerImg;
const hurdleSprite = new Image();
hurdleSprite.src = hurdleImg;
const fruitSprite = new Image();
fruitSprite.src = fruitImg;
const bgSprite = new Image();
bgSprite.src = bgImg;


const jumpSound = new Audio(jumpSoundFile);
const collectSound = new Audio(collectSoundFile);
const gameOverSound = new Audio(gameOverSoundFile);

let gameState = "start";
let score = 0;
const keys = {};

const initialPlayer = {
width: monkeyWidth,
height: monkeyHeight,
x: 30,
y: h - 80,
verticalSpeed: 0,
horizontalSpeed: 8,
jumpPower: -12,
gravity: 0.5,
};

let player = { ...initialPlayer };
let hurdles = [];
let fruits = [];
let hurdleTimer = 0;
let fruitTimer = 0;

function resetGame() {
if (score > highScore) {
highScore = score;
localStorage.setItem("highscore", highScore);
}

score = 0;
player = { ...initialPlayer };
hurdles = [];
fruits = [];
hurdleTimer = 0;
fruitTimer = 0;
gameState = "playing";
gameLoop();
}

function spawn() {
hurdleTimer++;
fruitTimer++;

if (hurdleTimer > Math.random() * 60 + 80) {
hurdles.push({
x: w,
y: h - 50,
width: 40,
height: 50,
});
hurdleTimer = 0;
}

if (fruitTimer > Math.random() * 120 + 120) {
fruits.push({
x: w,
y: h - (Math.random() * 20 + 110),
width: 30,
height: 30,
});
fruitTimer = 0;
}
}

function update() {
player.verticalSpeed += player.gravity;
player.y += player.verticalSpeed;

if (player.y + player.height > h) {
player.y = h - player.height;
player.verticalSpeed = 0;
}

spawn();
hurdles.forEach((h) => (h.x -= 5));
fruits.forEach((f) => (f.x -= 5));

// collisions with hurdles
hurdles.forEach((h) => {
if (
player.x < h.x + h.width &&
player.x + player.width > h.x &&
player.y < h.y + h.height &&
player.y + player.height > h.y
) {
if (gameState !== "gameover") {
gameState = "gameover";
gameOverSound.currentTime = 0;
gameOverSound.play();
}
}
});

// collisions with fruits
fruits.forEach((f, i) => {
if (
player.x < f.x + f.width &&
player.x + player.width > f.x &&
player.y < f.y + f.height &&
player.y + player.height > f.y
) {
score += 30;
fruits.splice(i, 1);
collectSound.currentTime = 0;
collectSound.play();
}
});

hurdles = hurdles.filter((h) => h.x + h.width > 0);
fruits = fruits.filter((f) => f.x + f.width > 0);
frameCount++;


if (frameCount % 10 === 0) {
score++; // increase once every 10 frames
}
}

function draw() {
ctx.clearRect(0, 0, w, h);
ctx.drawImage(bgSprite, 0, 0, w, h);

if (gameState === "start") {
ctx.fillStyle = "black";
ctx.font = "32px Arial";
ctx.fillText("Press SPACE to Start", w / 2 - 150, h / 2);
ctx.font = "20px Arial";
ctx.fillText("Controls: UP arrow key.", w / 2 - 160, h / 2 + 40);
ctx.fillText("Collect bananas. Dodge mushrooms.", w / 2 - 170, h / 2 + 70);
return;
}

ctx.drawImage(playerSprite, player.x, player.y, player.width, player.height);

hurdles.forEach((h) => ctx.drawImage(hurdleSprite, h.x, h.y, h.width, h.height));

fruits.forEach((f) => ctx.drawImage(fruitSprite, f.x, f.y, f.width, f.height));

ctx.fillStyle = "white";
ctx.font = "20px";
ctx.fillText(`Score: ${score}`, 10, 20);
ctx.fillText(`High Score: ${highScore}`, 10, 50);

if (gameState === "gameover") {
ctx.fillStyle = "black";
ctx.font = "40px";
ctx.fillText("Game Over", w / 2 - 100, h / 2);
ctx.font = "20px";
ctx.fillText("Press SPACE to restart", w / 2 - 120, h / 2 + 40);
}
}

function gameLoop() {
if (gameState === "playing") {
update();
draw();
requestAnimationFrame(gameLoop);
} else {
draw();
}
}

const keyDownHandler = (e) => {
keys[e.key] = true;

if (gameState === "start" && e.code === "Space") resetGame();
if (gameState === "gameover" && e.code === "Space") resetGame();

if (e.code === "ArrowUp" && player.y + player.height >= h) {
player.verticalSpeed = player.jumpPower;
jumpSound.currentTime = 0;
jumpSound.play();
}
};

const keyUpHandler = (e) => {
keys[e.key] = false;
};

window.addEventListener("keydown", keyDownHandler);
window.addEventListener("keyup", keyUpHandler);

draw();

return () => {
window.removeEventListener("keydown", keyDownHandler);
window.removeEventListener("keyup", keyUpHandler);
};
}, []);

return (
<div className="container flex justify-center items-center m-2">
<canvas ref={canvasRef} style={{ border: "2px solid black" }}></canvas>
</div>

);
}

export default SpriteGame;
9 changes: 7 additions & 2 deletions App/src/routes/Routers.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ import Donate from "../components/Donate/Donate";
import Profile from "../pages/Profile";
import Adopt from "../components/Adopt/Adopt";
import Quiz from "../components/Quiz/Quiz";
import Game from "../components/Game/Crossword";
import Gamelist from "../components/Game/Gamelist";
import Crossword from "../components/Game/Crossword";
import Spritegame from "../components/Game/SpriteGame";
import Proposal from "../components/Proposal/Proposal";


Expand All @@ -40,7 +42,10 @@ function Routers() {
<Route path="/adopt" element={<Adopt />} />
<Route path="/quiz" element={<Quiz />} />
<Route path="/profile" element={<Profile />} />
<Route path="/game" element={<Game />} />
<Route path="/game" element={<Gamelist/>}>
<Route path="crossword" element={<Crossword/>} />
<Route path="spritegame" element={<Spritegame />} />
</Route>
<Route path="/proposal" element={<Proposal />} />
</Routes>

Expand Down
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading