const canvas = document.getElementById("game-canvas"); const context = canvas.getContext("2d"); const statusElement = document.getElementById("connection-status"); let socket = null; const localPlayerIds = []; const playerOneKeys = createEmptyKeyState(); const playerTwoKeys = createEmptyKeyState(); let latestState = { arena: { width: canvas.width, height: canvas.height }, players: [], bullets: [] }; function resolveWebSocketUrl() { const overrideUrl = readEndpointOverride(); if (overrideUrl !== null) { return overrideUrl; } const protocol = selectProtocolFromWindowLocation(); const host = window.location.host; const defaultPath = "/ws"; const url = `${protocol}://${host}${defaultPath}`; return url; } function readEndpointOverride() { const bodyElement = document.body; if (bodyElement === null) { return null; } const rawValue = bodyElement.getAttribute("data-ws-endpoint"); if (rawValue === null) { return null; } const trimmedValue = rawValue.trim(); if (trimmedValue === "") { return null; } const normalizedUrl = normalizeEndpoint(trimmedValue); return normalizedUrl; } function normalizeEndpoint(endpoint) { if (endpoint.startsWith("ws://") === true || endpoint.startsWith("wss://") === true) { return endpoint; } if (endpoint.startsWith("http://") === true || endpoint.startsWith("https://") === true) { try { const parsedHttpUrl = new URL(endpoint); const webSocketProtocol = parsedHttpUrl.protocol === "https:" ? "wss:" : "ws:"; parsedHttpUrl.protocol = webSocketProtocol; const convertedUrl = parsedHttpUrl.toString(); return convertedUrl; } catch (error) { console.error("URL HTTP fournie invalide pour le WebSocket", error); } } const protocol = selectProtocolFromWindowLocation(); const host = window.location.host; if (endpoint.startsWith("/") === true) { const absoluteUrl = `${protocol}://${host}${endpoint}`; return absoluteUrl; } const absoluteUrlFromRelative = `${protocol}://${host}/${endpoint}`; return absoluteUrlFromRelative; } function selectProtocolFromWindowLocation() { if (window.location.protocol === "https:") { return "wss"; } return "ws"; } function createEmptyKeyState() { return { up: false, down: false, left: false, right: false, shoot: false }; } function connect() { const url = resolveWebSocketUrl(); socket = new WebSocket(url); socket.addEventListener("open", function handleOpen() { setStatusConnected(true); }); socket.addEventListener("close", function handleClose() { setStatusConnected(false); window.setTimeout(connect, 1000); }); socket.addEventListener("error", function handleError() { setStatusConnected(false); socket.close(); }); socket.addEventListener("message", function handleMessage(event) { try { const message = JSON.parse(event.data); if (message.type === "welcome") { setLocalPlayerIds(message.payload.playerIds); latestState = message.payload.state; } if (message.type === "state") { latestState = message.payload; } } catch (error) { console.error("Erreur de décodage du message", error); } }); } function setStatusConnected(isConnected) { if (isConnected === true) { statusElement.textContent = "Connecté"; statusElement.classList.remove("status--disconnected"); statusElement.classList.add("status--connected"); } else { statusElement.textContent = "Déconnecté"; statusElement.classList.remove("status--connected"); statusElement.classList.add("status--disconnected"); } } function handleKeyChange(event, isDown) { if (event.repeat === true) { return; } const code = event.code; let wasHandled = false; if (code === "KeyZ" || code === "KeyW") { playerOneKeys.up = isDown; wasHandled = true; } if (code === "KeyS") { playerOneKeys.down = isDown; wasHandled = true; } if (code === "KeyQ" || code === "KeyA") { playerOneKeys.left = isDown; wasHandled = true; } if (code === "KeyD") { playerOneKeys.right = isDown; wasHandled = true; } if (code === "Space") { playerOneKeys.shoot = isDown; wasHandled = true; } if (code === "ArrowUp") { playerTwoKeys.up = isDown; wasHandled = true; } if (code === "ArrowDown") { playerTwoKeys.down = isDown; wasHandled = true; } if (code === "ArrowLeft") { playerTwoKeys.left = isDown; wasHandled = true; } if (code === "ArrowRight") { playerTwoKeys.right = isDown; wasHandled = true; } if (code === "Enter") { playerTwoKeys.shoot = isDown; wasHandled = true; } if (wasHandled === true) { event.preventDefault(); } } function sendInput() { if (socket === null) { return; } if (socket.readyState !== WebSocket.OPEN) { return; } if (localPlayerIds.length === 0) { return; } const playersPayload = {}; const playerOneId = localPlayerIds[0] ?? null; const playerTwoId = localPlayerIds[1] ?? null; if (typeof playerOneId === "string") { playersPayload[playerOneId] = buildInputFromKeys(playerOneKeys); } if (typeof playerTwoId === "string") { playersPayload[playerTwoId] = buildInputFromKeys(playerTwoKeys); } if (Object.keys(playersPayload).length === 0) { return; } socket.send(JSON.stringify({ type: "input", payload: { players: playersPayload } })); } function draw() { const arenaWidth = latestState.arena.width; const arenaHeight = latestState.arena.height; context.clearRect(0, 0, canvas.width, canvas.height); context.fillStyle = "rgba(15, 23, 42, 0.65)"; context.fillRect(0, 0, canvas.width, canvas.height); drawGrid(arenaWidth, arenaHeight); drawPlayers(); drawBullets(); window.requestAnimationFrame(draw); } function drawGrid(width, height) { context.strokeStyle = "rgba(148, 163, 184, 0.15)"; context.lineWidth = 1; const step = 80; for (let x = step; x < width; x += step) { context.beginPath(); context.moveTo(x, 0); context.lineTo(x, height); context.stroke(); } for (let y = step; y < height; y += step) { context.beginPath(); context.moveTo(0, y); context.lineTo(width, y); context.stroke(); } } function drawPlayers() { latestState.players.forEach(function drawPlayer(player) { const shipSize = 24; const angle = player.angle; const noseX = player.x + Math.cos(angle) * shipSize; const noseY = player.y + Math.sin(angle) * shipSize; const leftWingX = player.x + Math.cos(angle + Math.PI * 0.75) * shipSize * 0.6; const leftWingY = player.y + Math.sin(angle + Math.PI * 0.75) * shipSize * 0.6; const rightWingX = player.x + Math.cos(angle - Math.PI * 0.75) * shipSize * 0.6; const rightWingY = player.y + Math.sin(angle - Math.PI * 0.75) * shipSize * 0.6; const isFirstLocalPlayer = player.id === (localPlayerIds[0] ?? null); const isSecondLocalPlayer = player.id === (localPlayerIds[1] ?? null); if (isFirstLocalPlayer === true) { context.fillStyle = "rgba(34, 211, 238, 0.9)"; } else { if (isSecondLocalPlayer === true) { context.fillStyle = "rgba(134, 239, 172, 0.9)"; } else { context.fillStyle = "rgba(244, 114, 182, 0.9)"; } } context.beginPath(); context.moveTo(noseX, noseY); context.lineTo(leftWingX, leftWingY); context.lineTo(rightWingX, rightWingY); context.closePath(); context.fill(); }); } function buildInputFromKeys(keysState) { return { up: keysState.up, down: keysState.down, left: keysState.left, right: keysState.right, shoot: keysState.shoot }; } function resetKeyState(keysState) { keysState.up = false; keysState.down = false; keysState.left = false; keysState.right = false; keysState.shoot = false; } function setLocalPlayerIds(newIds) { localPlayerIds.length = 0; resetKeyState(playerOneKeys); resetKeyState(playerTwoKeys); if (Array.isArray(newIds) === false) { return; } newIds.forEach(function storePlayerId(value) { if (typeof value === "string") { localPlayerIds.push(value); } }); } function drawBullets() { context.fillStyle = "rgba(248, 250, 252, 0.9)"; latestState.bullets.forEach(function drawBullet(bullet) { context.beginPath(); context.arc(bullet.x, bullet.y, 6, 0, Math.PI * 2); context.fill(); }); } window.addEventListener("keydown", function handleKeyDown(event) { handleKeyChange(event, true); }); window.addEventListener("keyup", function handleKeyUp(event) { handleKeyChange(event, false); }); window.setInterval(sendInput, 60); connect(); draw();