initial
This commit is contained in:
62
utils/GameErrors.js
Normal file
62
utils/GameErrors.js
Normal file
@@ -0,0 +1,62 @@
|
||||
import {Locator} from "@popiplay/slot-game-kit";
|
||||
|
||||
function getErrorDescriptionByCode(code) {
|
||||
switch (code) {
|
||||
case 101:
|
||||
case 102:
|
||||
case 103:
|
||||
case 104:
|
||||
case 105:
|
||||
case 201:
|
||||
case 202:
|
||||
case 203:
|
||||
case 204:
|
||||
case 205:
|
||||
return Locator.locales.get("the_bet_has_not_been_accepted");
|
||||
case 206:
|
||||
return Locator.locales.get("your_freespins_are_ended");
|
||||
case 301:
|
||||
return Locator.locales.get('you_have_insufficient_funds');
|
||||
case 302:
|
||||
case 303:
|
||||
case 401:
|
||||
case 402:
|
||||
case "301nodep":
|
||||
case "206i":
|
||||
case "some_error":
|
||||
case "many_tabs_error":
|
||||
return Locator.locales.get(code);
|
||||
case "default":
|
||||
default: {
|
||||
const textLocale = Locator.locales.get("default").split("{code}");
|
||||
return `${textLocale[0]} ${code} ${textLocale[1]}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function checkForErrors(response, showPreloader) {
|
||||
switch (true) {
|
||||
case (response instanceof NetworkError):
|
||||
throw response;
|
||||
case ("errors" in response):
|
||||
throw new ResponseResultError(response.errors[0].code, showPreloader);
|
||||
}
|
||||
}
|
||||
|
||||
export class ResponseResultError extends Error {
|
||||
/**
|
||||
*
|
||||
* @param {number} code the response error code
|
||||
*/
|
||||
constructor(code, showPreloader) {
|
||||
super(getErrorDescriptionByCode(code));
|
||||
this.code = code;
|
||||
this.showPreloader = showPreloader;
|
||||
}
|
||||
}
|
||||
|
||||
export class NetworkError extends Error {
|
||||
constructor() {
|
||||
super("The client can't connect to the server");
|
||||
}
|
||||
}
|
||||
20
utils/Numbers.js
Normal file
20
utils/Numbers.js
Normal file
@@ -0,0 +1,20 @@
|
||||
export default class Numbers {
|
||||
static format(number, exponent = 2) {
|
||||
if (typeof number !== 'number') return number;
|
||||
if (number === 0) {
|
||||
return '0';
|
||||
}
|
||||
return number.toFixed(exponent)
|
||||
}
|
||||
|
||||
static getInfinityInsteadOfNegative(number) {
|
||||
if (number < 0) {
|
||||
return '∞';
|
||||
}
|
||||
return number;
|
||||
}
|
||||
|
||||
static addThousandSeparators(number, delimiter = ',') {
|
||||
return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, delimiter);
|
||||
}
|
||||
}
|
||||
28
utils/PixiHtmlContainer.js
Normal file
28
utils/PixiHtmlContainer.js
Normal file
@@ -0,0 +1,28 @@
|
||||
import { Container, Transform } from 'pixi.js';
|
||||
import { div } from './htmlUtils.js';
|
||||
import {Locator} from "@popiplay/slot-game-kit";
|
||||
|
||||
export class PixiHtmlContainer extends Container {
|
||||
constructor(parentDom, className, style = {}) {
|
||||
super();
|
||||
this.element = div(className);
|
||||
this.domTransform = new Transform();
|
||||
Object.assign(this.element.style, style);
|
||||
parentDom?.append(this.element);
|
||||
|
||||
Locator.viewport.on('resize', this.updateTransform, this);
|
||||
}
|
||||
|
||||
updateTransform() {
|
||||
super.updateTransform();
|
||||
const globalTransform = this.parent.transform.worldTransform;
|
||||
const decomposition = globalTransform.decompose(this.domTransform);
|
||||
let transform = `scale(${decomposition.scale.x}, ${decomposition.scale.y}) translate(-50%, -50%)`
|
||||
this.element.style.transform = transform;
|
||||
}
|
||||
|
||||
destroy(...args) {
|
||||
Locator.viewport.off('resize', this.updateTransform, this);
|
||||
super.destroy(...args);
|
||||
}
|
||||
}
|
||||
16
utils/SeededRandom.js
Normal file
16
utils/SeededRandom.js
Normal file
@@ -0,0 +1,16 @@
|
||||
export class SeededRandom {
|
||||
constructor(seed) {
|
||||
this.seed = seed;
|
||||
}
|
||||
|
||||
// Generates a random number
|
||||
next() {
|
||||
this.seed = (this.seed * 9301 + 49297) % 233280;
|
||||
return this.seed / 233280;
|
||||
}
|
||||
|
||||
// Generates a random integer within a range
|
||||
nextInt(min, max) {
|
||||
return Math.floor(this.next() * (max - min + 1)) + min;
|
||||
}
|
||||
}
|
||||
8
utils/SpineHelper.js
Normal file
8
utils/SpineHelper.js
Normal file
@@ -0,0 +1,8 @@
|
||||
export class SpineHelper {
|
||||
static skip(spine, trackIndex = 0) {
|
||||
const track = spine.state.tracks[trackIndex];
|
||||
track.trackTime = track.animation.duration;
|
||||
track.loop = false;
|
||||
spine.update(0);
|
||||
}
|
||||
}
|
||||
30
utils/TextHelper.js
Normal file
30
utils/TextHelper.js
Normal file
@@ -0,0 +1,30 @@
|
||||
export class TextHelper {
|
||||
static scaleToFit(textObject, size) {
|
||||
if (!size.width) size.width = Infinity;
|
||||
if (!size.height) size.height = Infinity;
|
||||
if (textObject.style.wordWrap) {
|
||||
wordWrapScaleToFit(textObject, size);
|
||||
} else {
|
||||
nonWordWrapScaleToFit(textObject, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function wordWrapScaleToFit(textObject, size) {
|
||||
textObject.scale.set(1);
|
||||
if (!textObject.style.defaultWordWrapWidth) {
|
||||
textObject.style.defaultWordWrapWidth = textObject.style.wordWrapWidth;
|
||||
}
|
||||
textObject.style.wordWrapWidth = textObject.style.defaultWordWrapWidth;
|
||||
const scaleStep = 0.99;
|
||||
while (size.width < textObject.width || size.height < textObject.height) {
|
||||
textObject.scale.set(textObject.scale.x * scaleStep, textObject.scale.y * scaleStep);
|
||||
textObject.style.wordWrapWidth = textObject.style.wordWrapWidth / scaleStep;
|
||||
}
|
||||
}
|
||||
|
||||
function nonWordWrapScaleToFit(textObject, size) {
|
||||
textObject.scale.set(1);
|
||||
const factor = Math.min(1, size.width / textObject.width, size.height / textObject.height);
|
||||
textObject.scale.set(factor);
|
||||
}
|
||||
88
utils/create.js
Normal file
88
utils/create.js
Normal file
@@ -0,0 +1,88 @@
|
||||
import * as PIXI from "pixi.js";
|
||||
import {Assets} from "pixi.js";
|
||||
|
||||
export const createElement = (displayObject, props, ...children) => {
|
||||
if (!props) props = {};
|
||||
if (!props.args) props.args = [];
|
||||
|
||||
const obj = new displayObject(...props.args)
|
||||
|
||||
if (props && props.ref) props.ref(obj);
|
||||
|
||||
children.forEach(child => addChild(obj, child));
|
||||
|
||||
obj.once('added', () => {
|
||||
if (props) setProps(obj, props);
|
||||
})
|
||||
|
||||
return obj
|
||||
}
|
||||
|
||||
export const addChild = (parent, children) => {
|
||||
if (Array.isArray(children))
|
||||
children.forEach(nestedChild => {
|
||||
addChild(parent, nestedChild)
|
||||
});
|
||||
|
||||
else {
|
||||
parent.addChild(children)
|
||||
}
|
||||
};
|
||||
|
||||
export function setProps(object, props) {
|
||||
Object.keys(props).forEach(property => {
|
||||
if (property === 'texture') {
|
||||
object.texture = getTexture(props.texture);
|
||||
return;
|
||||
}
|
||||
if (property === 'textures') {
|
||||
object.textures = getTextures(props.textures)
|
||||
return;
|
||||
}
|
||||
if (property === 'styles') {
|
||||
applyStyles(object, props)
|
||||
return;
|
||||
}
|
||||
if (property === 'position') {
|
||||
object.position.set(...props.position)
|
||||
return;
|
||||
}
|
||||
object[property] = props[property]
|
||||
})
|
||||
}
|
||||
|
||||
export function getTexture(texture) {
|
||||
if (typeof texture === 'string') {
|
||||
return Assets.get(texture)
|
||||
}
|
||||
if (texture instanceof PIXI.Texture) {
|
||||
return texture;
|
||||
}
|
||||
return PIXI.Texture.WHITE
|
||||
}
|
||||
|
||||
function getTextures(textures) {
|
||||
return textures.map((texture) => getTexture(texture))
|
||||
}
|
||||
|
||||
export function applyStyles(object, props) {
|
||||
if (!object.styles) {
|
||||
Object.defineProperty(object, 'styles', {
|
||||
get: () => object._styles,
|
||||
set: (value) => {
|
||||
if (!object._styles) object._styles = {};
|
||||
Object.assign(object._styles, value);
|
||||
const styles = {}
|
||||
for (const key in object._styles) {
|
||||
if (typeof object._styles[key] === 'function') {
|
||||
styles[key] = object._styles[key]();
|
||||
} else {
|
||||
styles[key] = object._styles[key];
|
||||
}
|
||||
}
|
||||
setProps(object, styles)
|
||||
}
|
||||
})
|
||||
}
|
||||
object.styles = props.styles
|
||||
}
|
||||
20
utils/delayedPromise.js
Normal file
20
utils/delayedPromise.js
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
*
|
||||
* @returns {(Promise & {resolve: (r?: any)=>void; reject: (reason)=>void;})}
|
||||
*/
|
||||
export function delayedPromise() {
|
||||
let res = null;
|
||||
let rej = null;
|
||||
|
||||
const deferredPromise = new Promise((resolve, reject) => {
|
||||
res = resolve;
|
||||
rej = reject;
|
||||
});
|
||||
|
||||
Object.assign(deferredPromise, {
|
||||
resolve: res,
|
||||
reject: rej,
|
||||
});
|
||||
|
||||
return deferredPromise;
|
||||
}
|
||||
11
utils/handleShowRTPInRulesOption.js
Normal file
11
utils/handleShowRTPInRulesOption.js
Normal file
@@ -0,0 +1,11 @@
|
||||
export default function handleShowRTPInRulesOption() {
|
||||
if (!__OPTIONS__.ui.show_rtp_in_rules) {
|
||||
const styleEl = document.createElement('style');
|
||||
styleEl.textContent = `
|
||||
#section_rtp {
|
||||
display: none !important;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(styleEl);
|
||||
}
|
||||
}
|
||||
12
utils/htmlUtils.js
Normal file
12
utils/htmlUtils.js
Normal file
@@ -0,0 +1,12 @@
|
||||
export function div(className) {
|
||||
const view = document.createElement('div');
|
||||
view.className = className;
|
||||
return view;
|
||||
}
|
||||
export function button(name, callback, className = '') {
|
||||
const view = document.createElement('button');
|
||||
view.className = className;
|
||||
view.addEventListener('pointerdown', callback);
|
||||
view.innerHTML = name;
|
||||
return view;
|
||||
}
|
||||
7
utils/isIframe.js
Normal file
7
utils/isIframe.js
Normal file
@@ -0,0 +1,7 @@
|
||||
export function isInIframe() {
|
||||
try {
|
||||
return window.self !== window.top;
|
||||
} catch (e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
11
utils/isSafariOnIOS.js
Normal file
11
utils/isSafariOnIOS.js
Normal file
@@ -0,0 +1,11 @@
|
||||
export function isSafariOnIphone() {
|
||||
var userAgent = window.navigator.userAgent;
|
||||
var isIOS = /iPad|iPhone|iPod/.test(userAgent) && !window.MSStream;
|
||||
var isChrome = /CriOS/.test(userAgent);
|
||||
var isOpera = /OPiOS/.test(userAgent);
|
||||
var isSafari = /Safari/.test(userAgent) && !isChrome && !isOpera;
|
||||
|
||||
if (isIOS && isChrome && !isOpera) {
|
||||
return false;
|
||||
} else return isIOS && isSafari;
|
||||
}
|
||||
22
utils/parseWinsData.js
Normal file
22
utils/parseWinsData.js
Normal file
@@ -0,0 +1,22 @@
|
||||
export const parseWinsData = (winsArray, currency) => {
|
||||
if (!winsArray || winsArray.length === 0) return ;
|
||||
const totalWin = winsArray.reduce((total, [_, win]) => {
|
||||
return total + win;
|
||||
}, 0);
|
||||
const totalWinString = currency.getFormattedValue(totalWin);
|
||||
const wins = winsArray.map(([type, amount, map, lineIndex]) => {
|
||||
return {
|
||||
type,
|
||||
amount,
|
||||
amountString: currency.getFormattedValue(amount),
|
||||
lineSlotsIndexMap: map,
|
||||
lineIndex
|
||||
}
|
||||
})
|
||||
const result = {
|
||||
totalWin,
|
||||
totalWinString,
|
||||
wins
|
||||
}
|
||||
return result
|
||||
}
|
||||
21
utils/sentry.js
Normal file
21
utils/sentry.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import * as Sentry from '@sentry/browser';
|
||||
|
||||
const isSentryAvailable = !!__SENTRY_DSN && !!__SENTRY_RELEASE;
|
||||
|
||||
export function initSentry() {
|
||||
if(!isSentryAvailable) return undefined;
|
||||
|
||||
const sentry = Sentry.init({
|
||||
environment: window.__OPTIONS__.environment,
|
||||
dsn: __SENTRY_DSN,
|
||||
release: __SENTRY_RELEASE,
|
||||
attachStacktrace: true,
|
||||
ignoreErrors: [],
|
||||
});
|
||||
|
||||
console.log({ __SENTRY_RELEASE, __SENTRY_DSN });
|
||||
|
||||
if (window.user_id) Sentry.setUser({id: window.user_id});
|
||||
|
||||
return sentry;
|
||||
}
|
||||
54
utils/setupDom.js
Normal file
54
utils/setupDom.js
Normal file
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Sets up the DOM structure for the canvas.
|
||||
*
|
||||
* @param {HTMLElement} canvas - The canvas element to be inserted into the DOM.
|
||||
*/
|
||||
export default function setupDom(canvas, groot) {
|
||||
document.body.addEventListener('contextmenu', (e) => e.preventDefault());
|
||||
|
||||
const content = document.createElement('div');
|
||||
content.classList.add('content');
|
||||
document.body.appendChild(content);
|
||||
const container = document.createElement('div');
|
||||
container.classList.add('canvas-wrapper');
|
||||
content.appendChild(container);
|
||||
canvas.classList.add('webgl');
|
||||
container.appendChild(canvas);
|
||||
|
||||
groot && groot.appendChild(container);
|
||||
groot && content.appendChild(groot);
|
||||
|
||||
const spacer = document.createElement('div');
|
||||
spacer.classList.add('spacer');
|
||||
document.body.appendChild(spacer);
|
||||
|
||||
// trick FairyGUI Stage styles
|
||||
enableTouchScroll();
|
||||
}
|
||||
|
||||
export function enableTouchScroll(selector = 'body') {
|
||||
const el = document.querySelector(selector);
|
||||
if (el) {
|
||||
el.style.setProperty('touch-action', 'auto', 'important');
|
||||
}
|
||||
|
||||
// Delete global * { touch-action: none }
|
||||
[...document.styleSheets].forEach((sheet) => {
|
||||
try {
|
||||
[...sheet.cssRules].forEach((rule, index) => {
|
||||
if (rule.selectorText === '*' && rule.cssText.includes('touch-action')) {
|
||||
sheet.deleteRule(index);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
// Ignore errors if CORS
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function disableTouchScroll(selector = 'body') {
|
||||
const el = document.querySelector(selector);
|
||||
if (el) {
|
||||
el.style.setProperty('touch-action', 'none', 'important');
|
||||
}
|
||||
}
|
||||
19
utils/styles.js
Normal file
19
utils/styles.js
Normal file
@@ -0,0 +1,19 @@
|
||||
|
||||
export const createStyles = (sheet) => {
|
||||
return new Proxy(sheet, {
|
||||
get: (target, property, receiver) => {
|
||||
const medias = getMedia(target).filter((media) => {
|
||||
if (!target[media].hasOwnProperty(property)) return false;
|
||||
return matchMedia(media).matches;
|
||||
});
|
||||
if (medias.length === 0) return target[property];
|
||||
return target[medias[medias.length-1]][property];
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function getMedia(target) {
|
||||
const properties = Object.keys(target);
|
||||
const regex = new RegExp(/\(.*\)/);
|
||||
return properties.filter((property) => regex.test(property))
|
||||
}
|
||||
151
utils/utils.js
Normal file
151
utils/utils.js
Normal file
@@ -0,0 +1,151 @@
|
||||
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<void>} 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<void>} 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;
|
||||
}
|
||||
Reference in New Issue
Block a user