From 0802007a61d91c06274f8c21c767aaacf317675e Mon Sep 17 00:00:00 2001 From: Simon Oberzier Date: Tue, 10 Feb 2026 17:56:21 +0100 Subject: [PATCH] Arrows for planning --- public/index.html | 130 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 107 insertions(+), 23 deletions(-) diff --git a/public/index.html b/public/index.html index 937229a..4ea08eb 100644 --- a/public/index.html +++ b/public/index.html @@ -15,6 +15,8 @@ --black: #1d3557; --white: #ffffff; --green: #10b981; + --arrow-primary: #f59e0b; + --arrow-secondary: #10b981; } body { @@ -132,6 +134,8 @@ width: 100%; height: auto; display: block; + user-select: none; + -webkit-user-select: none; } .edge { @@ -175,7 +179,29 @@ stroke-dasharray: 4; } - /* Victory Banner */ + .arrow { + pointer-events: none; + stroke-width: 4; + stroke-linecap: round; + opacity: 1; + } + + .arrow-pri { + stroke: var(--arrow-primary); + } + + .arrow-sec { + stroke: var(--arrow-secondary); + } + + #head-pri { + fill: var(--arrow-primary); + } + + #head-sec { + fill: var(--arrow-secondary); + } + #victory-banner { position: absolute; top: 50%; @@ -329,7 +355,7 @@ - +

WOLF & @@ -364,7 +390,16 @@

SPIEL BEENDET

