diff --git a/src/client/css/play.css b/src/client/css/play.css index 997cbc237..eba44e1e8 100644 --- a/src/client/css/play.css +++ b/src/client/css/play.css @@ -1037,31 +1037,15 @@ button.join-button, button.copy-button { transform: translate(-50%, -50%); background-color: rgba(255, 255, 255, 0.949); box-shadow: 0px 0px 12px 0px rgba(0, 0, 0, 0.918); - background-image: url("/img/game/spritesheet64_cburnett.png"); - /* The html document pre-loads only the png because it doesn't know if webp or avif is supported. */ - /* @supports (background-image: url('/img/game/spritesheet64_cburnett.webp')) { - background-image: url('/img/game/spritesheet64_cburnett.webp'); - } - @supports (background-image: url('/img/game/spritesheet64_cburnett.avif')) { - background-image: url('/img/game/spritesheet64_cburnett.avif'); - } */ - background-repeat: no-repeat; - background-size: 0; } .promotecolor { display: flex; justify-content: space-evenly; flex-wrap: wrap; - background-image: inherit; - background-repeat: no-repeat; - background-size: 0; } -.promotepiececontainer { - background-image: inherit; - background-repeat: no-repeat; - background-size: 0; +.promotepiece { width: 80px; height: 80px; padding: 3px; @@ -1069,214 +1053,14 @@ button.join-button, button.copy-button { border-radius: 10px; } -.promotepiececontainer:hover { +.promotepiece:hover { background-color: rgba(0, 0, 0, 0.099); } -.promotepiececontainer:active { +.promotepiece:active { background-color: rgba(0, 0, 0, 0.158); } -.promotepiece { - width: 80px; - height: 80px; - background-image: inherit; - background-repeat: no-repeat; - /* NEEDS TO BE as many times greater than 100% as there are pieces in a row in the spritesheet! 8 pieces => 800% */ - background-size: 800%; -} - -/* Spritesheet piece locations */ - -.pawnsW { - background-position-x: calc(100%/7 * 0); - background-position-y: calc(100%/7 * 0); -} - -.pawnsB { - background-position-x: calc(100%/7 * 1); - background-position-y: calc(100%/7 * 0); -} - -.knightsW { - background-position-x: calc(100%/7 * 2); - background-position-y: calc(100%/7 * 0); -} - -.knightsB { - background-position-x: calc(100%/7 * 3); - background-position-y: calc(100%/7 * 0); -} - -.bishopsW { - background-position-x: calc(100%/7 * 4); - background-position-y: calc(100%/7 * 0); -} - -.bishopsB { - background-position-x: calc(100%/7 * 5); - background-position-y: calc(100%/7 * 0); -} - -.rooksW { - background-position-x: calc(100%/7 * 6); - background-position-y: calc(100%/7 * 0); -} - -.rooksB { - background-position-x: calc(100%/7 * 7); - background-position-y: calc(100%/7 * 0); -} - -.queensW { - background-position-x: calc(100%/7 * 0); - background-position-y: calc(100%/7 * 1); -} - -.queensB { - background-position-x: calc(100%/7 * 1); - background-position-y: calc(100%/7 * 1); -} - -.kingsW { - background-position-x: calc(100%/7 * 2); - background-position-y: calc(100%/7 * 1); -} - -.kingsB { - background-position-x: calc(100%/7 * 3); - background-position-y: calc(100%/7 * 1); -} - -.chancellorsW { - background-position-x: calc(100%/7 * 4); - background-position-y: calc(100%/7 * 1); -} - -.chancellorsB { - background-position-x: calc(100%/7 * 5); - background-position-y: calc(100%/7 * 1); -} - -.archbishopsW { - background-position-x: calc(100%/7 * 6); - background-position-y: calc(100%/7 * 1); -} - -.archbishopsB { - background-position-x: calc(100%/7 * 7); - background-position-y: calc(100%/7 * 1); -} - -.amazonsW { - background-position-x: calc(100%/7 * 0); - background-position-y: calc(100%/7 * 2); -} - -.amazonsB { - background-position-x: calc(100%/7 * 1); - background-position-y: calc(100%/7 * 2); -} - -.guardsW { - background-position-x: calc(100%/7 * 2); - background-position-y: calc(100%/7 * 2); -} - -.guardsB { - background-position-x: calc(100%/7 * 3); - background-position-y: calc(100%/7 * 2); -} - -.hawksW { - background-position-x: calc(100%/7 * 6); - background-position-y: calc(100%/7 * 2); -} - -.hawksB { - background-position-x: calc(100%/7 * 7); - background-position-y: calc(100%/7 * 2); -} - -.camelsW { - background-position-x: calc(100%/7 * 0); - background-position-y: calc(100%/7 * 3); -} - -.camelsB { - background-position-x: calc(100%/7 * 1); - background-position-y: calc(100%/7 * 3); -} - -.giraffesW { - background-position-x: calc(100%/7 * 2); - background-position-y: calc(100%/7 * 3); -} - -.giraffesB { - background-position-x: calc(100%/7 * 3); - background-position-y: calc(100%/7 * 3); -} - -.zebrasW { - background-position-x: calc(100%/7 * 4); - background-position-y: calc(100%/7 * 3); -} - -.zebrasB { - background-position-x: calc(100%/7 * 5); - background-position-y: calc(100%/7 * 3); -} - -.knightridersW { - background-position-x: calc(100%/7 * 6); - background-position-y: calc(100%/7 * 3); -} - -.knightridersB { - background-position-x: calc(100%/7 * 7); - background-position-y: calc(100%/7 * 3); -} - -.rosesW { - background-position-x: calc(100%/7 * 4); - background-position-y: calc(100%/7 * 4); -} - -.rosesB { - background-position-x: calc(100%/7 * 5); - background-position-y: calc(100%/7 * 4); -} - -.centaursW { - background-position-x: calc(100%/7 * 6); - background-position-y: calc(100%/7 * 4); -} - -.centaursB { - background-position-x: calc(100%/7 * 7); - background-position-y: calc(100%/7 * 4); -} - -.royalCentaursW { - background-position-x: calc(100%/7 * 0); - background-position-y: calc(100%/7 * 5); -} - -.royalCentaursB { - background-position-x: calc(100%/7 * 1); - background-position-y: calc(100%/7 * 5); -} - -.royalQueensW { - background-position-x: calc(100%/7 * 2); - background-position-y: calc(100%/7 * 5); -} - -.royalQueensB { - background-position-x: calc(100%/7 * 3); - background-position-y: calc(100%/7 * 5); -} /* Pause UI */ diff --git a/src/client/scripts/esm/chess/api/fetchPieceSVGs.ts b/src/client/scripts/esm/chess/api/fetchPieceSVGs.ts index 00d548e80..e8515d979 100644 --- a/src/client/scripts/esm/chess/api/fetchPieceSVGs.ts +++ b/src/client/scripts/esm/chess/api/fetchPieceSVGs.ts @@ -1,11 +1,9 @@ /** - * Fetches SVG elements from a specified SVG file based on an array of element IDs. - * Returns an array of matching SVG elements. + * Fetches piece SVGs from the server. */ - /** * Fetches SVG elements from an SVG file located at the provided relative URL and * returns an array of SVG elements matching the provided IDs. @@ -26,14 +24,12 @@ async function fetchPieceSVGs(relativeURL: string, svgIds: string[]): Promise { const svgElement = svgDoc.querySelector(`#${svgId}`) as SVGElement; - if (!svgElement) { - throw new Error(`SVG with ID ${svgId} not found`); - } + if (!svgElement) throw new Error(`SVG with ID ${svgId} not found from server-sent svg data.`); // Push the found SVG element into the array svgElements.push(svgElement); - } + }); // Return the array of SVG elements return svgElements; diff --git a/src/client/scripts/esm/chess/rendering/spritesheetGenerator.ts b/src/client/scripts/esm/chess/rendering/spritesheetGenerator.ts index 08f3468de..77d7ca8a6 100644 --- a/src/client/scripts/esm/chess/rendering/spritesheetGenerator.ts +++ b/src/client/scripts/esm/chess/rendering/spritesheetGenerator.ts @@ -1,33 +1,34 @@ +/** + * This script takes a list of images, and converts it into a renderable + * spritesheet, also returning the textue locations of each image. + */ + // @ts-ignore import math from "../../util/math.js"; -// @ts-ignore -import colorutil from "../util/colorutil.js"; - +// @ts-ignore import type { Coords } from "../logic/movesets.js"; /** * The preferred image width each pieces image in a spreadsheet should be. - * This may round UP in order to make the spritesheet's total width a POWER OF 2. - * - * BUT, the image width will never cause the spritesheet's width to exceed WebGL's capacity! -*/ -const preferredImgSize = 512; // Each image is 512x512px + * This may be a little higher, in order to make the spritesheet's total width a POWER OF 2. + * BUT, the spritesheet's width will NEVER exceed WebGL's capacity! + */ +const preferredImgSize = 512; /** * Generates a spritesheet from an array of HTMLImageElement objects. * The spritesheet is created by arranging the images in the smallest square grid. * Each image is placed in a grid of 512x512px. - * - * @param gl + * @param gl - The webgl rendering context that will be rendering this spritesheet. We need this to determine the maximum-supported size. * @param images - An array of HTMLImageElement objects to be merged into a spritesheet. * @returns A promise that resolves with the generated spritesheet as an HTMLImageElement. */ async function generateSpritesheet(gl: WebGL2RenderingContext, images: HTMLImageElement[]) { // Ensure there are images provided - if (images.length === 0) throw new Error('No images provided.'); + if (images.length === 0) throw new Error('No images provided when generating spritesheet.'); // Calculate the grid size: Find the smallest square grid to fit all images const numImages = images.length; @@ -37,17 +38,16 @@ async function generateSpritesheet(gl: WebGL2RenderingContext, images: HTMLImage /** * The actual maximum size each image could be before exceeding web GL's boundaries. - * This is not how big we actually want to render the textures because we want to still cap them at 512. + * This is not how big we actually want to render the textures because we prefer they be 512x512. */ const maxImgSizePerMaxTextureSize = maxTextureSize / gridSize; - const spritesheetSizeIfPreferredImgSizeUsed = math.roundUpToPowerOf2(preferredImgSize * gridSize); // NOT a power of 2 !!!! - const actualImgSizeIfUsingPreferredImgSize = spritesheetSizeIfPreferredImgSizeUsed / gridSize; // IS a power of 2 :) + const spritesheetSizeIfPreferredImgSizeUsed = math.roundUpToPowerOf2(preferredImgSize * gridSize); // Round up to nearest power of 2 + const actualImgSizeIfUsingPreferredImgSize = spritesheetSizeIfPreferredImgSizeUsed / gridSize; /** Whichever is smaller of the two */ const actualImgSize = Math.min(actualImgSizeIfUsingPreferredImgSize, maxImgSizePerMaxTextureSize); - // Calculate the total width and height of the canvas (spritesheet) const canvasWidth = gridSize * actualImgSize; const canvasHeight = gridSize * actualImgSize; @@ -57,6 +57,7 @@ async function generateSpritesheet(gl: WebGL2RenderingContext, images: HTMLImage canvas.width = canvasWidth; canvas.height = canvasHeight; const ctx = canvas.getContext('2d'); + if (ctx === null) throw new Error('2D context null.') // Positioning variables let xIndex = 0; @@ -64,12 +65,11 @@ async function generateSpritesheet(gl: WebGL2RenderingContext, images: HTMLImage // Draw all the images onto the canvas for (let i = 0; i < numImages; i++) { - const x = xIndex * actualImgSize; const y = yIndex * actualImgSize; // Draw the image at the current position - ctx?.drawImage(images[i]!, x, y, actualImgSize, actualImgSize); + ctx.drawImage(images[i]!, x, y, actualImgSize, actualImgSize); // Update the position for the next image xIndex++; @@ -85,22 +85,20 @@ async function generateSpritesheet(gl: WebGL2RenderingContext, images: HTMLImage // Return a promise that resolves when the image is loaded await spritesheetImage.decode(); - const spritesheetData = generateSpriteSheetData(images, gridSize); return { spritesheet: spritesheetImage, spritesheetData }; } /** - * Generates the sprite sheet data (texture coordinates) for each image. - * + * Generates the sprite sheet data (texture coordinates and width) for each image. * @param images - An array of HTMLImageElement objects to be merged into a spritesheet. + * @param gridSize - How many images fit one-way. * @returns A sprite data object with texture coordinates for each image. */ function generateSpriteSheetData(images: HTMLImageElement[], gridSize: number) { - // Create the sprite data object - const texLocs: { [key: string]: Coords } = {}; const pieceWidth = 1 / gridSize; + const texLocs: { [key: string]: Coords } = {}; // Positioning variables let x = 0; @@ -108,15 +106,12 @@ function generateSpriteSheetData(images: HTMLImageElement[], gridSize: number) { // Loop through the images to create the sprite data images.forEach(image => { - const texX = (x / gridSize); // Normalize the x texture coordinate - const texY = 1 - (y + 1) / gridSize; // Normalize the y texture coordinate - - // Assuming the image has an ID, use it as the key for the data object - const imageId = image.id; - const mappedKey = mapIdToKey(imageId); + const texX = (x / gridSize); + const texY = 1 - (y + 1) / gridSize; - // Store the texture coordinates in the spriteData object - texLocs[mappedKey] = [texX, texY]; + // Store the texture coordinates + // Use the image id as the key for the data object + texLocs[image.id] = [texX, texY]; // Update the position for the next image x++; @@ -132,17 +127,6 @@ function generateSpriteSheetData(images: HTMLImageElement[], gridSize: number) { }; } -/** - * Maps IDs like "pawn-white" to "pawnsW" and "pawn-black" to "pawnsB". - */ -function mapIdToKey(id: string): string { - const [pieceSingular, color] = id.split('-'); // ['pawn','white'] - const colorSuffix = colorutil.getColorExtensionFromColor(color); - if (colorSuffix === null) throw new Error(`Color not valid: "${color}"`); - return `${pieceSingular}s${colorSuffix}`; -} - export { generateSpritesheet }; - \ No newline at end of file diff --git a/src/client/scripts/esm/chess/rendering/svgtoimageconverter.ts b/src/client/scripts/esm/chess/rendering/svgtoimageconverter.ts index c0f1c8370..43453bd17 100644 --- a/src/client/scripts/esm/chess/rendering/svgtoimageconverter.ts +++ b/src/client/scripts/esm/chess/rendering/svgtoimageconverter.ts @@ -1,13 +1,30 @@ + +/** Converts a list of SVGs into a list of HTMLImageElements */ +async function convertSVGsToImages(svgElements: SVGElement[]) { + const readyImages: HTMLImageElement[] = []; + try { + for (const svgElement of svgElements) { + const img = await svgToImage(svgElement); // You can adjust width and height as needed + // document.body.appendChild(img); + readyImages.push(img); + } + } catch (e) { + console.log("Error caught while converting SVGs to Images:"); + console.log((e as Error).stack); + } + return readyImages; +} + + /** * Converts an SVG element to an Image element by serializing the SVG and creating a data URL. - * The image is resized to the specified width and height after it loads. - * + * The image does NOT have a specified width or height. * @param svgElement - The SVG element to convert into an image. * @returns A promise that resolves with the created image element. */ -async function svgToImage(svgElement: SVGElement): Promise { - const svgID = svgElement.id; // 'pawn-white' +function svgToImage(svgElement: SVGElement): Promise { + const svgID = svgElement.id; // 'pawnsW' // Serialize the SVG element back to a string const svgString = new XMLSerializer().serializeToString(svgElement); @@ -21,17 +38,13 @@ async function svgToImage(svgElement: SVGElement): Promise { // Convert SVG string to a data URL using encodeURIComponent for better encoding const svgData = `data:image/svg+xml;charset=UTF-8,${encodeURIComponent(svgString)}`; img.src = svgData; - img.id = svgID; + img.id = svgID; // Set its ID here so its easy to find it in the document later return new Promise((resolve, reject) => { img.onload = () => { - // Resize the image after it loads (optional) - // img.width = width; - // img.height = height; // console.log(`Image ${svgID} converted successfully from a provided SVG.`); resolve(img); }; - img.onerror = (err) => { console.error(`Error loading image with ID "${svgID}"`, err); reject(new Error(`Failed to load image with ID "${svgID}"`)); @@ -42,5 +55,6 @@ async function svgToImage(svgElement: SVGElement): Promise { export { - svgToImage + convertSVGsToImages, + svgToImage, }; diff --git a/src/client/scripts/esm/game/chess/game.js b/src/client/scripts/esm/game/chess/game.js index eb518b4df..78927f03b 100644 --- a/src/client/scripts/esm/game/chess/game.js +++ b/src/client/scripts/esm/game/chess/game.js @@ -239,6 +239,7 @@ function unloadGame() { guiclock.resetClocks(); spritesheet.deleteSpritesheet(); + guipromotion.resetUI(); } /** Called when a game is loaded, loads the event listeners for when we are in a game. */ diff --git a/src/client/scripts/esm/game/chess/selection.js b/src/client/scripts/esm/game/chess/selection.js index 7a3cddd46..841d6252e 100644 --- a/src/client/scripts/esm/game/chess/selection.js +++ b/src/client/scripts/esm/game/chess/selection.js @@ -102,7 +102,7 @@ function isPawnCurrentlyPromoting() { return pawnIsPromoting; } /** * Flags the currently selected pawn to be promoted next frame. * Call when a choice is made on the promotion UI. - * @param {boolean} type + * @param {string} type */ function promoteToType(type) { promoteTo = type; } @@ -180,6 +180,7 @@ function handleMovingSelectedPiece(coordsClicked, pieceClickedType) { if (specialdetect.isPawnPromotion(gamefile, pieceSelected.type, coordsClicked)) { const color = colorutil.getPieceColorFromType(pieceSelected.type); guipromotion.open(color); + perspective.unlockMouse(); pawnIsPromoting = coordsClicked; return; } diff --git a/src/client/scripts/esm/game/gui/guipromotion.js b/src/client/scripts/esm/game/gui/guipromotion.js deleted file mode 100644 index 0b9b77b59..000000000 --- a/src/client/scripts/esm/game/gui/guipromotion.js +++ /dev/null @@ -1,205 +0,0 @@ - -// Import Start -import perspective from '../rendering/perspective.js'; -import selection from '../chess/selection.js'; -import style from './style.js'; -// Import End - -"use strict"; - -/** - * This script handles our promotion menu, when - * pawns reach the promotion line. - */ - -// Variables - -// Promotion -const element_Promote = document.getElementById('promote'); -const element_PromoteWhite = document.getElementById('promotewhite'); -const element_PromoteBlack = document.getElementById('promoteblack'); - -const element_amazonsW = document.getElementById('amazonsW'); -const element_queensW = document.getElementById('queensW'); -const element_knightridersW = document.getElementById('knightridersW'); -const element_chancellorsW = document.getElementById('chancellorsW'); -const element_archbishopsW = document.getElementById('archbishopsW'); -const element_rooksW = document.getElementById('rooksW'); -const element_bishopsW = document.getElementById('bishopsW'); -const element_rosesW = document.getElementById('rosesW'); -const element_hawksW = document.getElementById('hawksW'); -const element_giraffesW = document.getElementById('giraffesW'); -const element_zebrasW = document.getElementById('zebrasW'); -const element_camelsW = document.getElementById('camelsW'); -const element_centaursW = document.getElementById('centaursW'); -const element_knightsW = document.getElementById('knightsW'); -const element_guardsW = document.getElementById('guardsW'); - -const element_amazonsB = document.getElementById('amazonsB'); -const element_queensB = document.getElementById('queensB'); -const element_knightridersB = document.getElementById('knightridersB'); -const element_chancellorsB = document.getElementById('chancellorsB'); -const element_archbishopsB = document.getElementById('archbishopsB'); -const element_rooksB = document.getElementById('rooksB'); -const element_bishopsB = document.getElementById('bishopsB'); -const element_rosesB = document.getElementById('rosesB'); -const element_hawksB = document.getElementById('hawksB'); -const element_giraffesB = document.getElementById('giraffesB'); -const element_zebrasB = document.getElementById('zebrasB'); -const element_camelsB = document.getElementById('camelsB'); -const element_centaursB = document.getElementById('centaursB'); -const element_knightsB = document.getElementById('knightsB'); -const element_guardsB = document.getElementById('guardsB'); - -let selectionOpen = false; // True when promotion GUI visible. Do not listen to navigational controls in the mean time - -// Functions - -function isUIOpen() { return selectionOpen; } - -function open(color) { - selectionOpen = true; - style.revealElement(element_Promote); - if (color === 'white') { - style.hideElement(element_PromoteBlack); - style.revealElement(element_PromoteWhite); - } else { - style.hideElement(element_PromoteWhite); - style.revealElement(element_PromoteBlack); - } - initListeners_promotion(); - perspective.unlockMouse(); -} - -/** Closes the promotion UI */ -function close() { - selectionOpen = false; - style.hideElement(element_Promote); - closeListeners_promotion(); -} - -function initListeners_promotion() { - element_amazonsW.addEventListener('click', callback_promote); - element_queensW.addEventListener('click', callback_promote); - element_knightridersW.addEventListener('click', callback_promote); - element_chancellorsW.addEventListener('click', callback_promote); - element_archbishopsW.addEventListener('click', callback_promote); - element_rooksW.addEventListener('click', callback_promote); - element_bishopsW.addEventListener('click', callback_promote); - element_rosesW.addEventListener('click', callback_promote); - element_hawksW.addEventListener('click', callback_promote); - element_giraffesW.addEventListener('click', callback_promote); - element_zebrasW.addEventListener('click', callback_promote); - element_camelsW.addEventListener('click', callback_promote); - element_centaursW.addEventListener('click', callback_promote); - element_knightsW.addEventListener('click', callback_promote); - element_guardsW.addEventListener('click', callback_promote); - - element_amazonsB.addEventListener('click', callback_promote); - element_queensB.addEventListener('click', callback_promote); - element_knightridersB.addEventListener('click', callback_promote); - element_chancellorsB.addEventListener('click', callback_promote); - element_archbishopsB.addEventListener('click', callback_promote); - element_rooksB.addEventListener('click', callback_promote); - element_bishopsB.addEventListener('click', callback_promote); - element_rosesB.addEventListener('click', callback_promote); - element_hawksB.addEventListener('click', callback_promote); - element_giraffesB.addEventListener('click', callback_promote); - element_zebrasB.addEventListener('click', callback_promote); - element_camelsB.addEventListener('click', callback_promote); - element_centaursB.addEventListener('click', callback_promote); - element_knightsB.addEventListener('click', callback_promote); - element_guardsB.addEventListener('click', callback_promote); -} - -function closeListeners_promotion() { - element_amazonsW.removeEventListener('click', callback_promote); - element_queensW.removeEventListener('click', callback_promote); - element_knightridersW.removeEventListener('click', callback_promote); - element_chancellorsW.removeEventListener('click', callback_promote); - element_archbishopsW.removeEventListener('click', callback_promote); - element_rooksW.removeEventListener('click', callback_promote); - element_bishopsW.removeEventListener('click', callback_promote); - element_rosesW.removeEventListener('click', callback_promote); - element_hawksW.removeEventListener('click', callback_promote); - element_giraffesW.removeEventListener('click', callback_promote); - element_zebrasW.removeEventListener('click', callback_promote); - element_camelsW.removeEventListener('click', callback_promote); - element_centaursW.removeEventListener('click', callback_promote); - element_knightsW.removeEventListener('click', callback_promote); - element_guardsW.removeEventListener('click', callback_promote); - - element_amazonsB.removeEventListener('click', callback_promote); - element_queensB.removeEventListener('click', callback_promote); - element_knightridersB.removeEventListener('click', callback_promote); - element_chancellorsB.removeEventListener('click', callback_promote); - element_archbishopsB.removeEventListener('click', callback_promote); - element_rooksB.removeEventListener('click', callback_promote); - element_bishopsB.removeEventListener('click', callback_promote); - element_rosesB.removeEventListener('click', callback_promote); - element_hawksB.removeEventListener('click', callback_promote); - element_giraffesB.removeEventListener('click', callback_promote); - element_zebrasB.removeEventListener('click', callback_promote); - element_camelsB.removeEventListener('click', callback_promote); - element_centaursB.removeEventListener('click', callback_promote); - element_knightsB.removeEventListener('click', callback_promote); - element_guardsB.removeEventListener('click', callback_promote); -} - -/** - * Inits the promotion UI. Hides promotions not allowed, reveals promotions allowed. - * @param {Object} promotionsAllowed - An object that contains the information about what promotions are allowed. - * It contains 2 properties, `white` and `black`, both of which are arrays which may look like `['queens', 'bishops']`. - */ -function initUI(promotionsAllowed) { // { } - promotionsAllowed = promotionsAllowed || { white: [], black: [] }; - const white = promotionsAllowed.white; // ['queens','bishops'] - const black = promotionsAllowed.black; - - if (white.includes('amazons')) style.revealElement(element_amazonsW); else style.hideElement(element_amazonsW); - if (white.includes('queens')) style.revealElement(element_queensW); else style.hideElement(element_queensW); - if (white.includes('knightriders')) style.revealElement(element_knightridersW); else style.hideElement(element_knightridersW); - if (white.includes('chancellors')) style.revealElement(element_chancellorsW); else style.hideElement(element_chancellorsW); - if (white.includes('archbishops')) style.revealElement(element_archbishopsW); else style.hideElement(element_archbishopsW); - if (white.includes('rooks')) style.revealElement(element_rooksW); else style.hideElement(element_rooksW); - if (white.includes('bishops')) style.revealElement(element_bishopsW); else style.hideElement(element_bishopsW); - if (white.includes('roses')) style.revealElement(element_rosesW); else style.hideElement(element_rosesW); - if (white.includes('hawks')) style.revealElement(element_hawksW); else style.hideElement(element_hawksW); - if (white.includes('giraffes')) style.revealElement(element_giraffesW); else style.hideElement(element_giraffesW); - if (white.includes('zebras')) style.revealElement(element_zebrasW); else style.hideElement(element_zebrasW); - if (white.includes('camels')) style.revealElement(element_camelsW); else style.hideElement(element_camelsW); - if (white.includes('centaurs')) style.revealElement(element_centaursW); else style.hideElement(element_centaursW); - if (white.includes('knights')) style.revealElement(element_knightsW); else style.hideElement(element_knightsW); - if (white.includes('guards')) style.revealElement(element_guardsW); else style.hideElement(element_guardsW); - - if (black.includes('amazons')) style.revealElement(element_amazonsB); else style.hideElement(element_amazonsB); - if (black.includes('queens')) style.revealElement(element_queensB); else style.hideElement(element_queensB); - if (black.includes('knightriders')) style.revealElement(element_knightridersB); else style.hideElement(element_knightridersB); - if (black.includes('chancellors')) style.revealElement(element_chancellorsB); else style.hideElement(element_chancellorsB); - if (black.includes('archbishops')) style.revealElement(element_archbishopsB); else style.hideElement(element_archbishopsB); - if (black.includes('rooks')) style.revealElement(element_rooksB); else style.hideElement(element_rooksB); - if (black.includes('bishops')) style.revealElement(element_bishopsB); else style.hideElement(element_bishopsB); - if (black.includes('roses')) style.revealElement(element_rosesB); else style.hideElement(element_rosesB); - if (black.includes('hawks')) style.revealElement(element_hawksB); else style.hideElement(element_hawksB); - if (black.includes('giraffes')) style.revealElement(element_giraffesB); else style.hideElement(element_giraffesB); - if (black.includes('zebras')) style.revealElement(element_zebrasB); else style.hideElement(element_zebrasB); - if (black.includes('camels')) style.revealElement(element_camelsB); else style.hideElement(element_camelsB); - if (black.includes('centaurs')) style.revealElement(element_centaursB); else style.hideElement(element_centaursB); - if (black.includes('knights')) style.revealElement(element_knightsB); else style.hideElement(element_knightsB); - if (black.includes('guards')) style.revealElement(element_guardsB); else style.hideElement(element_guardsB); -} - -function callback_promote(event) { - event = event || window.event; - - const type = event.srcElement.classList[1]; - selection.promoteToType(type); - close(); -} - -export default { - isUIOpen, - open, - close, - initUI -}; \ No newline at end of file diff --git a/src/client/scripts/esm/game/gui/guipromotion.ts b/src/client/scripts/esm/game/gui/guipromotion.ts new file mode 100644 index 000000000..a3ee9347e --- /dev/null +++ b/src/client/scripts/esm/game/gui/guipromotion.ts @@ -0,0 +1,116 @@ + +/** + * This script handles our promotion menu, when + * pawns reach the promotion line. + */ + +import spritesheet from '../rendering/spritesheet.js'; +// @ts-ignore +import selection from '../chess/selection.js'; +// @ts-ignore +import style from './style.js'; +// @ts-ignore +import colorutil from '../../chess/util/colorutil.js'; + +"use strict"; + + +// Variables -------------------------------------------------------------------- + + +const element_Promote = document.getElementById('promote'); +const element_PromoteWhite = document.getElementById('promotewhite'); +const element_PromoteBlack = document.getElementById('promoteblack'); + +let selectionOpen = false; // True when promotion GUI visible. Do not listen to navigational controls in the mean time + + +// Functions -------------------------------------------------------------------- + + +function isUIOpen() { return selectionOpen; } + +function open(color: string) { + selectionOpen = true; + style.revealElement(element_Promote!); + if (color === 'white') style.revealElement(element_PromoteWhite!); + else if (color === 'black') style.revealElement(element_PromoteBlack!); + else throw new Error(`Promotion UI does not support color "${color}"`); +} + +/** Closes the promotion UI */ +function close() { + selectionOpen = false; + style.hideElement(element_PromoteWhite!); + style.hideElement(element_PromoteBlack!); + style.hideElement(element_Promote!); +} + +/** + * Inits the promotion UI. Hides promotions not allowed, reveals promotions allowed. + * @param {Object} promotionsAllowed - An object that contains the information about what promotions are allowed. + * It contains 2 properties, `white` and `black`, both of which are arrays which may look like `['queens', 'bishops']`. + */ +function initUI(promotionsAllowed: { [color: string]: string[]} | undefined) { + if (promotionsAllowed === undefined) return; + const white = promotionsAllowed['white']!; // ['queens','bishops'] + const black = promotionsAllowed['black']!; + + if (element_PromoteWhite!.childElementCount > 0 || element_PromoteBlack!.childElementCount > 0) { + throw new Error("Must reset promotion UI before initiating it, or promotions leftover from the previous game will bleed through."); + } + + const whiteExt = colorutil.getColorExtensionFromColor('white'); + const blackExt = colorutil.getColorExtensionFromColor('black'); + + const whiteSVGs = spritesheet.getCachedSVGElements(white.map(promotion => promotion + whiteExt)); + const blackSVGs = spritesheet.getCachedSVGElements(black.map(promotion => promotion + blackExt)); + + // Create and append allowed promotion options for white + whiteSVGs.forEach(svg => { + // TODO: Make a copy instead of modifying the cached piece + svg.classList.add('promotepiece'); + svg.addEventListener('click', callback_promote); + element_PromoteWhite!.appendChild(svg); + }); + + // Create and append allowed promotion options for black + blackSVGs.forEach(svg => { + // TODO: Make a copy instead of modifying the cached piece + svg.classList.add('promotepiece'); + svg.addEventListener('click', callback_promote); + element_PromoteBlack!.appendChild(svg); + }); +} + +/** + * Resets the promotion UI by clearing all promotion options. + */ +function resetUI() { + while (element_PromoteWhite!.firstChild) { + const svg = element_PromoteWhite!.firstChild; + element_PromoteWhite!.removeChild(svg); + svg.removeEventListener('click', callback_promote); + } + while (element_PromoteBlack!.firstChild) { + const svg = element_PromoteBlack!.firstChild; + element_PromoteBlack!.removeChild(svg); + svg.removeEventListener('click', callback_promote); + } +} + +function callback_promote(event: Event) { + const type = (event.currentTarget as HTMLElement).id; + // TODO: Dispatch a custom 'promote-selected' event! + // That way this script doesn't depend on selection.js + selection.promoteToType(type); + close(); +} + +export default { + isUIOpen, + open, + close, + initUI, + resetUI, +}; \ No newline at end of file diff --git a/src/client/scripts/esm/game/rendering/spritesheet.ts b/src/client/scripts/esm/game/rendering/spritesheet.ts index f8cb7b742..b1461f1a9 100644 --- a/src/client/scripts/esm/game/rendering/spritesheet.ts +++ b/src/client/scripts/esm/game/rendering/spritesheet.ts @@ -1,22 +1,22 @@ /** - * This script stores the texture coordinates - * of each piece in our spritesheet. + * This script stores the spritesheet FOR THE CURRENT GAME, + * and all the piece's texture coordinates within it. * - * It should have ZERO dependancies! + * If no game is loaded, no spritesheet is loaded. */ - import { fetchPieceSVGs } from '../../chess/api/fetchPieceSVGs.js'; -import { Coords } from '../../chess/logic/movesets.js'; import { generateSpritesheet } from '../../chess/rendering/spritesheetGenerator.js'; -import { svgToImage } from '../../chess/rendering/svgtoimageconverter.js'; +import { convertSVGsToImages } from '../../chess/rendering/svgtoimageconverter.js'; // @ts-ignore import typeutil from '../../chess/util/typeutil.js'; // @ts-ignore import jsutil from '../../util/jsutil.js'; // @ts-ignore import texture from './texture.js'; +// @ts-ignore +import colorutil from '../../chess/util/colorutil.js'; // Type Definitions ---------------------------------------------------------- @@ -24,6 +24,7 @@ import texture from './texture.js'; // @ts-ignore import type gamefile from '../../chess/logic/gamefile.js'; +import type { Coords } from '../../chess/logic/movesets.js'; // Variables --------------------------------------------------------------------------- @@ -67,7 +68,7 @@ const typesThatDontNeedAnSVG = ['voids']; const cachedPieceTypes: string[] = []; /** * Piece SVG Elements that we have fetch-requested from the server, up to this point. - * In the form: 'pawn-white': SVGElement + * In the form: { 'pawnsW': SVGElement } */ const cachedPieceSVGs: { [svgID: string]: SVGElement } = {}; @@ -76,11 +77,22 @@ const cachedPieceSVGs: { [svgID: string]: SVGElement } = {}; // Functions --------------------------------------------------------------------------- -/** - * Loads the spritesheet texture - * @param gl - The webgl context being used} gl - * @param gamefile - */ +function getSpritesheet() { + if (!spritesheet) throw new Error("Should not be getting the spritesheet when not loaded!"); + return spritesheet!; +} + +function getSpritesheetDataPieceWidth() { + if (!spritesheetData) throw new Error("Should not be getting piece width when the spritesheet is not loaded!"); + return spritesheetData!.pieceWidth; +} + +function getSpritesheetDataTexLocation(type: number) { + if (!spritesheetData) throw new Error("Should not be getting texture locations when the spritesheet is not loaded!"); + return spritesheetData!.texLocs[type]!; +} + +/** Loads the spritesheet texture we'll be using to render the provided gamefile's pieces */ async function initSpritesheetForGame(gl: WebGL2RenderingContext, gamefile: gamefile) { /** All piece types in the game. */ @@ -99,11 +111,7 @@ async function initSpritesheetForGame(gl: WebGL2RenderingContext, gamefile: game console.log("Finished acquiring all piece SVGs!"); /** The SVG elements we will use in the game to construct our spritesheet */ - const svgElements = svgIDs.map(id => { - const cachedSVG = cachedPieceSVGs[id]; - if (cachedSVG === undefined) throw new Error(`Piece SVG of ID "${id}" required for game wasn't cached! We shouldn't have reached this part of the code if the fetch requests didn't succeed.`); - return cachedSVG; - }); + const svgElements = getCachedSVGElements(svgIDs); // Convert each SVG element to an Image const readyImages: HTMLImageElement[] = await convertSVGsToImages(svgElements); @@ -121,6 +129,11 @@ async function initSpritesheetForGame(gl: WebGL2RenderingContext, gamefile: game spritesheetData = spritesheetAndSpritesheetData.spritesheetData; } +function deleteSpritesheet() { + spritesheet = undefined; + spritesheetData = undefined; +} + /** * Tests what of the provided types we don't have yet, * fetches them, and appends them to our cache. @@ -162,7 +175,7 @@ async function fetchAllPieceSVGs(types: string[]) { /** * Returns a string of the ids of the svgs of * each color that makes up all of the provided types. - * `['pawn','obstacle'] => ['pawn-white','pawn-black','obstacle-neutral'] + * `['pawn','obstacle'] => ['pawnsW','pawnsB','obstaclesN'] */ function getSVG_IDsFromPieceTypes(pieceTypes: string[]) { // In singular form const svgIDs: string[] = []; @@ -173,39 +186,24 @@ function getSVG_IDsFromPieceTypes(pieceTypes: string[]) { // In singular form /** * Returns a string of the ids of the * svgs of each color that makes up a type. - * 'pawn' => ['pawn-white','pawn-black'] + * 'pawn' => ['pawnsW','pawnsB'] */ function getSVG_IDs_From_PieceType(type: string): string[] { const svgIDs: string[] = []; const pieceInPluralForm = type + 's'; const isNeutral = typeutil.neutralTypes.includes(pieceInPluralForm); + if (isNeutral) { - svgIDs.push(type + '-neutral'); + svgIDs.push(pieceInPluralForm + colorutil.getColorExtensionFromColor('neutral')); } else { - svgIDs.push(type + '-white'); - svgIDs.push(type + '-black'); + svgIDs.push(pieceInPluralForm + colorutil.getColorExtensionFromColor('white')); + svgIDs.push(pieceInPluralForm + colorutil.getColorExtensionFromColor('black')); } return svgIDs; } -/** Converts a list of SVGs into a list of HTMLImageElements */ -async function convertSVGsToImages(svgElements: SVGElement[]) { - const readyImages: HTMLImageElement[] = []; - try { - for (const svgElement of svgElements) { - const img = await svgToImage(svgElement); // You can adjust width and height as needed - // document.body.appendChild(img); - readyImages.push(img); - } - } catch (e) { - console.log("Error caught while converting SVGs to Images:"); - console.log((e as Error).stack); - } - return readyImages; -} - // Do this by default whenever we load the page, as EVERY variant requires most of these pieces! (async function fetchAndCacheClassicalPieceSVGs() { console.log("Fetching all Classical SVGs..."); @@ -216,21 +214,13 @@ async function convertSVGsToImages(svgElements: SVGElement[]) { console.log("Fetched all Classical SVGs!"); })(); -function getSpritesheet() { - return spritesheet; -} - -function getSpritesheetDataPieceWidth() { - return spritesheetData?.pieceWidth; -} - -function getSpritesheetDataTexLocation(type: number) { - return spritesheetData?.texLocs[type]!; -} - -function deleteSpritesheet() { - spritesheet = undefined; - spritesheetData = undefined; +function getCachedSVGElements(svgIDs: string[]) { + /** The SVG elements we will use in the game to construct our spritesheet */ + return svgIDs.map(id => { + const cachedSVG = cachedPieceSVGs[id]; + if (cachedSVG === undefined) throw new Error(`Piece SVG of ID "${id}" required for game wasn't cached!`); + return cachedSVG; + }); } @@ -241,4 +231,5 @@ export default { getSpritesheetDataPieceWidth, getSpritesheetDataTexLocation, deleteSpritesheet, + getCachedSVGElements, }; \ No newline at end of file diff --git a/src/client/svg/pieces/classical.svg b/src/client/svg/pieces/classical.svg index 560d4d84b..5e852aed0 100644 --- a/src/client/svg/pieces/classical.svg +++ b/src/client/svg/pieces/classical.svg @@ -1,14 +1,14 @@
- - - - - - - - - - - - + + + + + + + + + + + +
\ No newline at end of file diff --git a/src/client/svg/pieces/fairy/amazon.svg b/src/client/svg/pieces/fairy/amazon.svg index 3414d3616..d33ae2558 100644 --- a/src/client/svg/pieces/fairy/amazon.svg +++ b/src/client/svg/pieces/fairy/amazon.svg @@ -1,4 +1,4 @@
- - + +
\ No newline at end of file diff --git a/src/client/svg/pieces/fairy/archbishop.svg b/src/client/svg/pieces/fairy/archbishop.svg index 9230b2810..be17ac207 100644 --- a/src/client/svg/pieces/fairy/archbishop.svg +++ b/src/client/svg/pieces/fairy/archbishop.svg @@ -1,4 +1,4 @@
- - + +
\ No newline at end of file diff --git a/src/client/svg/pieces/fairy/camel.svg b/src/client/svg/pieces/fairy/camel.svg index 07f243208..c6eb1caeb 100644 --- a/src/client/svg/pieces/fairy/camel.svg +++ b/src/client/svg/pieces/fairy/camel.svg @@ -1,4 +1,4 @@
- - + +
\ No newline at end of file diff --git a/src/client/svg/pieces/fairy/centaur.svg b/src/client/svg/pieces/fairy/centaur.svg index de94a3635..786f3d1e4 100644 --- a/src/client/svg/pieces/fairy/centaur.svg +++ b/src/client/svg/pieces/fairy/centaur.svg @@ -1,4 +1,4 @@
- - + +
\ No newline at end of file diff --git a/src/client/svg/pieces/fairy/chancellor.svg b/src/client/svg/pieces/fairy/chancellor.svg index 818ceb36e..5892e1447 100644 --- a/src/client/svg/pieces/fairy/chancellor.svg +++ b/src/client/svg/pieces/fairy/chancellor.svg @@ -1,4 +1,4 @@
- - + +
\ No newline at end of file diff --git a/src/client/svg/pieces/fairy/commoner.svg b/src/client/svg/pieces/fairy/commoner.svg index adb4974eb..40c0fa6b2 100644 --- a/src/client/svg/pieces/fairy/commoner.svg +++ b/src/client/svg/pieces/fairy/commoner.svg @@ -1,4 +1,4 @@
- - + +
\ No newline at end of file diff --git a/src/client/svg/pieces/fairy/drago.svg b/src/client/svg/pieces/fairy/drago.svg index 9c0ae8b09..8a7891cfe 100644 --- a/src/client/svg/pieces/fairy/drago.svg +++ b/src/client/svg/pieces/fairy/drago.svg @@ -1,4 +1,4 @@
- - + +
\ No newline at end of file diff --git a/src/client/svg/pieces/fairy/dragon.svg b/src/client/svg/pieces/fairy/dragon.svg index 8887cfc9b..0b32a37f9 100644 --- a/src/client/svg/pieces/fairy/dragon.svg +++ b/src/client/svg/pieces/fairy/dragon.svg @@ -1,4 +1,4 @@
- - + +
\ No newline at end of file diff --git a/src/client/svg/pieces/fairy/giraffe.svg b/src/client/svg/pieces/fairy/giraffe.svg index 418a8b6d6..1cee1742b 100644 --- a/src/client/svg/pieces/fairy/giraffe.svg +++ b/src/client/svg/pieces/fairy/giraffe.svg @@ -1,4 +1,4 @@
- - + +
\ No newline at end of file diff --git a/src/client/svg/pieces/fairy/guard.svg b/src/client/svg/pieces/fairy/guard.svg index 84887b64f..c6bcf784b 100644 --- a/src/client/svg/pieces/fairy/guard.svg +++ b/src/client/svg/pieces/fairy/guard.svg @@ -1,4 +1,4 @@
- - + +
\ No newline at end of file diff --git a/src/client/svg/pieces/fairy/hawk.svg b/src/client/svg/pieces/fairy/hawk.svg index 669765190..418eb8d84 100644 --- a/src/client/svg/pieces/fairy/hawk.svg +++ b/src/client/svg/pieces/fairy/hawk.svg @@ -1,4 +1,4 @@
- - + +
\ No newline at end of file diff --git a/src/client/svg/pieces/fairy/huygen.svg b/src/client/svg/pieces/fairy/huygen.svg index e8af533dc..722973485 100644 --- a/src/client/svg/pieces/fairy/huygen.svg +++ b/src/client/svg/pieces/fairy/huygen.svg @@ -1,4 +1,4 @@
- - + +
\ No newline at end of file diff --git a/src/client/svg/pieces/fairy/kelpie.svg b/src/client/svg/pieces/fairy/kelpie.svg index 5f14f15d7..23f37613e 100644 --- a/src/client/svg/pieces/fairy/kelpie.svg +++ b/src/client/svg/pieces/fairy/kelpie.svg @@ -1,4 +1,4 @@
- - + +
\ No newline at end of file diff --git a/src/client/svg/pieces/fairy/knightrider.svg b/src/client/svg/pieces/fairy/knightrider.svg index 284eeda22..592d2badc 100644 --- a/src/client/svg/pieces/fairy/knightrider.svg +++ b/src/client/svg/pieces/fairy/knightrider.svg @@ -1,4 +1,4 @@
- - + +
\ No newline at end of file diff --git a/src/client/svg/pieces/fairy/obstacle.svg b/src/client/svg/pieces/fairy/obstacle.svg index d046bdfd1..21a74bea8 100644 --- a/src/client/svg/pieces/fairy/obstacle.svg +++ b/src/client/svg/pieces/fairy/obstacle.svg @@ -1,3 +1,3 @@
- +
\ No newline at end of file diff --git a/src/client/svg/pieces/fairy/rose.svg b/src/client/svg/pieces/fairy/rose.svg index 03d2ea89d..6652a6a53 100644 --- a/src/client/svg/pieces/fairy/rose.svg +++ b/src/client/svg/pieces/fairy/rose.svg @@ -1,4 +1,4 @@
- - + +
\ No newline at end of file diff --git a/src/client/svg/pieces/fairy/royalcentaur.svg b/src/client/svg/pieces/fairy/royalcentaur.svg index 73682c580..ff2282d92 100644 --- a/src/client/svg/pieces/fairy/royalcentaur.svg +++ b/src/client/svg/pieces/fairy/royalcentaur.svg @@ -1,4 +1,4 @@
- - + +
\ No newline at end of file diff --git a/src/client/svg/pieces/fairy/royalqueen.svg b/src/client/svg/pieces/fairy/royalqueen.svg index 6c79b8de4..b897c8a6c 100644 --- a/src/client/svg/pieces/fairy/royalqueen.svg +++ b/src/client/svg/pieces/fairy/royalqueen.svg @@ -1,4 +1,4 @@
- - + +
\ No newline at end of file diff --git a/src/client/svg/pieces/fairy/unicorn.svg b/src/client/svg/pieces/fairy/unicorn.svg index bee305e5e..63a01e951 100644 --- a/src/client/svg/pieces/fairy/unicorn.svg +++ b/src/client/svg/pieces/fairy/unicorn.svg @@ -1,4 +1,4 @@
- - + +
\ No newline at end of file diff --git a/src/client/svg/pieces/fairy/unicorno.svg b/src/client/svg/pieces/fairy/unicorno.svg index 8f880fdc3..08a4cd7bf 100644 --- a/src/client/svg/pieces/fairy/unicorno.svg +++ b/src/client/svg/pieces/fairy/unicorno.svg @@ -1,4 +1,4 @@
- - + +
\ No newline at end of file diff --git a/src/client/svg/pieces/fairy/zebra.svg b/src/client/svg/pieces/fairy/zebra.svg index fe41af565..bc267425b 100644 --- a/src/client/svg/pieces/fairy/zebra.svg +++ b/src/client/svg/pieces/fairy/zebra.svg @@ -1,4 +1,4 @@
- - + +
\ No newline at end of file diff --git a/src/client/views/play.ejs b/src/client/views/play.ejs index c1485d6bf..78480601d 100644 --- a/src/client/views/play.ejs +++ b/src/client/views/play.ejs @@ -448,40 +448,11 @@