dao-game / commit
Lots of changes:
game.js | 224 +++++++++++++++++++++++++++++++++++++++++++++++++------------ index.html | 8 +-- styles.css | 62 ++++++++++++++--- 3 files changed, 236 insertions(+), 58 deletions(-) diff --git a/game.js b/game.js index aa4619b..0efce2a 100644 --- a/game.js +++ b/game.js @@ -1,10 +1,8 @@ -// DOM references -const cells = () => Array.from(document.querySelectorAll("td")); -const redPieces = () => Array.from(document.querySelectorAll(".red-piece")); -const bluePieces = () => Array.from(document.querySelectorAll(".blue-piece")); -const redTurnText = () => Array.from(document.querySelectorAll(".red-turn-text")); -const blueTurnText = () => Array.from(document.querySelectorAll(".blue-turn-text")); -const divider = () => Array.from(document.querySelector("#divider")); +// Constants +const selectedPieceClassName = 'selected-piece'; +const selectedPlayerTextClassName = 'selected-player-text'; +const activePlayerPieceClassName = 'active-player-piece'; +const blinkingClassName = 'blinking'; // board const board = [ @@ -13,16 +11,24 @@ const board = [ null, 4, 5, null, 6, null, null, 7 ]; +const rows = 4; +const cols = 4; -/** @type {{element: Element, type: string, listener: (event: Event) => void}[]} */ -const listeners = []; +// DOM references +const cells = () => Array.from(document.querySelectorAll("td")); +const redPieces = () => Array.from(document.querySelectorAll(".red-piece")); +const bluePieces = () => Array.from(document.querySelectorAll(".blue-piece")); +const redTurnText = () => Array.from(document.querySelectorAll(".red-turn-text")); +const blueTurnText = () => Array.from(document.querySelectorAll(".blue-turn-text")); +const divider = () => Array.from(document.querySelector("#divider")); // player properties let isRedTurn = false; -/** @type {NodeListOf<Element>} */ -let playerPieces; let selectedPieceId = -1; +/** @type {{element: Element, type: string, listener: (event: Event) => void}[]} */ +const listeners = []; + /** * @param {Element} element * @param {string} eventName @@ -40,19 +46,50 @@ const removeListeners = () => { listeners.length = 0; } +class Coord { + /** + * Creates an instance of Coord. + * @param {number} row + * @param {number} col + * @memberof Coord + */ + constructor(row, col) { + this.row = row; + this.col = col; + } +} +/** + * @param {Coord} data + * @return {number} + */ +const coordToBoardIndex = ({ row, col }) => { + const index = row * 4 + col; + return index; +}; +/** + * @param {number} index + * @return {Coord} + */ +const boardIndexToCoord = (index) => { + const col = index % 4; + const row = Math.floor(index / 4); + return { row, col }; +}; + const resetBoard = () => { // resetPieces /** @param {Element} piece */ const resetPiece = (piece) => { - piece.classList.remove(selectedPieceClassName); - piece.classList.remove(activePlayerPieceClassName); + //piece.classList.remove(selectedPieceClassName); + // piece.classList.remove(activePlayerPieceClassName); }; redPieces().forEach(resetPiece); bluePieces().forEach(resetPiece); // resetCells cells().forEach(cell => { - cell.classList.remove('blinking'); + cell.classList.remove(blinkingClassName); + cell.style.zIndex = 0 }); // remove all listeners @@ -61,65 +98,165 @@ const resetBoard = () => { const setActivePlayerPieces = () => { // mark active pieces - playerPieces = isRedTurn ? redPieces() : bluePieces() + const playerPieces = isRedTurn ? redPieces() : bluePieces() playerPieces.forEach(pp => { pp.classList.add(activePlayerPieceClassName); - // pp.addEventListener('click', selectPiece); addListener(pp, 'click', selectPiece); }); + const otherPieces = isRedTurn ? bluePieces() : redPieces(); + otherPieces.forEach(op => { + if (op.classList.contains(activePlayerPieceClassName)) { + op.classList.toggle(activePlayerPieceClassName); + } + }); }; +/** + * @param {number} pieceId + * @returns {number[]} + */ +const getAllowedMoveIndicesForIndex = (boardIndex) => { + // clock face directions + const directions = [ + { row: 1, col: 0 }, // up + { row: 1, col: 1 }, // up-right + { row: 0, col: 1 }, // right + { row: -1, col: 1 }, // right-down + { row: -1, col: 0 }, // down + { row: -1, col: -1 }, // down-left + { row: 0, col: -1 }, // left + { row: 1, col: -1 }, // left-up + ] + const coord = boardIndexToCoord(boardIndex); + + const farthestCoords = directions + .map(direction => getFarthestCoord(coord, direction)) + .filter(farCoord => farCoord !== undefined); + + const farthestIndices = farthestCoords.map(coordToBoardIndex); + return farthestIndices; +}; +/** + * + * + * @param {Coord} startCoord + * @param {Coord} direction + * @returns {Coord} + */ +const getFarthestCoord = (startCoord, direction) => { + let farthestPos = undefined; + let testPos = startCoord; + while (true) { + const pos = new Coord(testPos.row + direction.row, testPos.col + direction.col); + const index = coordToBoardIndex(pos); + const withinBounds = + pos.row >= 0 && pos.row < rows && + pos.col >= 0 && pos.col < cols; + const isOccupied = Number.isInteger(board[index]); + if (isOccupied || !withinBounds) { + break; + } else { + farthestPos = pos; + testPos = pos; + } + } + return farthestPos; +}; + +/** + * + * + * @param {number} pieceId + * @return {Element} + */ +const getPieceElementById = (pieceId) => { + return document.getElementById(pieceId); +} + /** @param {Event} event */ const selectPiece = (event) => { - resetBoard(); - setActivePlayerPieces(); + // resetBoard(); + // setActivePlayerPieces(); + const previousSelectedPieceId = selectedPieceId; + if (previousSelectedPieceId >= 0) { + const previousPiece = getPieceElementById(previousSelectedPieceId); + if (previousPiece.classList.contains(selectedPieceClassName)) { + previousPiece.classList.toggle(selectedPieceClassName); + } + cells().forEach(cell => { + cell.classList.remove(blinkingClassName); + }); + } // read event data /** @type {Element} */ const piece = event.target; const pieceId = parseInt(piece.id); - const pieceIndex = board.indexOf(pieceId); // set as selected selectedPieceId = pieceId; piece.classList.add(selectedPieceClassName); - // blink allowed moves - const allowedMoveIndices = board - .map((value, index) => { - const isOccupied = index === pieceIndex || value !== null; - return isOccupied ? -1 : index; - }) - .filter(v => v >= 0); + // hook up allowed moves for: + // 1. blink + // 2. complete the move + const pieceIndex = board.indexOf(pieceId); + const allowedMoveIndices = getAllowedMoveIndicesForIndex(pieceIndex); allowedMoveIndices.forEach(i => { const cell = cells()[i]; - // cell.addEventListener('click', () => makeMove(i)) addListener(cell, 'click', () => makeMove(i)); - cell.classList.add('blinking'); + cell.classList.add(blinkingClassName); }); }; -const selectedPieceClassName = 'selected-piece'; -const selectedPlayerTextClassName = 'selected-player-text'; -const activePlayerPieceClassName = 'active-player-piece'; - const makeMove = (destIndex) => { // find current piece - const piece = document.getElementById(selectedPieceId); + const piece = getPieceElementById(selectedPieceId); const pieceId = Number.parseInt(piece.id) const pieceIndex = board.indexOf(pieceId); - // move piece to destination cell - const pieceHtml = cells()[pieceIndex].innerHTML; - cells()[pieceIndex].innerHTML = ''; - cells()[destIndex].innerHTML = pieceHtml; + // do animation logic + const startCoords = boardIndexToCoord(pieceIndex); + const endCoords = boardIndexToCoord(destIndex); + const coordDiff = new Coord( + endCoords.row - startCoords.row, + endCoords.col - startCoords.col); + + const cellWidth = cells()[0].clientWidth; + const cellHeight = cells()[0].clientHeight; + + const diffX = coordDiff.col * cellWidth; + const diffY = coordDiff.row * cellHeight; - // move piece on board - board[pieceIndex] = null; - board[destIndex] = pieceId; + // const startCell = cells()[pieceIndex]; + // const destCell = cells()[destIndex]; - checkForWin(); - nextMove(); + removeListeners(); + + const animation = piece.animate([ + // keyframes + { opacity: 1 }, + { opacity: 0 }, + ], { + duration: 500, + iterations: 1 + }); + + animation.onfinish = () => { + // move piece to destination cell + const pieceHtml = cells()[pieceIndex].innerHTML; + cells()[pieceIndex].innerHTML = ''; + cells()[destIndex].innerHTML = pieceHtml; + + // move piece on board + board[pieceIndex] = null; + board[destIndex] = pieceId; + + checkForWin(); + const elem = getPieceElementById(pieceId); + elem.classList.remove(selectedPieceClassName); + nextMove(); + }; } const nextMove = () => { @@ -140,12 +277,13 @@ const nextMove = () => { redTurnText().forEach(t => t.classList.add(selectedPlayerTextClassName)); } + selectedPieceId = -1; resetBoard(); setActivePlayerPieces(); }; const checkForWin = () => { - + console.error(`testing win for ${isRedTurn ? 'red' : 'blue'}`); }; diff --git a/index.html b/index.html index 50e6c89..817ac73 100644 --- a/index.html +++ b/index.html @@ -12,7 +12,7 @@ <body> <main> <div class="red-turn-text mobile"> - Red's turn + Red </div> <table> @@ -44,16 +44,16 @@ <div class="desktop"> <div class="red-turn-text"> - Red's turn + Red </div> <p id="divider">|</p> <div class="blue-turn-text"> - Blue's turn + Blue </div> </div> <div class="blue-turn-text mobile"> - Blue's turn + Blue </div> </main> </body> diff --git a/styles.css b/styles.css index cd48150..777906d 100644 --- a/styles.css +++ b/styles.css @@ -19,6 +19,7 @@ body { div { display: flex; justify-content: space-between; + z-index: 0; } .mobile { @@ -71,26 +72,28 @@ p { .red-piece { width: 20px; height: 20px; - padding: 12px; + padding: 10px; background-color: crimson; - border: 1px solid white; border-radius: 50px; + animation: activeToDisabled 1s 1; } .blue-piece { width: 20px; height: 20px; - padding: 12px; + padding: 10px; background-color: mediumblue; - border: 1px solid white; border-radius: 50px; + animation: activeToDisabled 1s 1; } .active-player-piece { - border: 5px solid white; + width: 35px; + height: 35px; + animation: disabledToActive 1s 1; } .selected-piece { - border-color: greenyellow; + animation: activePulsing 1s infinite; } .selected-player-text { @@ -98,17 +101,54 @@ p { } .blinking { - /* opacity: .5; */ - /* background-color: brown; */ - /* color: aqua; */ - animation: blinkingText 1s infinite; + animation: blinkingText 1.5s infinite; } @keyframes blinkingText { 0%{opacity: 1;} - 50%{opacity: .5;} + 50%{opacity: .3;} 100%{opacity: 1;} } +@keyframes disabledToActive { + from { + width: 20px; + height: 20px; + } + to { + width: 35px; + height: 35px; + } +} +@keyframes activeToDisabled { + from { + width: 35px; + height: 35px; + } + to { + width: 20px; + height: 20px; + } +} + + +/* .pulsing { + animation: pulsingSize 1.5s infinite; +} */ +@keyframes activePulsing { + from { + width: 35px; + height: 35px; + } + 50% { + width: 25px; + height: 25px; + } + to { + width: 35px; + height: 35px; + } +} + /* breaks */ /* @media screen and (max-width: 560px) {