Initial commit

This commit is contained in:
2025-03-13 16:05:09 +01:00
commit 5950d5ae9d
44 changed files with 5505 additions and 0 deletions

2
.gitattributes vendored Normal file
View File

@@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto

130
.gitignore vendored Normal file
View File

@@ -0,0 +1,130 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

2
README.md Normal file
View File

@@ -0,0 +1,2 @@
# doubleSnake
Snake aber zu zweit!

32
Speicher.txt Normal file
View File

@@ -0,0 +1,32 @@
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
<script src="/socket.io/socket.io.js"></script>
/**@param {} x*/
/**@typeof {} x*/
https://codedamn.com/news/nodejs/use-json-web-token-jwt-in-nodejs
<div class="screen">
<nav>
<h1>Spiel-Code:</h1>
<h1 class="message">1A3B5C</h1>
</nav>
<div id="playerList">
<div id="player1" class="player">
<h1>Spieler 1</h1>
<h2>test</h2>
</div>
<div id="player2" class="player">
<h1>Spieler 2</h1>
<h2>test</h2>
</div>
</div>
</div>

2311
backend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

26
backend/package.json Normal file
View File

@@ -0,0 +1,26 @@
{
"name": "doublesnake_backend",
"version": "1.0.0",
"description": "Eine Snake Game welches man zu zweit Spielen kann!",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "node ./src/server.js",
"nm": "nodemon ./src/server.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"bcrypt": "^5.1.1",
"body-parser": "^1.20.3",
"dotenv": "^16.4.7",
"express": "^4.21.2",
"express-session": "^1.18.1",
"mysql2": "^3.12.0",
"socket.io": "^4.8.1",
"socket.io-client": "^4.8.1"
},
"devDependencies": {
"nodemon": "^3.1.9"
}
}

View File

@@ -0,0 +1,23 @@
const mySql = require("mysql2");
const UserManager = require("./UserManager/UserManager");
class DataBaseManager {
constructor(host, database) {
this.connection = mySql.createConnection({
host: host,
user: "root",
password:"",
database: database
});
this.connection.connect((err) => {
if (err) {
throw err;
}
});
this.usermanager = new UserManager(this.connection);
}
}
module.exports = DataBaseManager;

View File

@@ -0,0 +1,27 @@
const bcrypt = require("bcrypt");
class User {
constructor(userJSON) {
this.id = userJSON.id;
this.username = userJSON.username;
this.email = userJSON.email;
this.password = userJSON.password;
this.fullName = userJSON.fullName;
this.createdAt = userJSON.createdAt;
}
async doesPassMatch(passwordPlain) {
return await bcrypt.compare(passwordPlain, this.password);
}
toUserJSON(){
return {
id: this.id,
username: this.username,
email: this.email,
fullName: this.fullName,
}
}
}
module.exports = User;

View File

@@ -0,0 +1,102 @@
const mySql = require("mysql2")
const bcrypt = require("bcrypt")
const User = require("./User")
class UserManager {
/**@param {mySql.Connection} connection*/
constructor(connection) {
this.connection = connection;
}
// Gebe zumindest {username: "x", password: "x"} mit!
// 0: Fehler (User Existiert schon oder Fehler) | 1: Funktioniert (User Erstellt!)
async createUser(userJson) {
if (!userJson.username || !userJson.password) return 0;
const user = await this.getUser({username: userJson.username});
if(user) return 0;
try {
const passHash = await bcrypt.hash(userJson.password, 10);
this.connection.query(`INSERT INTO users (username, email, password, fullName) VALUES ("${userJson.username}",` +
` ${userJson.email ? `"${userJson.email}"` : "NULL"}, "${passHash}", ${userJson.fullName ? `"${userJson.fullName}"` : "NULL"})`);
return 1;
} catch (error) {
console.log(error);
}
}
// Gebe nichts mit um alle user zu bekommen oder {id: 1} oder {username: "x"} um darauf einen bestimmten zu bekommen;
async getUser(userData) {
let selectionString = "SELECT * FROM users";
if(userData?.id){
selectionString = `SELECT * FROM users WHERE id=${userData.id}`;
}
else if(userData?.username){
selectionString = `SELECT * FROM users WHERE username="${userData.username}"`;
}
try {
const response = await this.connection.promise().query(selectionString);
if(response[0].length === 0){
return null;
}
else if(response[0].length === 1){
const x = response[0][0];
return new User(x);
}
else{
let userArray = [];
response[0].forEach(x => {
const user = new User(x);
userArray.push(user);
});
return userArray;
}
} catch (error) {
console.log(error);
}
}
// 0: User Existiert nicht | 1: Funktioniert
// id = 1 | newUserData = {email: "x", password: "xx", "fullName": "xxx"} <-- BRaucht alle Argeumente sonst null, username nicht änderbar
async updateUser(id, newUserData){
const user = await this.getUser({id});
if(!user) return 0;
if(!newUserData.password) return 0;
try {
this.connection.query(
`UPDATE users SET
username="${newUserData.username}",
email=${newUserData.email ? `"${newUserData.email}"` : "NULL"},
fullName=${newUserData.fullName ? `"${newUserData.fullName}"` : "NULL"},
password="${newUserData.password}"
WHERE id = ${id}`
)
return 1;
} catch (error) {
console.log(error)
}
}
// 0: Es gibt den User nicht | 1: Funktioniert
async delteUser(id){
const user = await this.getUser({id});
if(!user) return 0;
try {
this.connection.query(`DELETE FROM users where id=${id}`);
return 1;
} catch (error) {
console.log(error)
}
}
}
module.exports = UserManager;

View File

