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' }));
};