| import http from "node:http"; | |
| import fs from "node:fs"; | |
| import path from "node:path"; | |
| import { WebSocketServer } from "ws"; | |
| const PORT = 8787; | |
| const ROOT = path.resolve(process.cwd()); | |
| const server = http.createServer((req, res) => { | |
| const url = req.url === "/" ? "/index.html" : req.url; | |
| const filePath = path.join(ROOT, url); | |
| fs.readFile(filePath, (err, data) => { | |
| if (err) { | |
| res.writeHead(404, { "Content-Type": "text/plain" }); | |
| res.end("Not found"); | |
| return; | |
| } | |
| const ext = path.extname(filePath); | |
| const typeMap = { | |
| ".html": "text/html", | |
| ".css": "text/css", | |
| ".js": "text/javascript" | |
| }; | |
| res.writeHead(200, { "Content-Type": typeMap[ext] ?? "text/plain" }); | |
| res.end(data); | |
| }); | |
| }); | |
| const wss = new WebSocketServer({ server }); | |
| const palette = ["#4ed1ff", "#ffb347", "#7cff6b", "#ff6be5", "#9f8bff", "#ffd86b"]; | |
| let paletteIndex = 0; | |
| const players = new Map(); | |
| const broadcast = (payload) => { | |
| const message = JSON.stringify(payload); | |
| for (const client of wss.clients) { | |
| if (client.readyState === 1) { | |
| client.send(message); | |
| } | |
| } | |
| }; | |
| wss.on("connection", (socket) => { | |
| let playerId = null; | |
| socket.on("message", (data) => { | |
| let message; | |
| try { | |
| message = JSON.parse(data.toString()); | |
| } catch { | |
| return; | |
| } | |
| if (message.type === "join") { | |
| playerId = `p_${Math.random().toString(36).slice(2, 9)}`; | |
| const color = palette[paletteIndex % palette.length]; | |
| paletteIndex += 1; | |
| const name = String(message.name || "Pilot").slice(0, 14); | |
| players.set(playerId, { | |
| id: playerId, | |
| name, | |
| color, | |
| x: 0, | |
| y: 0, | |
| score: 0, | |
| shield: 100, | |
| wave: 1, | |
| lastSeen: Date.now() | |
| }); | |
| socket.send(JSON.stringify({ type: "welcome", id: playerId, color })); | |
| return; | |
| } | |
| if (message.type === "state" && playerId && players.has(playerId)) { | |
| const player = players.get(playerId); | |
| player.x = message.x; | |
| player.y = message.y; | |
| player.score = message.score; | |
| player.shield = message.shield; | |
| player.wave = message.wave; | |
| player.lastSeen = Date.now(); | |
| } | |
| }); | |
| socket.on("close", () => { | |
| if (playerId) { | |
| players.delete(playerId); | |
| } | |
| }); | |
| }); | |
| setInterval(() => { | |
| const now = Date.now(); | |
| for (const [id, player] of players.entries()) { | |
| if (now - player.lastSeen > 8000) { | |
| players.delete(id); | |
| } | |
| } | |
| broadcast({ type: "roster", players: Array.from(players.values()) }); | |
| }, 120); | |
| server.listen(PORT, () => { | |
| console.log(`Star Courier running on http://localhost:${PORT}`); | |
| }); | |