@@ -0,0 +1,68 @@
const express = require("express");
const sessions = require("express-session");
const bp = require("body-parser");
const path = require("path");
const DataBaseManager = require("../Database/DataBaseManager");
const AccountRoute = require("./Routes/AccountRoute");
const DashboardRoute = require("./Routes/DashboardRoute");
class ExpressManager{
/**@param {DataBaseManager} dbManager*/
constructor(dbManager){
this.db = dbManager;
this.app = express();
this.sessionMiddleware = sessions({
secret: process.env.SESSION_KEY,
saveUninitialized: false,
resave: false,
cookie: {maxAge: 24 * 60 * 60 * 1000}
});
this.app.use((req, res, next) => { this.logger(req, res, next) });
this.app.use(this.sessionMiddleware);
this.app.use((req, res, next) => { this.needAuth(req, res, next) });
this.app.use(bp.urlencoded({extended: false}));
this.app.use(express.json());
this.app.use(bp.json());
this.app.use(express.static(path.join(__dirname, "./../../../frontend")));
// Routen wo man für Angemeldet sein muss
this.authRoutes = [
"/api/dashboard",
"/dashboard"
];
// Routen Einbinden
this.app.use("/api/account/", new AccountRoute(this.db).router);
this.app.use("/api/dashboard/", new DashboardRoute(this.db).router);
}
/**@param {express.Request} req @param {express.Response} res @param {express.NextFunction} next*/
logger(req, res, next){
const date = new Date();
console.log(`${date.toTimeString().slice(0, 8)} | ${req.method} | ${req.url}`);
next();
}
/**@param {express.Request} req @param {express.Response} res @param {express.NextFunction} next*/
needAuth(req, res, next){
let isProtectedRoute = false;
this.authRoutes.forEach(route => {
if(req.url.startsWith(route)) isProtectedRoute = true;
});
// Geht zum Login wenn User versucht Routen aufzurufen wofür man angemeldet sein muss
if(isProtectedRoute && !req.session.user?.isSet) return res.redirect("/login");
// Geht zum Dashboard wenn der Nutzer versucht sich zu registrieren oder einzuloggen wenn er angemeldet ist
if(req.session.user?.isSet && (req.url.startsWith("/login") || req.url.startsWith("/register"))){
return res.redirect("/dashboard");
}
next();
}
}
module.exports = ExpressManager;

View File

@@ -0,0 +1,84 @@
const express = require("express");
const User = require("../../Database/UserManager/User");
const DataBaseManager = require("../../Database/DataBaseManager");
const bcrypt = require("bcrypt");
class AccountRoute {
/**@param {DataBaseManager} dbManager*/
constructor(dbManager) {
this.router = express.Router();
this.db = dbManager;
this.router.post("/register", async (req, res) => await this.register(req, res));
this.router.post("/login", async (req, res) => await this.login(req, res));
this.router.post("/update", async (req, res) => await this.update(req, res));
this.router.get("/logout", async (req, res) => await this.logout(req, res));
}
/**@param {express.Request} req @param {express.Response}*/
async register(req, res){
const body = req.body;
if(!body.username || !body.password) return res.redirect("/register?error=1");
const user = new User(body);
const result = await this.db.usermanager.createUser(user);
if(result !== 1) return res.redirect("/register?error=2");
res.redirect("/login");
}
/**@param {express.Request} req @param {express.Response}*/
async login(req, res){
const body = req.body;
if(!body.username || !body.password) return res.redirect("/login?error=1")
const user = await this.db.usermanager.getUser({username: body.username});
if(!user) return res.redirect("/login?error=2")
const passwordMatch = await user.doesPassMatch(body.password);
if(!passwordMatch) return res.redirect("/login?error=2")
req.session.user = {
id: user.id,
username: user.username,
isSet: true
}
res.redirect("/dashboard")
}
/**@param {express.Request} req @param {express.Response}*/
async update(req, res){
const user = await this.db.usermanager.getUser({id: req.session.user.id});
const body = req.body;
if(user.username !== body.username){
const checkUser = await this.db.usermanager.getUser({username: body.username});
if(checkUser) return res.redirect("/dashboard/account/?error=1");
user.username = body.username;
}
user.email = body.email;
user.fullName = body.fullName;
if(body.password !== ""){
const passHash = await bcrypt.hash(body.password, 10);
user.password = passHash;
}
await this.db.usermanager.updateUser(user.id, user);
res.redirect("/dashboard/account");
}
/**@param {express.Request} req @param {express.Response}*/
async logout(req, res){
req.session.destroy();
res.redirect("/");
}
}
module.exports = AccountRoute;

View File

@@ -0,0 +1,23 @@
const express = require("express");
const User = require("../../Database/UserManager/User");
const DataBaseManager = require("../../Database/DataBaseManager");
class DashboardRoute{
/**@param {DataBaseManager} dbManager*/
constructor(dbManager){
this.router = express.Router();
this.db = dbManager;
this.router.get("/", async (req, res) => await this.userInfo(req, res))
}
/**@param {express.Request} req @param {express.Response}*/
async userInfo(req, res){
const user = await this.db.usermanager.getUser({id: req.session.user.id});
res.json(user.toUserJSON());
}
}
module.exports = DashboardRoute;

View File

@@ -0,0 +1,50 @@
const socketIO = require("socket.io");
const SocketUser = require("./SocketUser");
class Lobby {
/** @param {socketIO.Server} io @param {Map<number, SocketUser>} users*/
constructor(io, code) {
this.io = io;
this.code = code;
/** @type {Array<SocketUser>} */
this.users = [];
}
addUser(user) {
if (this.users.length >= 2) return 1;
this.users.push(user);
this.sendLobbyUserUpdate();
return 0;
}
removeUser(user) {
const index = this.users.findIndex(u => u.id === user.id);
if (index === -1) return 1;
this.users.splice(index, 1);
this.sendLobbyUserUpdate();
if (this.users.length === 0) return 2;
return 0;
}
sendLobbyUserUpdate() {
const data = {
code: this.code,
users: this.users.map(user => ({
id: user.id,
username: user.username
}))
}
this.io.to(`lobby-${this.code}`).emit("lobbyUserUpdate", data);
}
}
module.exports = Lobby;

View File

@@ -0,0 +1,12 @@
const socketIO = require("socket.io");
class SocketUser {
/** @param {number} id @param {string} username @param {socketIO.Socket} socket */
constructor(id, username, socket) {
this.id = id;
this.username = username;
this.socket = socket;
}
}
module.exports = SocketUser;

View File

@@ -0,0 +1,60 @@
const socketIO = require("socket.io");
const LobbyManager = require("./LobbyManager");
const SocketUser = require("./Classes/SocketUser");
class ClientHandler {
/** @param {socketIO.Socket} socket @param {LobbyManager} lobbyManager */
constructor(socket, lobbyManager) {
this.socket = socket;
this.lobbyManager = lobbyManager;
this.user = new SocketUser(
this.socket.request.session.user.id,
this.socket.request.session.user.username,
this.socket
);
this.currentLobbyCode = null;
this.socket.on("createLobby", () => { this.createLobby() })
this.socket.on("joinLobby", (code) => { this.joinLobby(code) })
this.socket.on("refreshLobby", () => { this.refreshLobby() })
this.socket.on("disconnect", () => { this.disconnect() })
}
createLobby(){
if(this.currentLobbyCode) return;
const lobbyCode = this.lobbyManager.createLobby(this.user);
this.currentLobbyCode = lobbyCode;
this.socket.join(`lobby-${lobbyCode}`);
this.socket.emit("lobbyCreated", lobbyCode);
this.refreshLobby();
}
joinLobby(code){
if(this.currentLobbyCode) return;
const response = this.lobbyManager.joinLobby(code, this.user);
if(response === 1) return this.socket.emit("lobbyJoinError", "Diese Lobby existiert nicht");
if(response === 2) return this.socket.emit("lobbyJoinError", "Die Lobby ist schon voll");
this.currentLobbyCode = response;
this.socket.join(`lobby-${response}`)
this.socket.emit("lobbyJoined", response);
this.refreshLobby();
}
refreshLobby(){
this.lobbyManager.refreshLobby(this.currentLobbyCode);
}
disconnect(){
this.lobbyManager.removeFromLobby(this.currentLobbyCode, this.user);
}
}
module.exports = ClientHandler;

