diff --git a/src/client/scripts/esm/chess/util/gamefileutility.ts b/src/client/scripts/esm/chess/util/gamefileutility.ts index 71b173db..8fdc8cc0 100644 --- a/src/client/scripts/esm/chess/util/gamefileutility.ts +++ b/src/client/scripts/esm/chess/util/gamefileutility.ts @@ -99,7 +99,7 @@ function forEachPieceInPiecesByType(callback: PieceIterator, piecesByType: Piece * @param callback * @param typeList - A list of piece coordinates of a specific type, that MAY include undefined placeholders. */ -function forEachPieceInTypeList(callback: CoordsIterator, typeList: (Coords | undefined)[]) { // typeList = pieces organized by type +function forEachPieceInTypeList(callback: CoordsIterator, typeList: TypeList) { // typeList = pieces organized by type for (const coords of typeList) { if (coords === undefined) continue; // An undefined placeholder callback(coords); diff --git a/src/client/scripts/esm/game/chess/gameloader.ts b/src/client/scripts/esm/game/chess/gameloader.ts index 49ed5392..65dbf450 100644 --- a/src/client/scripts/esm/game/chess/gameloader.ts +++ b/src/client/scripts/esm/game/chess/gameloader.ts @@ -42,6 +42,8 @@ import perspective from "../rendering/perspective.js"; import camera from "../rendering/camera.js"; +// Type Definitions -------------------------------------------------------------------- + interface GameOptions { metadata: MetaData, @@ -95,13 +97,6 @@ interface VariantOptions { // Variables -------------------------------------------------------------------- -/** - * True if we are in ANY type of game, whether local, online, analysis, or editor. - * - * If we're on the title screen or the lobby, this will be false. - */ -let inAGame: boolean = false; - /** The type of game we are in, whether local or online, if we are in a game. */ let typeOfGameWeAreIn: undefined | 'local' | 'online'; @@ -115,7 +110,7 @@ let typeOfGameWeAreIn: undefined | 'local' | 'online'; * If we're on the title screen or the lobby, this will be false. */ function areInAGame(): boolean { - return inAGame; + return typeOfGameWeAreIn !== undefined; } /** @@ -199,24 +194,17 @@ async function loadGame( fromWhitePerspective: boolean, allowEditCoords: boolean ) { - // console.log("Loading game with game options:"); - // console.log(gameOptions); - - await gameslot.loadGamefile(gameOptions.metadata, fromWhitePerspective, { // Pass in the pre-existing moves - moves: gameOptions.moves, - variantOptions: gameOptions.variantOptions, - gameConclusion: gameOptions.gameConclusion, - clockValues: gameOptions.clockValues + gameslot.loadGamefile({ + metadata: gameOptions.metadata, + viewWhitePerspective: fromWhitePerspective, + allowEditCoords, + additional: { // Pass in the pre-existing moves + moves: gameOptions.moves, + variantOptions: gameOptions.variantOptions, + gameConclusion: gameOptions.gameConclusion, + clockValues: gameOptions.clockValues + } }); - - // Opening the guinavigation needs to be done in here so that pasting a game still opens it. - const gamefile = gameslot.getGamefile()!; - guinavigation.open({ allowEditCoords }); // Editing your coords allowed in local games - guiclock.set(gamefile); - - sound.playSound_gamestart(); - - inAGame = true; } function unloadGame() { @@ -227,7 +215,6 @@ function unloadGame() { gameslot.unloadGame(); perspective.disable(); gui.prepareForOpen(); - inAGame = false; typeOfGameWeAreIn = undefined; } diff --git a/src/client/scripts/esm/game/chess/gameslot.ts b/src/client/scripts/esm/game/chess/gameslot.ts index 1583aa86..4581b84a 100644 --- a/src/client/scripts/esm/game/chess/gameslot.ts +++ b/src/client/scripts/esm/game/chess/gameslot.ts @@ -1,6 +1,5 @@ /** - * * Whether we're in a local game, online game, analysis board, or board editor, * what they ALL have in common is a gamefile! This script stores THAT gamefile! * @@ -60,6 +59,7 @@ import guipromotion from "../gui/guipromotion.js"; import loadingscreen from "../gui/loadingscreen.js"; import spritesheet from "../rendering/spritesheet.js"; import movesequence from "./movesequence.js"; +import thread from "../../util/thread.js"; // Variables --------------------------------------------------------------- @@ -119,60 +119,70 @@ function isLoadedGameViewingWhitePerspective() { return youAreColor === 'white'; }; -/** - * Loads a gamefile onto the board. - * Generates the gamefile and organizes its lines. Inits the promotion UI, - * mesh of all the pieces, and toggles miniimage rendering. (everything visual) - * @param metadata - An object containing the property `Variant`, and optionally `UTCDate` and `UTCTime`, which can be used to extract the version of the variant. Without the date, the latest version will be used. - * @param viewWhitePerspective - True if we should be viewing the game from white's perspective, false for black's perspective. - * @param [options] - Options for constructing the gamefile. - * @param [options.moves] - Existing moves, if any, to forward to the front of the game. Should be specified if reconnecting to an online game or pasting a game. Each move should be in the most compact notation, e.g., `['1,2>3,4','10,7>10,8Q']`. - * @param [options.variantOptions] - If a custom position is needed, for instance, when pasting a game, then these options should be included. - * @param [options.gameConclusion] - The conclusion of the game, if loading an online game that has already ended. - * @param [options.clockValues] - Any already existing clock values for the gamefile. - */ -async function loadGamefile( +/** Options for loading a game. */ +interface LoadOptions { + /** The metadata of the game */ metadata: MetaData, + /** True if we should be viewing the game from white's perspective, false for black's perspective. */ viewWhitePerspective: boolean, - { moves, variantOptions, gameConclusion, clockValues }: { + /** Whether the coordinate field box should be editable. */ + allowEditCoords: boolean, + /** Additional options that may go into the gamefile constructor. + * Typically used if we're pasting a game, or reloading an online one. */ + additional?: { + /** Existing moves, if any, to forward to the front of the game. Should be specified if reconnecting to an online game or pasting a game. Each move should be in the most compact notation, e.g., `['1,2>3,4','10,7>10,8Q']`. */ moves?: string[], + /** If a custom position is needed, for instance, when pasting a game, then these options should be included. */ variantOptions?: any, + /** The conclusion of the game, if loading an online game that has already ended. */ gameConclusion?: string | false, + /** Any already existing clock values for the gamefile. */ clockValues?: ClockValues, - } = {} -) { + } +} +/** + * Loads a gamefile onto the board. + */ +async function loadGamefile(loadOptions: LoadOptions) { if (loadedGamefile) throw new Error("Must unloadGame() before loading a new one."); - console.log('Started loading'); + console.log('Started loading game...'); gameIsLoading = true; // Has to be awaited to give the document a chance to repaint. await loadingscreen.open(); + + // The game should be considered loaded once the LOGICAL stuff is finished, + // but the loading animation should only be closed when + // both the LOGICAL and GRAPHICAL stuff are both finished. - const newGamefile = new gamefile(metadata, { moves, variantOptions, gameConclusion, clockValues }); + // First load the LOGICAL stuff... + loadedGamefile = loadLogical(loadOptions); + console.log('Finished loading LOGICAL game stuff.'); + gameIsLoading = false; + // Play the start game sound once LOGICAL stuff is finished loading, + // so that the sound will still play in chrome, with the tab hidden, and + // someone accepts your invite. (In that scenario, the graphical loading is blocked) + sound.playSound_gamestart(); - try { - await spritesheet.initSpritesheetForGame(gl, newGamefile); - } catch (e) { // An error ocurred during the fetching of piece svgs and spritesheet gen - await loadingscreen.onError(e as Event); - } - guipromotion.initUI(newGamefile.gameRules.promotionsAllowed); + // Next start loading the GRAPHICAL stuff... + await loadGraphical(loadOptions); - // Rewind one move so that we can, after a short delay, animate the most recently played move. - const lastmove = moveutil.getLastMove(newGamefile.moves); - if (lastmove !== undefined) { - // Rewind one move - movepiece.applyMove(newGamefile, lastmove, false); + // Logical and Graphical loadings are done! + // We can now close the loading screen. - // A small delay to animate the most recently played move. - animateLastMoveTimeoutID = setTimeout(() => { - if (moveutil.areWeViewingLatestMove(newGamefile)) return; // Already viewing the lastest move - movesequence.viewFront(newGamefile); // Updates to front even when they view different moves - movesequence.animateMove(lastmove, true); - }, delayOfLatestMoveAnimationOnRejoinMillis); - } + // Has to be awaited to give the document a chance to repaint. + await loadingscreen.close(); + startStartingTransition(); + console.log('Finished loading GRAPHICAL game stuff.'); +} + +/** Loads all of the logical components of a game */ +function loadLogical(loadOptions: LoadOptions): gamefile { - youAreColor = viewWhitePerspective ? 'white' : 'black'; + const newGamefile = new gamefile(loadOptions.metadata, loadOptions.additional); + + youAreColor = loadOptions.viewWhitePerspective ? 'white' : 'black'; // If the game has more lines than this, then we turn off arrows at the start to prevent a lag spike. const lineCountToDisableArrows = 16; @@ -185,26 +195,44 @@ async function loadGamefile( arrows.setMode(0); } - // The only time the document should listen for us pasting a game, is when a game is already loaded. - // If a game WASN'T loaded, then we wouldn't be on a screen that COULD load a game!! initCopyPastGameListeners(); - loadedGamefile = newGamefile; - // Immediately conclude the game if we loaded a game that's over already if (gamefileutility.isGameOver(newGamefile)) concludeGame(); - // Has to be awaited to give the document a chance to repaint. - await loadingscreen.close(); - - startStartingTransition(); - console.log('Finished loading'); - perspective.resetRotations(); + return newGamefile; +} - // Regenerate the mesh of all the pieces. - piecesmodel.regenModel(newGamefile, options.getPieceRegenColorArgs()); +/** Loads all of the graphical components of a game */ +async function loadGraphical(loadOptions: LoadOptions) { + // Opening the guinavigation needs to be done in gameslot.ts instead of gameloader.ts so pasting games still opens it + guinavigation.open({ allowEditCoords: loadOptions.allowEditCoords }); // Editing your coords allowed in local games + guiclock.set(gamefile); + guipromotion.initUI(loadedGamefile!.gameRules.promotionsAllowed); + perspective.resetRotations(loadOptions.viewWhitePerspective); - gameIsLoading = false; + try { + await spritesheet.initSpritesheetForGame(gl, loadedGamefile!); + } catch (e) { // An error ocurred during the fetching of piece svgs and spritesheet gen + await loadingscreen.onError(e as Event); + } + + // Rewind one move so that we can, after a short delay, animate the most recently played move. + const lastmove = moveutil.getLastMove(loadedGamefile!.moves); + if (lastmove !== undefined) { + // Rewind one move + movepiece.applyMove(loadedGamefile!, lastmove, false); + + // A small delay to animate the most recently played move. + animateLastMoveTimeoutID = setTimeout(() => { + if (moveutil.areWeViewingLatestMove(loadedGamefile!)) return; // Already viewing the lastest move + movesequence.viewFront(loadedGamefile!); // Updates to front even when they view different moves + movesequence.animateMove(lastmove, true); + }, delayOfLatestMoveAnimationOnRejoinMillis); + } + + // Regenerate the mesh of all the pieces. + piecesmodel.regenModel(loadedGamefile!, options.getPieceRegenColorArgs()); } /** The canvas will no longer render the current game */ diff --git a/src/client/scripts/esm/game/gui/loadingscreen.ts b/src/client/scripts/esm/game/gui/loadingscreen.ts index a77b1a5c..af0b6c3e 100644 --- a/src/client/scripts/esm/game/gui/loadingscreen.ts +++ b/src/client/scripts/esm/game/gui/loadingscreen.ts @@ -1,7 +1,8 @@ /** * This script manages the spinny pawn loading animation - * while a game is loading a gamefile and generating the spritesheet + * while a game is loading both the LOGICAL and + * GRAPHICAL (spritesheet) aspects. */ // @ts-ignore @@ -14,7 +15,7 @@ import thread from "../../util/thread.js"; import style from "./style.js"; -const loadingScreen: HTMLElement = (document.querySelector('.game-loading-screen') as HTMLElement); +const loadingScreen: HTMLElement = document.querySelector('.game-loading-screen') as HTMLElement; /** Lower = loading checkerboard closer to black */ const darknessLevel = 0.22; diff --git a/src/client/scripts/esm/game/rendering/perspective.js b/src/client/scripts/esm/game/rendering/perspective.js index c55ee286..1efc1114 100644 --- a/src/client/scripts/esm/game/rendering/perspective.js +++ b/src/client/scripts/esm/game/rendering/perspective.js @@ -95,9 +95,9 @@ function disable() { } // Sets rotations to orthographic view. Sensitive to if we're white or black. -function resetRotations() { +function resetRotations(viewWhitePerspective) { rotX = 0; - rotZ = gameslot.getOurColor() === 'black' ? 180 : 0; // Will be undefined if not in online game + rotZ = viewWhitePerspective ? 0 : 180; console.log(rotZ); updateIsViewingBlackPerspective();