import thisHtml from './minigame.html';
import thisCss from './minigame.css';
import stylesCss from '../../styles.css';
import rootCss from '../../root.css';
import { clone, delay, extractErrorMsg, getSaferSubstring, getTimestampInTicks, } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
import { IbGibAddr, } from "@ibgib/ts-gib/dist/types.mjs";
import { IbGib_V1 } from "@ibgib/ts-gib/dist/V1/types.mjs";
import { getIbAndGib, getIbGibAddr } from "@ibgib/ts-gib/dist/helper.mjs";
import { GIB_DELIMITER } from "@ibgib/ts-gib/dist/V1/constants.mjs";
import { MetaspaceService } from "@ibgib/core-gib/dist/witness/space/metaspace/metaspace-types.mjs";
import { getTjpAddr } from "@ibgib/core-gib/dist/common/other/ibgib-helper.mjs";
import { mut8Timeline } from "@ibgib/core-gib/dist/timeline/timeline-api.mjs";
import { tellUserFunctionInfo } from '@ibgib/web-gib/dist/api/commands/chat/tell-user.mjs';
import { helloWorldFunctionInfo } from '@ibgib/web-gib/dist/api/commands/chat/hello-world.mjs';
import { alertUser, highlightElement, promptForConfirm, promptForText, shadowRoot_getElementById, } from "@ibgib/web-gib/dist/helpers.web.mjs";
import { getGlobalMetaspace_waitIfNeeded, } from "@ibgib/web-gib/dist/helpers.mjs";
import { IbGibDynamicComponentInstanceBase, IbGibDynamicComponentMetaBase } from "@ibgib/web-gib/dist/ui/component/ibgib-dynamic-component-bases.mjs";
import { ElementsBase, IbGibDynamicComponentInstance, IbGibDynamicComponentInstanceInitOpts, } from "@ibgib/web-gib/dist/ui/component/component-types.mjs";
import { Settings_Minigame, } from "@ibgib/web-gib/dist/common/settings/settings-types.mjs";
import { getSectionName, getSettingsSection } from "@ibgib/web-gib/dist/common/settings/settings-helpers.mjs";
import { SettingsType } from "@ibgib/web-gib/dist/common/settings/settings-constants.mjs";
import { getComponentSvc } from "@ibgib/web-gib/dist/ui/component/ibgib-component-service.mjs";
import {
GLOBAL_LOG_A_LOT,
// ARMY_STORE, BEE_KEY, BLANK_GIB_DB_NAME,
} from "../../constants.mjs";
import { getComponentCtorArg, getCurrentActiveProjectComponent, } from "../../helpers.web.mjs";
import { MinigameFiniteStateMachine, MinigameGamePhase, MinigameIbGib_V1 } from "../../common/minigame/minigame-types.mjs";
import { isMinigameIbGib_V1, } from "../../common/minigame/minigame-helper.mjs";
import {
AGENT_AVAILABLE_FUNCTIONS_MINIGAMEAGENT,
MINIGAME_REL8N_NAME,
} from "../../common/minigame/minigame-constants.mjs";
// import {
// Minigame_TypingGameState, Minigame_TypingGameMeta,
// } from "../../common/minigame/typing/typing-types.mjs";
// import {
// MINIGAME_GAME_VARIANT_TYPING_VALUES, MinigameGameVariant_Typing
// } from "../../common/minigame/typing/typing-constants.mjs";
import { TYPING_COMPONENT_NAME, TypingComponentInstance } from "./typing/typing-component-one-file.mjs";
import { getAppShellSvc } from "../../ui/shell/app-shell-service.mjs";
import { Metronome } from "../../common/minigame/metronome.mjs";
const logalot = GLOBAL_LOG_A_LOT;
export const MINIGAME_COMPONENT_NAME: string = 'ibgib-minigame';
export class MinigameComponentMeta extends IbGibDynamicComponentMetaBase {
protected override lc: string = `[${MinigameComponentMeta.name}]`;
/**
* temporary regexp path for our initial dev. this component will become
* attached to actual ib^gib addrs
*/
routeRegExp?: RegExp = new RegExp(MINIGAME_COMPONENT_NAME);
// routeRegExp?: RegExp = /apps\/web1\/gib\/contact.html/;
componentName: string = MINIGAME_COMPONENT_NAME;
constructor() {
super(getComponentCtorArg());
customElements.define(this.componentName, MinigameComponentInstance);
}
/**
* for a minigame, we don't have any additional info in the path.
*/
async createInstance({
path,
ibGibAddr
}: {
path: string;
ibGibAddr: IbGibAddr;
}): Promise {
const lc = `${this.lc}[${this.createInstance.name}]`;
try {
if (logalot) { console.log(`${lc} starting... (I: genuuid)`); }
const component = document.createElement(this.componentName) as MinigameComponentInstance;
await component.initialize({
ibGibAddr,
meta: this,
html: thisHtml,
css: [rootCss, stylesCss, thisCss],
});
return component;
} catch (error) {
console.error(`${lc} ${extractErrorMsg(error)}`);
throw error;
} finally {
if (logalot) { console.log(`${lc} complete.`); }
}
}
}
interface MinigameElements extends ElementsBase {
nameEl: HTMLElement;
timerEl: HTMLElement;
/**
* sets beats per minute
*/
metronomeBpmEl: HTMLInputElement;
/**
* toggle metronome tick
*/
metronomeBtnEl: HTMLButtonElement;
pauseBtnEl: HTMLButtonElement;
playBtnEl: HTMLButtonElement;
/**
* aka stop button
*/
abortBtnEl: HTMLButtonElement;
nextGameBtnEl: HTMLButtonElement;
restartBtnEl: HTMLButtonElement;
maximizeBtnEl: HTMLButtonElement;
minigameInstructionsBtnEl: HTMLButtonElement;
/**
* used when pausing the game
*/
pauseScreenEl: HTMLDivElement;
// minigameResumeBtnEl: HTMLButtonElement;
/**
* contains the concrete minigame component, (i.e. where the actual game is
* injected)
*/
minigameComponentDivEl: HTMLDivElement;
}
export type MinigameChildComponentInstance = any;// RawComponentInstance | TextEditorComponentInstance;
export class MinigameComponentInstance
extends IbGibDynamicComponentInstanceBase
implements IbGibDynamicComponentInstance {
protected override lc: string = `[${MinigameComponentInstance.name}]`;
metaspace: MetaspaceService | undefined;
private gameFSM: MinigameFiniteStateMachine | undefined = undefined;
private isMinigame = true;
constructor() {
super();
if (!window.AudioContext && !(window as any).webkitAudioContext) {
console.error(`no audio context available (E: c094e8547495c99c0898cfd8ebb37c25)`);
}
}
/**
* creates the concrete minigame component to be injected.
*
* atow (06/2025) only the typing component is available.
*
* ## notes
*
* I've kept the name the same as the tabbed component's that activate child
* ibgibs, e.g., in the projects and project components. Not sure if this is the right thing to do,
* but I figure the minigame is basically a parent component and we're
* activating - in this case - a single concrete "child".
*/
protected async activateIbGib({ addr, ibGib, }: { addr?: IbGibAddr; ibGib?: IbGib_V1; }): Promise {
const lc = `${this.lc}[${this.activateIbGib.name}]`;
try {
if (logalot) { console.log(`${lc} starting... (I: genuuid)`); }
if (!addr && !ibGib) { throw new Error(`(UNEXPECTED) both addr and ibGib falsy? either addr or ibGib required. (E: genuuid)`); }
addr ??= getIbGibAddr({ ibGib });
this.ibGibAddr = addr;
await this.loadIbGib({ getLatest: true });
ibGib = this.ibGib;
if (!ibGib) { throw new Error(`(UNEXPECTED) ibGib false after loading? (E: cc3cb678360b6c23b843fe58b58dee25)`); }
addr = getIbGibAddr({ ibGib });
// let minigameSettings: Settings_Minigame | undefined = await this.getCurrentMinigameSettings();
// if (!minigameSettings) {
// throw new Error(`(UNEXPECTED) couldn't get current minigame settings? i thought this would initialized by now. (E: genuuid)`);
// }
const tjpAddr = getTjpAddr({ ibGib, defaultIfNone: 'incomingAddr' });
if (!tjpAddr) { throw new Error(`(UNEXPECTED) tjpAddr falsy? 'incomingAddr' was used as the default option. (E: genuuid)`); }
// ibgib is loaded.
this.isMinigame = isMinigameIbGib_V1(ibGib);
if (!this.isMinigame) { return; /* <<<< returns early */ }
// we know we have a minigame. load the concrete component that
// corresponds to the exact details of the minigame ibgib.
const { data } = ibGib;
if (!data) { throw new Error(`(UNEXPECTED) ibGib.data falsy after verifying that it is a MinigameIbGib_V1? (E: 178b183483fde4e4e8491a88d8a76825)`); }
if (data.gameType !== 'typing') {
throw new Error(`${data.gameType} not implemented. only "typing" is implemented right now. (E: dd3f080b752f6ba0af08d4082984ad25)`);
}
// get typing component and inject it
const componentSvc = await getComponentSvc();
const typingComponent =
await componentSvc.getComponentInstance({
path: TYPING_COMPONENT_NAME,
ibGibAddr: addr,
useRegExpPrefilter: true,
}) as TypingComponentInstance;
if (!typingComponent) { throw new Error(`(UNEXPECTED) typingComponent falsy? couldn't get a typingComponent? (E: d316285d8fd80adf986f46038279d325)`); }
if (this.gameFSM) {
debugger; // minigame comp: unload fsm when activating new minigame ibgib? does this ever hit?
// this.gameFSM.phase
}
this.gameFSM = typingComponent;
if (!this.elements) { throw new Error(`(UNEXPECTED) this.elements falsy? how did we get here before init of elements? (E: 349b58bdd3e8e1c976a7f6fb257aef25)`); }
const { minigameComponentDivEl } = this.elements;
// anything before injecting?
await componentSvc.inject({
parentEl: minigameComponentDivEl,
componentToInject: typingComponent,
});
// update settings for currently active ibgib
await this.renderUI();
} catch (error) {
console.error(`${lc} ${extractErrorMsg(error)}`);
throw error;
} finally {
if (logalot) { console.log(`${lc} complete.`); }
}
}
// private async activateIbGib_typing({
// addr,
// ibGib,
// data,
// }: {
// addr: string;
// ibGib: MinigameIbGib_V1;
// data: MinigameData_V1;
// }): Promise {
// const lc = `${this.lc}[${this.activateIbGib_typing.name}]`;
// try {
// if (logalot) { console.log(`${lc} starting... (I: 0df74db944783bbec8a0537f58c9d225)`); }
// switch (data.gameVariant) {
// case MinigameGameVariant_Typing.parrot:
// await this.forwardGame_parrot({
// addr,
// ibGib,
// data,
// });
// break;
// case MinigameGameVariant_Typing.antiphony:
// throw new Error(`not implemented antiphony variant (E: da104857e4887cbd384f75e8a8fe0725)`);
// // await this.forwardGame_parrot({
// // addr,
// // ibGib,
// // data,
// // });
// break;
// default:
// throw new Error(`(UNEXPECTED) unknown MinigameGameVariant (${data.gameVariant})? expected one of ${MINIGAME_GAME_VARIANT_TYPING_VALUES} (E: 2af65880dfa89b1cd5c83ffa4e00b825)`);
// }
// } catch (error) {
// console.error(`${lc} ${extractErrorMsg(error)}`);
// throw error;
// } finally {
// if (logalot) { console.log(`${lc} complete.`); }
// }
// }
override async initialize(opts: IbGibDynamicComponentInstanceInitOpts): Promise {
const lc = `${this.lc}[${this.initialize.name}]`;
try {
if (logalot) { console.log(`${lc} starting... (I: genuuid)`); }
await super.initialize(opts);
await this.loadIbGib({ getLatest: true });
this.metaspace = await getGlobalMetaspace_waitIfNeeded();
this.agentsInitialized = this.initAgents();
} catch (error) {
console.error(`${lc} ${extractErrorMsg(error)}`);
throw error;
} finally {
if (logalot) { console.log(`${lc} complete.`); }
}
}
override async created(): Promise {
const lc = `${this.lc}[${this.created.name}]`;
try {
if (logalot) { console.log(`${lc} starting... (I: genuuid)`); }
// const { meta, htmlPath, scriptPaths, cssPaths } = opts;
// at this point, this.ibGib should be loaded with the latest
// minigame ibgib
if (!this.ibGib) { throw new Error(`(UNEXPECTED) this.ibGib falsy? It is assumed at this point that we have a valid ibGib to work with. (E: genuuid)`); }
await this.initElements();
await this.initSettings();
await this.agentsInitialized;
await this.activateIbGib({ ibGib: this.ibGib });
await this.renderUI();
// await this.agent!.witness(ROOT); // don't auto-prompt at this time because during dev it's extremely annoying and stresses out the agents
// spin off because created has to finish
const minigameSettings = await this.getCurrentMinigameSettings();
// i think this is where we would "start"/"resume" based on the
// current game state/phase/variant
// if ((minigameSettings?.openChildTjpAddrs ?? []).length === 0) {
// // first run
// this.showInfoTab();
// } else {
// // not first run, so just reopen the old tabs
// this.reopenOldTabs();
// }
} catch (error) {
console.error(`${lc} ${extractErrorMsg(error)}`);
throw error;
} finally {
if (logalot) { console.log(`${lc} complete.`); }
}
}
override async handleContextUpdated(): Promise {
const lc = `${this.lc}[${this.handleContextUpdated.name}]`;
try {
if (logalot) { console.log(`${lc} starting... (I: genuuid)`); }
await super.handleContextUpdated();
await this.renderUI();
} catch (error) {
console.error(`${lc} ${extractErrorMsg(error)}`);
throw error;
} finally {
if (logalot) { console.log(`${lc} complete.`); }
}
}
private async initElements(): Promise {
const lc = `${this.lc}[${this.initElements.name}]`;
try {
if (logalot) { console.log(`${lc} starting... (I: genuuid)`); }
const shadowRoot = this.shadowRoot;
if (!shadowRoot) { throw new Error(`(UNEXPECTED) shadowRoot falsy? (E: genuuid)`); }
// #region header
const headerEl = shadowRoot_getElementById(shadowRoot, 'minigame-header');
const timerEl = shadowRoot_getElementById(shadowRoot, 'minigame-timer');
// #endregion header
const contentEl = shadowRoot_getElementById(shadowRoot, 'minigame-content');
const nameEl = shadowRoot_getElementById(shadowRoot, 'minigame-name');
const minigameInstructionsBtnEl = shadowRoot_getElementById(shadowRoot, 'minigame-instructions-btn');
minigameInstructionsBtnEl.addEventListener('click', (event) => {
event.preventDefault();
event.stopPropagation();
alertUser({ title: this.ibGib?.data?.name, msg: `${this.ibGib?.data?.description}\n\nInstructions:\n\n${this.ibGib?.data?.instructions}` });
});
const maximizeBtnEl = shadowRoot_getElementById(shadowRoot, 'maximize-btn');
maximizeBtnEl.addEventListener('click', () => {
const appShellSvc = getAppShellSvc();
appShellSvc.collapse({
panelNames: ['leftPanel', 'rightPanel', 'footerPanel'],
});
});
const minigameComponentDivEl = shadowRoot_getElementById(shadowRoot, 'minigame-component-div');
const pauseScreenEl = shadowRoot_getElementById(shadowRoot, 'minigame-pause-screen');
pauseScreenEl.style.display = 'none';
pauseScreenEl.style.zIndex = "5";
// #region footer
const footerEl = shadowRoot_getElementById(shadowRoot, 'minigame-footer');
// metronomeBtnEl - metronome play/pause toggle
const metronomeBpmEl = shadowRoot_getElementById(shadowRoot, 'metronome-bpm');
metronomeBpmEl.addEventListener('input', async (event) => {
this.metronome.info.bpm = Number.parseInt(metronomeBpmEl.value || "120");
})
const metronomeBtnEl = shadowRoot_getElementById(shadowRoot, 'metronome-play-pause-btn');
metronomeBtnEl.addEventListener('click', async (event) => {
event.preventDefault();
event.stopPropagation();
await this.toggleMetronome();
});
// pauseBtnEl
const pauseBtnEl = shadowRoot_getElementById(shadowRoot, 'minigame-header-pause-btn');
pauseBtnEl.addEventListener('click', async (event) => {
await this.pause();
});
// playBtnEl
const playBtnEl = shadowRoot_getElementById(shadowRoot, 'minigame-header-play-btn');
playBtnEl.addEventListener('click', async (event) => {
await this.play();
});
// abortBtnEl
const abortBtnEl = shadowRoot_getElementById(shadowRoot, 'minigame-header-abort-btn');
abortBtnEl.addEventListener('click', async (event) => {
await this.abort();
});
// nextGameBtnEl
const nextGameBtnEl = shadowRoot_getElementById(shadowRoot, 'minigame-header-next-btn');
nextGameBtnEl.addEventListener('click', async (event) => {
await this.nextGame();
});
// restartBtnEl
const restartBtnEl = shadowRoot_getElementById(shadowRoot, 'minigame-header-restart-btn');
restartBtnEl.addEventListener('click', async (event) => {
await this.restart();
});
// #endregion footer
this.elements = {
headerEl,
contentEl,
nameEl,
pauseScreenEl,
// minigameResumeBtnEl,
minigameComponentDivEl,
metronomeBpmEl,
metronomeBtnEl,
footerEl,
timerEl,
playBtnEl,
pauseBtnEl,
abortBtnEl,
nextGameBtnEl,
restartBtnEl,
minigameInstructionsBtnEl,
maximizeBtnEl,
};
// has to run after this.elements set
await this.initSettings();
// await this.initTimer();
} catch (error) {
console.error(`${lc} ${extractErrorMsg(error)}`);
throw error;
} finally {
if (logalot) { console.log(`${lc} complete.`); }
}
}
private metronome = new Metronome({
bpm: 120,
duration: 0.1,
frequency: 440,
volume: 0.5,
audioCtx: new (window.AudioContext || (window as any).webkitAudioContext)(),
});
private async toggleMetronome(): Promise {
const lc = `${this.lc}[${this.toggleMetronome.name}]`;
try {
if (logalot) { console.log(`${lc} starting... (I: 005fe78b1f51d2d7f4055688f13d6825)`); }
// todo: update metronome bpm from UI
const bpmStr = this.elements!.metronomeBpmEl.value || '60';
const bpm = Number.parseInt(bpmStr);
this.metronome.info.bpm = bpm;
if (this.metronome.isTicking) {
await this.metronome.stop();
} else {
await this.metronome.start();
}
} catch (error) {
console.error(`${lc} ${extractErrorMsg(error)}`);
throw error;
} finally {
if (logalot) { console.log(`${lc} complete.`); }
}
}
private async pause(): Promise {
const lc = `${this.lc}[${this.pause.name}]`;
try {
if (logalot) { console.log(`${lc} starting... (I: 268c84faf1ec217571de75e8d3ef6825)`); }
if (!this.elements) { throw new Error(`(UNEXPECTED) this.elements falsy? (E: 48ef4c6ed678779324821aa8ed44c425)`); }
const { pauseScreenEl, minigameComponentDivEl } = this.elements;
if (!this.gameFSM) { throw new Error(`(UNEXPECTED) this.gameFSM falsy? (E: 7b7abfac43c845d4c8eb8938c1312825)`); }
if (this.gameFSM.gamePhase === MinigameGamePhase.paused) {
console.warn(`${lc} (UNEXPECTED) already paused? (W: 119d83ea5c0849c5066b0a78fb244525)`)
return; /* <<<< returns early */
}
// const dialog = document.getElementById('fullscreen-dialog') as HTMLDivElement;
// dialog.showModal();
pauseScreenEl.style.display = 'flex';
minigameComponentDivEl.style.display = 'none';
await this.gameFSM.pause();
await this.renderUI();
} catch (error) {
console.error(`${lc} ${extractErrorMsg(error)}`);
throw error;
} finally {
if (logalot) { console.log(`${lc} complete.`); }
}
}
private async play(): Promise {
const lc = `${this.lc}[${this.play.name}]`;
try {
if (logalot) { console.log(`${lc} starting... (I: genuuid)`); }
if (!this.elements) { throw new Error(`(UNEXPECTED) this.elements falsy? (E: genuuid)`); }
const { pauseScreenEl, minigameComponentDivEl } = this.elements;
// const dialog = document.getElementById('fullscreen-dialog') as HTMLDivElement;
if (!this.ibGib?.data?.playable) {
console.warn(`${lc} not playable (W: 3680a81400efea0728940af8bec0c125)`);
return; /* <<<< returns early */
}
if (!this.gameFSM) { throw new Error(`(UNEXPECTED) this.gameFSM falsy? (E: bcf0088119668a4ec3bbd5a8f9e20f25)`); }
if (this.gameFSM.gamePhase === MinigameGamePhase.playing) {
console.warn(`${lc} already playing... (W: 119d83ea5c0849c5066b0a78fb244525)`)
return; /* <<<< returns early */
}
await this.gameFSM.play();
this.startTimer();
await this.renderUI();
} catch (error) {
console.error(`${lc} ${extractErrorMsg(error)}`);
throw error;
} finally {
if (logalot) { console.log(`${lc} complete.`); }
}
}
private async restart(): Promise {
const lc = `${this.lc}[${this.restart.name}]`;
try {
if (logalot) { console.log(`${lc} starting... (I: genuuid)`); }
if ([MinigameGamePhase.playing, MinigameGamePhase.paused].includes(this.ibGib!.data!.gamePhase as any)) {
const confirm = await promptForConfirm({
msg: `Abort playing this game and restart?`,
yesLabel: `Yes, this game was lame but had promise.`,
noLabel: `No, this is good. I just screwed up.`,
});
if (!confirm) {
console.log(`${lc} user canceled restart. (I: 0aa4c8c182e87a838853bef42ce78f25)`);
return; /* <<<< returns early */
}
}
if (!this.elements) { throw new Error(`(UNEXPECTED) this.elements falsy? (E: genuuid)`); }
const { pauseScreenEl, minigameComponentDivEl } = this.elements;
// if (!this.ibGib?.data?.playable) {
// console.warn(`${lc} not playable (W: 3680a81400efea0728940af8bec0c125)`);
// return; /* <<<< returns early */
// }
if (!this.gameFSM) { throw new Error(`(UNEXPECTED) this.gameFSM falsy? (E: bcf0088119668a4ec3bbd5a8f9e20f25)`); }
pauseScreenEl.style.display = 'none';
minigameComponentDivEl.style.display = 'flex';
await this.gameFSM.restart();
this.stopTimer();
await this.renderUI();
} catch (error) {
console.error(`${lc} ${extractErrorMsg(error)}`);
throw error;
} finally {
if (logalot) { console.log(`${lc} complete.`); }
}
}
private async abort(): Promise {
const lc = `${this.lc}[${this.abort.name}]`;
try {
if (logalot) { console.log(`${lc} starting... (I: genuuid)`); }
if (!this.elements) { throw new Error(`(UNEXPECTED) this.elements falsy? (E: genuuid)`); }
const confirm = await promptForConfirm({
msg: `Abort playing this game?`,
yesLabel: `Yes, I'm done with this one.`,
noLabel: `No, let's keep going.`,
});
if (!confirm) {
console.log(`${lc} user canceled aborting. (I: eef296edf4bd1340e1d78d7860c61825)`);
return; /* <<<< returns early */
}
const { pauseScreenEl, minigameComponentDivEl } = this.elements;
// const dialog = document.getElementById('fullscreen-dialog') as HTMLDivElement;
if (!this.gameFSM) { throw new Error(`(UNEXPECTED) this.gameFSM falsy? (E: genuuid)`); }
if (this.gameFSM.gamePhase !== MinigameGamePhase.playing) {
console.warn(`${lc} not playing... (W: genuuid)`)
return; /* <<<< returns early */
}
await this.gameFSM.abort();
this.stopTimer();
await this.renderUI();
} catch (error) {
console.error(`${lc} ${extractErrorMsg(error)}`);
throw error;
} finally {
if (logalot) { console.log(`${lc} complete.`); }
}
}
private async nextGame(): Promise {
const lc = `${this.lc}[${this.nextGame.name}]`;
try {
if (logalot) { console.log(`${lc} starting... (I: genuuid)`); }
if (!this.elements) { throw new Error(`(UNEXPECTED) this.elements falsy? (E: genuuid)`); }
if (!this.gameFSM) { throw new Error(`(UNEXPECTED) this.gameFSM falsy? (E: genuuid)`); }
const confirm = this.gameFSM.gamePhase === MinigameGamePhase.playing ?
await promptForConfirm({
msg: `Abort playing this game and start the next one in the project?`,
yesLabel: `Yes, I'm done with this one.`,
noLabel: `No, let's keep going.`,
}) :
await promptForConfirm({
msg: `Start the next game in the project?`,
yesLabel: `Yes, next please.`,
noLabel: `No, let's keep playing this game.`,
});
if (!confirm) {
console.log(`${lc} user canceled skip to next game. (I: genuuid)`);
return; /* <<<< returns early */
}
// get the current project ibgib
const currentProjectComponent = getCurrentActiveProjectComponent();
const projectIbGib = currentProjectComponent.ibGib!;
// find the index of the the current minigame
let nextIndex: number;
const minigameAddrs = projectIbGib.rel8ns![MINIGAME_REL8N_NAME]!;
const currentMinigameIndex = minigameAddrs.findIndex(addr => {
const { gib } = getIbAndGib({ ibGibAddr: addr });
const tjpGib = gib.includes(GIB_DELIMITER) ?
gib.split(GIB_DELIMITER).at(-1)! :
gib;
const thisTjpGib = getIbAndGib({ ibGibAddr: this.tjpAddr }).gib;
return thisTjpGib === tjpGib;
});
if (currentMinigameIndex >= 0) {
if (currentMinigameIndex === (minigameAddrs.length - 1)) {
// current minigame is the last in the list
const startAtBeginning = await promptForConfirm({
msg: `This is the last minigame. Would you like to load the first one?`,
yesLabel: `Yes, load the very first minigame`,
noLabel: `Nevermind, stay here.`,
});
if (startAtBeginning) {
nextIndex = 0;
} else {
return; /* <<<< returns early */
}
} else {
// guaranteed to have an index one higher than the current
// index
nextIndex = currentMinigameIndex + 1;
}
} else {
console.error(`${lc} could not find the index of the current minigame? Does this minigame (${this.ibGibAddr}) exist on the current active project? Or have we moved on to minigames on project child ibgibs? (E: a247f4ce8469ba5ed848207365579e25)`);
await alertUser({ title: 'whoops', msg: `We seem to not be able to find another minigame. Sorry about that! Hey at least you have the current game.` });
return; /* <<<< returns early */
}
// load the next minigame
let nextMinigameAddr = minigameAddrs.at(nextIndex);
if (nextMinigameAddr) {
await currentProjectComponent.activateIbGib({
// activateIbGib loads the latest in the timeline
addr: nextMinigameAddr,
});
} else {
await alertUser({
title: 'hmm...',
msg: `Well, we thought we had it but something seems to have gone wrong and we lost it. You'll have to manually load another minigame. Contact us about this error though please. (E: 370e6bc457e5d37564cb4fe83cbc7525)`,
});
return; /* <<<< returns early */
}
} catch (error) {
console.error(`${lc} ${extractErrorMsg(error)}`);
throw error;
} finally {
if (logalot) { console.log(`${lc} complete.`); }
}
}
protected override async initSettings(): Promise {
const lc = `${this.lc}[${this.initSettings.name}]`;
try {
if (logalot) { console.log(`${lc} starting... (I: genuuid)`); }
await super.initSettings();
if (!this.settings) { throw new Error(`(UNEXPECTED) this.settings falsy after init? (E: genuuid)`); }
if (!this.settings.ibGib) { throw new Error(`(UNEXPECTED) this.settings.ibGib falsy after init? (E: genuuid)`); }
let minigameSettings = await this.getCurrentMinigameSettings();
if (!minigameSettings) {
// need to create a new minigame settings section
minigameSettings = { type: 'minigame', }
// update the current settings and persist
const sectionName_default = await getSectionName({
settingsType: SettingsType.minigame,
useCase: 'default',
});
const sectionName_current = await getSectionName({
settingsType: SettingsType.minigame,
useCase: 'current',
});
const _newSettings = await mut8Timeline({
timeline: this.settings!.ibGib!,
metaspace: this.metaspace!,
mut8Opts: {
dataToAddOrPatch: {
sections: {
[sectionName_default]: minigameSettings,
[sectionName_current]: minigameSettings,
}
}
},
});
}
// const sectionName_generalCurrent = await getSectionName({
// settingsType: 'general',
// useCase: 'current',
// });
// data.sections[sectionName_generalDefault] = clone(DEFAULT_SETTINGS_GENERAL);
// data.sections[sectionName_generalCurrent] = clone(DEFAULT_SETTINGS_GENERAL);
} catch (error) {
console.error(`${lc} ${extractErrorMsg(error)}`);
throw error;
} finally {
if (logalot) { console.log(`${lc} complete.`); }
}
}
private async getCurrentMinigameSettings(): Promise {
const lc = `${this.lc}[${this.getCurrentMinigameSettings.name}]`;
try {
if (logalot) { console.log(`${lc} starting... (I: genuuid)`); }
if (!this.settings) { throw new Error(`(UNEXPECTED) this.settings falsy? shouldn't this be initialized by now? (E: genuuid (E: genuuid)`); }
if (!this.settings.ibGib) { throw new Error(`(UNEXPECTED) this.settings.ibGib falsy? shouldn't this be initialized by now? especially since this.settings is truthy? (E: genuuid)`); }
const minigameSettings = await getSettingsSection({
sectionName: await getSectionName({
settingsType: 'minigame',
useCase: 'current',
}),
settingsIbGib: this.settings.ibGib,
}) as (Settings_Minigame | undefined);
if (!minigameSettings) {
if (logalot) { console.log(`${lc} minigameSettings does not exist. returning undefined. (I: genuuid)`) }
}
return minigameSettings;
// if (!minigameSettings) { throw new Error(`(UNEXPECTED) couldn't get minigame settings? should be guaranteed after init. (E: genuuid)`); }
// return minigameSettings;
} catch (error) {
console.error(`${lc} ${extractErrorMsg(error)}`);
throw error;
} finally {
if (logalot) { console.log(`${lc} complete.`); }
}
}
/**
* rerender
*/
protected override async renderUI(): Promise {
const lc = `${this.lc}[${this.renderUI.name}]`;
try {
if (logalot) { console.log(`${lc} starting... (I: genuuid)`); }
await super.renderUI();
const { elements, ibGib } = this;
if (!elements) {
console.warn(`${lc} (UNEXPECTED) tried to render but haven't initialized elements? (W: genuuid)`);
return; /* <<<< returns early */
}
if (!ibGib) {
debugger; // warning - this.ibGib falsy? in minigame comp
console.log(`${lc} this.ibGib falsy? returning early... (W: genuuid)`)
return; /* <<<< returns early */
}
const { data } = ibGib;
if (!data) { throw new Error(`(UNEXPECTED) this.ibGib.data falsy? (E: df3f386eb978cc232921e668e16af825)`); }
const {
// headerEl, contentEl, footerEl,
nameEl,
playBtnEl, pauseBtnEl, abortBtnEl, restartBtnEl,
pauseScreenEl, minigameComponentDivEl,
// maximizeBtnEl,
} = elements;
if (!this.isMinigame) {
minigameComponentDivEl.textContent = `This ibGib ain't a minigame 🤨`
return; /* <<<< returns early */
}
const minigameSettings = await this.getCurrentMinigameSettings();
if (!minigameSettings) {
console.error(`${lc} (UNEXPECTED) minigameSettings falsy? NOTE: THIS DID NOT THROW. ONLY LOGGED ERROR. (E: 6fb9fd2fda681dee94b355389ee2c225)`);
}
nameEl.textContent = data.name ?? '[minigame no name?]'
nameEl.textContent += ` (v${data.n ?? '?'})`
nameEl.title = data.description;
// if (!this.gameFSM) { throw new Error(`(UNEXPECTED) this.gameFSM falsy? (E: 0b3e91927fb8b6ca285de925821ef825)`); }
const { gamePhase } = data;
if (!gamePhase) { throw new Error(`(UNEXPECTED) gamePhase falsy? (E: 095462d507ea7db7b78658486d01db25)`); }
playBtnEl.disabled =
!data.playable ||
[MinigameGamePhase.playing, MinigameGamePhase.aborted, MinigameGamePhase.complete].includes(gamePhase as any);
pauseBtnEl.disabled =
gamePhase !== MinigameGamePhase.playing;
abortBtnEl.disabled =
gamePhase !== MinigameGamePhase.playing;
restartBtnEl.disabled =
[MinigameGamePhase.init, MinigameGamePhase.ready].includes(gamePhase as any);
// data.gamePhase !== MinigameGamePhase.playing;
// const description = this.ibGib?.data?.description;
// if (description) {
// descEl.textContent = description;
// } else {
// descEl.style.display = 'none';
// }
if (gamePhase === MinigameGamePhase.paused) {
pauseScreenEl.style.display = 'flex';
minigameComponentDivEl.style.display = 'none';
} else {
pauseScreenEl.style.display = 'none';
minigameComponentDivEl.style.display = 'flex';
}
} catch (error) {
console.error(`${lc} ${extractErrorMsg(error)}`);
throw error;
} finally {
if (logalot) { console.log(`${lc} complete.`); }
}
}
// private async calculatePlayingTimeInTicks(): Promise {
// const lc = `${this.lc}[${this.calculatePlayingTimeInTicks.name}]`;
// try {
// if (logalot) { console.log(`${lc} starting... (I: 51828b278e0889887d238e05fb691325)`); }
// if (!this.ibGib) { throw new Error(`(UNEXPECTED) this.ibGib falsy? (E: bb27a8b3ed5838cdffba61f471112d25)`); }
// if (!this.ibGib.data) { throw new Error(`(UNEXPECTED) this.ibGib.data falsy? (E: bd335e090cd26ee7e8d3f8595d087a25)`); }
// const { gameMeta, gameState, gamePhase } = this.ibGib.data;
// const interactions = gameState as Minigame_TypingInteraction[]; // code smell here of course. I need to add interactions to all
// // should calculate the playing time based on the
// // gameState.interactions via the timestamps.
// let totalTime = 0;
// if (gamePhase === MinigameGamePhase.init) {
// totalTime = 0;
// return totalTime; /* <<<< returns early */
// }
// const firstStimulus = interactions.at(0)?.stimulus ?? gameState.currentStimulus;
// if (!firstStimulus) {
// console.warn(`${lc} no firstStimulus? returning 0. (W: genuuid)`)
// return 0; /* <<<< returns early */
// }
// const startTimestamp = firstStimulus.timestampInTicks;
// if (!startTimestamp) {
// console.error(`${lc} firstStimulus didn't have a timestamp? returning 0. (E: genuuid)`);
// return 0; /* <<<< returns early */
// }
// const startTime = Number.parseInt(startTimestamp);
// let pauseFound = false;
// let currentIndex = 1; // index 0 is first stimulus above
// do {
// // look for the next game phase being paused in the history
// const remainingInteractions = interactions.slice(currentIndex);
// remainingInteractions.findIndex
// } while (pauseFound);
// const now_timestampInTicks = getTimestampInTicks();
// const now_number = Number.parseInt(now_timestampInTicks);
// if (gamePhase === MinigameGamePhase.playing) {
// totalTime = now_number - startTime;
// } else if ([MinigameGamePhase.aborted, MinigameGamePhase.complete].includes(gamePhase as any)) {
// let lastTimestamp: string;
// const lastResponse = gameState.interactions.at(-1)?.response;
// if (lastResponse) {
// if (lastResponse.timestampInTicks) {
// lastTimestamp = lastResponse.timestampInTicks;
// } else {
// console.error(`${lc} (UNEXPECTED) lastResponse.timestampInTicks falsy? returning 0 (E: genuuid)`);
// return 0; /* <<<< returns early */
// }
// } else {
// lastTimestamp = now_timestampInTicks;
// }
// const lastTime = Number.parseInt(lastTimestamp);
// totalTime = lastTime - startTime;
// } else if ([MinigameGamePhase.paused].includes(gamePhase as any)) {
// return this.#timerValueInTicks;
// } else {
// // debugger; // typing comp calc timestamp when does this hit?
// return 0;
// }
// if (logalot) { console.log(`${lc} totalTime: ${totalTime} ticks (I: 4f2cd7eac4740a9a48a8ecf80adfb525)`); }
// return totalTime;
// } catch (error) {
// console.error(`${lc} ${extractErrorMsg(error)}`);
// throw error;
// } finally {
// if (logalot) { console.log(`${lc} complete.`); }
// }
// }
#timerInterval: any | undefined;
#timerValueInSeconds: number = 0;
private startTimer(): void {
const lc = `${this.lc}[${this.startTimer.name}]`;
try {
if (logalot) { console.log(`${lc} starting... (I: a9b8281444c8523b68de98e85cbc1825)`); }
if (this.#timerInterval) {
if (logalot) { console.log(`${lc} timer already started. (we were paused?) (I: b09df8d777f80dafd1c2b2dd2ef29825)`); }
return; /* <<<< returns early */
}
this.#timerInterval = setInterval(async () => {
const { gamePhase } = this.gameFSM!;
if (!gamePhase) {
console.warn(`${lc} gamePhase falsy? what? this is expected to be truthy. (W: 25a87ffdb683d197587d2c6e30557825)`);
debugger; // want to see if gamePhase is ever falsy
return; /* <<<< returns early */
}
switch (gamePhase) {
case MinigameGamePhase.ready:
// this.#timerValueInTicks++; // don't increment during ready
break;
case MinigameGamePhase.playing:
this.#timerValueInSeconds++;
break;
case MinigameGamePhase.paused:
// this.#timerValueInTicks++; // don't increment when paused
break;
default:
this.stopTimer();
break;
}
this.renderUI_updateTimer();
}, 1000);
} catch (error) {
console.error(`${lc} ${extractErrorMsg(error)}`);
throw error;
} finally {
if (logalot) { console.log(`${lc} complete.`); }
}
}
private stopTimer(): void {
const lc = `[${this.stopTimer.name}]`;
try {
if (logalot) { console.log(`${lc} starting... (I: 630d585c15acae8dff0a6b28c8c36825)`); }
if (this.#timerInterval) {
clearInterval(this.#timerInterval);
this.#timerInterval = undefined;
} else {
console.error(`${lc} no timer started? (E: genuuid)`);
}
} catch (error) {
console.error(`${lc} ${extractErrorMsg(error)}`);
throw error;
} finally {
if (logalot) { console.log(`${lc} complete.`); }
}
}
private renderUI_updateTimer() {
const lc = `${this.lc}[${this.renderUI_updateTimer.name}]`;
try {
if (logalot) { console.log(`${lc} starting... (I: genuuid)`); }
const { elements, } = this;
if (!elements) { throw new Error(`(UNEXPECTED) this.elements falsy? (E: genuuid)`); }
const { timerEl, } = elements;
// convert #timerValueInTicks to show as HH:MM:SS
/**
* going to pull out hours and minutes. this will be the working
* seconds remaining after each time we pull time out.
*/
let runningTotalSeconds = this.#timerValueInSeconds; // Assuming ticks are milliseconds
const hours = Math.floor(runningTotalSeconds / 3600);
runningTotalSeconds -= hours * 3600;
const minutes = Math.floor((runningTotalSeconds % 3600) / 60);
runningTotalSeconds -= minutes * 60;
const seconds = runningTotalSeconds % 60;
const formattedTime = hours === 0 ?
[
// hours.toString().padStart(2, '0'),
minutes.toString().padStart(2, '0'),
seconds.toString().padStart(2, '0'),
].join(':') :
[
hours.toString().padStart(2, '0'),
minutes.toString().padStart(2, '0'),
seconds.toString().padStart(2, '0'),
].join(':');
timerEl.textContent = formattedTime;
} catch (error) {
console.error(`${lc} ${extractErrorMsg(error)}`);
throw error;
} finally {
if (logalot) { console.log(`${lc} complete.`); }
}
}
override async disconnected(): Promise {
const lc = `${this.lc}[${this.disconnected.name}]`;
try {
if (logalot) { console.log(`${lc} starting... (I: genuuid)`); }
// no action atow
} catch (error) {
console.error(`${lc} ${extractErrorMsg(error)}`);
throw error;
} finally {
if (logalot) { console.log(`${lc} complete.`); }
}
}
protected override async initAgents(): Promise {
const lc = `${this.lc}[${this.initAgents.name}]`;
try {
if (logalot) { console.log(`${lc} starting... (I: genuuid)`); }
await this.loadAgentsCoupledToIbGib();
if (!this.agent) { throw new Error(`(UNEXPECTED) agent falsy after loadAgentsCoupledToIbGib? (E: genuuid)`); }
await this.agent.updateAvailableFunctions({
availableFunctions: AGENT_AVAILABLE_FUNCTIONS_MINIGAMEAGENT,
});
} catch (error) {
console.error(`${lc} ${extractErrorMsg(error)}`);
throw error;
} finally {
if (logalot) { console.log(`${lc} complete.`); }
}
}
// private getAPIKey(): Promise {
// const fn = this.getFnGetAPIKey();
// return fn();
// }
// private getFnGetAPIKey(): () => Promise {
// const lc = `${this.lc}[${this.getFnGetAPIKey.name}]`;
// try {
// if (logalot) { console.log(`${lc} starting... (I: genuuid)`); }
// const fn = async () => {
// let apiKey = await storageGet({
// dbName: BLANK_GIB_DB_NAME, storeName: ARMY_STORE,
// key: BEE_KEY,
// });
// return apiKey ?? '';
// };
// return fn;
// } catch (error) {
// console.error(`${lc} ${extractErrorMsg(error)}`);
// throw error;
// } finally {
// if (logalot) { console.log(`${lc} complete.`); }
// }
// }
}