View File

@@ -0,0 +1,65 @@
const socketIO = require("socket.io");
const Lobby = require("./Classes/Lobby");
const SocketUser = require("./Classes/SocketUser");
class LobbyManager {
/** @param {socketIO.Server} io */
constructor(io) {
this.io = io;
/** @type {Map<string, Lobby>}*/
this.lobbys = new Map();
// Zeigt die Anzahl der Lobbys an | Für Testing
// setInterval(() => {
// console.log(this.lobbys.size);
// }, 1000);
}
/** @param {SocketUser} user */
createLobby(user){
const code = this.generateNonExistingCode();
const lobby = new Lobby(this.io, code);
lobby.addUser(user);
this.lobbys.set(code, lobby);
return code;
}
/** @param {string} code @param {SocketUser} user */
joinLobby(code, user){
if (!this.lobbys.has(code)) return 1;
const lobby = this.lobbys.get(code);
const response = lobby.addUser(user);
if(response === 1) return 2;
return lobby.code;
}
generateNonExistingCode() {
let code;
do {
code = Math.random().toString(36).substring(2, 8).toUpperCase();
} while (this.lobbys.has(code));
return code;
}
/** @param {SocketUser} user */
removeFromLobby(code, user){
if(!this.lobbys.has(code)) return;
const lobby = this.lobbys.get(code);
const reponse = lobby.removeUser(user);
if (reponse === 2) this.lobbys.delete(code);
}
refreshLobby(code){
if(!this.lobbys.has(code)) return;
const lobby = this.lobbys.get(code);
lobby.sendLobbyUserUpdate();
}
}
module.exports = LobbyManager;

View File

@@ -0,0 +1,69 @@
const socketIO = require("socket.io");
const http = require("http");
const DataBaseManager = require("../Database/DataBaseManager");
const ExpressManager = require("../Express/ExpressManager");
const LobbyManager = require("./LobbyManager/LobbyManager");
const ClientHandler = require("./LobbyManager/ClientHandler");
require("dotenv").config();
class SocketIOManager {
/** @param {DataBaseManager} dbManager @param {ExpressManager} expressManager */
constructor(dbManager, expressManager) {
this.db = dbManager;
this.express = expressManager;
this.server = http.createServer(this.express.app);
/** @type {import("socket.io").Server} */
this.io = socketIO(this.server);
// Dadurch bekommen wir Zugriff auf den Eingeloggten Nutzer
this.io.use((socket, next) => {
this.express.sessionMiddleware(socket.request, socket.request.res || {}, next);
});
this.io.use((socket, next) => {this.useAuth(socket, next)});
/*
Der LobbyManager kümmert sich um alle Lobbys
Der LobbyHandler kümmert sich um die einzelnen Anfragen der Clients
*/
this.lobbyManager = new LobbyManager(this.io);
this.io.on("connection", (socket) => { this.sendToRightManager(socket) });
// Startet express und socket.io
this.server.listen(process.env.PORT, () => {
console.log(`Der Server ist gestartet und unter http://localhost:${process.env.PORT} erreichbar!`);
});
}
/** @param {socketIO.Socket} socket @param {socketIO.ExtendedError} next */
useAuth(socket, next){
// User muss eingeloggt sein
if(!socket.request.session.user) return next(new Error("Nicht Eingeloggt!"));
// Der User darf nur eine verbindung gleichzeitig haben
const userId = socket.request.session.user.id;
const socketConnections = this.io.sockets.sockets;
socketConnections.forEach((socket) => {
if(socket.request.session.user.id === userId) return next(new Error("Irgendwer anders spielt schon!"));
});
next();
}
/** @param {socketIO.Socket} socket - The socket instance. */
sendToRightManager(socket){
socket.on("requestIdentification", () => {
socket.emit("identification", socket.request.session.user);
});
socket.on("hereForLobby", () => {
new ClientHandler(socket, this.lobbyManager);
});
}
}
module.exports = SocketIOManager;

7
backend/src/server.js Normal file
View File

@@ -0,0 +1,7 @@
const DataBaseManager = require("./Database/DataBaseManager");
const ExpressManager = require("./Express/ExpressManager");
const SocketIOManager = require("./SocketIO/SocketIOManager");
const databaseManager = new DataBaseManager("localhost", "doublesnake");
const expressManger = new ExpressManager(databaseManager);
const socketIOManager = new SocketIOManager(databaseManager, expressManger);

View File

@@ -0,0 +1,63 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Account Bearbeiten</title>
<link rel="stylesheet" href="../../style/generalStyle.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container menu">
<h1 class="title">Account</h1>
<h2>Ändere deine Benutzerdaten uns speichere sie ab!</h2>
<div class="buttonMenu container">
<form action="/api/account/update" method="post" class="container">
<h2 class="error" id="errorMsg"></h2>
<div class="splitContainerX">
<div>
<div class="formSection">
<p>Username:</p>
<input id="username" name="username" type="text" placeholder="Username">
</div>
<div class="formSection">
<p>Email:</p>
<input id="email" name="email" type="email" placeholder="E-mail">
</div>
</div>
<div>
<div class="formSection">
<p>Voller Name:</p>
<input id="fullName" name="fullName" type="text" placeholder="Name">
</div>
<div class="formSection">
<p>Neues Passwort:</p>
<input id="password" name="password" type="password" placeholder="Neues Password">
</div>
</div>
</div>
<input type="submit" value="Speichern">
</form>
<div class="container navButtons">
<button id="dashboardBtn">
<div class="buttonIcon">
<svg xmlns="http://www.w3.org/2000/svg" height="35px" viewBox="0 -960 960 960" width="35px"
fill="#ffffff">
<path d="M520-200 80-480l440-280-137 240h497v80H383l137 240Z" />
</svg>
</div>
<div class="buttonText">
Dashboard
</div>
</button>
</div>
</div>
</div>
</body>
<script src="../../scripts/ButtonManager.js"></script>
<script src="../../scripts/ErrorHandler.js"></script>
<script src="./index.js"></script>
</html>

View File

@@ -0,0 +1,20 @@
class AccountManager{
constructor(){
this.username = document.getElementById("username");
this.email = document.getElementById("email");
this.fullName = document.getElementById("fullName");
this.setUserData();
}
async setUserData(){
const respone = await fetch("/api/dashboard");
this.data = await respone.json();
this.username.value = this.data.username;
this.email.value = this.data.email;
this.fullName.value = this.data.fullName;
}
}
new AccountManager();

View File

@@ -0,0 +1,3 @@
#dashboardBtn{
background-color: orange;
}

View File

@@ -0,0 +1,83 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dashboard</title>
<link rel="stylesheet" href="../style/generalStyle.css">
<link rel="stylesheet" href="./style.css">
</head>
<body>
<div class="container menu">
<h1 class="title">Dashboard</h1>
<div class="container buttonMenu">
<h2 id="data" class="message"></h2>
<button id="lobbyBtn">
<div class="buttonIcon">
<svg xmlns="http://www.w3.org/2000/svg" height="35px" viewBox="0 -960 960 960" width="35px"
fill="#FFFFFF">
<path d="M640-200q-27 0-52.5-5T538-219q58-49 90-117t32-144q0-76-32-144t-90-117q24-9 49.5-14t52.5-5q117 0 198.5 81.5T920-480q0 117-81.5
198.5T640-200Zm-320 0q-117 0-198.5-81.5T40-480q0-117 81.5-198.5T320-760q27 0 52.5 5t49.5 14q-17 14-32 30.5T362-676q-10-2-20.5-3t-21.5-1q-83
0-141.5 58.5T120-480q0 83 58.5 141.5T320-280q11 0 21.5-1t20.5-3q13 18 28 34.5t32 30.5q-24 9-49.5 14t-52.5 5Zm160-50q-57-39-88.5-100T360-480q0-69
31.5-130T480-710q57 39 88.5 100T600-480q0 69-31.5 130T480-250Z" />
</svg>
</div>
<div class="buttonText">
Lobby
</div>
</button>
<button id="accountBtn">
<div class="buttonIcon">
<svg xmlns="http://www.w3.org/2000/svg" height="35px" viewBox="0 -960 960 960" width="35px"
fill="#FFFFFF">
<path d="M480-480q-66 0-113-47t-47-113q0-66 47-113t113-47q66 0 113 47t47 113q0 66-47 113t-113 47ZM160-160v-112q0-34
17.5-62.5T224-378q62-31 126-46.5T480-440q66 0 130 15.5T736-378q29 15 46.5 43.5T800-272v112H160Zm80-80h480v-32q0-11
-5.5-20T700-306q-54-27-109-40.5T480-360q-56 0-111 13.5T260-306q-9 5-14.5 14t-5.5 20v32Zm240-320q33 0 56.5-23.5T560
-640q0-33-23.5-56.5T480-720q-33 0-56.5 23.5T400-640q0 33 23.5 56.5T480-560Zm0-80Zm0 400Z" />
</svg>
</div>
<div class="buttonText">
Account
</div>
</button>
<div class="container navButtons">
<button id="homeBtn">
<div class="buttonIcon">
<svg xmlns="http://www.w3.org/2000/svg" height="35px" viewBox="0 -960 960 960" width="35px"
fill="#ffffff">
<path d="M520-200 80-480l440-280-137 240h497v80H383l137 240Z" />
</svg>
</div>
<div class="buttonText">
Startseite
</div>
</button>
<button id="logoutBtn">
<div class="buttonIcon">
<svg xmlns="http://www.w3.org/2000/svg" height="35px" viewBox="0 -960 960 960" width="35px"
fill="#FFFFFF">
<path d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h280v80H200v560h280v80H200Zm440-160-55-58
102-102H360v-80h327L585-622l55-58 200 200-200 200Z" />
</svg>
</div>
<div class="buttonText">
Ausloggen
</div>
</button>
</div>
</div>
</div>
</body>
<script src="../scripts/ButtonManager.js"></script>
<script src="./index.js"></script>
</html>

View File

@@ -0,0 +1,15 @@
class DataFetcher {
constructor() {
this.greetingText = document.getElementById("data");
this.init();
}
async init() {
const reponse = await fetch("/api/dashboard");
this.data = await reponse.json();
this.greetingText.innerText = `Hallo ${this.data.fullName ? this.data.fullName : this.data.username}, starte hier Double-Snake!`
}
}
new DataFetcher();

View File

@@ -0,0 +1,69 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Lobby</title>
<link rel="stylesheet" href="../../style/generalStyle.css">
<link rel="stylesheet" href="./style.css">
</head>
<body>
<div class="container menu">
<h1 class="title">Lobby</h1>
<main class="container buttonMenu">
<h2 class="error" id="errorMsg"></h2>
<div class="container navButtons" id="mainDiv">
<button id="createBtn">
<div class="buttonIcon">
<svg xmlns="http://www.w3.org/2000/svg" height="35px" viewBox="0 -960 960 960" width="35px"
fill="#FFFFFF">
<path
d="m272-440 208 120 208-120-168-97v137h-80v-137l-168 97Zm168-189v-17q-44-13-72-49.5T340-780q0-58 41-99t99-41q58
0 99 41t41 99q0 48-28 84.5T520-646v17l280 161q19 11 29.5 29.5T840-398v76q0 22-10.5 40.5T800-252L520-91q-19 11-40 11t-40
-11L160-252q-19-11-29.5-29.5T120-322v-76q0-22 10.5-40.5T160-468l280-161Zm0 378L200-389v67l280 162 280-162v-67L520-251q-19
11-40 11t-40-11Zm40-469q25 0 42.5-17.5T540-780q0-25-17.5-42.5T480-840q-25 0-42.5 17.5T420-780q0 25 17.5 42.5T480-720Zm0 560Z" />
</svg>
</div>
<div class="buttonText">
Code Erstellen
</div>
</button>
<button id="joinBtn">
<div class="buttonIcon">
<svg xmlns="http://www.w3.org/2000/svg" height="35px" viewBox="0 -960 960 960" width="35px"
fill="#FFFFFF">
<path
d="M220-520 80-600v-160l140-80 140 80v160l-140 80Zm0-92 60-34v-68l-60-34-60 34v68l60 34Zm440 123v-93l140 82v280L560-80
320-220v-280l140-82v93l-60 35v188l160 93 160-93v-188l-60-35Zm-140 89v-480h360l-80 120 80 120H600v240h-80Zm40 69ZM220-680Z" />
</svg>
</div>
<div class="buttonText">
Mit Code Beitreten
</div>
</button>
</div>
<div class="container">
<button id="leaveBtn">
<div class="buttonIcon">
<svg xmlns="http://www.w3.org/2000/svg" height="35px" viewBox="0 -960 960 960" width="35px"
fill="#FFFFFF">
<path
d="M200-120q-33 0-56.5-23.5T120-200v-160h80v160h560v-560H200v160h-80v-160q0-33 23.5-56.5T200-840h560q33 0
56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm220-160-56-58 102-102H120v-80h346L364-622l56-58 200 200-200 200Z" />
</svg>
</div>
<div class="buttonText">
Verlassen
</div>
</button>
</div>
</main>
</div>
</body>
<script src="https://cdn.socket.io/4.7.2/socket.io.min.js"></script>
<script src="./scripts/index.js" type="module"></script>
</html>

View File

@@ -0,0 +1,86 @@
class LobbyHandler {
/**@param {import("../../../../../backend/node_modules/socket.io-client".Socket} socket Autocompletions VSC*/
constructor(socket) {
this.socket = socket;
this.errorMsg = document.getElementById("errorMsg");
this.code = null;
this.playerList = [];
this.user = null;
this.socket.emit("requestIdentification");
this.socket.on("identification", (user) => { this.user = user });
// Für das Erstellen
this.socket.on("lobbyCreated", (code) => { this.joinedLobby(code) });
// Für das Beitreten
this.socket.on("lobbyJoinError", (msg) => { this.showJoinError(msg) });
this.socket.on("lobbyJoined", (code) => { this.joinedLobby(code) });
// Für das Aktualisieren
this.socket.on("lobbyUserUpdate", (data) => { this.lobbyUserUpdate(data) });
}
handleCreateClick() {
this.socket.emit("createLobby");
}
handleJoinClick(){
const code = prompt("Bitte gib den 6 Stelligen Code ein");
this.socket.emit("joinLobby", code);
}
showJoinError(msg){
this.errorMsg.innerText = msg;
this.errorMsg.style.display = "block";
}
joinedLobby(code) {
this.code = code;
this.updateUI();
console.log(`Lobby beigerteten mit Code: ${code}`);
}
updateUI() {
const lobbyContainer = document.getElementById("mainDiv");
let playerHTML = "";
this.playerList.forEach((player, index) => {
const amI = player.id === this.user.id ? " (Ich)" : "";
playerHTML += `
<div class="player" id="player${index + 1}">
<h1>Spieler ${index + 1}${amI}</h1>
<h1>${player.username}</h1>
</div>
`;
lobbyContainer.innerHTML = `
<div class="screen">
<nav>
<h1>Spiel-Code:</h1>
<h1 class="message">${this.code}</h1>
</nav>
<div id="playerList">
${playerHTML}
</div>
</div>
`;
});
this.errorMsg.style.display = "none";
}
lobbyUserUpdate(data) {
this.playerList = data.users;
this.updateUI();
}
}
export default LobbyHandler;

View File

@@ -0,0 +1,52 @@
import LobbyHandler from "./Handler/LobbyHandler.js";
class ServerConnectionManager{
constructor(){
/**@type {import("../../../../backend/node_modules/socket.io-client".Socket} für Autocompletions VSC*/
this.socket = io(`http://${window.location.hostname}:${window.location.port}`);
this.createBtn = document.getElementById("createBtn");
this.joinBtn = document.getElementById("joinBtn");
this.leaveBtn = document.getElementById("leaveBtn");
this.lobbyHandler = new LobbyHandler(this.socket);
this.basicSetup();
this.addButtonHandler();
}
basicSetup(){
this.socket.emit("hereForLobby");
this.socket.on("connect", () => {
console.log("Verbindung zum Server hergestellt!");
});
this.socket.on("connect_error", (error) => {
console.log(error);
window.location.pathname = "/dashboard";
});
this.socket.on("disconnect", () => {
console.log("Die verbindung wurde unterbrochen!");
window.location.pathname = "/dashboard";
})
}
addButtonHandler(){
this.createBtn.addEventListener("click", () => {
this.lobbyHandler.handleCreateClick();
});
this.joinBtn.addEventListener("click", () => {
this.lobbyHandler.handleJoinClick();
});
this.leaveBtn.addEventListener("click", () => {
this.socket.disconnect();
window.location.pathname = "/dashboard";
});
}
}
new ServerConnectionManager();

View File

@@ -0,0 +1,66 @@
button {
width: 350px;
}
#joinBtn{
background-color: rgb(0, 119, 255);
}
#leaveBtn{
background-color: orange;
}
.screen{
background-color: rgba(0, 0, 0, 0.6);
border: 2px solid rgba(255, 255, 255, 0.6);
border-radius: 15px;
height: 35vh;
width: 700px;
}
.screen nav{
display: flex;
flex-direction: row;
justify-content: space-evenly;
align-items: center;
height: 20%;
background-color: rgba(255, 255, 255, 0.377);
border-radius: 10px 10px 0 0;
}
#playerList{
display: flex;
flex-direction: column;
justify-content: start;
align-items: center;
height: 80%;
width: 100%;
}
.player{
display: flex;
flex-direction: row;
justify-content: space-evenly;
align-items: center;
width: 100%;
height: 50%;
}
.player h1{
width: 50%;
text-align: center;
}
#player1{
background-color: rgba(255, 8, 0, 0.377);
border-radius: 0 0 0 0;
}
#player2{
background-color: rgba(0, 89, 255, 0.377);
border-radius: 0 0 10px 10px;
}

View File

@@ -0,0 +1,23 @@
button{
width: 280px;
}
.buttonMenu{
height: 60%;
}
#joinBtn{
background-color: rgb(0, 119, 255);
}
#homeBtn{
background-color: rgb(122, 122, 0);
}
#accountBtn{
background-color: orange;
}
#logoutBtn{
background-color: rgb(103, 11, 156);
}

78
frontend/index.html Normal file
View File

@@ -0,0 +1,78 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Test</title>
<link rel="stylesheet" href="./style/generalStyle.css">
<link rel="stylesheet" href="./style.css">
</head>
<body>
<div class="container menu">
<h1 class="title">Double-Snake</h1>
<div class="buttonMenu container">
<button id="tutorialBtn">
<div class="buttonIcon">
<svg xmlns="http://www.w3.org/2000/svg" height="35px" viewBox="0 -960 960 960" width="35px"
fill="#ffffff">
<path d="M320-200v-560l440 280-440 280Zm80-280Zm0 134 210-134-210-134v268Z" />
</svg>
</div>
<div class="buttonText">
Anleitung
</div>
</button>
<button id="loginBtn">
<div class="buttonIcon">
<svg xmlns="http://www.w3.org/2000/svg" height="35px" viewBox="0 -960 960 960" width="35px"
fill="#ffffff">
<path
d="M480-120v-80h280v-560H480v-80h280q33
0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H480Zm-80-160-55-58 102-102H120v-80h327L345-622l55-58 200 200-200 200Z" />
</svg>
</div>
<div class="buttonText">
Login
</div>
</button>
<button id="registerBtn">
<div class="buttonIcon">
<svg xmlns="http://www.w3.org/2000/svg" height="35px" viewBox="0 -960 960 960" width="35px"
fill="#ffffff">
<path
d="M80-160v-112q0-33
17-62t47-44q51-26 115-44t141-18q30 0 58.5 3t55.5 9l-70 70q-11-2-21.5-2H400q-71 0-127.5 17T180-306q-9 5-14.5 14t-5.5 20v32h250l80 80H80Zm542
16L484-282l56-56 82 82 202-202 56 56-258 258ZM400-480q-66 0-113-47t-47-113q0-66 47-113t113-47q66 0 113 47t47 113q0 66-47 113t-113
47Zm10 240Zm-10-320q33 0 56.5-23.5T480-640q0-33-23.5-56.5T400-720q-33 0-56.5 23.5T320-640q0 33 23.5 56.5T400-560Zm0-80Z" />
</svg>
</div>
<div class="buttonText">
Registrieren
</div>
</button>
<button id="dashboardBtn">
<div class="buttonIcon">
<svg xmlns="http://www.w3.org/2000/svg" height="35px" viewBox="0 -960 960 960" width="35px"
fill="#ffffff">
<path d="M480-320q48 0 85.5-28.5T620-422H340q17 45 54.5 73.5T480-320ZM380-480q25 0 42.5-17.5T440-540q0-25-17.5-42.5T380-600q-25
0-42.5 17.5T320-540q0 25 17.5 42.5T380-480Zm200 0q25 0 42.5-17.5T640-540q0-25-17.5-42.5T580-600q-25 0-42.5 17.5T520-540q0 25 17.5
42.5T580-480ZM305-704l112-145q12-16 28.5-23.5T480-880q18 0 34.5 7.5T543-849l112 145 170 57q26 8 41 29.5t15 47.5q0 12-3.5 24T866-523L756-367l4
164q1 35-23 59t-56 24q-2 0-22-3l-179-50-179 50q-5 2-11 2.5t-11 .5q-32 0-56-24t-23-59l4-165L95-523q-8-11-11.5-23T80-570q0-25 14.5-46.5T135-647l170-57Zm49
69-194 64 124 179-4 191 200-55 200 56-4-192 124-177-194-66-126-165-126 165Zm126 135Z" />
</svg>
</div>
<div class="buttonText">
Dashboard
</div>
</button>
</div>
</div>
</body>
<script src="./scripts/ButtonManager.js"></script>
<script src="./index.js"></script>
</html>

43
frontend/index.js Normal file
View File

@@ -0,0 +1,43 @@
class ButtonLoginChecker{
constructor(){
this.loggedOutButtons = [
{ id: "loginBtn", route: "/login" },
{ id: "registerBtn", route: "/register" }
];
this.loggedInButtons = [
{ id: "dashboardBtn", route: "/dashboard" },
];
this.init();
}
async init(){
const isLoggedIn = await this.checkIsLoggedIn();
this.showButton(isLoggedIn);
}
showButton(isLoggedIn){
const buttons = isLoggedIn ? this.loggedOutButtons : this.loggedInButtons;
buttons.forEach(button => {
document.getElementById(button.id).style.display = "none";
})
}
async checkIsLoggedIn(){
try{
const response = await fetch("/api/dashboard");
return !response.redirected;
}
catch(error){
console.error(error);
return false;
}
}
}
new ButtonLoginChecker();

65
frontend/login/index.html Normal file
View File

@@ -0,0 +1,65 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Einloggen</title>
<link rel="stylesheet" href="../style/generalStyle.css">
<link rel="stylesheet" href="./style.css">
</head>
<body>
<div class="container menu">
<h1 class="title">Einloggen</h1>
<div class="buttonMenu container">
<h2 class="error" id="errorMsg"></h2>
<form action="/api/account/login" method="post" class="container">
<div class="formSection">
<p>Username:</p>
<input name="username" type="text" placeholder="Username">
</div>
<div class="formSection">
<p>Passwort:</p>
<input name="password" type="password" placeholder="Password">
</div>
<input type="submit" value="Einloggen" id="loginBtn">
</form>
<div class="container navButtons">
<button id="homeBtn">
<div class="buttonIcon">
<svg xmlns="http://www.w3.org/2000/svg" height="35px" viewBox="0 -960 960 960" width="35px"
fill="#ffffff">
<path d="M520-200 80-480l440-280-137 240h497v80H383l137 240Z" />
</svg>
</div>
<div class="buttonText">
Startseite
</div>
</button>
<button id="registerBtn">
<div class="buttonIcon">
<svg xmlns="http://www.w3.org/2000/svg" height="35px" viewBox="0 -960 960 960" width="35px"
fill="#ffffff">
<path
d="M80-160v-112q0-33
17-62t47-44q51-26 115-44t141-18q30 0 58.5 3t55.5 9l-70 70q-11-2-21.5-2H400q-71 0-127.5 17T180-306q-9 5-14.5 14t-5.5 20v32h250l80
80H80Zm542
16L484-282l56-56 82 82 202-202 56 56-258 258ZM400-480q-66 0-113-47t-47-113q0-66 47-113t113-47q66 0 113 47t47 113q0 66-47 113t-113
47Zm10 240Zm-10-320q33 0 56.5-23.5T480-640q0-33-23.5-56.5T400-720q-33 0-56.5 23.5T320-640q0 33 23.5 56.5T400-560Zm0-80Z" />
</svg>
</div>
<div class="buttonText">
Registieren
</div>
</button>
</div>
</div>
</div>
</body>
<script src="../scripts/ButtonManager.js"></script>
<script src="../scripts/ErrorHandler.js"></script>
</html>

