import { Locator } from "@popiplay/slot-game-kit"; import { SKIN_TYPES } from "../src/config"; /** * Creates a callback function that executes a specific callback after a defined count of invocations. * * @param {number} n - The count threshold that triggers the callback execution. * @param {Function} callback - The function to execute after `n` invocations. Defaults to an empty function. * @param {Function} eachCallback - The function to execute on each invocation. Defaults to an empty function. * @return {Function} A new function that increments a count each time it's called, executes `eachCallback`, * and if the count equals `n`, executes `callback` and resets the count. * * @example * // Logs 'Hello, third time!' every third invocation * const logEveryTime = (...args) => console.log('Called with', args); * const sayHelloEveryThirdTime = () => console.log('Hello, third time!'); * const thresholdCallback = createThresholdCallback(3, sayHelloEveryThirdTime, logEveryTime); * * thresholdCallback('test'); // Logs 'Called with ["test"]' * thresholdCallback('again'); // Logs 'Called with ["again"]' * thresholdCallback('and again'); // Logs 'Called with ["and again"]' and 'Hello, third time!' * thresholdCallback('another one'); // Logs 'Called with ["another one"]' * */ export function createThresholdCallback( n, callback = () => { }, eachCallback = () => { } ) { let counter = 0; return function (...args) { counter++; eachCallback(...args); if (n === counter) { counter = 0; callback(...args); } } } /** * Asynchronously waits for a specified event to be emitted once on the target object. * * @async * @param {EventEmitter} target - The EventEmitter object on which to listen for the event. * @param {string} event - The name of the event to listen for. * @return {Promise} A promise that resolves when the specified event is emitted on the target. * * Usage: * ```javascript * await waitForEventOnce(someObject, 'eventName'); * // Following code won't execute until 'eventName' is emitted on 'someObject' for the first time. * ``` */ export async function waitForEventOnce(target, event) { return new Promise((resolve) => { target.once(event, () => { resolve() }); }) } /** * Asynchronously waits for the first event from a list of events to be emitted. * * @async * @param {Array<{ target: EventEmitter, event: string }>} events - An array of objects, each containing an EventEmitter object and the event to listen for. * @return {Promise} A Promise that resolves as soon as one of the specified events is emitted on its respective EventEmitter. * * Usage: * ```javascript * const events = [ * { target: someObject1, event: 'eventName1'}, * { target: someObject2, event: 'eventName2'} * ]; * * await waitForFirstEmittedEvent(events); * // Following code won't execute until either 'eventName1' is emitted on 'someObject1' or 'eventName2' is emitted on 'someObject2' for the first time. * ``` */ export async function waitForFirstEmittedEvent(events) { const promises = events.map(({ target, event }) => { return waitForEventOnce(target, event); }) return Promise.race(promises) } export function capitalizeFirstLetter(str) { return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase(); } export const transpose = (matrix) => matrix[0].map((_, i) => matrix.map(row => row[i])); export const isObject = (obj) => obj && typeof obj === 'object' && !Array.isArray(obj); /** * @description - Deeply merges two objects. * * @param {Object} target - The target object to merge properties into. * @param {Object} source - The source object from which to copy properties. * @return {Object} The merged object. * * @example * const obj1 = { a: 1, b: { c: 2 } }; * const obj2 = { b: { d: 3 }, e: 4 }; * const result = deepMerge(obj1, obj2); * // result is { a: 1, b: { c: 2, d: 3 }, e: 4 } */ export function deepMerge(target, source) { if (!isObject(target) || !isObject(source)) { return source; } const merged = { ...target }; Object.keys(source).forEach(key => { if (isObject(source[key])) { if (!target[key]) { merged[key] = source[key]; } else { merged[key] = deepMerge(target[key], source[key]); } } else { merged[key] = source[key]; } }); return merged; } export const getSkinType = (threshhold) => { return Locator.viewport.cropWidthRatio < threshhold ? SKIN_TYPES.PORT : SKIN_TYPES.LAND; } export const getStyle = (styleTempl, dynamicStyle, threshhold) => { const skinType = getSkinType(threshhold); const hasDynamicStyle = !!dynamicStyle && typeof dynamicStyle === 'object' && !!Object.entries(dynamicStyle).length; const additionalStyles = dynamicStyle?.[skinType] || dynamicStyle; return hasDynamicStyle ? deepMerge(styleTempl, additionalStyles) : styleTempl; } export const getRandomInt = (min, max) => { min = Math.ceil(min); max = Math.floor(max); return Math.floor(Math.random() * (max - min + 1)) + min; }