dao-game / commit
Initial commit:
.vscode/launch.json | 15 ++++ game.js | 153 +++++++++++++++++++++++++++++++++++++++ index.html | 61 ++++++++++++++++ styles.css | 201 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 430 insertions(+) diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..faf7542 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "pwa-chrome", + "request": "launch", + "name": "Launch Chrome against localhost", + "url": "http://127.0.0.1:5500/", + "webRoot": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/game.js b/game.js new file mode 100644 index 0000000..aa4619b --- /dev/null +++ b/game.js @@ -0,0 +1,153 @@ +// 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")); + +// board +const board = [ + 0, null, null, 1, + null, 2, 3, null, + null, 4, 5, null, + 6, null, null, 7 +]; + +/** @type {{element: Element, type: string, listener: (event: Event) => void}[]} */ +const listeners = []; + +// player properties +let isRedTurn = false; +/** @type {NodeListOf<Element>} */ +let playerPieces; +let selectedPieceId = -1; + +/** + * @param {Element} element + * @param {string} eventName + * @param {Event => void} callback + */ +const addListener = (element, type, listener) => { + element.addEventListener(type, listener); + listeners.push({ element, type, listener }); +} +const removeListeners = () => { + listeners.forEach(value => { + const { element, type, listener } = value; + element.removeEventListener(type, listener); + }); + listeners.length = 0; +} + +const resetBoard = () => { + // resetPieces + /** @param {Element} piece */ + const resetPiece = (piece) => { + piece.classList.remove(selectedPieceClassName); + piece.classList.remove(activePlayerPieceClassName); + }; + redPieces().forEach(resetPiece); + bluePieces().forEach(resetPiece); + + // resetCells + cells().forEach(cell => { + cell.classList.remove('blinking'); + }); + + // remove all listeners + removeListeners(); +}; + +const setActivePlayerPieces = () => { + // mark active pieces + playerPieces = isRedTurn ? redPieces() : bluePieces() + playerPieces.forEach(pp => { + pp.classList.add(activePlayerPieceClassName); + // pp.addEventListener('click', selectPiece); + addListener(pp, 'click', selectPiece); + }); +}; + +/** @param {Event} event */ +const selectPiece = (event) => { + resetBoard(); + setActivePlayerPieces(); + + // 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); + allowedMoveIndices.forEach(i => { + const cell = cells()[i]; + // cell.addEventListener('click', () => makeMove(i)) + addListener(cell, 'click', () => makeMove(i)); + cell.classList.add('blinking'); + }); +}; + +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 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; + + // move piece on board + board[pieceIndex] = null; + board[destIndex] = pieceId; + + checkForWin(); + nextMove(); +} + +const nextMove = () => { + // reset text + redTurnText().forEach(t => t.classList.remove(selectedPlayerTextClassName)); + blueTurnText().forEach(t => t.classList.remove(selectedPlayerTextClassName)); + + // changePlayer + // 1. flip flag + // 2. set text + // 3. set click callbacks + if (isRedTurn) { + isRedTurn = false; + blueTurnText().forEach(t => t.classList.add(selectedPlayerTextClassName)); + } + else { + isRedTurn = true; + redTurnText().forEach(t => t.classList.add(selectedPlayerTextClassName)); + } + + resetBoard(); + setActivePlayerPieces(); +}; + +const checkForWin = () => { + +}; + + +// start! +nextMove(); \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..50e6c89 --- /dev/null +++ b/index.html @@ -0,0 +1,61 @@ +<!DOCTYPE html> +<html lang="en"> + + <head> + <meta charset="utf-8"> + <title>Dao Game</title> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="styles.css" /> + <script defer type="module" src="game.js"></script> + </head> + + <body> + <main> + <div class="red-turn-text mobile"> + Red's turn + </div> + + <table> + <tr> + <td class="white"><p class='red-piece' id='0'></p></td> + <td class="black"></td> + <td class="white"></td> + <td class="black"><p class='blue-piece' id='1'></p></td> + </tr> + <tr> + <td class="black"></td> + <td class="white"><p class='red-piece' id='2'></p></td> + <td class="black"><p class='blue-piece' id='3'></p></td> + <td class="white"></td> + </tr> + <tr> + <td class="white"></td> + <td class="black"><p class='blue-piece' id='4'></p></td> + <td class="white"><p class='red-piece' id='5'></p></td> + <td class="black"></td> + </tr> + <tr> + <td class="black"><p class='blue-piece' id='6'></p></td> + <td class="white"></td> + <td class="black"></td> + <td class="white"><p class='red-piece' id='7'></p></td> + </tr> + </table> + + <div class="desktop"> + <div class="red-turn-text"> + Red's turn + </div> + <p id="divider">|</p> + <div class="blue-turn-text"> + Blue's turn + </div> + </div> + + <div class="blue-turn-text mobile"> + Blue's turn + </div> + </main> + </body> + +</html> \ No newline at end of file diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..cd48150 --- /dev/null +++ b/styles.css @@ -0,0 +1,201 @@ +* { + margin: auto; +} + +main { + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; + height: 100vh; +} + +body { + overflow: hidden; + margin: 0px; + background-color: white; +} + +div { + display: flex; + justify-content: space-between; +} + +.mobile { + display: none; +} + +.desktop { + margin: 0; + width: 100%; +} + +td { + width: 65px; + height: 65px; + text-align: center; +} + +/* this puts the circles slightly higher up */ +p { + display: inline-block; +} + +#divider { + font-size: 90px; + margin-left: 15px; + margin-right: 15px; +} + +.red-turn-text { + text-align: center; + color: lightgray; + font-family: "open sans"; + font-size: 55px; +} + +.blue-turn-text { + text-align: center; + font-family: "open sans"; + font-size: 55px; + color: lightgray; +} + +.white { + background-color: orange; +} +.black { + background-color: burlywood; +} + +.red-piece { + width: 20px; + height: 20px; + padding: 12px; + background-color: crimson; + border: 1px solid white; + border-radius: 50px; +} +.blue-piece { + width: 20px; + height: 20px; + padding: 12px; + background-color: mediumblue; + border: 1px solid white; + border-radius: 50px; +} + +.active-player-piece { + border: 5px solid white; +} + +.selected-piece { + border-color: greenyellow; +} + +.selected-player-text { + color: black; +} + +.blinking { + /* opacity: .5; */ + /* background-color: brown; */ + /* color: aqua; */ + animation: blinkingText 1s infinite; +} +@keyframes blinkingText { + 0%{opacity: 1;} + 50%{opacity: .5;} + 100%{opacity: 1;} +} + +/* breaks */ + +/* @media screen and (max-width: 560px) { + .red-piece { + height: 4.5vw; + width: 4.5vw; + } + + .black-piece { + height: 4.5vw; + width: 4.5vw; + } + + td { + width: 12vw; + height: 12vw; + } + + .red-turn-text { + font-size: 40px; + } + + .black-turn-text { + font-size: 40px; + } + + table { + margin: 0; + } + + .mobile { + display: block; + } + + .desktop { + display: none; + } + + .red-turn-text { + -webkit-transform:rotate(-180deg); + -moz-transform:rotate(-180deg); + -o-transform:rotate(-180deg); + transform:rotate(-180deg); + filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2); + } +} + +@media screen and (max-width: 380px) { + .red-piece { + height: 3vw; + width: 3vw; + } + + .black-piece { + height: 3vw; + width: 3vw; + } +} + +@media screen and (max-width: 345px) { + .red-piece { + height: 2.5vw; + width: 2.5vw; + } + + .black-piece { + height: 2.5vw; + width: 2.5vw; + } +} + +@media screen and (max-width: 300px) { + .red-piece { + height: 1.5vw; + width: 1.5vw; + } + + .black-piece { + height: 1.5vw; + width: 1.5vw; + } + + .red-turn-text { + font-size: 35px; + } + + .black-turn-text { + font-size: 35px; + } +} */ \ No newline at end of file