11
frontend/login/style.css Normal file
View File

@@ -0,0 +1,11 @@
#homeBtn{
background-color: rgb(0, 119, 255);
}
#registerBtn{
background-color: rgb(122, 122, 0);
}
#loginBtn{
width: 234px;
}

View File

@@ -0,0 +1,76 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Registieren</title>
<link rel="stylesheet" href="../style/generalStyle.css">
<link rel="stylesheet" href="./style.css">
</head>
<body>
<div class="container menu">
<h1 class="title">Registieren</h1>
<div class="buttonMenu container">
<form action="/api/account/register" method="post" class="container">
<h2 class="error" id="errorMsg"></h2>
<div class="splitContainerX">
<div>
<div class="formSection">
<p>Username:</p>
<input name="username" type="text" placeholder="Username">
</div>
<div class="formSection">
<p>Email:</p>
<input name="email" type="email" placeholder="E-mail">
</div>
</div>
<div>
<div class="formSection">
<p>Voller Name:</p>
<input name="fullName" type="text" placeholder="Name">
</div>
<div class="formSection">
<p>Passwort:</p>
<input name="password" type="password" placeholder="Password">
</div>
</div>
</div>
<input type="submit" value="Registieren" id="registerBtn">
</form>
<div class="container navButtons">
<button id="homeBtn">
<div class="buttonIcon">
<svg xmlns="http://www.w3.org/2000/svg" height="35px" viewBox="0 -960 960 960" width="35px"
fill="#ffffff">
<path d="M520-200 80-480l440-280-137 240h497v80H383l137 240Z" />
</svg>
</div>
<div class="buttonText">
Startseite
</div>
</button>
<button id="loginBtn">
<div class="buttonIcon">
<svg xmlns="http://www.w3.org/2000/svg" height="35px" viewBox="0 -960 960 960" width="35px"
fill="#ffffff">
<path
d="M480-120v-80h280v-560H480v-80h280q33
0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H480Zm-80-160-55-58 102-102H120v-80h327L345-622l55-58 200 200-200 200Z" />
</svg>
</div>
<div class="buttonText">
Login
</div>
</button>
</div>
</div>
</div>
</body>
<script src="../scripts/ButtonManager.js"></script>
<script src="../scripts/ErrorHandler.js"></script>
</html>

View File

@@ -0,0 +1,11 @@
#homeBtn{
background-color: rgb(0, 119, 255);
}
#loginBtn{
background-color: rgb(122, 122, 0);
}
#registerBtn{
width: 234px;
}

View File

@@ -0,0 +1,27 @@
class ButtonManager {
constructor() {
this.buttons = [
{ id: "homeBtn", route: "/" },
{ id: "loginBtn", route: "/login" },
{ id: "registerBtn", route: "/register" },
{ id: "dashboardBtn", route: "/dashboard" },
{ id: "lobbyBtn", route: "/dashboard/lobby" },
{ id: "accountBtn", route: "/dashboard/account" },
{ id: "logoutBtn", route: "/api/account/logout" },
{ id: "tutorialBtn", route: "https://www.youtube.com/watch?v=1fkV5rB13jQ" }
];
this.addEventListener();
}
addEventListener() {
this.buttons.forEach(button => {
const element = document.getElementById(button.id);
if (!element) return;
element.addEventListener("click", () => { window.location.href = button.route });
});
}
}
new ButtonManager();

View File

@@ -0,0 +1,54 @@
class ErrorHandler {
constructor() {
this.errorH2 = document.getElementById("errorMsg");
this.errorList = [
{ route: "/login",
errors: [
{ code: 1, msg: "Du musst ein Username und ein Passwort eingeben!", errorHighligher: ["username", "password"] },
{ code: 2, msg: "Username oder Passwort falsch!", errorHighligher: ["username", "password"] }
]
},
{ route: "/register",
errors: [
{ code: 1, msg: "Du musst zumindest Username und Passwort angeben!", errorHighligher: ["username", "password"] },
{ code: 2, msg: "Username ist schon vergeben!", errorHighligher: ["username"] }
]
},
{ route: "/dashboard/account",
errors: [
{ code: 1, msg: "Dieser Username ist schon vergeben!", errorHighligher: ["username"] },
]
}
];
this.showPossibleError();
}
showPossibleError() {
const route = this.getCurrentRoute();
const error = this.checkError();
if(!error) return;
const errorJSON= this.errorList.find(x => x.route === route).errors.find(x => x.code === error);
this.errorH2.innerText = errorJSON.msg;
this.errorH2.style.display = "block";
errorJSON.errorHighligher.forEach(element => {
document.getElementsByName(element)[0].classList.add("important");
});
}
getCurrentRoute() {
return window.location.pathname.slice(0, -1);
}
checkError() {
const urlParams = new URLSearchParams(window.location.search);
return parseInt(urlParams.get("error"));
}
}
new ErrorHandler();

11
frontend/style.css Normal file
View File

@@ -0,0 +1,11 @@
#loginBtn{
background-color: rgb(0, 119, 255);
}
#registerBtn{
background-color: rgb(122, 122, 0);
}
#dashboardBtn{
background-color: orange;
}

View File

