diff --git a/demo/index.html b/demo/index.html index 2025ec0..2f03f9a 100644 --- a/demo/index.html +++ b/demo/index.html @@ -5,6 +5,8 @@ Operation BattleGrid: Strategic Frontiers + +
diff --git a/demo/moves.engine.js b/demo/moves.engine.js new file mode 100644 index 0000000..f7bc469 --- /dev/null +++ b/demo/moves.engine.js @@ -0,0 +1,213 @@ +let colPerPiece = {}; +const columns = "ABCDEFGHIJK".split(""); +for (let col of columns) { + for (let row = 1; row <= 11; row++) { + const cellId = `cell-${col}${row}`; + colPerPiece[cellId] = null; + } +} + +function suggestMoves(cell) { + clearHighlights(); + const piece = cell.querySelector(".piece"); + const pieceType = piece.getAttribute("pieceType"); + const name = piece.getAttribute("name"); + let moves; + switch (name) { + case INFANTRY: + moves = getInfantryMoves(cell.id, pieceType); + legalMoves = moves; + highlightSquares(moves); + break; + case TANK: + moves = getTankMoves(cell.id); + legalMoves = moves; + highlightSquares(moves); + break; + case GHOST: + moves = getGhostMoves(cell.id); + legalMoves = moves; + highlightSquares(moves); + break; + default: + console.log("Not Implemented"); + } +} + +function getInfantryMoves(currentCell, pieceColor) { + const moves = []; + + // Extract the column and row from the current cell + const column = currentCell.charAt(5); + const row = parseInt(currentCell.slice(6)); + + // Determine the direction of movement based on the piece color + const direction = pieceColor === "black" ? 1 : -1; + + // Forward moves (1 or 2 squares) + for (let i = 1; i <= 2; i++) { + const newRow = row + i * direction; + if (newRow >= 1 && newRow <= 11) { + moves.push(`cell-${column}${newRow}`); + } + } + + // Diagonal captures (up to 2 squares) + const columnCharCode = column.charCodeAt(0); + for (let i = 1; i <= 2; i++) { + const newRow = row + i * direction; + if (newRow >= 1 && newRow <= 11) { + // Left diagonal capture + if (columnCharCode - i >= "A".charCodeAt(0)) { + const id = `cell-${String.fromCharCode(columnCharCode - i)}${newRow}`; + checkIfCellContainsAPiece(id) ? moves.push(id) : null; + } + // Right diagonal capture + if (columnCharCode + i <= "K".charCodeAt(0)) { + const id = `cell-${String.fromCharCode(columnCharCode + i)}${newRow}`; + checkIfCellContainsAPiece(id) ? moves.push(id) : null; + } + } + } + + return moves; +} + +function getTankMoves(currentCell) { + console.log(currentCell); + const moves = []; + const currentColumn = currentCell.charAt(5); + const currentRow = parseInt(currentCell.slice(6)); + const cell = document.querySelector(`#${currentCell}`); + const currentCellPiece = cell.querySelector(".piece"); + + const columns = "ABCDEFGHIJK".split(""); + const rows = Array.from({ length: 11 }, (_, i) => i + 1); + + columns.forEach((col) => { + if (col !== currentColumn) { + const id = `cell-${col}${currentRow}`; + const cellShouldBeAdded = checkIfCellShouldBeActive(id, currentCellPiece.getAttribute("pieceType")); + if (cellShouldBeAdded) moves.push(`cell-${col}${currentRow}`); + } + }); + + // Vertical moves (all cells in the same column) + rows.forEach((row) => { + if (row !== currentRow) { + const id = `cell-${currentColumn}${row}`; + const cellShouldBeAdded = checkIfCellShouldBeActive(id, currentCellPiece.getAttribute("pieceType")); + if (cellShouldBeAdded) moves.push(`cell-${currentColumn}${row}`); + } + }); + + return moves; +} + +function getGhostMoves(currentCell) { + const moves = []; + const currentColumn = currentCell.charAt(5); + const currentRow = parseInt(currentCell.slice(6)); + const cell = document.querySelector(`#${currentCell}`); + const currentCellPiece = cell.querySelector(".piece"); + + const columns = "ABCDEFGHIJK".split(""); + const rows = Array.from({ length: 11 }, (_, i) => i + 1); + + // Define possible moves for the custom piece + const ghostMoves = [ + { col: 2, row: 1 }, + { col: 1, row: 2 }, + { col: -1, row: 2 }, + { col: -2, row: 1 }, + { col: -2, row: -1 }, + { col: -1, row: -2 }, + { col: 1, row: -2 }, + { col: 2, row: -1 }, + { col: 3, row: 1 }, + { col: 3, row: -1 }, + ]; + + columns.forEach((col) => { + rows.forEach((row) => { + if (!(col === currentColumn && row === currentRow)) { + const id = `cell-${col}${row}`; + const cellShouldBeAdded = checkIfCellShouldBeActive(id, currentCellPiece.getAttribute("pieceType")); + + // Check if the cell is a valid move for the custom piece + for (const move of ghostMoves) { + const targetCol = currentColumn.charCodeAt(0) + move.col; + const targetRow = currentRow + move.row; + + if (col.charCodeAt(0) === targetCol && row === targetRow && cellShouldBeAdded) { + moves.push(`cell-${col}${row}`); + } + } + } + }); + }); + console.log(moves); + return moves; +} + +/** + * utils functions + **/ +function highlightSquares(cellIds) { + cellIds.forEach((cellId) => { + const cell = document.getElementById(cellId); + if (cell) { + checkIfCellContainsAPiece(cell.id) + ? cell.classList.add("capture-highlight") + : cell.classList.add("positive-highlight"); + } + }); +} + +function clearHighlights() { + const captureHighlights = document.querySelectorAll(".capture-highlight"); + captureHighlights.forEach((element) => { + element.classList.remove("capture-highlight"); + }); + + const positiveHighlights = document.querySelectorAll(".positive-highlight"); + positiveHighlights.forEach((element) => { + element.classList.remove("positive-highlight"); + }); +} + +function checkIfCellContainsAPiece(id) { + const piece = colPerPiece[id].querySelector(".piece"); + if (piece) { + return true; + } + + return false; +} + +function checkIfCellShouldBeActive(id, type) { + try { + const piece = colPerPiece[id].querySelector(".piece"); + console.log(piece, id, type); + if (piece) { + if (piece.getAttribute("pieceType") === type) { + return false; + } + } + + return true; + } catch (e) { + return true; + } +} + +/** + * Check if the move if legal from the moves piece is allowed to move + * @param {*} cellId + */ +function isMoveLegal(cellId) { + if (legalMoves.includes(cellId)) { + return true; + } + return false; +} diff --git a/demo/pieces.js b/demo/pieces.js new file mode 100644 index 0000000..12eeac6 --- /dev/null +++ b/demo/pieces.js @@ -0,0 +1,114 @@ +const INFANTRY = "infantry"; +const TANK = "tank"; +const GHOST = "ghost"; +const ECHO = "echo"; +const DRONE = "drone"; +const PEACEKEEPER = "peacekeeper"; +const COMMAND_CENTER = "command_center"; + +const PIECE_TYPE = { + BLACK: 'black', + WHITE: 'white' +} + +const whitePiecesOrder = [ + { + name: INFANTRY, + icon: "♙", + }, + { + name: TANK, + icon: "♖", + }, + { + name: GHOST, + icon: "♘", + }, + { + name: ECHO, + icon: "◎", + }, + { + name: DRONE, + icon: "♗", + }, + { + name: PEACEKEEPER, + icon: "♕", + }, + { + name: COMMAND_CENTER, + icon: "♔", + }, + { + name: PEACEKEEPER, + icon: "♕", + }, + { + name: DRONE, + icon: "♗", + }, + { + name: ECHO, + icon: "◎", + }, + { + name: GHOST, + icon: "♘", + }, + { + name: TANK, + icon: "♖", + }, +]; + +const blackPiecesOrder = [ + { + name: INFANTRY, + icon: "♟", + }, + { + name: TANK, + icon: "♜", + }, + { + name: GHOST, + icon: "♞", + }, + { + name: ECHO, + icon: "◉", + }, + { + name: DRONE, + icon: "♝", + }, + { + name: PEACEKEEPER, + icon: "♛", + }, + { + name: COMMAND_CENTER, + icon: "♚", + }, + { + name: PEACEKEEPER, + icon: "♛", + }, + { + name: DRONE, + icon: "♝", + }, + { + name: ECHO, + icon: "◉", + }, + { + name: GHOST, + icon: "♞", + }, + { + name: TANK, + icon: "♜", + }, +]; diff --git a/demo/script.js b/demo/script.js index abd8c55..ae780c5 100644 --- a/demo/script.js +++ b/demo/script.js @@ -1,238 +1,245 @@ -// Piece icons -const whitePiecesOrder = ["♙", "♖", "♘", "◎", "♗", "♕", "♔", "♕", "♗", "◎", "♘", "♖"]; -const blackPiecesOrder = ["♟", "♜", "♞", "◉", "♝", "♛", "♚", "♛", "♝", "◉", "♞", "♜"]; - // Game state let selectedCell = null; let moveHistory = []; let lastMovedPiece = "black"; +let legalMoves = []; // Utility functions const createLabelCell = (text) => { - const labelCell = document.createElement("div"); - labelCell.className = "label"; - labelCell.textContent = text; - return labelCell; + const labelCell = document.createElement("div"); + labelCell.className = "label"; + labelCell.textContent = text; + return labelCell; }; -const createPiece = (icon, color) => { - const piece = document.createElement("span"); - piece.className = `piece ${color}`; - piece.setAttribute("pieceType", color); - piece.textContent = icon; - return piece; +const createPiece = (icon, color, location, name) => { + const piece = document.createElement("span"); + piece.className = `piece ${color}`; + piece.setAttribute("pieceType", color); + piece.setAttribute("name", name); + piece.setAttribute("location", location); + piece.textContent = icon; + return piece; }; function resetGameState() { - // Clear the move history, selected cell, and grid - moveHistory = []; - selectedCell = null; - lastMovedPiece = "black"; - document.getElementById("grid").innerHTML = ""; + // Clear the move history, selected cell, and grid + moveHistory = []; + selectedCell = null; + lastMovedPiece = "black"; + document.getElementById("grid").innerHTML = ""; - initializeBoard(); + initializeBoard(); } function undoMove() { - const lastMove = moveHistory.pop(); - if (lastMove) { - lastMove.from.appendChild(lastMove.movedPiece); - if (lastMove.capturedPiece) { - lastMove.to.appendChild(lastMove.capturedPiece); - } - lastMovedPiece = lastMove.movedPiece.getAttribute("pieceType") === "white" ? "black" : "white"; + const lastMove = moveHistory.pop(); + if (lastMove) { + lastMove.from.appendChild(lastMove.movedPiece); + if (lastMove.capturedPiece) { + lastMove.to.appendChild(lastMove.capturedPiece); } + lastMovedPiece = lastMove.movedPiece.getAttribute("pieceType") === "white" ? "black" : "white"; + } } function exportMoves() { - if (moveHistory.length === 0) return; - - const moveStrings = moveHistory.map(move => { - const fromCellId = move.from.id.replace("cell-", ""); - const toCellId = move.to.id.replace("cell-", ""); - const movedPiece = move.movedPiece.textContent; - const capturedPiece = move.capturedPiece ? move.capturedPiece.textContent : ""; - return `${movedPiece} from ${fromCellId} to ${toCellId}${capturedPiece ? ` capturing ${capturedPiece}` : ""}`; - }); - - const exportString = moveStrings.join(";"); - console.log(exportString); - - // Copy to clipboard or present it to the user in some way - navigator.clipboard.writeText(exportString).then(() => { - alert("Moves exported to clipboard!"); - }).catch(err => { - console.error("Could not copy moves to clipboard: ", err); + if (moveHistory.length === 0) return; + + const moveStrings = moveHistory.map((move) => { + const fromCellId = move.from.id.replace("cell-", ""); + const toCellId = move.to.id.replace("cell-", ""); + const movedPiece = move.movedPiece.textContent; + const capturedPiece = move.capturedPiece ? move.capturedPiece.textContent : ""; + return `${movedPiece} from ${fromCellId} to ${toCellId}${capturedPiece ? ` capturing ${capturedPiece}` : ""}`; + }); + + const exportString = moveStrings.join(";"); + console.log(exportString); + + // Copy to clipboard or present it to the user in some way + navigator.clipboard + .writeText(exportString) + .then(() => { + alert("Moves exported to clipboard!"); + }) + .catch((err) => { + console.error("Could not copy moves to clipboard: ", err); }); } function importMoves(importString) { - if (importString.trim().length === 0) return; - - // Clear current game state before importing new moves - resetGameState(); - - const moveStrings = importString.split(";"); - moveStrings.forEach(moveString => { - if (moveString.trim() === "") return; - - // Extract the details of the move - const [movedPiece, fromText, fromCellId, toText, toCellId] = moveString.split(" "); - const captured = moveString.includes("capturing"); - - // Find the corresponding cells in the DOM - const fromCell = document.getElementById(`cell-${fromCellId}`); - const toCell = document.getElementById(`cell-${toCellId}`); - - // If there is a capturing move, handle the captured piece - let capturedPieceElement = null; - if (captured) { - capturedPieceElement = toCell.querySelector(".piece"); - if (capturedPieceElement) { - toCell.removeChild(capturedPieceElement); - } - } - - // Move the piece to the new cell - let pieceElement = fromCell.querySelector(".piece"); - if (!pieceElement) { - let color = whitePiecesOrder.includes(movedPiece) ? "white" : "black"; - pieceElement = document.createElement("span"); - pieceElement.className = `piece ${color}`; - pieceElement.setAttribute("pieceType", color); - pieceElement.textContent = movedPiece; - } - toCell.appendChild(pieceElement); - - // Update the move history - moveHistory.push({ - from: fromCell, - to: toCell, - movedPiece: pieceElement, - capturedPiece: capturedPieceElement - }); - lastMovedPiece = pieceElement.getAttribute("pieceType"); + if (importString.trim().length === 0) return; + + // Clear current game state before importing new moves + resetGameState(); + + const moveStrings = importString.split(";"); + moveStrings.forEach((moveString) => { + if (moveString.trim() === "") return; + + // Extract the details of the move + const [movedPiece, fromText, fromCellId, toText, toCellId] = moveString.split(" "); + const captured = moveString.includes("capturing"); + + // Move the piece to the new cell + let pieceElement = fromCell.querySelector(".piece"); + if (!pieceElement) { + let color = whitePiecesOrder.includes(movedPiece) ? "white" : "black"; + pieceElement = document.createElement("span"); + pieceElement.className = `piece ${color}`; + pieceElement.setAttribute("pieceType", color); + pieceElement.textContent = movedPiece; + } + toCell.appendChild(pieceElement); + + // Update the move history + moveHistory.push({ + from: fromCell, + to: toCell, + movedPiece: pieceElement, + capturedPiece: capturedPieceElement, }); + lastMovedPiece = pieceElement.getAttribute("pieceType"); + }); } function handleImportClick() { - const importString = prompt("Please enter the move history string:"); - if (importString) { - importMoves(importString); - } + const importString = prompt("Please enter the move history string:"); + if (importString) { + importMoves(importString); + } } function handleSkipClick() { - lastMovedPiece = lastMovedPiece === "white" ? "black" : "white"; - if (selectedCell) { - selectedCell.classList.remove("selected"); - selectedCell = null; - } + lastMovedPiece = lastMovedPiece === "white" ? "black" : "white"; + if (selectedCell) { + selectedCell.classList.remove("selected"); + selectedCell = null; + } } // Event handlers function onCellClick(event) { - const cell = event.currentTarget; - - if (selectedCell && selectedCell !== cell) { - const pieceToMove = selectedCell.querySelector(".piece"); - if (pieceToMove) { - const capturedPiece = cell.querySelector(".piece"); - if (capturedPiece) { - if (pieceToMove.className === capturedPiece.className) { - selectedCell.classList.remove("selected"); - selectedCell = null; - capturedPiece.click(); - return; - } - cell.removeChild(capturedPiece); - } - - cell.appendChild(pieceToMove); - - moveHistory.push({ - from: selectedCell, - to: cell, - movedPiece: pieceToMove, - capturedPiece: capturedPiece - }); - - selectedCell.classList.remove("selected"); - selectedCell = null; - lastMovedPiece = pieceToMove.getAttribute("pieceType"); - } - } else if (cell.querySelector(".piece")) { - const piece = cell.querySelector(".piece"); - if (piece.getAttribute("pieceType") === lastMovedPiece) return; + const cell = event.currentTarget; + + if (selectedCell && selectedCell !== cell) { + // Move isn't legal but deselect state + if (!isMoveLegal(cell.id)) { + selectedCell.classList.remove("selected"); + selectedCell = null; + legalMoves = []; + clearHighlights(); + } - if (selectedCell) { - selectedCell.classList.remove("selected"); + const pieceToMove = selectedCell.querySelector(".piece"); + + if (pieceToMove) { + const capturedPiece = cell.querySelector(".piece"); + if (capturedPiece) { + if (pieceToMove.className === capturedPiece.className) { + selectedCell.classList.remove("selected"); + selectedCell = null; + capturedPiece.click(); + return; } + cell.removeChild(capturedPiece); + colPerPiece[cell.id] = null; + } + + cell.appendChild(pieceToMove); + + // Update Cellid to cell reference + colPerPiece[selectedCell.id] = null; + colPerPiece[cell.id] = cell; + + moveHistory.push({ + from: selectedCell, + to: cell, + movedPiece: pieceToMove, + capturedPiece: capturedPiece, + }); + + selectedCell.classList.remove("selected"); + selectedCell = null; + lastMovedPiece = pieceToMove.getAttribute("pieceType"); + clearHighlights(); + } + } else if (cell.querySelector(".piece")) { + const piece = cell.querySelector(".piece"); + if (piece.getAttribute("pieceType") === lastMovedPiece) return; - cell.classList.add("selected"); - selectedCell = cell; + if (selectedCell) { + selectedCell.classList.remove("selected"); } + + cell.classList.add("selected"); + selectedCell = cell; + suggestMoves(cell); + } } // Initialize the game board const initializeBoard = () => { - const grid = document.getElementById("grid"); - grid.appendChild(createLabelCell(" ")); - - // Create column labels A-K - for (let i = 0; i < 11; i++) { - grid.appendChild(createLabelCell(String.fromCharCode("A".charCodeAt(0) + i))); + const grid = document.getElementById("grid"); + grid.appendChild(createLabelCell(" ")); + + // Create column labels A-K + for (let i = 0; i < 11; i++) { + grid.appendChild(createLabelCell(String.fromCharCode("A".charCodeAt(0) + i))); + } + + // Create grid cells with pieces + for (let row = 1; row <= 11; row++) { + grid.appendChild(createLabelCell(row.toString())); + + for (let col = 1; col <= 11; col++) { + const cell = document.createElement("div"); + cell.className = "cell"; + cell.id = `cell-${String.fromCharCode("A".charCodeAt(0) + col - 1)}${row}`; + // Update ColPerPiece CellId + colPerPiece[cell.id] = cell; + + if (row === 1 || row === 11) { + const color = row === 1 ? "black" : "white"; + const icons = row === 1 ? blackPiecesOrder : whitePiecesOrder; + cell.appendChild(createPiece(icons[col].icon, color, cell.id, icons[col].name)); + } else if (row === 2 || row === 10) { + const color = row === 2 ? "black" : "white"; + const icons = row === 2 ? blackPiecesOrder : whitePiecesOrder; + cell.appendChild(createPiece(icons[0].icon, color, cell.id, icons[0].name)); + } + + if (row % 2 == col % 2) { + // If both row and col are even or odd, the cell should be dark + cell.classList.add("dark-square"); + } + + grid.appendChild(cell); } + } - // Create grid cells with pieces - for (let row = 1; row <= 11; row++) { - grid.appendChild(createLabelCell(row.toString())); - - for (let col = 1; col <= 11; col++) { - const cell = document.createElement("div"); - cell.className = "cell"; - cell.id = `cell-${String.fromCharCode("A".charCodeAt(0) + col - 1)}${row}`; - - if (row === 1 || row === 11) { - const color = (row === 1) ? "black" : "white"; - const icons = (row === 1) ? blackPiecesOrder : whitePiecesOrder; - cell.appendChild(createPiece(icons[col], color)); - } else if (row === 2 || row === 10) { - const color = (row === 2) ? "black" : "white"; - const icons = (row === 2) ? blackPiecesOrder : whitePiecesOrder; - cell.appendChild(createPiece(icons[0], color)); - } - - if (row % 2 == col % 2) { - // If both row and col are even or odd, the cell should be dark - cell.classList.add("dark-square"); - } - - grid.appendChild(cell); - } - } + // Attach event listeners to cells + const cells = document.querySelectorAll(".cell"); + cells.forEach((cell) => { + cell.addEventListener("click", onCellClick); + }); - // Attach event listeners to cells - const cells = document.querySelectorAll(".cell"); - cells.forEach(cell => { - cell.addEventListener("click", onCellClick); - }); + // Attach event listeners to buttons + const restartButton = document.getElementById("restart"); + restartButton.addEventListener("click", resetGameState); + + const undoButton = document.getElementById("undo"); + undoButton.addEventListener("click", undoMove); + + const exportMovesButton = document.getElementById("export"); + exportMovesButton.addEventListener("click", exportMoves); + + const importButton = document.getElementById("import"); + importButton.addEventListener("click", handleImportClick); - // Attach event listeners to buttons - const restartButton = document.getElementById("restart"); - restartButton.addEventListener("click", resetGameState); - - const undoButton = document.getElementById("undo"); - undoButton.addEventListener("click", undoMove); - - const exportMovesButton = document.getElementById("export"); - exportMovesButton.addEventListener("click", exportMoves); - - const importButton = document.getElementById("import"); - importButton.addEventListener("click", handleImportClick); - - const skipButton = document.getElementById("skip"); - skipButton.addEventListener("click", handleSkipClick); + const skipButton = document.getElementById("skip"); + skipButton.addEventListener("click", handleSkipClick); }; // When the DOM is fully loaded, initialize the game board diff --git a/demo/styles.css b/demo/styles.css index f1013bd..7b07d6f 100644 --- a/demo/styles.css +++ b/demo/styles.css @@ -104,6 +104,15 @@ body { outline: none; } +.positive-highlight { + background-color: rgb(130, 190, 40); + box-shadow: 0 0 10px #ff0; +} + +.capture-highlight { + background-color: rgb(175, 21, 21); + box-shadow: 0 0 10px #ff0; +} @media (max-width:720px){ .center-container{ width: 95%; @@ -175,4 +184,4 @@ body { width: 30px; height: 30px; } - } \ No newline at end of file + }