mirror of
https://github.com/bubblecup-12/VogelSocialMedia.git
synced 2025-07-05 18:18:47 +00:00
flappy Bird
This commit is contained in:
parent
90a4a5fd59
commit
9f9a21818a
13 changed files with 397 additions and 67 deletions
|
@ -5,7 +5,7 @@ import { Request, Response, NextFunction } from "express";
|
|||
// Configure multer to store files in memory
|
||||
const multerInstance = multer({
|
||||
storage: multer.memoryStorage(),
|
||||
limits: { fileSize: 5 * 1024 * 1024 }, // Limit file size to 5 MB
|
||||
limits: { fileSize: 30 * 1024 * 1024 }, // Limit file size to 30 MB
|
||||
});
|
||||
|
||||
export const upload = (req: Request, res: Response, next: NextFunction) => {
|
||||
|
|
|
@ -4,7 +4,7 @@ import { StatusCodes } from "http-status-codes";
|
|||
|
||||
const multerInstance = multer({
|
||||
storage: multer.memoryStorage(),
|
||||
limits: { fileSize: 5 * 1024 * 1024 }, // 5 MB
|
||||
limits: { fileSize: 30 * 1024 * 1024 }, // 30 MB
|
||||
});
|
||||
|
||||
export const upload = (req: Request, res: Response, next: NextFunction) => {
|
||||
|
|
|
@ -11,7 +11,7 @@ services:
|
|||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD", "pg_isready", "-U", "postgres"]
|
||||
test: ["CMD", "pg_isready", "-U", "${DB_USER}"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 5
|
||||
|
|
BIN
code/frontend/public/assets/images/flapp.png
Normal file
BIN
code/frontend/public/assets/images/flapp.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 128 KiB |
BIN
code/frontend/public/assets/images/flipp.png
Normal file
BIN
code/frontend/public/assets/images/flipp.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 119 KiB |
BIN
code/frontend/public/assets/images/logoWithoutStick.png
Normal file
BIN
code/frontend/public/assets/images/logoWithoutStick.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 140 KiB |
|
@ -1,6 +1,6 @@
|
|||
.App {
|
||||
text-align: center;
|
||||
min-height: 100vh;
|
||||
min-height: calc(100vh - var(--header-height));
|
||||
}
|
||||
|
||||
.App-logo {
|
||||
|
|
|
@ -8,32 +8,31 @@ import Header from "./components/Header";
|
|||
import Profile from "./pages/Profile";
|
||||
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
|
||||
import { Auth } from "./api/Auth";
|
||||
|
||||
|
||||
|
||||
import { NotFound } from "./pages/404Page/NotFoundPage";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<Auth>
|
||||
<Router>
|
||||
<div className="App">
|
||||
<Header />
|
||||
<Routes>
|
||||
<Route
|
||||
path="/login"
|
||||
element={<LoginAndSignUpPage signupProp={false} />}
|
||||
></Route>
|
||||
<Route
|
||||
path="/register"
|
||||
element={<LoginAndSignUpPage signupProp={true} />}
|
||||
></Route>
|
||||
<Route path="/profile" element={<Profile />}></Route>
|
||||
</Routes>
|
||||
<Footer />
|
||||
</div>
|
||||
</Router>
|
||||
</Auth>
|
||||
);
|
||||
return (
|
||||
<Auth>
|
||||
<Router>
|
||||
<Header />
|
||||
<div className="App">
|
||||
<Routes>
|
||||
<Route path="*" element={<NotFound />} />
|
||||
<Route
|
||||
path="/login"
|
||||
element={<LoginAndSignUpPage signupProp={false} />}
|
||||
></Route>
|
||||
<Route
|
||||
path="/register"
|
||||
element={<LoginAndSignUpPage signupProp={true} />}
|
||||
></Route>
|
||||
<Route path="/profile" element={<Profile />}></Route>
|
||||
</Routes>
|
||||
</div>
|
||||
<Footer />
|
||||
</Router>
|
||||
</Auth>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import axios from "axios";
|
||||
import { refreshToken } from "./refreshToken";
|
||||
|
||||
const excludedUrls: string[] = ["/user/login", "/user/regiser"];
|
||||
const excludedUrls: string[] = ["/user/login", "/user/register"];
|
||||
|
||||
const api = axios.create({
|
||||
baseURL: "http://localhost:3001/api",
|
||||
|
|
|
@ -1,59 +1,59 @@
|
|||
.base-header {
|
||||
z-index: 10;
|
||||
width: 100vw;
|
||||
display: flex;
|
||||
height: var(--header-height);
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
left: 0;
|
||||
border-radius: 0rem !important;
|
||||
z-index: 10;
|
||||
width: 100vw;
|
||||
display: flex;
|
||||
height: var(--header-height);
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
left: 0;
|
||||
border-radius: 0rem !important;
|
||||
}
|
||||
@media only screen and (min-width: 768px) {
|
||||
.base-header {
|
||||
height: var(--header-height);
|
||||
}
|
||||
.base-header {
|
||||
height: var(--header-height);
|
||||
}
|
||||
}
|
||||
|
||||
.header-title {
|
||||
color: var(--Rotkehlchen-orange-default);
|
||||
color: var(--Rotkehlchen-orange-default);
|
||||
}
|
||||
.header-icon-feather {
|
||||
height: 35px;
|
||||
height: 35px;
|
||||
}
|
||||
.header-icon-menu {
|
||||
cursor: pointer;
|
||||
height: 45px;
|
||||
cursor: pointer;
|
||||
height: 45px;
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
margin: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
@media only screen and (min-width: 768px) {
|
||||
.header-icon {
|
||||
margin: 40px;
|
||||
}
|
||||
.header-icon {
|
||||
margin: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.drawer-list {
|
||||
background-color: var(--dark-blue);
|
||||
min-height: 100vh;
|
||||
min-width: 13rem;
|
||||
background-color: var(--dark-blue);
|
||||
min-height: 100vh;
|
||||
min-width: 13rem;
|
||||
}
|
||||
|
||||
.drawer-list-item {
|
||||
color: white;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.drawer-list-item-button {
|
||||
height: 10vh;
|
||||
height: 10vh;
|
||||
}
|
||||
.drawer-list-item-button:hover {
|
||||
background-color: var(--dark-blue-hover);
|
||||
background-color: var(--dark-blue-hover);
|
||||
}
|
||||
|
|
243
code/frontend/src/pages/404Page/NotFoundPage.tsx
Normal file
243
code/frontend/src/pages/404Page/NotFoundPage.tsx
Normal file
|
@ -0,0 +1,243 @@
|
|||
import React, { useEffect, useRef, useState } from "react";
|
||||
import "./notFound.css";
|
||||
import ButtonPrimary from "../../components/ButtonRotkehlchen";
|
||||
|
||||
type Block = {
|
||||
x: number;
|
||||
height: number;
|
||||
passed: boolean;
|
||||
};
|
||||
|
||||
const GAP = 300;
|
||||
const BLOCK_WIDTH = 60;
|
||||
const GRAVITY = 0.2;
|
||||
const FLAP_VELOCITY = -8;
|
||||
const BIRD_SIZE = 25;
|
||||
const BLOCK_SPAWN_DISTANCE = 400; // px
|
||||
|
||||
export const NotFound = () => {
|
||||
const screenWidth = window.innerWidth;
|
||||
const screenHeight = window.innerHeight;
|
||||
|
||||
const targetRef = useRef<HTMLSpanElement>(null);
|
||||
const gameRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const [score, setScore] = useState(0);
|
||||
const [gameOver, setGameOver] = useState(false);
|
||||
const [hasStarted, setHasStarted] = useState(false);
|
||||
const [textPos, setTextPos] = useState<number>(screenWidth / 2);
|
||||
const [rotation, setRotation] = useState(0);
|
||||
const [renderBlocks, setRenderBlocks] = useState<Block[]>([]);
|
||||
const [birdPos, setBirdPos] = useState({ x: 0, y: 0 });
|
||||
|
||||
const distanceSinceLastBlock = useRef(0);
|
||||
const speedRef = useRef(2);
|
||||
const birdPosRef = useRef({ x: 0, y: 0 });
|
||||
const velocityRef = useRef(0);
|
||||
const blocksRef = useRef<Block[]>([]);
|
||||
const scoreRef = useRef(0);
|
||||
const hasStartedRef = useRef(hasStarted);
|
||||
const gameOverRef = useRef(gameOver);
|
||||
const textPosRef = useRef(textPos);
|
||||
|
||||
useEffect(() => {
|
||||
if (targetRef.current && gameRef.current) {
|
||||
const rect = targetRef.current.getBoundingClientRect();
|
||||
const gameBox = gameRef.current.getBoundingClientRect();
|
||||
const yPos = rect.top - gameBox.top;
|
||||
const x = rect.left + rect.width / 2;
|
||||
const y = yPos + 15;
|
||||
birdPosRef.current = { x, y };
|
||||
setBirdPos({ x, y });
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.code === "Space") {
|
||||
e.preventDefault();
|
||||
if (!gameOverRef.current) {
|
||||
setHasStarted(true);
|
||||
hasStartedRef.current = true;
|
||||
velocityRef.current = FLAP_VELOCITY;
|
||||
}
|
||||
} else if (e.code === "Enter") {
|
||||
e.preventDefault();
|
||||
if (gameOverRef.current) {
|
||||
window.location.reload();
|
||||
}
|
||||
} else if (e.code === "KeyR") {
|
||||
window.location.reload();
|
||||
}
|
||||
};
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
return () => window.removeEventListener("keydown", handleKeyDown);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
let lastBlockTime = Date.now();
|
||||
let animationId: number;
|
||||
|
||||
function loop() {
|
||||
if (!hasStartedRef.current || gameOverRef.current) {
|
||||
setRenderBlocks([...blocksRef.current]);
|
||||
setBirdPos({ ...birdPosRef.current });
|
||||
return;
|
||||
}
|
||||
|
||||
velocityRef.current += GRAVITY;
|
||||
birdPosRef.current.y += velocityRef.current;
|
||||
setRotation(Math.max(Math.min(velocityRef.current * 2, 90), -15));
|
||||
|
||||
textPosRef.current -= speedRef.current;
|
||||
setTextPos(textPosRef.current);
|
||||
|
||||
distanceSinceLastBlock.current += speedRef.current;
|
||||
|
||||
if (distanceSinceLastBlock.current >= BLOCK_SPAWN_DISTANCE) {
|
||||
const blockHeight = Math.random() * (screenHeight - GAP - 100) + 50;
|
||||
blocksRef.current.push({
|
||||
x: screenWidth,
|
||||
height: blockHeight,
|
||||
passed: false,
|
||||
});
|
||||
distanceSinceLastBlock.current = 0;
|
||||
}
|
||||
|
||||
blocksRef.current = blocksRef.current
|
||||
.map((block) => {
|
||||
const newX = block.x - speedRef.current;
|
||||
let passed = block.passed;
|
||||
|
||||
if (
|
||||
newX <= birdPosRef.current.x + BIRD_SIZE &&
|
||||
newX + BLOCK_WIDTH >= birdPosRef.current.x - BIRD_SIZE
|
||||
) {
|
||||
if (
|
||||
birdPosRef.current.y + BIRD_SIZE >= block.height + GAP ||
|
||||
birdPosRef.current.y - BIRD_SIZE <= block.height
|
||||
) {
|
||||
setGameOver(true);
|
||||
gameOverRef.current = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
!passed &&
|
||||
newX + BLOCK_WIDTH < birdPosRef.current.x - BIRD_SIZE
|
||||
) {
|
||||
scoreRef.current += 1;
|
||||
speedRef.current += 0.1;
|
||||
setScore(scoreRef.current);
|
||||
passed = true;
|
||||
}
|
||||
|
||||
return { ...block, x: newX, passed };
|
||||
})
|
||||
.filter((block) => block.x + BLOCK_WIDTH > 0);
|
||||
|
||||
if (
|
||||
birdPosRef.current.y >= screenHeight - BIRD_SIZE ||
|
||||
birdPosRef.current.y <= 0
|
||||
) {
|
||||
setGameOver(true);
|
||||
gameOverRef.current = true;
|
||||
}
|
||||
|
||||
setRenderBlocks([...blocksRef.current]);
|
||||
setBirdPos({ ...birdPosRef.current });
|
||||
|
||||
if (!gameOverRef.current) {
|
||||
animationId = requestAnimationFrame(loop);
|
||||
}
|
||||
}
|
||||
|
||||
if (hasStarted) {
|
||||
hasStartedRef.current = true;
|
||||
gameOverRef.current = false;
|
||||
animationId = requestAnimationFrame(loop);
|
||||
}
|
||||
|
||||
return () => cancelAnimationFrame(animationId);
|
||||
}, [hasStarted]);
|
||||
|
||||
const handleGameClick = () => {
|
||||
if (gameOver) return;
|
||||
if (!hasStarted) {
|
||||
setHasStarted(true);
|
||||
hasStartedRef.current = true;
|
||||
velocityRef.current = FLAP_VELOCITY;
|
||||
} else {
|
||||
velocityRef.current = FLAP_VELOCITY;
|
||||
}
|
||||
};
|
||||
|
||||
const birdSprite =
|
||||
velocityRef.current === 0
|
||||
? "/assets/images/logoWithoutStick.png"
|
||||
: velocityRef.current > 0
|
||||
? "/assets/images/flipp.png"
|
||||
: "/assets/images/flapp.png";
|
||||
|
||||
return (
|
||||
<div className="game-container" ref={gameRef} onClick={handleGameClick}>
|
||||
<img
|
||||
src={birdSprite}
|
||||
className="bird"
|
||||
style={{
|
||||
top: birdPos.y,
|
||||
left: birdPos.x,
|
||||
transform: `translate(-50%, -50%) rotate(${rotation}deg)`,
|
||||
}}
|
||||
alt="bird"
|
||||
/>
|
||||
<div
|
||||
className="text"
|
||||
style={{
|
||||
left: textPos,
|
||||
}}
|
||||
>
|
||||
<h1 key={"404"}>
|
||||
4<span ref={targetRef}>0</span>4 Not Found
|
||||
</h1>
|
||||
</div>
|
||||
{hasStarted && !gameOver && (
|
||||
<div className="points body-l">Score: {score}</div>
|
||||
)}
|
||||
{renderBlocks.map((block, index) => (
|
||||
<React.Fragment key={index}>
|
||||
<div
|
||||
className="tree-trunk top"
|
||||
style={{
|
||||
top: 0,
|
||||
left: block.x,
|
||||
width: BLOCK_WIDTH,
|
||||
height: block.height,
|
||||
}}
|
||||
></div>
|
||||
<div
|
||||
className="tree-trunk bottom"
|
||||
style={{
|
||||
top: block.height + GAP,
|
||||
left: block.x,
|
||||
width: BLOCK_WIDTH,
|
||||
height: screenHeight - block.height - GAP,
|
||||
}}
|
||||
></div>
|
||||
</React.Fragment>
|
||||
))}
|
||||
{gameOver && (
|
||||
<div className="gameOver small-title">
|
||||
<h1>You have killed the bird</h1>
|
||||
<p>Your Score is {score}</p>
|
||||
<ButtonPrimary
|
||||
style="primary"
|
||||
label="restart"
|
||||
type="reset"
|
||||
onClick={() => window.location.reload()}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
81
code/frontend/src/pages/404Page/notFound.css
Normal file
81
code/frontend/src/pages/404Page/notFound.css
Normal file
|
@ -0,0 +1,81 @@
|
|||
.text {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin: 0;
|
||||
transform: translate(-50%, -50%);
|
||||
color: white;
|
||||
font-size: 48px;
|
||||
font-weight: bold;
|
||||
z-index: 5;
|
||||
}
|
||||
.game-container {
|
||||
width: 100vw;
|
||||
height: calc(100vh - var(--header-height));
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.bird {
|
||||
position: relative;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(0, -10px);
|
||||
color: black;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.points {
|
||||
position: fixed;
|
||||
top: 70px;
|
||||
left: 10px;
|
||||
background-color: var(--Rotkehlchen-gray);
|
||||
padding: 5px 10px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 0 8px rgba(0, 0, 0, 0.2);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
z-index: 50;
|
||||
}
|
||||
.gameOver {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: rgba(13, 10, 56, 0.71);
|
||||
color: white;
|
||||
padding: 30px;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 0 20px rgba(0, 0, 0, 0.3);
|
||||
text-align: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
.tree-trunk {
|
||||
width: 60px;
|
||||
position: absolute;
|
||||
background: repeating-linear-gradient(
|
||||
45deg,
|
||||
#8b5a2b,
|
||||
#8b5a2b 10px,
|
||||
#a0522d 10px,
|
||||
#a0522d 20px
|
||||
);
|
||||
|
||||
box-shadow: inset 0 0 10px #5c4033;
|
||||
}
|
||||
.bottom {
|
||||
border-start-start-radius: 20px;
|
||||
border-start-end-radius: 20px;
|
||||
}
|
||||
.top {
|
||||
border-end-start-radius: 20px;
|
||||
border-end-end-radius: 20px;
|
||||
}
|
|
@ -44,12 +44,12 @@ function LoginAndSignUpPage({ signupProp }: { signupProp: boolean }) {
|
|||
setErrorMessages(undefined);
|
||||
try {
|
||||
const response = signup
|
||||
? await api.post("http://localhost:3001/api/user/register", {
|
||||
? await api.post("/user/register", {
|
||||
email: formData.email,
|
||||
username: formData.username,
|
||||
password: formData.password,
|
||||
})
|
||||
: await api.post("http://localhost:3001/api/user/login", {
|
||||
: await api.post("/user/login", {
|
||||
username: formData.username,
|
||||
password: formData.password,
|
||||
});
|
||||
|
@ -65,7 +65,14 @@ function LoginAndSignUpPage({ signupProp }: { signupProp: boolean }) {
|
|||
await setUserState();
|
||||
navigate(returnTo, { replace: true });
|
||||
} catch (err: any) {
|
||||
setErrorMessages(err.response.data);
|
||||
if (err.response?.data) {
|
||||
setErrorMessages(err.response.data);
|
||||
} else {
|
||||
setErrorMessages({
|
||||
error: "Error",
|
||||
details: [{ message: err.message || "Something went wrong." }],
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -142,10 +149,10 @@ function LoginAndSignUpPage({ signupProp }: { signupProp: boolean }) {
|
|||
minLength={signup ? 8 : undefined}
|
||||
/>
|
||||
</div>
|
||||
{errorMessages && (
|
||||
{errorMessages && errorMessages?.details?.length > 0 && (
|
||||
<div className="error-messages">
|
||||
{errorMessages.details.map((detial, index) => (
|
||||
<p key={index}>{detial.message}</p>
|
||||
{errorMessages.details.map((detail, index) => (
|
||||
<p key={index}>{detail.message}</p>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue