diff --git a/App/src/assets/audio/collect-sound.mp3 b/App/src/assets/audio/collect-sound.mp3
new file mode 100644
index 0000000..eaa0ef2
Binary files /dev/null and b/App/src/assets/audio/collect-sound.mp3 differ
diff --git a/App/src/assets/audio/collision-sound.mp3 b/App/src/assets/audio/collision-sound.mp3
new file mode 100644
index 0000000..d1c5532
Binary files /dev/null and b/App/src/assets/audio/collision-sound.mp3 differ
diff --git a/App/src/assets/audio/gameover-sound.mp3 b/App/src/assets/audio/gameover-sound.mp3
new file mode 100644
index 0000000..f5a8309
Binary files /dev/null and b/App/src/assets/audio/gameover-sound.mp3 differ
diff --git a/App/src/assets/audio/jump-sound.mp3 b/App/src/assets/audio/jump-sound.mp3
new file mode 100644
index 0000000..017dc5f
Binary files /dev/null and b/App/src/assets/audio/jump-sound.mp3 differ
diff --git a/App/src/assets/images/banana.png b/App/src/assets/images/banana.png
new file mode 100644
index 0000000..293ad89
Binary files /dev/null and b/App/src/assets/images/banana.png differ
diff --git a/App/src/assets/images/forest.jpg b/App/src/assets/images/forest.jpg
new file mode 100644
index 0000000..669a50c
Binary files /dev/null and b/App/src/assets/images/forest.jpg differ
diff --git a/App/src/assets/images/monkey.png b/App/src/assets/images/monkey.png
new file mode 100644
index 0000000..c8c0d60
Binary files /dev/null and b/App/src/assets/images/monkey.png differ
diff --git a/App/src/assets/images/mushroom.png b/App/src/assets/images/mushroom.png
new file mode 100644
index 0000000..a823b55
Binary files /dev/null and b/App/src/assets/images/mushroom.png differ
diff --git a/App/src/components/Game/Gamelist.jsx b/App/src/components/Game/Gamelist.jsx
new file mode 100644
index 0000000..fdbc7f4
--- /dev/null
+++ b/App/src/components/Game/Gamelist.jsx
@@ -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 (
+
+ {isBaseRoute ? (
+
+
Choose a Game
+
+
+
+ Crossword
+
+
+ Sprite Game
+
+
🐘 🐊
+
+ ) : (
+
+
+ ⬅ Back
+
+
+ )}
+
+
+
+ );
+}
+
+export default Gamelist;
diff --git a/App/src/components/Game/SpriteGame.jsx b/App/src/components/Game/SpriteGame.jsx
new file mode 100644
index 0000000..c17579e
--- /dev/null
+++ b/App/src/components/Game/SpriteGame.jsx
@@ -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 (
+
+
+
+
+ );
+}
+
+export default SpriteGame;
diff --git a/App/src/routes/Routers.jsx b/App/src/routes/Routers.jsx
index b3d7382..11683cf 100644
--- a/App/src/routes/Routers.jsx
+++ b/App/src/routes/Routers.jsx
@@ -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";
@@ -40,7 +42,10 @@ function Routers() {
} />
} />
} />
- } />
+ }>
+ } />
+ } />
+
} />
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..bd81b2b
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,6 @@
+{
+ "name": "WildQuest",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {}
+}