Files
WolfSheepGame/server.js

255 lines
8.6 KiB
JavaScript

const express = require("express");
const http = require("http");
const net = require("net");
const WebSocket = require("ws");
const app = express();
app.use(express.static("public"));
const server = http.createServer(app);
const wss = new WebSocket.Server({ server });
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: [] };
const pos_coords = { 0: [0,1], 1: [1,0], 2: [1,1], 3: [1,2], 4: [2,0], 5: [2,1], 6: [2,2], 7: [3,0], 8: [3,1], 9: [3,2], 10: [4,1] };
const dir_map = { "n": [0,-1], "s": [0,1], "o": [1,0], "w": [-1,0], "no": [1,-1], "nw": [-1,-1], "so": [1,1], "sw": [-1,1] };
function getDir(from, to) {
const dX = pos_coords[to][0] - pos_coords[from][0];
const dY = pos_coords[to][1] - pos_coords[from][1];
return Object.keys(dir_map).find(k => dir_map[k][0] === dX && dir_map[k][1] === dY);
}
const rooms = {};
const botServer = net.createServer((socket) => {
let roomId = `BOT_${Math.random().toString(36).substring(7)}`;
let botName = "UnknownBot";
socket.write("SEND: <your token>\n");
socket.on("data", (data) => {
const msg = data.toString().trim();
if (!rooms[roomId]) {
botName = msg.split(" ").pop();
rooms[roomId] = {
game: { wolf: 5, sheep: [0, 1, 3], turn: "sheep", moveCount: 0, winner: null, lastMove: null },
players: [{ role: "wolf", isBot: true, socket: socket }], // Bot defaults to Wolf
nextFirstRole: "sheep"
};
broadcastRoomList();
return;
}
const room = rooms[roomId];
const bot = room.players.find(p => p.isBot);
const game = room.game;
if (game.turn !== bot.role || game.winner) return;
let from, to;
if (bot.role === "wolf") {
from = game.wolf;
const targetDir = msg.toLowerCase();
to = board_wolf[from].find(t => getDir(from, t) === targetDir);
} else {
const sheepIndex = parseInt(msg[0]) - 1;
const targetDir = msg.substring(1).toLowerCase();
if (game.sheep[sheepIndex] !== undefined) {
from = game.sheep[sheepIndex];
to = board_sheep[from].find(t => getDir(from, t) === targetDir);
}
}
if (to !== undefined) {
handleMove(roomId, bot.role, { from, to });
}
});
socket.on("close", () => {
delete rooms[roomId];
broadcastRoomList();
});
});
function swapRoles(room) {
room.players.forEach(p => {
p.role = (p.role === "wolf") ? "sheep" : "wolf";
if (p.isBot) p.botInitialized = false;
});
}
function sendBotState(roomId) {
const room = rooms[roomId];
const bot = room.players.find(p => p.isBot);
if (!bot || !bot.socket) return;
const game = room.game;
if (game.winner) {
const result = (game.winner === bot.role) ? "you win" : "you loose";
bot.socket.write(`#gameends ${result}\n`);
return;
}
if (!bot.botInitialized) {
bot.socket.write(`#role ${bot.role === "wolf" ? "WOLF" : "SCHAF"}\n`);
bot.botInitialized = true;
}
if (game.turn === bot.role) {
bot.socket.write(`#pos_opponent ${bot.role === "wolf" ? game.sheep.join(" ") : game.wolf}\n`);
bot.socket.write(`#pos_you ${bot.role === "wolf" ? game.wolf : game.sheep.join(" ")}\n`);
let moves = [];
if (bot.role === "wolf") {
board_wolf[game.wolf].forEach(t => {
if (!game.sheep.includes(t)) moves.push(getDir(game.wolf, t));
});
} else {
game.sheep.forEach(s => {
board_sheep[s].forEach(t => {
if (t !== game.wolf && !game.sheep.includes(t)) moves.push(`${s}${getDir(s, t)}`);
});
});
}
bot.socket.write(`#moves ${moves.join(" ")}\n`);
}
}
function broadcastRoomList() {
const list = JSON.stringify({ type: "room_list", rooms: getRoomList() });
wss.clients.forEach(client => {
if (client.readyState === WebSocket.OPEN && !client.roomId) client.send(list);
});
}
function getRoomList() {
return Object.keys(rooms).map(id => ({
id,
players: rooms[id].players.length,
isBotRoom: rooms[id].players.some(p => p.isBot)
}));
}
function handleMove(roomId, role, { from, to }) {
const room = rooms[roomId];
if (!room || room.game.winner || room.game.turn !== role) return;
const game = room.game;
const allowed = (role === "wolf") ? board_wolf[from] : board_sheep[from];
if (!allowed?.includes(to) || game.sheep.includes(to) || game.wolf === to) return;
if (role === "wolf") { game.wolf = to; game.turn = "sheep"; }
else { game.sheep[game.sheep.indexOf(from)] = to; game.turn = "wolf"; }
game.moveCount++;
game.winner = checkWinConditions(game);
broadcastState(roomId);
if (room.players.some(p => p.isBot)) sendBotState(roomId);
}
function broadcastState(roomId) {
const room = rooms[roomId];
if (!room) return;
room.players.forEach(p => {
if (p.ws?.readyState === WebSocket.OPEN) {
p.ws.send(JSON.stringify({ type: "state", game: room.game, yourRole: p.role, onlineCount: room.players.length }));
}
});
}
function checkWinConditions(game) {
if (game.wolf === 0 || game.moveCount >= 40) return "wolf";
const canWolfMove = board_wolf[game.wolf].some(to => !game.sheep.includes(to));
if (!canWolfMove) return "sheep";
return null;
}
wss.on("connection", (ws, req) => {
const urlParams = new URLSearchParams(req.url.split('?')[1]);
let roomId = urlParams.get("room");
let sessionId = urlParams.get("sessionId");
if (!roomId || roomId === "null") {
ws.send(JSON.stringify({ type: "room_list", rooms: getRoomList() }));
return;
}
if (!rooms[roomId]) {
rooms[roomId] = {
game: { wolf: 5, sheep: [0, 1, 3], turn: "sheep", moveCount: 0, winner: null, lastMove: null },
players: [], nextFirstRole: "sheep"
};
}
const room = rooms[roomId];
ws.roomId = roomId;
ws.sessionId = sessionId;
let player = room.players.find(p => p.sessionId === sessionId);
if (player) {
player.ws = ws;
} else if (room.players.length < 2) {
let role;
if (room.players.length === 1) {
role = (room.players[0].role === "wolf") ? "sheep" : "wolf";
} else {
role = "sheep";
}
player = { ws, role, sessionId, isBot: false };
room.players.push(player);
}
ws.send(JSON.stringify({ type: "init", role: player.role, roomId }));
broadcastRoomList();
broadcastState(roomId);
if (room.players.some(p => p.isBot)) sendBotState(roomId);
ws.on("close", () => {
const currentRoom = rooms[ws.roomId];
if (!currentRoom) return;
currentRoom.players = currentRoom.players.filter(p => p.sessionId !== ws.sessionId);
const humanRemaining = currentRoom.players.some(p => !p.isBot);
if (currentRoom.players.length === 0 || (!humanRemaining && currentRoom.players.some(p => p.isBot))) {
const bot = currentRoom.players.find(p => p.isBot);
if (bot && bot.socket) bot.socket.destroy();
delete rooms[ws.roomId];
} else {
broadcastState(ws.roomId);
}
broadcastRoomList();
});
ws.on("message", msg => {
let data = JSON.parse(msg);
const playerObj = room.players.find(p => p.sessionId === sessionId);
if (!playerObj) return;
if (data.type === "restart") {
swapRoles(room);
room.game = { wolf: 5, sheep: [0, 1, 3], turn: "sheep", moveCount: 0, winner: null, lastMove: null };
room.players.forEach(p => {
if (p.ws && p.ws.readyState === WebSocket.OPEN) {
p.ws.send(JSON.stringify({ type: "init", role: p.role, roomId }));
}
});
broadcastState(roomId);
if (room.players.some(p => p.isBot)) sendBotState(roomId);
} else {
handleMove(roomId, playerObj.role, data);
}
});
});
server.listen(3030, () => console.log("Web Server running on 3030"));
botServer.listen(3031, () => console.log("Bot TCP Server running on 3031"));