From b5d7d53bdba487493716358fccc90d65cb68bdc0 Mon Sep 17 00:00:00 2001 From: Simon Oberzier Date: Mon, 9 Feb 2026 21:24:45 +0100 Subject: [PATCH] Initial commit --- Dockerfile | 7 ++ package.json | 8 ++ public/index.html | 198 ++++++++++++++++++++++++++++++++++++++++++++++ server.js | 134 +++++++++++++++++++++++++++++++ start.sh | 4 + 5 files changed, 351 insertions(+) create mode 100644 Dockerfile create mode 100644 package.json create mode 100644 public/index.html create mode 100644 server.js create mode 100644 start.sh diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..54571e1 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,7 @@ +FROM node:18 +WORKDIR /app +COPY package.json . +RUN npm install +COPY . . +EXPOSE 3030 +CMD ["node","server.js"] diff --git a/package.json b/package.json new file mode 100644 index 0000000..a5dac5a --- /dev/null +++ b/package.json @@ -0,0 +1,8 @@ +{ + "name": "wolf-sheep", + "version": "1.0.0", + "dependencies": { + "express": "^4.18.2", + "ws": "^8.15.1" + } +} diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..e8599cf --- /dev/null +++ b/public/index.html @@ -0,0 +1,198 @@ + + + + + + Wolf & Schafe + + + + + +

Verbinde…

+
+ + + + + + + + + \ No newline at end of file diff --git a/server.js b/server.js new file mode 100644 index 0000000..7812d3c --- /dev/null +++ b/server.js @@ -0,0 +1,134 @@ +const express = require("express"); +const http = require("http"); +const WebSocket = require("ws"); +const { randomBytes } = require("crypto"); + +const app = express(); +app.use(express.static("public")); + +const server = http.createServer(app); +const wss = new WebSocket.Server({ server }); + +// Define both movement sets +const board_wolf = { + 0:[1,2,3], 1:[0,2,4,5], 2:[0,1,3,5], 3:[0,2,5,6], 4:[1,5,7], + 5:[1,2,3,4,6,7,8,9], 6:[3,5,9], 7:[4,5,8,10], 8:[5,7,9,10], + 9:[5,6,8,10], 10:[7,8,9] +}; + +const board_sheep = { + 0:[1,2,3], 1:[2,4,5], 2:[1,3,5], 3:[2,5,6], 4:[5,7], + 5:[4,6,7,8,9], 6:[5,9], 7:[8,10], 8:[7,9,10], 9:[8,10], 10:[] +}; + +// Rooms: roomId -> { game, players } +const rooms = {}; + +// Broadcast state to all players in a room +function broadcast(roomId) { + const room = rooms[roomId]; + if (!room) return; + const msg = JSON.stringify({ type:"state", game: room.game }); + room.players.forEach(p => { + if(p.ws.readyState === WebSocket.OPEN) p.ws.send(msg); + }); +} + +// Select the correct allowed moves based on role +function handleMove(roomId, role, {from, to}) { + const room = rooms[roomId]; + if(!room) return; + + const game = room.game; + if(game.turn !== role) return; + + // Select the correct allowed moves based on role + const allowedMoves = (role === "wolf") ? board_wolf[from] : board_sheep[from]; + + if(!allowedMoves?.includes(to)) return; + if(game.sheep.includes(to) || game.wolf === to) return; + + if(role === "wolf") { + if(from !== game.wolf) return; + game.wolf = to; + game.turn = "sheep"; + } else { + const i = game.sheep.indexOf(from); + if(i === -1) return; + game.sheep[i] = to; + game.turn = "wolf"; + } + broadcast(roomId); + } + +// Heartbeat +function heartbeat() { this.isAlive = true; } + +wss.on("connection", (ws, req) => { // Added 'req' parameter + ws.isAlive = true; + ws.on("pong", heartbeat); + + // Use req.url to get the query string + const urlParams = new URLSearchParams(req.url.split('?')[1]); + let roomId = urlParams.get("room"); + + // Logic for creating or joining + if (!roomId || !rooms[roomId]) { + // If no ID provided, or the provided ID doesn't exist, create a NEW one + // UNLESS you want people to be able to create specific room names: + if (!roomId) roomId = randomBytes(3).toString("hex"); + + if (!rooms[roomId]) { + rooms[roomId] = { + game: { wolf: 5, sheep: [0, 1, 3], turn: "sheep" }, + players: [] + }; + } + } + + const room = rooms[roomId]; + + // Reject if room full + if(room.players.length >= 2){ + ws.send(JSON.stringify({type:"full"})); + ws.close(); + return; + } + + const role = room.players.length === 0 ? "sheep" : "wolf"; + room.players.push({ ws, role, roomId }); + + // Send role + room info + ws.send(JSON.stringify({ type:"role", role, roomId })); + broadcast(roomId); + + ws.on("message", msg=>{ + let data; + try{ data = JSON.parse(msg); } catch{return;} + + if(data.type==="restart"){ + room.game = { wolf:5, sheep:[0,1,3], turn:"sheep" }; + broadcast(roomId); + return; + } + + handleMove(roomId, role, data); + }); + + ws.on("close", ()=>{ + room.players = room.players.filter(p => p.ws !== ws); + if(room.players.length === 0) delete rooms[roomId]; // cleanup empty rooms + }); +}); + +// Ping-pong to prevent idle disconnect +const interval = setInterval(()=>{ + wss.clients.forEach(ws=>{ + if(!ws.isAlive) return ws.terminate(); + ws.isAlive = false; + ws.ping(); + }); +}, 30000); + +server.on("close", ()=>clearInterval(interval)); +server.listen(3030, ()=>console.log("Server läuft auf Port 3030")); \ No newline at end of file diff --git a/start.sh b/start.sh new file mode 100644 index 0000000..a4eab50 --- /dev/null +++ b/start.sh @@ -0,0 +1,4 @@ +docker stop wolf-sheep-container +docker rm wolf-sheep-container +docker build -t wolf-sheep . +docker run -d -p 3030:3030 --name wolf-sheep-container wolf-sheep