@@ -0,0 +1,207 @@
@import url('https://fonts.googleapis.com/css2?family=Sigmar&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Kanit:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&family=Preahvihear&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Kanit:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&family=Preahvihear&family=Sigmar&display=swap');
* {
margin: 0;
padding: 0;
font-family: "Preahvihear", serif;
font-weight: 400;
font-style: normal;
}
body {
background-color: rgb(40, 85, 51);
background-image: linear-gradient(rgba(0, 0, 0, 0.5) 2px, transparent 1px),
linear-gradient(90deg, rgba(0, 0, 0, 0.5) 2px, transparent 1px);
background-size: 55px 55px;
color: white;
}
.container {
display: flex;
flex-direction: column;
justify-content: space-evenly;
align-items: center;
}
.splitContainerX {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
.navButtons{
flex-direction: row;
justify-content: space-evenly;
width: 50%;
}
@keyframes rainbow-glow {
0% {
text-shadow: 0 0 10px red, 0 0 20px red, 0 0 30px red;
}
20% {
text-shadow: 0 0 10px orange, 0 0 20px orange, 0 0 30px orange;
}
40% {
text-shadow: 0 0 10px rgb(204, 0, 255), 0 0 20px rgb(204, 0, 255), 0 0 30px rgb(204, 0, 255);
}
60% {
text-shadow: 0 0 10px pink, 0 0 20px pink, 0 0 30px pink;
}
80% {
text-shadow: 0 0 10px blue, 0 0 20px blue, 0 0 30px blue;
}
100% {
text-shadow: 0 0 10px purple, 0 0 20px purple, 0 0 30px purple;
}
}
.title {
font-family: "Sigmar", serif;
font-weight: 400;
font-style: normal;
font-size: 100px;
animation: rainbow-glow 15s infinite alternate;
user-select: none;
}
button,
.button,
input[type=submit] {
display: flex;
flex-direction: row;
align-items: center;
justify-content: start;
background-color: rgb(233, 47, 47);
border: 2px solid rgba(255, 255, 255, 0.6);
border-radius: 10px;
height: 60px;
width: 250px;
color: white;
font-size: 25px;
transition: transform 0.2s ease-in-out;
}
.buttonIcon,
.buttonText {
height: 100%;
width: 25%;
display: flex;
align-items: center;
justify-content: center;
}
.buttonText {
width: 75%;
}
button:hover,
.button:hover,
input[type=submit]:hover {
transform: scale(1.1);
}
button:active,
.button:active,
input[type=submit]:active {
transform: translateY(1px);
}
.menu {
height: 100vh;
justify-content: center;
}
.buttonMenu {
height: 50vh;
width: 100vw;
}
input[type=text],
input[type=email],
input[type=password] {
background-color: rgba(255, 255, 255, 0.75);
border: 2px solid rgba(0, 0, 0, 0.6);
border-radius: 10px;
height: 25px;
width: 220px;
padding: 5px;
color: black;
font-weight: 600px;
font-size: 20px;
transition: transform 0.2s ease-in-out;
}
input[type=submit] {
margin-top: 15px;
}
input[type=text]:focus,
input[type=email]:focus,
input[type=password]:focus {
outline: none;
transform: scale(1.05);
}
.formSection {
display: flex;
flex-direction: column;
margin: 10px;
}
.formSection p {
font-size: 20px;
}
.important {
border-color: red !important;
}
.important:focus {
outline: none;
border-color: red !important;
}
.error {
display: none;
font-family: "Kanit", serif;
font-weight: 600;
font-style: normal;
font-size: 25px;
color: red;
background-color: rgba(255, 255, 255, 0.6);
padding: 5px;
border-radius: 15px;
}
.message{
font-size: 25px;
background-color: rgba(0, 0, 0, 0.6);
border: 2px solid rgba(255, 255, 255, 0.6);
padding: 5px;
border-radius: 15px;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,18 @@
{
"name": "backend",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "node ./src/index.js"
},
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"body-parser": "^1.20.3",
"express": "^4.21.2",
"socket.io": "^4.8.1",
"socket.io-client": "^4.8.1"
}
}

View File

@@ -0,0 +1,103 @@
const express = require("express");
const http = require("http");
const socketIO = require("socket.io");
const bp = require("body-parser");
const path = require("path");
const _PORT = 3000;
const app = express();
const server = http.createServer(app);
/** @type {import("socket.io").Server} */
const io = socketIO(server);
app.use(bp.urlencoded({ extended: false }));
app.use(express.static(path.join(__dirname, "../../frontend")));
app.use((req, res, next) => {
console.log(`${req.method} | ${req.url}`);
next();
})
const playerList = [];
io.on('connection', (socket) => {
console.log("Es ist ein neuer Spieler beigetreten");
const player = {
id: socket.id,
x: 100,
y: 100,
socket: socket,
};
playerList.push(player);
emitPlayerMovement();
socket.on("moveUp", () => {
const player = playerList.find((p) => p.id === socket.id);
if (player) {
player.y -= 10;
}
emitPlayerMovement();
});
socket.on("moveDown", () => {
const player = playerList.find((p) => p.id === socket.id);
if (player) {
player.y += 10;
}
emitPlayerMovement();
});
socket.on("moveRight", () => {
const player = playerList.find((p) => p.id === socket.id);
if (player) {
player.x += 10;
}
emitPlayerMovement();
});
socket.on("moveLeft", () => {
const player = playerList.find((p) => p.id === socket.id);
if (player) {
player.x -= 10;
}
emitPlayerMovement();
});
// Handle player disconnect
socket.on("disconnect", () => {
console.log(`Spieler ${socket.id} hat das Spiel verlassen`);
const index = playerList.findIndex((p) => p.id === socket.id);
if (index !== -1) {
playerList.splice(index, 1);
}
emitPlayerMovement();
});
socket.on("disconnect", () => {
playerList.splice(playerList.findIndex(p => p.id === socket.id), 1);
})
});
function emitPlayerMovement() {
const playerMap = playerList.map(x => {
return {
id: x.id,
x: x.x,
y: x.y
}
});
io.emit("updatePos", playerMap);
}
server.listen(_PORT, () => {
console.log(`Backend gestartet auf http://localhost:${_PORT}`);
})

View File

@@ -0,0 +1,17 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Erste Socket.io Seite</title>
<link rel="stylesheet" href="style/style.css" />
</head>
<body>
<h1>Multigame</h1>
</body>
<script src="https://cdn.socket.io/4.7.2/socket.io.min.js"></script>
<script src="index.js" type="module"></script>
<canvas id="game" height="300px" width="300px" id> </canvas>
</html>

View File

@@ -0,0 +1,37 @@
/** @type {import("../backend/node_modules/socket.io-client").Socket} */
const socket = io("http://localhost:3000");
const gameScreen = document.getElementById("game");
const ctx = gameScreen.getContext("2d")
ctx.fillStyle = "red";
socket.on("connect", () => {
console.log(`Connected as ${socket.id}`);
});
let keysPressed = new Set();
document.addEventListener("keydown", (e) => {
keysPressed.add(e.key);
});
document.addEventListener("keyup", (e) => {
keysPressed.delete(e.key);
});
setInterval(() => {
if (keysPressed.has("ArrowUp")) socket.emit("moveUp");
if (keysPressed.has("ArrowDown")) socket.emit("moveDown");
if (keysPressed.has("ArrowRight")) socket.emit("moveRight");
if (keysPressed.has("ArrowLeft")) socket.emit("moveLeft");
}, 50);
socket.on("updatePos", (map) => {
ctx.clearRect(0, 0, gameScreen.width, gameScreen.height);
map.forEach(e => {
ctx.beginPath();
ctx.arc(e.x, e.y, 10, 0, Math.PI * 2, false);
ctx.fill();
});
})

View File

@@ -0,0 +1,3 @@
#game{
background-color: grey;
}