Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Separated logical game loading from graphical #392

Open
wants to merge 3 commits into
base: game-loader
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/client/scripts/esm/chess/util/gamefileutility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
39 changes: 13 additions & 26 deletions src/client/scripts/esm/game/chess/gameloader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ import perspective from "../rendering/perspective.js";
import camera from "../rendering/camera.js";


// Type Definitions --------------------------------------------------------------------


interface GameOptions {
metadata: MetaData,
Expand Down Expand Up @@ -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';

Expand All @@ -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;
}

/**
Expand Down Expand Up @@ -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() {
Expand All @@ -227,7 +215,6 @@ function unloadGame() {
gameslot.unloadGame();
perspective.disable();
gui.prepareForOpen();
inAGame = false;
typeOfGameWeAreIn = undefined;
}

Expand Down
130 changes: 79 additions & 51 deletions src/client/scripts/esm/game/chess/gameslot.ts
Original file line number Diff line number Diff line change
@@ -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!
*
Expand Down Expand Up @@ -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 ---------------------------------------------------------------
Expand Down Expand Up @@ -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;
Expand All @@ -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 */
Expand Down
5 changes: 3 additions & 2 deletions src/client/scripts/esm/game/gui/loadingscreen.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions src/client/scripts/esm/game/rendering/perspective.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Loading