- + + + + + + + + + +
@@ -384,11 +419,15 @@ let roomId = params.get("room"); let isLocal = params.get("mode") === "local"; - // --- STATE & HISTORY --- let ws, myRole, selected = null; let state = null; let history = []; let historyIndex = -1; + let plannedArrows = []; + + let dragStartNode = null; + let dragStartTime = 0; + let rightClickStart = null; const pos = { 0: [80, 210], 1: [180, 110], 2: [180, 210], 3: [180, 310], 4: [280, 110], 5: [280, 210], 6: [280, 310], 7: [380, 110], 8: [380, 210], 9: [380, 310], 10: [480, 210] }; const edges = [[0, 1], [0, 2], [0, 3], [1, 2], [2, 3], [1, 4], [2, 5], [3, 6], [4, 5], [5, 6], [4, 7], [5, 8], [6, 9], [7, 8], [8, 9], [5, 1], [5, 3], [5, 7], [5, 9], [7, 10], [8, 10], [9, 10]]; @@ -410,6 +449,7 @@ const isAtEnd = historyIndex === history.length - 1; if (newState.moveCount === 0) { history = [JSON.parse(JSON.stringify(newState))]; + plannedArrows = []; } else if (!state || newState.moveCount > state.moveCount) { history.push(JSON.parse(JSON.stringify(newState))); } @@ -466,35 +506,39 @@ nextState.moveCount++; nextState.winner = checkWinConditions(nextState); history = history.slice(0, historyIndex + 1); + plannedArrows = []; updateHistory(nextState, true); } function checkWinConditions(g) { - // 1. Wolf reaches sheep starting line (Row 0) if (g.wolf === 0) return "wolf"; - - // 2. Max moves reached if (g.moveCount >= 40) return "wolf"; - - // 3. Wolf is trapped const canWolfMove = bWolf[g.wolf].some(to => !g.sheep.includes(to)); if (!canWolfMove) return "sheep"; - - // 4. Sheep are trapped (Wolf wins) const canAnySheepMove = g.sheep.some(sPos => { return bSheep[sPos].some(to => to !== g.wolf && !g.sheep.includes(to)); }); if (!canAnySheepMove) return "wolf"; - return null; } + function toggleArrow(from, to, type) { + if (from === to || !bWolf[from].includes(to)) return; + const idx = plannedArrows.findIndex(a => a.from === from && a.to === to && a.type === type); + if (idx > -1) plannedArrows.splice(idx, 1); + else plannedArrows.push({ from, to, type }); + render(); + } + function render() { const viewState = history[historyIndex]; if (!viewState) return; const isViewingHistory = historyIndex < history.length - 1; const svg = document.querySelector("svg"); + + const defs = svg.querySelector("defs"); svg.innerHTML = ""; + svg.appendChild(defs); edges.forEach(([a, b]) => { const l = document.createElementNS("http://www.w3.org/2000/svg", "line"); @@ -537,17 +581,42 @@ const isPossible = canInteract && selected !== null && (viewState.turn === "wolf" ? bWolf[selected] : bSheep[selected]).includes(i) && !isWolf && !isSheep; const g = document.createElementNS("http://www.w3.org/2000/svg", "g"); - g.onclick = () => { - if (viewState.winner || (!isLocal && isViewingHistory)) return; - if (!isLocal && state.turn !== myRole) return; - const isMine = (viewState.turn === "wolf" && isWolf) || (viewState.turn === "sheep" && isSheep); - if (isMine) selected = i; - else if (isPossible) { - if (isLocal) handleLocalMove(selected, i); - else ws.send(JSON.stringify({ from: selected, to: i })); - selected = null; - } else selected = null; - render(); + + g.onmousedown = (e) => { + if (e.button === 0) { + dragStartNode = i; + dragStartTime = Date.now(); + } + if (e.button === 2) rightClickStart = i; + }; + + g.onmouseup = (e) => { + if (e.button === 0 && dragStartNode !== null) { + const duration = Date.now() - dragStartTime; + if (dragStartNode === i && duration < 250) { + if (viewState.winner || (!isLocal && isViewingHistory)) return; + if (!isLocal && state.turn !== myRole) return; + const isMine = (viewState.turn === "wolf" && isWolf) || (viewState.turn === "sheep" && isSheep); + if (isMine) selected = i; + else if (isPossible) { + plannedArrows = []; + if (isLocal) handleLocalMove(selected, i); + else ws.send(JSON.stringify({ from: selected, to: i })); + selected = null; + } else selected = null; + render(); + } else if (dragStartNode !== i) { + toggleArrow(dragStartNode, i, 'sec'); + } + dragStartNode = null; + } + + if (e.button === 2 && rightClickStart !== null) { + if (rightClickStart !== i) toggleArrow(rightClickStart, i, 'pri'); + else plannedArrows = []; + rightClickStart = null; + render(); + } }; const c = document.createElementNS("http://www.w3.org/2000/svg", "circle"); @@ -563,6 +632,20 @@ t.textContent = i; g.appendChild(t); svg.appendChild(g); } + + plannedArrows.forEach(arrow => { + const s = pos[arrow.from]; const e = pos[arrow.to]; + const dx = e[0] - s[0]; const dy = e[1] - s[1]; + const angle = Math.atan2(dy, dx); + const x1 = s[0] + Math.cos(angle) * 28; const y1 = s[1] + Math.sin(angle) * 28; + const x2 = e[0] - Math.cos(angle) * 32; const y2 = e[1] - Math.sin(angle) * 32; + const line = document.createElementNS("http://www.w3.org/2000/svg", "line"); + line.setAttribute("x1", x1); line.setAttribute("y1", y1); + line.setAttribute("x2", x2); line.setAttribute("y2", y2); + line.setAttribute("class", `arrow ${arrow.type === 'pri' ? 'arrow-pri' : 'arrow-sec'}`); + line.setAttribute("marker-end", `url(#arrowhead-${arrow.type})`); + svg.appendChild(line); + }); } function updateRoomList(rooms) { @@ -582,6 +665,7 @@ function startLocal() { window.location.search = `?mode=local`; } document.getElementById("restartBtn").onclick = () => { + plannedArrows = []; if (isLocal) initLocalState(); else ws.send(JSON.stringify({ type: 'restart' })); };