This commit is contained in:
Andrey Sharshov
2025-11-16 18:45:28 +01:00
commit dfa72178d5
187 changed files with 39934 additions and 0 deletions

6
.babelrc Normal file
View File

@@ -0,0 +1,6 @@
{
"presets": [
"@babel/preset-env",
"@babel/preset-typescript"
]
}

6
.barrelsby.json Normal file
View File

@@ -0,0 +1,6 @@
{
"directory": "./src",
"exclude": ["index.ts","**/stories/**"],
"delete": true,
"exportDefault": false
}

12
.editorconfig Normal file
View File

@@ -0,0 +1,12 @@
# This file is for unifying the coding style for different editors and IDEs.
# More information at http://EditorConfig.org
root = true
[*]
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 4
[{*.json,bower.json,.travis.yml}]

8
.eslintignore Normal file
View File

@@ -0,0 +1,8 @@
/docs/**
**/dist/**
**/lib/**
**/types/**
temp
docs
dist
node_modules

51
.eslintrc.json Normal file
View File

@@ -0,0 +1,51 @@
{
"extends": ["@pixi/eslint-config"],
"plugins": ["jsdoc", "no-mixed-operators"],
"settings": {
"jsdoc": {
"mode": "typescript",
"tagNamePreference": {
"method": "method",
"function": "function",
"extends": "extends",
"typeParam": "typeParam",
"api": "api"
}
}
},
"rules": {
"@typescript-eslint/no-unused-expressions": [1, {"allowShortCircuit": true, "allowTernary": true}],
"no-mixed-operators": "off",
"no-mixed-operators/no-mixed-operators": 1,
"@typescript-eslint/type-annotation-spacing": 1,
"jsdoc/multiline-blocks": [
1,
{ "noMultilineBlocks": true, "minimumLengthForMultiline": 115 }
],
"jsdoc/check-access": 1,
"jsdoc/check-alignment": 1,
"jsdoc/check-param-names": 1,
"jsdoc/check-property-names": 1,
"jsdoc/check-tag-names": 1,
"jsdoc/check-types": 1,
"jsdoc/check-values": 1,
"jsdoc/empty-tags": 1,
"jsdoc/implements-on-classes": 1,
"jsdoc/no-multi-asterisks": [1, { "allowWhitespace": true }],
"jsdoc/require-param": 1,
"jsdoc/require-param-description": 0,
"jsdoc/require-param-name": 1,
"jsdoc/require-param-type": [
"warn",
{ "contexts": ["TSMethodSignature"] }
],
"jsdoc/require-property": 1,
"jsdoc/require-property-description": 1,
"jsdoc/require-property-name": 1,
"jsdoc/require-property-type": 1,
"jsdoc/require-returns-description": 1,
"jsdoc/tag-lines": 1,
"jsdoc/valid-types": 1,
"max-len": ["warn", { "code": 150 }]
}
}

7
.gitattributes vendored Normal file
View File

@@ -0,0 +1,7 @@
*.js text eol=lf
*.ts text eol=lf
*.json text eol=lf
*.yml text eol=lf
*.md text eol=lf
*.txt text eol=lf

43
.gitignore vendored Normal file
View File

@@ -0,0 +1,43 @@
# sublime text files
*.sublime*
*.vscode*
*.*~*.TMP
test/lib
# temp files
.DS_Store
Thumbs.db
Desktop.ini
npm-debug.log
# project files
.project
# vim swap files
*.sw*
# emacs temp files
*~
\#*#
# project ignores
!.gitkeep
*__temp
node_modules
bin/
lib/
dist/
coverage/
temp
yarn.lock
pnpm-lock.yaml
# jetBrains IDE ignores
.idea
.vs-code
.eslintcache
docs/
example.api.json*
.npmrc

4
.gitlab-ci.yml Normal file
View File

@@ -0,0 +1,4 @@
include:
- project: infrastructure/gitlab_ci_templates
file: entrypoints/front/npm-entrypoint.gitlab-ci.yml
ref: main

37
.storybook/main.js Normal file
View File

@@ -0,0 +1,37 @@
const path = require('path');
module.exports = {
stories: ['../src/stories/**/*.stories.@(ts|tsx|js|jsx|mdx)'],
staticDirs: ['../src/stories/assets'],
output: '../docs/',
logLevel: 'debug',
addons: [
'@storybook/addon-docs',
'@storybook/addon-actions',
'@storybook/addon-backgrounds',
'@storybook/addon-controls',
'@storybook/addon-viewport',
'@storybook/addon-links',
'@storybook/addon-highlight',
'@storybook/addon-storysource',
'@storybook/addon-webpack5-compiler-babel',
'@chromatic-com/storybook'
],
core: {
channelOptions: { allowFunction: false, maxDepth: 10 },
disableTelemetry: true,
},
features: {
buildStoriesJson: true,
breakingChangesV7: true,
babelModeV7: true,
},
framework: '@pixi/storybook-webpack5',
webpackFinal: async (config) => {
config.resolve.alias = {
...config.resolve.alias,
src: path.resolve(__dirname, '../src'),
};
return config;
},
};

26
.storybook/preview.js Normal file
View File

@@ -0,0 +1,26 @@
export const parameters = {
layout: 'fullscreen',
pixi: {
applicationOptions: {
backgroundAlpha: 0,
resolution: 2,
antialias: true,
},
},
backgrounds: {
default: 'Dark',
values: [
{
name: 'Dark',
value: '#1b1c1d',
},
{
name: 'Light',
value: '#dddddd',
},
],
},
docs: {
iframeHeight: 600, // Устанавливает высоту для всех историй в документации
},
};

61
CHANGELOG.md Normal file
View File

@@ -0,0 +1,61 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
## [1.2.2]
### Added
- SingleSlotReelsSpinSystem settings now expose `slotStopDelay` to pause between individual slot stops within a column while `reelStopDelay` continues to pace the first slot in each column, enabling layered timing without manual timers.
- HoldAndWin stories expose `slotStopDelay` as a dedicated control to preview staggered slot timing.
- HoldAndWin stories configure fractional steps for all numeric controls to enable fine-grained tuning during demos.
- Added a stub `MaskingSystem` and wired it into Reels spin stories to prepare future masking orchestration work.
- Introduced `BlurSymbolsSystem` to centralise blur orchestration for reels symbols.
- Added dedicated Storybook demos for `MaskingSystem` and `BlurSymbolsSystem`, now mirroring the reel spin setup while piping Storybook controls through each system's `setSettings` method for live configuration tweaks.
### Changed
- Reels reel states no longer toggle blur directly; `ReelsSpinSystem` marks blur intent while `BlurSymbolsSystem` applies filters.
- `BlurSymbolsSystem` removes cached filter areas, zeroes padding, and supports runtime velocity/kernel/offset configuration through `setSettings`.
- `MaskingSystem` exposes an `enabled` flag so Storybook demos and games can disable masking without removing the system from st
ates.
### Fixed
- SingleSlotReelsSpinSystem trims transient slot symbols and collapses each delayed reel to its landed symbol so duplicates cannot appear when `slotStopDelay` is enabled.
- Column stop scheduling now respects `reelStopDelay` for the first slot in each column even while previous columns finish their queued slots, keeping staggered visuals aligned across reels.
- Reel symbols remain visible when blur and masking are enabled together by removing conflicting bitmap caching from the blur toggle.
- Motion blur applied to masked reel symbols now clips to the mask bounds so blur streaks no longer extend beyond the masked area.
## [1.2.1] - 2025-10-20
### Added
- Story: DoubleHoldAndWinStory showing two HoldAndWinMachine instances stacked vertically, each with different initMap/screenMap.
### Changed
- HoldAndWin stories: scaled all MachineContainer instances to 0.5 (half size). In DoubleHoldAndWinStory both containers are scaled; spacing uses containerTop.height and respects scale. centerView alignment remains correct.
## [1.2.0] - 2025-10-20
### Added
- Introduced HoldAndWin machine scaffold and Storybook story (features/HoldAndWin).
- Single-slot reels support:
- SingleSlotReelsSpinSystem
- SingleSlotReel
- Hold-and-win demo assets and manifest:
- configs/slotMachine.json
- symbols, anticipation, winline assets, bitmap fonts, textures
- Auto-trigger spin in HoldAndWin story with generated screenMap and applied spin settings.
### Changed
- ReelsSpinSystem: support preventReelsSpin map to disable start/stop for selected reels; skip prevented reels in the spin flow.
- ReelsMachine: integrate new maps/behavior for single-slot reels and preventReelsSpin handling.
- Reel: adjustments to support single-slot behavior.
- createScreenInputMap: generate maps aligned with the new HoldAndWin config.
- SlotMachineConfig: extended with properties required by HoldAndWin and updated spinning behavior.
- index.ts: export newly added modules.
- ReelsSpinSystem story: updated with example demonstrating disabled first/last reels.
## [1.1.23] - 2025-10-20
### Added
- Initialize CHANGELOG for the current branch.
### Changed
- Bump package version to 1.1.23.

22
README.md Normal file
View File

@@ -0,0 +1,22 @@
# SlotMachines Framework
## SingleSlotReelsSpinSystem settings
`SingleSlotReelsSpinSystem` extends the base reel system with per-slot timing controls. In addition to the common spin settings, it now supports a `slotStopDelay` option that waits after each slot in a column actually stops before triggering the next slot in that same column. Column-to-column pacing continues to use `reelStopDelay`, so the first slot of each column still fires on the familiar cadence while the remaining slots inherit the intra-column delay. This enables sequential stopping visuals without manual timers and keeps the machine symbol map in sync as every slot lands. When a delay is configured the system trims transient filler symbols and collapses each reel to the final landed symbol, so no duplicate entities remain on screen while the column settles. A dedicated `slotStopDelay` knob is now available in `HoldAndWin.stories.ts` to preview the staggered stop cadence directly in Storybook, and every numeric control in that story uses fractional steps for precise tuning.
## SlotMultipliersSystem
`SlotMultipliersSystem` renders a configurable overlay entity for every reel cell based on `machine.maps.slotMultipliers`. The default `SlotMultiplierEntity` boots a Spine asset with `in`/`out` animations, injects a centred red label into the `placehoder` slot, and automatically hides itself when the outro finishes. The accompanying Storybook story (`SlotMultipliersSystem.stories.ts`) wires the feature into `HoldAndWinMachine`, generates random multiplier maps on every spin, and demonstrates how to swap the display entity via the system settings. The machine itself does not create or register the system; consumers should instantiate `SlotMultipliersSystem`, push it into the relevant state lists, and supply their preferred entity class when wiring machines together.
The system now keeps overlays alive across state transitions so multipliers that remain active do not replay their `in` animation. Clear `slotMultipliers` (or unregister the system) when the overlays should disappear entirely.
## MaskingSystem
`MaskingSystem` manages a reusable rectangular mask sized through system settings (defaulting to the machine configuration). When active it walks through every child entity each frame and synchronises their `mask` property with their `isMasked` flag: entities opt-in by toggling the flag to `true`, at which point the system applies its mask if no other mask is present; when the flag returns to `false` the system restores the entity's original rendering by clearing its mask. The mask graphic is created once, attached to the machine container, tinted red for easy debugging, and automatically refreshed when the system enters or leaves a state. Set the new `enabled` setting to `false` to globally disable masking without deregistering the system; the Storybook controls mirror this flag so designers can toggle masks while keeping the state machine intact.
`ReelsSpinSystem` now toggles `isMasked` for each symbol while its reel state is `Spinning` and keeps the flag active through deceleration, clearing it only once the reel enters `Bouncing` or `Stopped`. This keeps the mask in place for every symbol that is still visibly moving and ensures it disappears as soon as the reel finishes its bounce or comes to rest.
Storybook now ships focused demos for the masking and blur orchestration helpers:
- `src/stories/MaskingSystem.stories.ts` mirrors the standard spin story boot flow and forwards the enable/size controls straight into `MaskingSystem#setSettings`, making it easy to iterate on viewport dimensions while reels spin.
- `src/stories/BlurSymbolsSystem.stories.ts` reuses the same template but exposes motion blur velocity, kernel size, and offset knobs that feed into `BlurSymbolsSystem#setSettings` for rapid strength tuning.

24637
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

115
package.json Normal file
View File

@@ -0,0 +1,115 @@
{
"name": "@popiplay/slot-machines",
"version": "1.2.1",
"description": "It is a library that contains slot machines and their components, that are extensible to allow them to be used in any project",
"homepage": "https://gitlab.popiplay.dev/fe/npm/slot-machines#readme",
"bugs": "https://gitlab.popiplay.dev/fe/npm/slot-machines/issues",
"repository": {
"type": "git",
"url": "git+https://gitlab.popiplay.dev/fe/npm/slot-machines.git"
},
"license": "ISC",
"author": "Andrey Sharshov <andrey.sharshov@popiplay.com>",
"sideEffects": false,
"exports": {
".": {
"import": "./lib/index.mjs",
"require": "./lib/index.js"
},
"./*": "./lib/*"
},
"main": "./lib/index.js",
"module": "./lib/index.mjs",
"types": "./lib/index.d.ts",
"files": [
"lib",
"dist/"
],
"scripts": {
"start": "npm i && npm run storybook",
"build": "xs build",
"clean": "xs clean",
"deploy": "xs deploy",
"docs": "xs docs && npm run storybook:build",
"lint": "xs lint --max-warnings 0",
"lint:fix": "xs lint --fix",
"prepare": "husky install",
"release": "npm i && xs bump,build,docs,publish,git-push",
"serve": "xs serve",
"storybook": "storybook dev -p 6006",
"storybook:build": "storybook build --output-dir docs/storybook",
"types": "xs types",
"watch": "xs watch",
"generate-barrels": "barrelsby --directory ./src --exclude index.ts, stories --delete --exportDefault false --singleQuotes"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.{ts,js,mjs}": [
"npm run lint:fix --"
]
},
"extensionConfig": {
"lint": [
"src"
],
"docsName": "Popiplay Slot Machines",
"docsTitle": "Popiplay Slot Machines",
"docsDescription": "API Documentation for Slot Machines components made with PixiJS",
"docsKeyword": "PixiJS, SlotMachine, components"
},
"keywords": [
"state",
"machine"
],
"peerDependencies": {
"@pixi/filter-motion-blur": "^5.1.1",
"@popiplay/state-machine": "^1.0.6",
"pixi-spine": "^4.0.4",
"pixi.js": "^7.4.2"
},
"devDependencies": {
"@babel/preset-env": "^7.26.0",
"@babel/preset-react": "^7.25.9",
"@babel/preset-typescript": "^7.26.0",
"@chromatic-com/storybook": "^3.2.1",
"@mdx-js/loader": "^3.1.0",
"@mdx-js/react": "^3.1.0",
"@pixi/extension-scripts": "^2.4.1",
"@pixi/filter-motion-blur": "^5.1.1",
"@pixi/storybook-renderer": "^1.0.0",
"@pixi/storybook-webpack5": "^1.0.0",
"@popiplay/state-machine": "^1.0.5",
"@rollup/plugin-commonjs": "^28.0.1",
"@storybook/addon-docs": "^8.4.0",
"@storybook/addon-essentials": "^8.4.0",
"@storybook/addon-interactions": "^8.4.0",
"@storybook/addon-links": "^8.4.0",
"@storybook/addon-storysource": "^8.4.0",
"@storybook/addon-webpack5-compiler-babel": "^3.0.3",
"@storybook/test": "^8.4.0",
"@storybook/types": "^8.4.0",
"@types/babel__core": "^7.20.5",
"@types/jest": "^29.5.14",
"babel-loader": "^9.2.1",
"eslint": "^8.57.1",
"eslint-plugin-jsdoc": "^50.4.3",
"eslint-plugin-no-mixed-operators": "^1.1.1",
"husky": "^9.1.6",
"jest": "^29.7.0",
"jest-raw-loader": "^1.0.1",
"lint-staged": "^15.2.10",
"storybook": "^8.4.0",
"tsc-alias": "^1.8.10",
"typescript": "^5.6.3",
"pixi-spine": "^4.0.4",
"pixi.js": "^7.4.2"
},
"publishConfig": {
"@popiplay:registry": "https://gitlab.popiplay.dev/api/v4/projects/fe%2Fnpm%2Fslot-machines/packages/npm/"
},
"dependencies": {}
}

View File

@@ -0,0 +1,12 @@
import {Spine} from "pixi-spine";
import {Assets} from "pixi.js";
import AnticipationFrame from "./AnticipationFrame";
export default class AnticipationBack extends AnticipationFrame {
public override create() {
this.spine = new Spine(Assets.get("anticipation_back").spineData);
this.addChild(this.spine);
this.spine.autoUpdate = false;
}
}

View File

@@ -0,0 +1,37 @@
import { Assets, Container } from 'pixi.js';
import { ITrackEntry, Spine } from 'pixi-spine';
export default class AnticipationFrame extends Container
{
protected spine: Spine;
constructor()
{
super();
this.create();
}
protected create()
{
this.spine = new Spine(Assets.get('anticipation').spineData);
this.addChild(this.spine);
this.spine.autoUpdate = false;
}
update(dt: number): void
{
this.spine.update(dt * 0.001);
}
show(): ITrackEntry
{
this.spine.state.setEmptyAnimation(0, 0.1);
this.spine.state.addAnimation(0, 'in', false, 0);
return this.spine.state.addAnimation(0, 'idle', true, 0);
}
hide(): ITrackEntry
{
return this.spine.state.setAnimation(0, 'out', false);
}
}

View File

@@ -0,0 +1,12 @@
import {Spine} from "pixi-spine";
import {Assets} from "pixi.js";
import AnticipationFrame from "./AnticipationFrame";
export default class AnticipationFront extends AnticipationFrame {
public override create() {
this.spine = new Spine(Assets.get("anticipation_front").spineData);
this.addChild(this.spine);
this.spine.autoUpdate = false;
}
}

74
src/entities/BasicWin.ts Normal file
View File

@@ -0,0 +1,74 @@
import { Container, Text } from 'pixi.js';
import { Spine } from 'pixi-spine';
export default class BasicWin extends Container
{
spine: Spine;
betValueText: Text;
isShown: boolean;
constructor()
{
super();
this._create();
}
_create()
{
this.betValueText = new Text();
this.betValueText.style = {
fill: 0xffffff,
stroke: 0x000000,
strokeThickness: 3,
fontSize: 40,
fontFamily: 'Arial',
letterSpacing: 0,
};
this.betValueText.anchor.set(0.5);
this.addChild(this.betValueText);
this.value = '';
this.isShown = false;
this.betValueText.visible = false;
}
get value(): string
{
return this.betValueText.text;
}
set value(formattedValue: string)
{
this.betValueText.text = formattedValue;
}
update(_dt:number)
{
}
show()
{
if (this.isShown) return;
this.betValueText.visible = true;
this.isShown = true;
}
hide()
{
if (!this.isShown) return;
this.isShown = false;
this.betValueText.visible = false;
}
hidden()
{
this.isShown = false;
this.betValueText.visible = false;
}
override destroy(_options?: object)
{
super.destroy(_options);
}
}

View File

@@ -0,0 +1,45 @@
import { ITrackEntry } from 'pixi-spine';
import { CanBeBlocked, CanPlayBouncing, CanPlayFalling, CanPlayIdle, CanPlayWin } from '../types/Entity';
import Symbol from './Symbol';
export default class CascadeSymbol extends Symbol implements
CanPlayIdle, CanPlayBouncing, CanBeBlocked, CanPlayWin, CanPlayFalling
{
public isBlocked: boolean = false;
/**
* Triggers the idle animation for the symbol and returns track entry.
* @returns A function to check the animation progress.
*/
idle(): ITrackEntry
{
return this.spine.state.setAnimation(0, 'idle', false);
}
/**
* Triggers the bounce animation and returns track entry.
* @returns A function to check the animation progress.
*/
bounce(): ITrackEntry
{
return this.spine.state.setAnimation(0, 'bounce', false);
}
/**
* Triggers the win animation and returns track entry.
* @returns A function to check the animation progress.
*/
win(): ITrackEntry
{
return this.spine.state.setAnimation(0, 'win', false);
}
/**
* Triggers the falling animation and returns track entry.
* @returns A function to check the animation progress.
*/
falling(): ITrackEntry
{
return this.spine.state.setAnimation(0, 'falling', false);
}
}

View File

@@ -0,0 +1,43 @@
import { CanPlayIn, CanPlayOut, CanPlaySpin, CanPlayWin } from '../types/Entity';
import { ITrackEntry } from '../types/ITrackEntry';
import ReelSymbol from './ReelSymbol';
/**
* @class ExpandedSymbol
* @extends ReelSymbol
* @implements CanPlayWin
* @implements CanPlayIn
* @implements CanPlayOut
* @implements CanPlaySpin
* @description Represents an expanded symbol in a slot machine game that can play various animations such as spin, in, and out.
*/
export default class ExpandedSymbol extends ReelSymbol implements CanPlayWin, CanPlayIn, CanPlayOut, CanPlaySpin
{
reelIndex: number;
/**
* Triggers the spin animation for the symbol.
* @returns {ITrackEntry} The track entry of the animation.
*/
public spin(): ITrackEntry
{
return this.spine.state.setAnimation(0, 'spin', false);
}
/**
* Sets the animation state to 'in' and returns the track entry.
* @returns {ITrackEntry} The track entry for the 'in' animation.
*/
public in(): ITrackEntry
{
return this.spine.state.setAnimation(0, 'in', false);
}
/**
* Sets the animation state to 'out' and returns the track entry.
* @returns {ITrackEntry} The track entry of the animation.
*/
public out(): ITrackEntry
{
return this.spine.state.setAnimation(0, 'out', false);
}
}

View File

@@ -0,0 +1,92 @@
import { ITrackEntry } from 'pixi-spine';
import { MaskData, Rectangle } from 'pixi.js';
import { CanBeBlocked, CanBeBlurred, CanBeLanded, CanBeMasked, CanPlayBouncing, CanPlayIdle, CanPlayWin } from '../types/Entity';
import Symbol from './Symbol';
export default class ReelSymbol extends Symbol implements
CanPlayIdle, CanPlayBouncing, CanBeBlocked, CanPlayWin, CanBeBlurred, CanBeLanded, CanBeMasked
{
public isBlocked: boolean = false;
public isLanded: boolean = false;
public isBlurred: boolean = false;
public blurEnabled: boolean = false;
public isMasked: boolean = false;
private filterAreaRect?: Rectangle;
/**
* Triggers the idle animation for the symbol and returns a function to check its progress.
* @returns A function to check the animation progress.
*/
public idle(): ITrackEntry
{
return this.spine.state.setAnimation(0, 'idle', false);
}
/**
* Triggers the bounce animation and returns a function to check its progress.
* @returns A function to check the animation progress.
*/
public bounce(): ITrackEntry
{
return this.spine.state.setAnimation(0, 'bounce', false);
}
/**
* Triggers the win animation and returns a function to check its progress.
* @returns A function to check the animation progress.
*/
public win(): ITrackEntry
{
return this.spine.state.setAnimation(0, 'win', false);
}
public anticipate()
{
return this.spine.state.setAnimation(0, 'anticipation', false);
}
/** Updates the blur clipping area to match the applied mask bounds. */
private updateBlurClipping(): void
{
if (!this.blurEnabled || !this.isMasked)
{
this.filterArea = null;
this.filterAreaRect = undefined;
return;
}
const mask = this.mask;
if (!mask || mask instanceof MaskData)
{
this.filterArea = null;
return;
}
const maskBounds = mask.getBounds(true);
if (!maskBounds.width || !maskBounds.height)
{
this.filterArea = null;
return;
}
if (!this.filterAreaRect)
{
this.filterAreaRect = maskBounds.clone();
}
else
{
this.filterAreaRect.copyFrom(maskBounds);
}
this.filterArea = this.filterAreaRect;
}
/** @inheritdoc */
public override update(dt: number): void
{
super.update(dt);
this.updateBlurClipping();
}
}

View File

@@ -0,0 +1,155 @@
import { Assets, Container, Text } from 'pixi.js';
import { Spine } from 'pixi-spine';
import { CanPlayIn, CanPlayOut, Updatable } from '../types/Entity';
import { ITrackEntry } from '../types/ITrackEntry';
/**
* Lightweight overlay entity for slot multipliers backed by a Spine animation.
*/
export default class SlotMultiplierEntity extends Container implements Updatable, CanPlayIn, CanPlayOut
{
private spine: Spine;
private assetKey: string;
private shown: boolean;
private label: Text;
private valueInternal = 0;
constructor(assetKey: string = 'slot_multiplier')
{
super();
this.assetKey = assetKey;
this.shown = false;
this.spine = this.createSpine();
this.visible = false;
this.addChild(this.spine);
this.label = this.createLabel();
this.attachLabel();
this.registerOutListener();
}
/**
* Exposes current visibility state used by systems to prevent redundant transitions.
*/
public isShown(): boolean
{
return this.shown;
}
/**
* Plays the "in" animation and reveals the entity.
*/
public in(): ITrackEntry
{
this.visible = true;
this.shown = true;
return this.spine.state.setAnimation(0, 'in', false);
}
/**
* Plays the "out" animation and keeps the entity visible until completion.
*/
public out(): ITrackEntry
{
this.shown = false;
return this.spine.state.setAnimation(0, 'out', false);
}
/**
* Current multiplier value displayed by the overlay text.
*/
public get value(): number
{
return this.valueInternal;
}
/**
* Updates the overlay text with the provided multiplier value.
*/
public set value(value: number)
{
this.valueInternal = value;
this.label.text = Number.isFinite(value) ? value.toString() + "x" : '';
}
/**
* Updates the underlying Spine animation.
*/
public update(dt: number): void
{
this.spine.update(dt * 0.001);
}
/**
* Creates the Spine instance for the multiplier overlay.
*/
private createSpine(): Spine
{
const asset = Assets.get(this.assetKey);
const spine = new Spine(asset.spineData);
spine.autoUpdate = false;
return spine;
}
/**
* Creates a red, centred text label for displaying the multiplier value.
*/
private createLabel(): Text
{
const text = new Text('', {
fill: 0xffffff,
fontSize: 24,
});
text.anchor.set(0.5);
text.scale.set(1, -1)
return text;
}
/**
* Attaches the label to the Spine placeholder slot container.
*/
private attachLabel(): void
{
const placeholderIndex = this.findPlaceholderSlotIndex();
if (placeholderIndex < 0)
{
this.addChild(this.label);
return;
}
const container = this.spine.slotContainers[placeholderIndex];
if (!container)
{
this.addChild(this.label);
return;
}
container.addChild(this.label);
}
/**
* Resolves the placeholder slot index from the Spine skeleton.
*/
private findPlaceholderSlotIndex(): number
{
return this.spine.skeleton.findSlotIndex('placeholder');
}
/**
* Hides the container once the "out" animation completes.
*/
private registerOutListener(): void
{
this.spine.state.addListener({
complete: (entry: ITrackEntry) =>
{
if (entry.animation.name !== 'out') return;
this.visible = false;
},
});
}
}

View File

@@ -0,0 +1,63 @@
import { ITrackEntry } from 'pixi-spine';
import {
CanBeBlocked,
CanPlayBouncing,
CanPlayFalling,
CanPlayIdle,
CanPlayIn,
CanPlayOut,
CanPlayWin
} from '../types/Entity';
import Symbol from './Symbol';
export default class StaticSymbol extends Symbol implements
CanPlayIdle, CanPlayBouncing, CanBeBlocked, CanPlayWin, CanPlayFalling, CanPlayIn, CanPlayOut
{
public isBlocked: boolean = false;
/**
* Triggers the idle animation for the symbol and returns track entry.
* @returns A function to check the animation progress.
*/
idle(): ITrackEntry
{
return this.spine.state.setAnimation(0, 'idle', false);
}
/**
* Triggers the bounce animation and returns track entry.
* @returns A function to check the animation progress.
*/
bounce(): ITrackEntry
{
return this.spine.state.setAnimation(0, 'bounce', false);
}
/**
* Triggers the win animation and returns track entry.
* @returns A function to check the animation progress.
*/
win(): ITrackEntry
{
return this.spine.state.setAnimation(0, 'win', false);
}
/**
* Triggers the falling animation and returns track entry.
* @returns A function to check the animation progress.
*/
falling(): ITrackEntry
{
return this.spine.state.setAnimation(0, 'falling', false);
}
in(delay = 0)
{
return this.spine.state.addAnimation(0, 'in', false, delay);
}
out(delay = 0)
{
return this.spine.state.addAnimation(0, 'out', false, delay);
}
}

89
src/entities/Symbol.ts Normal file
View File

@@ -0,0 +1,89 @@
import { Assets, Container } from 'pixi.js';
import { Spine } from 'pixi-spine';
import { Entity, Updatable } from '../types/Entity';
import { SymbolConfig } from '../types/SlotMachineConfig';
import { getUniqueId } from '../utils/getUniqueId';
/**
* @class Symbol
* @extends Container
* @description Represents a symbol in the slot machine with various animations and states.
*/
export default class Symbol extends Container implements Entity, Updatable
{
protected config: SymbolConfig;
private _id?: string;
protected spine: Spine;
/** Returns a unique identifier for the symbol instance. */
override get name(): string
{
if (!this._id)
{
this._id = getUniqueId();
}
return this._id;
}
/** Gets or sets the animation time scale for the symbol's spine animation. */
get timeScale(): number
{
return this.spine.state.timeScale;
}
set timeScale(timeScale: number)
{
this.spine.state.timeScale = timeScale;
}
/** Returns the key associated with the symbol configuration. */
get key(): string
{
return this.config.key;
}
/**
* Initializes a symbol with the provided configuration.
* @class
* @param config - Configuration settings for the symbol, including spine asset and skin.
*/
constructor(config: SymbolConfig)
{
super();
this.config = config;
this._create();
}
/**
* Creates and initializes the spine animation for the symbol based on its configuration.
* @private
*/
private _create(): void
{
this.spine = new Spine(Assets.get(this.config.spine).spineData);
this.spine.autoUpdate = false;
this.spine.skeleton.setSkinByName(this.config.skin);
this.spine.stateData.defaultMix = 0.5;
this.addChild(this.spine);
}
/**
* Updates the spine animation based on delta time.
* @param dt - Delta time in milliseconds since the last update.
*/
update(dt: number): void
{
this.spine.update(dt * 0.001);
}
public tint(hexColor: number): void
{
this.spine.tint = hexColor;
}
public reset()
{
this.spine.state.setEmptyAnimation(0, 0.1);
}
}

View File

@@ -0,0 +1,43 @@
import { CanPlayIn, CanPlayOut, CanPlaySpin, CanPlayWin } from '../types/Entity';
import { ITrackEntry } from '../types/ITrackEntry';
import ValuableReelSymbol from './ValuableReelSymbol';
/**
* @class ValuableExpandedSymbol
* @extends ValuableReelSymbol
* @implements CanPlayWin
* @implements CanPlayIn
* @implements CanPlayOut
* @implements CanPlaySpin
* @description Represents an expanded symbol in a slot machine game that can play various animations such as spin, in, and out.
*/
export default class ValuableExpandedSymbol extends ValuableReelSymbol implements CanPlayWin, CanPlayIn, CanPlayOut, CanPlaySpin
{
reelIndex: number;
/**
* Triggers the spin animation for the symbol.
* @returns {ITrackEntry} The track entry of the animation.
*/
public spin(): ITrackEntry
{
return this.spine.state.setAnimation(0, 'spin', false);
}
/**
* Sets the animation state to 'in' and returns the track entry.
* @returns {ITrackEntry} The track entry for the 'in' animation.
*/
public in(): ITrackEntry
{
return this.spine.state.setAnimation(0, 'in', false);
}
/**
* Sets the animation state to 'out' and returns the track entry.
* @returns {ITrackEntry} The track entry of the animation.
*/
public out(): ITrackEntry
{
return this.spine.state.setAnimation(0, 'out', false);
}
}

View File

@@ -0,0 +1,48 @@
import {BitmapText} from 'pixi.js';
import { SymbolConfig } from '../types/SlotMachineConfig';
import ReelSymbol from './ReelSymbol';
export default class ValuableReelSymbol extends ReelSymbol
{
private textObj: BitmapText;
private _value: number;
constructor(config: SymbolConfig)
{
super(config);
}
public set value(v: number)
{
if (v <= 0) return;
!this.textObj && this.createValueText();
this._value = v;
if (this.textObj) {
this.textObj.text = `X${v}`;
}
}
public get value(): number
{
return this._value;
}
private createValueText()
{
if (!this.config.fontStyle || !this.config.fontStyle.fontName) return;
this.textObj = new BitmapText(`X${this.config.value}`, this.config.fontStyle);
this.textObj.anchor.set(0.5);
this.textObj.scale.set(1, -1);
const slotIndex = this.spine.skeleton.findSlotIndex('value');
this.spine.slotContainers[slotIndex].addChild(this.textObj);
}
public override tint(hexColor: number): void
{
super.tint(hexColor);
if (this.textObj)
{
this.textObj.tint = hexColor;
}
}
}

View File

@@ -0,0 +1,439 @@
import {
Container,
Geometry,
Mesh,
MeshMaterial,
MIPMAP_MODES,
Point,
Program,
Texture,
WRAP_MODES,
} from 'pixi.js';
class ScrollingMeshMaterial extends MeshMaterial
{
constructor(texture: Texture)
{
const vertexSrc = `
precision mediump float;
attribute vec2 aVertexPosition;
attribute vec2 aTextureCoord;
uniform mat3 translationMatrix;
uniform mat3 projectionMatrix;
varying vec2 vTextureCoord;
void main(void) {
gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);
vTextureCoord = aTextureCoord;
}
`;
const fragmentSrc = `
precision mediump float;
varying vec2 vTextureCoord;
uniform sampler2D uSampler;
uniform float uTextureOffset;
void main(void) {
vec2 uv = vec2(vTextureCoord.x + uTextureOffset, vTextureCoord.y);
gl_FragColor = texture2D(uSampler, uv);
}
`;
const program = Program.from(vertexSrc, fragmentSrc);
super(texture, {
program,
uniforms: { uTextureOffset: 0 },
});
// Важно: разрешить повтор, иначе при смещении по U появятся швы
if (texture?.baseTexture)
{
texture.baseTexture.wrapMode = WRAP_MODES.REPEAT;
texture.baseTexture.mipmap = MIPMAP_MODES.ON;
}
}
}
/** Extend this class and override getters: texture, filletRadius, lineThickness, duration. */
export default class WebGLWinLine extends Container
{
private readonly mesh: Mesh;
private time: number = 0;
public state: 'stopped' | 'animating' = 'stopped';
get texture(): Texture
{
return Texture.from('textures/win_line.png');
}
get filletRadius()
{
return 30;
}
get lineThickness()
{
return 30;
}
/** duration in seconds */
get duration()
{
return 1;
}
constructor(points: number[][], slotWidth: number = 100, slotHeight: number = 100)
{
super();
const centers = points.map(([reel, row]) =>
new Point((reel * slotWidth) + (slotWidth / 2), (row * slotHeight) + (slotHeight / 2))
);
const centerCurveRaw = this.computeFilletCurve(centers, this.filletRadius);
const centerCurve = dedupeAndColinearCull(centerCurveRaw);
this.mesh = this.createThickLineMesh(centerCurve, this.lineThickness, this.texture);
this.addChild(this.mesh);
}
private computeFilletCurve(points: Point[], radius: number): Point[]
{
// Без филета/на острых углах важно не плодить нулевые сегменты
const result: Point[] = [];
if (points.length < 2) return [...points];
if (points.length === 2) return [points[0], points[1]];
const sampleArc = (center: Point, startAngle: number, endAngle: number, segments: number): Point[] =>
{
const arcPoints: Point[] = [];
segments = Math.max(2, segments);
const delta = (endAngle - startAngle) / (segments - 1);
for (let i = 0; i < segments; i++)
{
const a = startAngle + (delta * i);
arcPoints.push(new Point(center.x + (radius * Math.cos(a)), center.y + (radius * Math.sin(a))));
}
return arcPoints;
};
let prevT2: Point | null = null;
// --- первый узел ---
{
const [p0, p1, p2] = [points[0], points[1], points[2]];
const d1 = normalizeVector(p0.x - p1.x, p0.y - p1.y);
const d2 = normalizeVector(p2.x - p1.x, p2.y - p1.y);
const dot = clamp((d1[0] * d2[0]) + (d1[1] * d2[1]), -1, 1);
const theta = Math.acos(dot);
// если угол ≈ 0 (прямая), не создаём псевдо-дугу
if (theta < 1e-4)
{
result.push(p0, p1);
prevT2 = p1;
}
else
{
const offset = radius / Math.tan(theta / 2);
const T1 = new Point(p1.x + (d1[0] * offset), p1.y + (d1[1] * offset));
const T2 = new Point(p1.x + (d2[0] * offset), p1.y + (d2[1] * offset));
result.push(p0, T1);
const bisector = new Point(d1[0] + d2[0], d1[1] + d2[1]);
const bisectorLen = Math.hypot(bisector.x, bisector.y);
if (bisectorLen > 0)
{
const bisectorNorm = new Point(bisector.x / bisectorLen, bisector.y / bisectorLen);
const centerDistance = radius / Math.sin(theta / 2);
const C = new Point(p1.x + (bisectorNorm.x * centerDistance), p1.y + (bisectorNorm.y * centerDistance));
const startAngle = Math.atan2(T1.y - C.y, T1.x - C.x);
const arcSweep = Math.PI - theta;
const endAngle = startAngle + (cross2(T1, T2, C) < 0 ? -arcSweep : arcSweep);
const arcPoints = sampleArc(C, startAngle, endAngle, 10);
result.push(...arcPoints.slice(1));
prevT2 = T2;
}
else
{
// почти 180°: просто соединяем через вершину
result.push(p1);
prevT2 = p1;
}
}
}
// --- внутренние узлы ---
for (let i = 2; i < points.length - 1; i++)
{
const pPrev = points[i - 1];
const pCurr = points[i];
const pNext = points[i + 1];
const v1 = normalizeVector(pPrev.x - pCurr.x, pPrev.y - pCurr.y);
const v2 = normalizeVector(pNext.x - pCurr.x, pNext.y - pCurr.y);
const dot = clamp((v1[0] * v2[0]) + (v1[1] * v2[1]), -1, 1);
const theta = Math.acos(dot);
if (theta < 1e-4)
{
// прямая — не вставляем T1/T2
if (prevT2) result.push(pCurr);
prevT2 = pCurr;
continue;
}
const offset = radius / Math.tan(theta / 2);
const T1 = new Point(pCurr.x + (v1[0] * offset), pCurr.y + (v1[1] * offset));
const T2 = new Point(pCurr.x + (v2[0] * offset), pCurr.y + (v2[1] * offset));
if (prevT2) result.push(T1);
const bisector = new Point(v1[0] + v2[0], v1[1] + v2[1]);
const bisectorLen = Math.hypot(bisector.x, bisector.y);
if (bisectorLen > 0)
{
const bisectorNorm = new Point(bisector.x / bisectorLen, bisector.y / bisectorLen);
const centerDistance = radius / Math.sin(theta / 2);
const C = new Point(pCurr.x + (bisectorNorm.x * centerDistance), pCurr.y + (bisectorNorm.y * centerDistance));
const startAngle = Math.atan2(T1.y - C.y, T1.x - C.x);
const arcSweep = Math.PI - theta;
const endAngle = startAngle + (cross2(T1, T2, C) < 0 ? -arcSweep : arcSweep);
const arcPoints = sampleArc(C, startAngle, endAngle, 10);
result.push(...arcPoints.slice(1));
}
else
{
result.push(pCurr);
}
prevT2 = T2;
}
// --- хвост ---
const lastPoint = points[points.length - 1];
if (prevT2)
{
result.push(prevT2, lastPoint);
}
else
{
result.push(lastPoint);
}
return result;
}
/**
* Создает Mesh c толщиной по центральной кривой.
* Митер-нормали с ограничением устраняют «ломы» на острых углах без обязательного скругления.
* @param curvePoints
* @param thickness
* @param texture
*/
private createThickLineMesh(curvePoints: Point[], thickness: number, texture: Texture): Mesh
{
const vertices: number[] = [];
const uvs: number[] = [];
const indices: number[] = [];
const n = curvePoints.length;
const halfThickness = thickness / 2;
// накопленные длины для UV
let totalLength = 0;
const lengths: number[] = [0];
for (let i = 1; i < n; i++)
{
const dx = curvePoints[i].x - curvePoints[i - 1].x;
const dy = curvePoints[i].y - curvePoints[i - 1].y;
const seg = Math.hypot(dx, dy);
totalLength += seg;
lengths.push(totalLength);
}
// —— митер-нормали ——
const MITER_LIMIT = 4;
function unit(x: number, y: number)
{
const l = Math.hypot(x, y);
return l > 0 ? { x: x / l, y: y / l } : { x: 0, y: 0 };
}
function perp(v: {x: number;y: number}) { return { x: -v.y, y: v.x }; }
function computeNormal(i: number): { x: number; y: number }
{
if (n === 1) return { x: 0, y: 0 };
if (i === 0)
{
const d = unit(curvePoints[1].x - curvePoints[0].x, curvePoints[1].y - curvePoints[0].y);
return perp(d);
}
if (i === n - 1)
{
const d = unit(curvePoints[n - 1].x - curvePoints[n - 2].x, curvePoints[n - 1].y - curvePoints[n - 2].y);
return perp(d);
}
const dPrev = unit(curvePoints[i].x - curvePoints[i - 1].x, curvePoints[i].y - curvePoints[i - 1].y);
const dNext = unit(curvePoints[i + 1].x - curvePoints[i].x, curvePoints[i + 1].y - curvePoints[i].y);
const sum = { x: dPrev.x + dNext.x, y: dPrev.y + dNext.y };
const sumLen = Math.hypot(sum.x, sum.y);
if (sumLen < 1e-6)
{
// угол ~180°
return perp(dNext);
}
const t = { x: sum.x / sumLen, y: sum.y / sumLen }; // усреднённая касательная
const nAvg = perp(t);
// miter scale = 1 / dot(nAvg, nNext)
const nNext = perp(dNext);
const denom = (nAvg.x * nNext.x) + (nAvg.y * nNext.y);
let miterScale = 1 / Math.max(denom, 1e-4);
miterScale = Math.min(miterScale, MITER_LIMIT);
return { x: nAvg.x * miterScale, y: nAvg.y * miterScale };
}
// вершины + UV
for (let i = 0; i < n; i++)
{
const p = curvePoints[i];
const normal = computeNormal(i);
vertices.push(p.x - (normal.x * halfThickness), p.y - (normal.y * halfThickness));
vertices.push(p.x + (normal.x * halfThickness), p.y + (normal.y * halfThickness));
const u = totalLength > 0 ? lengths[i] / totalLength : 0;
uvs.push(u, 0);
uvs.push(u, 1);
}
// индексы (два треугольника на сегмент)
for (let i = 0; i < n - 1; i++)
{
const idx = i * 2;
indices.push(idx, idx + 1, idx + 2);
indices.push(idx + 1, idx + 3, idx + 2);
}
const geometry = new Geometry()
.addAttribute('aVertexPosition', vertices, 2)
.addAttribute('aTextureCoord', uvs, 2)
.addIndex(indices);
return new Mesh(geometry, new ScrollingMeshMaterial(texture));
}
public update(dt: number)
{
this.time += dt * 0.001;
if (this.state === 'stopped')
{
this.visible = false;
return;
}
if (this.state === 'animating')
{
this.visible = true;
const material = this.mesh.material as ScrollingMeshMaterial;
material.uniforms.uTextureOffset = 1 - (2 * this.time / this.duration);
if (material.uniforms.uTextureOffset <= -1)
{
material.uniforms.uTextureOffset = 1;
this.state = 'stopped';
}
}
}
public play()
{
this.state = 'animating';
this.time = 0;
const material = this.mesh.material as ScrollingMeshMaterial;
material.uniforms.uTextureOffset = 1;
}
}
/* ---------- utils ---------- */
function normalizeVector(x: number, y: number): [number, number]
{
const length = Math.hypot(x, y);
return length === 0 ? [0, 0] : [x / length, y / length];
}
function clamp(v: number, a: number, b: number)
{
return Math.max(a, Math.min(b, v));
}
function cross2(a: Point, b: Point, c: Point)
{
// cross( (A-C), (B-C) )
return ((a.x - c.x) * (b.y - c.y)) - ((a.y - c.y) * (b.x - c.x));
}
/**
* Удаляет дубликаты / почти-нулевые сегменты и промежуточные строго коллинеарные точки
* @param points
* @param eps
*/
function dedupeAndColinearCull(points: Point[], eps = 1e-4): Point[]
{
if (points.length <= 1) return [...points];
const out: Point[] = [];
for (const p of points)
{
const last = out[out.length - 1];
if (!last || Math.hypot(p.x - last.x, p.y - last.y) > eps) out.push(p);
}
if (out.length < 3) return out;
const pruned: Point[] = [out[0]];
for (let i = 1; i < out.length - 1; i++)
{
const a = out[i - 1];
const b = out[i];
const c = out[i + 1];
const abx = b.x - a.x;
const aby = b.y - a.y;
const bcx = c.x - b.x;
const bcy = c.y - b.y;
const cross = (abx * bcy) - (aby * bcx);
if (Math.abs(cross) > eps) pruned.push(b);
}
pruned.push(out[out.length - 1]);
return pruned;
}

98
src/entities/Winline.ts Normal file
View File

@@ -0,0 +1,98 @@
import { Assets, Container } from 'pixi.js';
import { IBone, Spine } from 'pixi-spine';
import { Updatable } from '../types/Entity';
import { ITrackEntry } from '../types/ITrackEntry';
interface IBoneExtended extends IBone
{
x: number;
y: number;
}
/**
* @class WinLine
* @extends Container
* @implements Updatable
* @description
* The `WinLine` class represents a visual line in a slot machine game that can be animated to show winning lines.
* It extends the `Container` class and implements the `Updatable` interface.
* The class uses Spine animations to display the win line and provides methods to set the line's position,
* update the animation state, and trigger idle and win animations.
*/
export default class WinLine extends Container implements Updatable
{
spine: Spine;
slotSize: { x: number, y: number };
constructor()
{
super();
this.spine = new Spine(Assets.get('win_line').spineData);
this.spine.autoUpdate = false;
this.spine.state.timeScale = 1;
this.spine.alpha = 0.8;
this.idle();
this.addChild(this.spine);
this.spine.state.addListener({
event: (_, event) =>
{
if (event.data.name === 'end')
{
this.idle();
}
}
});
this.zIndex = 10100;
this.slotSize = { x: 200, y: 200 };
this.scale.set(0.52);
this.position.set(0, this.slotSize.y * 0.5 * this.scale.y);
}
/**
* Sets the positions of the control bones based on the provided line map.
* @param lineMap - An array of numbers representing the slot indices for each reel.
* @returns void
*/
set(lineMap: number[]): void
{
(this.spine.skeleton.findBone('control_0') as IBoneExtended).y = -lineMap[0] * this.slotSize.y;
(this.spine.skeleton.findBone('control_6') as IBoneExtended).y = -lineMap[lineMap.length - 1] * this.slotSize.y;
lineMap.forEach((slotIndex: number, reelIndex: number) =>
{
const control = this.spine.skeleton.findBone(`control_${reelIndex + 1}`) as IBoneExtended;
if (!control) return;
control.y = -slotIndex * this.slotSize.y;
});
}
/**
* Updates the spine animation with the given delta time.
* @param dt - The delta time in milliseconds.
* @returns void
*/
update(dt: number): void
{
this.spine.update(dt * 0.001);
}
/**
* Sets the animation to 'idle' on the spine state.
* @returns {ITrackEntry} The track entry of the animation.
*/
idle(): ITrackEntry
{
return this.spine.state.setAnimation(0, 'idle', false);
}
/**
* Triggers the 'win' animation on the spine state.
* @returns {ITrackEntry} The track entry of the animation.
*/
win(): ITrackEntry
{
return this.spine.state.setAnimation(0, 'win', false);
}
}

92
src/events/Events.ts Normal file
View File

@@ -0,0 +1,92 @@
/**
* @class Events
* @description A basic event handling system that supports adding events, listeners, and emitting events.
*/
export default class Events
{
/** Stores events and associated data. */
private _events: Record<string, Array<any>>;
/** Stores listeners for specific events. */
private _listeners: Record<string, Array<{ event: string; fn: (data?: any) => void; context: any }>>;
/** Initializes an empty events storage and listeners storage. */
constructor()
{
this._events = {};
this._listeners = {};
}
/**
* Adds an event with associated data to be emitted later.
* @param event
* @param data
*/
addEvent(event: string, data?: any): void
{
if (!this._events[event])
{
this._events[event] = [];
}
this._events[event].push(data);
}
/**
* Registers a listener for a specific event.
* @param event
* @param fn
* @param context
*/
addListener(event: string, fn: (data?: any) => void, context: any): void
{
if (!this._listeners[event])
{
this._listeners[event] = [];
}
this._listeners[event].push({ event, fn, context });
}
/**
* Removes a specific listener for an event.
* @param event
* @param fn
* @param context
*/
removeEventListener(event: string, fn: (data?: any) => void, context: any): void
{
if (!this._listeners[event]) return;
this._listeners[event] = this._listeners[event].filter(
(listener) => listener.fn !== fn || listener.context !== context
);
}
/** Clears all stored events. */
clearEvents(): void
{
this._events = {};
}
/**
* Emits all stored events, invoking associated listeners with the event data.
* The listeners for each event receive each piece of data associated with that event.
*/
emitEvents(): void
{
for (const eventName in this._events)
{
const dataObjects = this._events[eventName];
const listeners = this._listeners[eventName];
if (!listeners) continue;
dataObjects.forEach((data) =>
{
listeners.forEach((listener) =>
{
listener.fn.call(listener.context, data);
});
});
delete this._events[eventName];
}
}
}

153
src/index.ts Normal file
View File

@@ -0,0 +1,153 @@
export { default as AnticipationFrame } from './entities/AnticipationFrame';
export * from './entities/AnticipationFrame';
export { default as CascadeSymbol } from './entities/CascadeSymbol';
export * from './entities/CascadeSymbol';
export { default as ExpandedSymbol } from './entities/ExpandedSymbol';
export * from './entities/ExpandedSymbol';
export { default as ReelSymbol } from './entities/ReelSymbol';
export * from './entities/ReelSymbol';
export { default as StaticSymbol } from './entities/StaticSymbol';
export * from './entities/StaticSymbol';
export { default as SlotMultiplierEntity } from './entities/SlotMultiplierEntity';
export * from './entities/SlotMultiplierEntity';
export { default as Symbol } from './entities/Symbol';
export * from './entities/Symbol';
export { default as ValuableExpandedSymbol } from './entities/ValuableExpandedSymbol';
export * from './entities/ValuableExpandedSymbol';
export { default as ValuableReelSymbol } from './entities/ValuableReelSymbol';
export * from './entities/ValuableReelSymbol';
export { default as Winline } from './entities/Winline';
export * from './entities/Winline';
export { default as Events } from './events/Events';
export * from './events/Events';
export { default as BaseMachine } from './slotMachines/BaseMachine';
export * from './slotMachines/BaseMachine';
export { default as CascadeMachine } from './slotMachines/CascadeMachine';
export * from './slotMachines/CascadeMachine';
export { default as ReelsMachine } from './slotMachines/ReelsMachine';
export * from './slotMachines/ReelsMachine';
export { default as StaticReelsMachine } from './slotMachines/StaticReelsMachine';
export * from './slotMachines/StaticReelsMachine';
export { default as HoldAndWinMachine } from './slotMachines/HoldAndWinMachine';
export * from './slotMachines/HoldAndWinMachine';
export { default as SpeedModes } from './speedModes/SpeedModes';
export { default as BasicStateMachine } from './states/BasicStateMachine';
export * from './states/BasicStateMachine';
export { default as AnticipationAnimationsSystem } from './systems/AnticipationAnimationsSystem';
export * from './systems/AnticipationAnimationsSystem';
export { default as AnticipationSymbolsSystem } from './systems/AnticipationSymbolsSystem';
export * from './systems/AnticipationSymbolsSystem';
export { default as AnticipationSystem } from './systems/AnticipationSystem';
export * from './systems/AnticipationSystem';
export { default as AnticipationTintingSystem } from './systems/AnticipationTintingSystem';
export * from './systems/AnticipationTintingSystem';
export { default as BaseSystem } from './systems/BaseSystem';
export * from './systems/BaseSystem';
export { default as CascadeRefillSystems } from './systems/CascadeRefillSystems';
export * from './systems/CascadeRefillSystems';
export { default as CascadeSpinSystem } from './systems/CascadeSpinSystem';
export * from './systems/CascadeSpinSystem';
export { default as AnimatedExpandedSymbolsSystem } from './systems/AnimatedExpandedSymbolsSystem';
export * from './systems/AnimatedExpandedSymbolsSystem';
export { default as SpinningExpandedSymbolsSystem } from './systems/SpinningExpandedSymbolsSystem';
export * from './systems/SpinningExpandedSymbolsSystem';
export { default as InitSpinningExpandedSymbolsSystem } from './systems/InitSpinningExpandedSymbolsSystem';
export * from './systems/InitSpinningExpandedSymbolsSystem';
export { default as GridSystem } from './systems/GridSystem';
export * from './systems/GridSystem';
export { default as HighlightLandedSymbolSystem } from './systems/HighlightLandedSymbolSystem';
export * from './systems/HighlightLandedSymbolSystem';
export { default as IdleSystem } from './systems/IdleSystem';
export * from './systems/IdleSystem';
export { default as InitSystem } from './systems/InitSystem';
export * from './systems/InitSystem';
export { default as InteractiveGridSystem } from './systems/InteractiveGridSystem';
export * from './systems/InteractiveGridSystem';
export { default as BlurSymbolsSystem } from './systems/BlurSymbolsSystem';
export * from './systems/BlurSymbolsSystem';
export { default as LayeringSystem } from './systems/LayeringSystem';
export * from './systems/LayeringSystem';
export { default as Acceleration } from './systems/reels/Acceleration';
export * from './systems/reels/Acceleration';
export { default as Bouncing } from './systems/reels/Bouncing';
export * from './systems/reels/Bouncing';
export { default as Deceleration } from './systems/reels/Deceleration';
export * from './systems/reels/Deceleration';
export { default as Reel } from './systems/reels/Reel';
export * from './systems/reels/Reel';
export { default as Spinning } from './systems/reels/Spinning';
export * from './systems/reels/Spinning';
export * from './systems/reels/states';
export { default as Stopped } from './systems/reels/Stopped';
export * from './systems/reels/Stopped';
export { default as ReelsSpinSystem } from './systems/ReelsSpinSystem';
export * from './systems/ReelsSpinSystem';
export { default as StaticReelsSpinSystem } from './systems/StaticReelsSpinSystem';
export * from './systems/StaticReelsSpinSystem';
export { default as SingleSlotReelsSpinSystem } from './systems/SingleSlotReelsSpinSystem';
export * from './systems/SingleSlotReelsSpinSystem';
export { default as SingleSlotReelsSystem } from './systems/SingleSlotReelsSpinSystem';
export { default as SingleSlotReel } from './systems/reels/SingleSlotReel';
export * from './systems/reels/SingleSlotReel';
export { default as StickySymbolsSystem } from './systems/StickySymbolsSystem';
export * from './systems/StickySymbolsSystem';
export { default as SlotMultipliersSystem } from './systems/SlotMultipliersSystem';
export * from './systems/SlotMultipliersSystem';
export { default as TintingSymbolsSystem } from './systems/TintingSymbolsSystem';
export * from './systems/TintingSymbolsSystem';
export { default as WinningSymbolAnimationsSystem } from './systems/WinningSymbolAnimationsSystem';
export * from './systems/WinningSymbolAnimationsSystem';
export { default as WinningSymbolTintingSystem } from './systems/WinningSymbolTintingSystem';
export * from './systems/WinningSymbolTintingSystem';
export * from './types/Entity';
export * from './types/ITrackEntry';
export * from './types/ScreenInputMap';
export * from './types/SlotMachineConfig';
export * from './utils/easing/easeInOutBack';
export * from './utils/getUniqueId';
export { default as GridUtils } from './utils/GridUtils';
export * from './utils/GridUtils';
export { default as AnimationProgressHook } from './utils/hooks/AnimationProgressHook';
export * from './utils/hooks/AnimationProgressHook';
export { default as DelayHook } from './utils/hooks/DelayHook';
export * from './utils/hooks/DelayHook';
export { default as GravityHook } from './utils/hooks/GravityHook';
export * from './utils/hooks/GravityHook';
export { default as Hook } from './utils/hooks/Hook';
export * from './utils/hooks/Hook';
export { default as HooksChain } from './utils/hooks/HooksChain';
export * from './utils/hooks/HooksChain';
export { default as ImmediateHook } from './utils/hooks/ImmediateHook';
export * from './utils/hooks/ImmediateHook';
export { default as Pool } from './utils/Pool';
export * from './utils/Pool';
export { default as AllWinSymbolSystem } from './systems/AllWinningSymbolSystem';
export * from './systems/AllWinningSymbolSystem';
export { default as ResetTintSystem } from './systems/ResetTintSystem';
export * from './systems/ResetTintSystem';
export { default as ResetWinningSymbolsSystem } from './systems/ResetWinningSymbolsSystem';
export * from './systems/ResetWinningSymbolsSystem';
export { default as SetAllWinLinesSystem } from './systems/SetAllWinLinesSystem';
export * from './systems/SetAllWinLinesSystem';
export { default as SetCurrentWinLinesSystem } from './systems/SetCurrentWinLinesSystem';
export * from './systems/SetCurrentWinLinesSystem';
export { default as SetWinningLineSymbolsSystem } from './systems/SetWinningLineSymbolsSystem';
export * from './systems/SetWinningLineSymbolsSystem';
export { default as WinLinesAnimationsSystem } from './systems/WinLinesAnimationsSystem';
export * from './systems/WinLinesAnimationsSystem';
export { default as WinningLinesIncrementSystem } from './systems/WinningLinesIncrementSystem';
export * from './systems/WinningLinesIncrementSystem';
export { default as WinningLinesResetIncrementSystem } from './systems/WinningLinesResetIncrementSystem';
export * from './systems/WinningLinesResetIncrementSystem';
export { default as DisplayTotalLinesWinSystem } from './systems/DisplayTotalLinesWinSystem';
export * from './systems/DisplayTotalLinesWinSystem';
export { default as DisplayLineWinValueSystem } from './systems/DisplayLineWinValueSystem';
export * from './systems/DisplayLineWinValueSystem';
export { default as BlockExitStateSystem } from './systems/BlockExitStateSystem';
export * from './systems/BlockExitStateSystem';
export { default as WebGLWinLine } from './entities/WebGLWinLine';
export * from './entities/WebGLWinLine';
export { default as WinningExpandedSymbolsAnimationSystem } from './systems/WinningExpandedSymbolsAnimationSystem';
export * from './systems/WinningExpandedSymbolsAnimationSystem';
export { default as MockSystem } from './systems/MockSystem';
export * from './systems/MockSystem';

View File

@@ -0,0 +1,192 @@
import { Container, DisplayObject } from 'pixi.js';
import Events from '../events/Events';
import SpeedModes from '../speedModes/SpeedModes';
import { Updatable } from '../types/Entity';
import { SlotMachineConfig } from '../types/SlotMachineConfig';
import GridUtils from '../utils/GridUtils';
/**
* @interface MachineInputs
* @description Interface for machine inputs.
*/
interface MachineInputs
{
[key: string]: any;
}
/**
* @interface MachineFlags
* @description Interface for machine flags.
*/
interface MachineFlags
{
[key: string]: boolean;
}
/**
* @interface MachineMaps
* @description Interface for machine maps.
*/
interface MachineMaps
{
[key: string]: any;
}
/**
* @interface MachineUtils
* @description Interface for machine utility objects.
*/
interface MachineUtils
{
[key: string]: any;
}
/**
* @class BaseMachine
* @extends Container
* @description A base class for creating a stateful machine with various customizable properties and systems.
*/
export default abstract class BaseMachine extends Container
{
public config: SlotMachineConfig;
public timeScale: number;
public events!: Events;
public inputs: MachineInputs;
public flags: MachineFlags;
public maps: MachineMaps;
public utils: MachineUtils;
public systems: Record<string, any>;
public sharedSystemData: Record<string, any>;
public speedModes: SpeedModes;
/**
* @class
* @param {SlotMachineConfig} config - Configuration object for the BaseMachine instance.
*/
protected constructor(config: SlotMachineConfig)
{
super();
this.config = config;
this.sortableChildren = true;
this.timeScale = 1;
this.inputs = {};
this.flags = {};
this.maps = {};
this.utils = {
grid: new GridUtils(this.config),
};
this.systems = {};
}
/**
* Core initialization method to set up various components.
* Subclasses should override create methods if customization is needed.
*/
protected initialize(): void
{
this.createSharedData();
this.createEvents();
this.createInputs();
this.createFlags();
this.createMaps();
this.createUtils();
this.createSystems();
this.createStateMachine();
this.createSpeedModes();
}
protected createEvents(): void
{
this.events = new Events();
}
protected createInputs(): void
{
this.inputs = {};
}
protected createFlags(): void
{
this.flags = {};
}
protected createMaps(): void
{
this.maps = {};
}
protected createUtils(): void
{
this.utils = {
grid: new GridUtils(this.config),
};
}
protected createSystems(): void
{
this.systems = {};
}
protected createSharedData(): void
{
this.sharedSystemData = {};
}
/**
Initializes the state machine (to be implemented by subclass).
* @throws {Error} Must be implemented in subclass.
*/
protected abstract createStateMachine(): void;
/**
Initializes different speed modes (to be implemented by subclass).
* @throws {Error} Must be implemented in subclass.
*/
protected createSpeedModes(): void
{
this.speedModes = new SpeedModes(this);
}
/**
* Creates a symbol based on the provided key (to be implemented by subclass).
* @param {string} key - The unique identifier for the symbol.
* @throws {Error} Must be implemented in subclass.
*/
public abstract createSymbol(key: string): any;
/**
* Updates the machine and its child entities based on a time delta.
* @param {number} dt - The delta time for the update cycle.
*/
public update(dt: number): void
{
this.speedModes.handleInput(this.inputs);
this._updateEntities(dt);
}
/**
* @private
* @param {number} dt - The delta time to pass to each child entity's update.
*/
private _updateEntities(dt: number): void
{
this.children.forEach((child) =>
{
if ((child as DisplayObject & Updatable).update)
{
(child as DisplayObject & Updatable).update(dt);
}
});
}
/**
* Clears the specified map by setting all elements to null.
* @param {string} mapName - The name of the map to clear.
*/
public nullMap(mapName: string): void
{
if (!this.maps[mapName]) return;
this.maps[mapName].fill(null);
}
}

View File

@@ -0,0 +1,121 @@
import CascadeSymbol from '../entities/CascadeSymbol';
import BasicStateMachine from '../states/BasicStateMachine';
import CascadeSpinSystem from '../systems/CascadeSpinSystem';
import GridSystem from '../systems/GridSystem';
import IdleSystem from '../systems/IdleSystem';
import InitSystem from '../systems/InitSystem';
import InteractiveGridSystem from '../systems/InteractiveGridSystem';
import LayeringSystem from '../systems/LayeringSystem';
import GridUtils from '../utils/GridUtils';
import BaseMachine from './BaseMachine';
/**
* @class CascadeMachine
* @extends BaseMachine
* @description The main class for managing slot machine reels. Initializes and maintains state, systems,
* and utilities to control the reel behavior, from initialization to spinning and stopping.
*/
export default class CascadeMachine extends BaseMachine
{
public stateMachine: BasicStateMachine;
/**
* Initializes the reel machine with configuration settings.
* Sets up inputs, flags, maps, utilities, systems, and the state machine.
* @param {object} config - Configuration settings for the machine.
*/
constructor(config: any)
{
super(config);
this.initialize();
this.stateMachine.init();
}
/**
* Initializes the inputs required for the reel machine.
* `initMap` is used for initial symbol mapping, `spin` for triggering spins,
* and `screenMap` for tracking symbol positions on the screen.
*/
protected override createInputs(): void
{
this.inputs = {
initMap: null,
spin: false,
screenMap: null,
turbo: false,
fastStop: false,
turboForced: false
};
}
/** Sets up flags to track the machine's state, such as whether initialization has completed. */
protected override createFlags(): void
{
this.flags = {
init: false,
isSpinning: false
};
}
/** Sets up maps used by the machine, including `symbols`, which holds the names of the symbols on reels. */
protected override createMaps(): void
{
this.maps = {
symbols: [],
stickySymbols: []
};
}
/** Sets up utilities for the machine, such as grid utilities used for positioning symbols. */
protected override createUtils(): void
{
this.utils = {
grid: new GridUtils(this.config),
};
}
/**
* Initializes and registers systems for the reel machine, including initialization,
* idle, grid, and spin systems, which manage various aspects of the reel behavior.
*/
protected override createSystems(): void
{
this.systems = {
init: new InitSystem(this),
idle: new IdleSystem(this),
grid: new GridSystem(this),
spin: new CascadeSpinSystem(this),
layering: new LayeringSystem(this),
interactive: new InteractiveGridSystem(this),
};
}
/** Initializes the state machine for managing reel states and transitions. */
protected createStateMachine(): void
{
this.stateMachine = new BasicStateMachine(this);
}
/**
* Updates the reel machine and its state based on the delta time.
* Processes inputs, updates the state machine, and triggers system updates.
* @param {number} dt - Time delta in milliseconds since the last update.
*/
override update(dt: number): void
{
this.stateMachine.handleInput(this);
this.stateMachine.update(dt);
super.update(dt);
}
/**
* Creates and returns a new Symbol instance based on the provided key.
* The symbol is retrieved from the machine's configuration.
* @param {string} key - The unique key for the symbol configuration.
* @returns {symbol} The newly created Symbol instance.
*/
createSymbol(key: string): CascadeSymbol
{
return new CascadeSymbol(this.config.symbols[key]);
}
}

View File

@@ -0,0 +1,91 @@
import ValuableReelSymbol from '../entities/ValuableReelSymbol';
import BasicStateMachine from '../states/BasicStateMachine';
import GridSystem from '../systems/GridSystem';
import IdleSystem from '../systems/IdleSystem';
import InitSystem from '../systems/InitSystem';
import InteractiveGridSystem from '../systems/InteractiveGridSystem';
import LayeringSystem from '../systems/LayeringSystem';
import GridUtils from '../utils/GridUtils';
import BaseMachine from './BaseMachine';
import SingleSlotReelsSpinSystem from '../systems/SingleSlotReelsSpinSystem';
/**
* @class HoldAndWinMachine
* @extends BaseMachine
* @description Minimal Hold & Win machine scaffold extending BaseMachine.
*/
export default class HoldAndWinMachine extends BaseMachine {
public stateMachine: BasicStateMachine;
constructor(config: any) {
super(config);
this.initialize();
this.stateMachine.init();
}
protected override createInputs(): void {
this.inputs = {
initMap: null,
spin: false,
screenMap: null,
turbo: false,
fastStop: false,
turboForced: false,
};
}
protected override createFlags(): void {
this.flags = {
init: false,
isSpinning: false,
};
}
protected override createSharedData(): void {
this.sharedSystemData = {
spinSystemState: 'stopped',
currentStoppingReel: 0,
};
}
protected override createMaps(): void {
this.maps = {
symbols: [],
stickySymbols: [],
preventReelStop: [],
preventReelsSpin: [],
slotMultipliers: [],
};
}
protected override createUtils(): void {
this.utils = {
grid: new GridUtils(this.config),
};
}
protected override createSystems(): void {
this.systems = {
init: new InitSystem(this),
idle: new IdleSystem(this),
grid: new GridSystem(this),
spin: new SingleSlotReelsSpinSystem(this),
layering: new LayeringSystem(this),
interactive: new InteractiveGridSystem(this),
};
}
protected createStateMachine(): void {
this.stateMachine = new BasicStateMachine(this);
}
override update(dt: number): void {
this.stateMachine.handleInput(this);
this.stateMachine.update(dt);
super.update(dt);
}
createSymbol(key: string): ValuableReelSymbol {
return new ValuableReelSymbol(this.config.symbols[key]);
}
}

View File

@@ -0,0 +1,182 @@
import AllWinningSymbolSystem from '../systems/AllWinningSymbolSystem';
import ValuableReelSymbol from '../entities/ValuableReelSymbol';
import ClassicReelsStateMachine from '../states/ClassicReelsStateMachine';
import CelebrateSystem from '../systems/CelebrateSystem';
import GridSystem from '../systems/GridSystem';
import IdleSystem from '../systems/IdleSystem';
import InitSystem from '../systems/InitSystem';
import InteractiveGridSystem from '../systems/InteractiveGridSystem';
import LayeringSystem from '../systems/LayeringSystem';
import ReelsSpinSystem from '../systems/ReelsSpinSystem';
import ResetTintSystem from '../systems/ResetTintSystem';
import ResetWinningSymbolsSystem from '../systems/ResetWinningSymbolsSystem';
import SetAllWinLinesSystem from '../systems/SetAllWinLinesSystem';
import SetCurrentWinLinesSystem from '../systems/SetCurrentWinLinesSystem';
import SetWinningLineSymbolsSystem from '../systems/SetWinningLineSymbolsSystem';
import TintingSymbolsSystem from '../systems/TintingSymbolsSystem';
import WinLinesAnimationsSystem from '../systems/WinLinesAnimationsSystem';
import WinningLinesIncrementSystem from '../systems/WinningLinesIncrementSystem';
import WinningLinesResetIncrementSystem from '../systems/WinningLinesResetIncrementSystem';
import WinningSymbolAnimationsSystem from '../systems/WinningSymbolAnimationsSystem';
import WinningSymbolTintingSystem from '../systems/WinningSymbolTintingSystem';
import GridUtils from '../utils/GridUtils';
import BaseMachine from './BaseMachine';
import DisplayTotalLinesWinSystem from "../systems/DisplayTotalLinesWinSystem";
import DisplayLineWinValueSystem from "../systems/DisplayLineWinValueSystem";
import BlockExitStateSystem from "../systems/BlockExitStateSystem";
import MaskingSystem from '../systems/MaskingSystem';
import BlurSymbolsSystem from '../systems/BlurSymbolsSystem';
/**
* @class ReelsMachine
* @extends BaseMachine
* @description The main class for managing slot machine reels. Initializes and maintains state, systems,
* and utilities to control the reel behavior, from initialization to spinning and stopping.
*/
export default class ReelsMachine extends BaseMachine
{
public stateMachine: ClassicReelsStateMachine;
/**
* Initializes the reel machine with configuration settings.
* Sets up inputs, flags, maps, utilities, systems, and the state machine.
* @param {object} config - Configuration settings for the machine.
*/
constructor(config: any)
{
super(config);
this.initialize();
this.stateMachine.init();
}
/**
* Initializes the inputs required for the reel machine.
* `initMap` is used for initial symbol mapping, `spin` for triggering spins,
* and `screenMap` for tracking symbol positions on the screen.
*/
protected override createInputs(): void
{
this.inputs = {
initMap: null,
spin: false,
screenMap: null,
turbo: false,
fastStop: false,
turboForced: false,
wins: null,
blockExitState: false,
};
}
/** Sets up flags to track the machine's state, such as whether initialization has completed. */
protected override createFlags(): void
{
this.flags = {
init: false,
isSpinning: false,
};
}
protected override createSharedData(): void
{
this.sharedSystemData = {
spinSystemState: 'stopped',
currentStoppingReel: 0,
reelsStates: [],
currentWinIndex: 0,
animateLines: [],
anticipatingSymbolsTests: [],
};
}
/** Sets up maps used by the machine, including `symbols`, which holds the names of the symbols on reels. */
protected override createMaps(): void
{
this.maps = {
symbols: [],
stickySymbols: [],
preventReelStop: [],
preventReelsSpin: [],
anticipatingReels: [],
winningSymbols: [],
tinting: []
};
}
/** Sets up utilities for the machine, such as grid utilities used for positioning symbols. */
protected override createUtils(): void
{
this.utils = {
grid: new GridUtils(this.config),
};
}
/**
* Initializes and registers systems for the reel machine, including initialization,
* idle, grid, and spin systems, which manage various aspects of the reel behavior.
*/
protected override createSystems(): void
{
this.systems = {
init: new InitSystem(this),
idle: new IdleSystem(this),
grid: new GridSystem(this),
spin: new ReelsSpinSystem(this),
layering: new LayeringSystem(this),
resetWinningSymbols: new ResetWinningSymbolsSystem(this),
allWinSymbols: new AllWinningSymbolSystem(this),
allWinLinesSet: new SetAllWinLinesSystem(this),
winLinesResetIncrement: new WinningLinesResetIncrementSystem(this),
winLinesIncrementing: new WinningLinesIncrementSystem(this),
setWinLineSymbols: new SetWinningLineSymbolsSystem(this),
setWinLine: new SetCurrentWinLinesSystem(this),
winLinesAnimations: new WinLinesAnimationsSystem(this),
celebrateSymbols: new CelebrateSystem(this),
winningSymbolsAnim: new WinningSymbolAnimationsSystem(this),
tintWinningSymbolsAnim: new WinningSymbolTintingSystem(this),
tinting: new TintingSymbolsSystem(this),
resetTinting: new ResetTintSystem(this),
displayTotalLinesWin: new DisplayTotalLinesWinSystem(this),
displayLineWin: new DisplayLineWinValueSystem(this),
interactive: new InteractiveGridSystem(this),
blockExitState: new BlockExitStateSystem(this),
masking: new MaskingSystem(this),
blurSymbols: new BlurSymbolsSystem(this),
};
}
/** Initializes the state machine for managing reel states and transitions. */
protected createStateMachine(): void
{
this.stateMachine = new ClassicReelsStateMachine(this);
}
/**
* Updates the reel machine and its state based on the delta time.
* Processes inputs, updates the state machine, and triggers system updates.
* @param {number} dt - Time delta in milliseconds since the last update.
*/
override update(dt: number): void
{
this.stateMachine.handleInput(this);
this.stateMachine.update(dt);
super.update(dt);
}
/**
* Creates and returns a new Symbol instance based on the provided key.
* The symbol is retrieved from the machine's configuration.
* @param {string} key - The unique key for the symbol configuration.
* @returns {symbol} The newly created Symbol instance.
*/
createSymbol(key: string): ValuableReelSymbol
{
return new ValuableReelSymbol(this.config.symbols[key]);
}
override destroy() {
Object.values(this.systems).forEach((system) => {
system.destroy()
})
}
}

View File

@@ -0,0 +1,121 @@
import StaticSymbol from '../entities/StaticSymbol';
import BasicStateMachine from '../states/BasicStateMachine';
import GridSystem from '../systems/GridSystem';
import IdleSystem from '../systems/IdleSystem';
import InitSystem from '../systems/InitSystem';
import InteractiveGridSystem from '../systems/InteractiveGridSystem';
import LayeringSystem from '../systems/LayeringSystem';
import StaticReelsSpinSystem from '../systems/StaticReelsSpinSystem';
import GridUtils from '../utils/GridUtils';
import BaseMachine from './BaseMachine';
/**
* @class StaticReelsMachine
* @extends BaseMachine
* @description The main class for managing slot machine reels. Initializes and maintains state, systems,
* and utilities to control the reel behavior, from initialization to spinning and stopping.
*/
export default class StaticReelsMachine extends BaseMachine
{
public stateMachine: BasicStateMachine;
/**
* Initializes the reel machine with configuration settings.
* Sets up inputs, flags, maps, utilities, systems, and the state machine.
* @param {object} config - Configuration settings for the machine.
*/
constructor(config: any)
{
super(config);
this.initialize();
this.stateMachine.init();
}
/**
* Initializes the inputs required for the reel machine.
* `initMap` is used for initial symbol mapping, `spin` for triggering spins,
* and `screenMap` for tracking symbol positions on the screen.
*/
protected override createInputs(): void
{
this.inputs = {
initMap: null,
spin: false,
screenMap: null,
turbo: false,
fastStop: false,
turboForced: false
};
}
/** Sets up flags to track the machine's state, such as whether initialization has completed. */
protected override createFlags(): void
{
this.flags = {
init: false,
isSpinning: false
};
}
/** Sets up maps used by the machine, including `symbols`, which holds the names of the symbols on reels. */
protected override createMaps(): void
{
this.maps = {
symbols: [],
stickySymbols: []
};
}
/** Sets up utilities for the machine, such as grid utilities used for positioning symbols. */
protected override createUtils(): void
{
this.utils = {
grid: new GridUtils(this.config),
};
}
/**
* Initializes and registers systems for the reel machine, including initialization,
* idle, grid, and spin systems, which manage various aspects of the reel behavior.
*/
protected override createSystems(): void
{
this.systems = {
init: new InitSystem(this),
idle: new IdleSystem(this),
grid: new GridSystem(this),
spin: new StaticReelsSpinSystem(this),
layering: new LayeringSystem(this),
interactive: new InteractiveGridSystem(this),
};
}
/** Initializes the state machine for managing reel states and transitions. */
protected createStateMachine(): void
{
this.stateMachine = new BasicStateMachine(this);
}
/**
* Updates the reel machine and its state based on the delta time.
* Processes inputs, updates the state machine, and triggers system updates.
* @param {number} dt - Time delta in milliseconds since the last update.
*/
override update(dt: number): void
{
this.stateMachine.handleInput(this);
this.stateMachine.update(dt);
super.update(dt);
}
/**
* Creates and returns a new Symbol instance based on the provided key.
* The symbol is retrieved from the machine's configuration.
* @param {string} key - The unique key for the symbol configuration.
* @returns {symbol} The newly created Symbol instance.
*/
createSymbol(key: string): StaticSymbol
{
return new StaticSymbol(this.config.symbols[key]);
}
}

View File

@@ -0,0 +1,107 @@
import BaseMachine from '../slotMachines/BaseMachine';
import { State, StateMachine } from '@popiplay/state-machine';
const SPEED_MODES = {
NORMAL: 0,
TURBO: 1
};
export type SpeedModesInput = {
turbo: boolean,
fastStop: boolean
turboForced: boolean;
};
export default class SpeedModes extends StateMachine<BaseMachine>
{
constructor(slotMachine: BaseMachine)
{
super(slotMachine);
this.provideStates([
new NormalMode(this),
new TurboMode(this),
]);
this.checkDependencies(slotMachine);
this.setState(SPEED_MODES.NORMAL);
}
applyNormalModeSettings()
{
// add settings logic in an extended class
}
applyTurboModeSettings()
{
// add settings logic in an extended class
}
private checkDependencies(slotMachine: BaseMachine): void
{
if (!slotMachine.inputs.hasOwnProperty('turbo'))
{
throw new Error('\'turbo\' property is required in the inputs');
}
if (!slotMachine.inputs.hasOwnProperty('fastStop'))
{
throw new Error('\'fastStop\' property is required in the inputs');
}
if (!slotMachine.inputs.hasOwnProperty('turboForced'))
{
throw new Error('\'fastStop\' property is required in the inputs');
}
}
}
class NormalMode extends State
{
machine: SpeedModes;
constructor(machine: SpeedModes)
{
super('NormalSpeedMode');
this.machine = machine;
}
override enter()
{
this.machine.applyNormalModeSettings();
}
override handleInput(input: SpeedModesInput)
{
if (input.turbo)
{
this.machine.setState(SPEED_MODES.TURBO);
}
else if (input.fastStop)
{
this.machine.setState(SPEED_MODES.TURBO);
}
else if (input.turboForced)
{
this.machine.setState(SPEED_MODES.TURBO);
}
}
}
class TurboMode extends State
{
machine: SpeedModes;
constructor(machine: SpeedModes)
{
super('TurboSpeedMode');
this.machine = machine;
}
override enter()
{
this.machine.applyTurboModeSettings();
}
override handleInput(input: SpeedModesInput)
{
if (!input.turbo && !input.fastStop && !input.turboForced)
{
this.machine.setState(SPEED_MODES.NORMAL);
}
}
}

View File

@@ -0,0 +1,140 @@
import BaseMachine from '../slotMachines/BaseMachine';
import { State, StateMachine } from '@popiplay/state-machine';
export enum STATES
{
INIT,
IDLE,
SPINNING
}
type BasicStateMachineStates = InitState | IdleState | SpinningState;
export default class BasicStateMachine extends StateMachine<BaseMachine, BasicStateMachineStates>
{
constructor(target: BaseMachine)
{
super(target);
this.provideStates([
new InitState(this),
new IdleState(this),
new SpinningState(this),
]);
}
override update(dt: number)
{
super.update(dt);
this.currentState.systems.forEach((systemId) =>
{
this.target.systems[systemId].update(dt);
});
}
onEnter()
{
this.currentState.systems.forEach((systemId) =>
{
this.target.systems[systemId].onEnter();
});
}
onLeave()
{
this.currentState.systems.forEach((systemId) =>
{
this.target.systems[systemId].onLeave();
});
}
init()
{
this.setState(STATES.INIT);
}
}
class InitState extends State
{
private machine: BasicStateMachine;
public systems: string[];
constructor(machine: BasicStateMachine)
{
super('InitState');
this.machine = machine;
this.systems = ['init', 'grid'];
}
override enter()
{
this.machine.onEnter();
}
override handleInput(input: any)
{
if (!input.flags.init) return;
this.machine.setState(STATES.IDLE);
}
override leave()
{
this.machine.onLeave();
}
}
class IdleState extends State
{
private machine: BasicStateMachine;
public systems: string[];
constructor(machine: BasicStateMachine)
{
super('IdleState');
this.machine = machine;
this.systems = ['grid', 'idle', 'layering', 'interactive'];
}
override enter()
{
this.machine.onEnter();
}
override handleInput(target: BaseMachine)
{
if (target.inputs.spin === true)
{
this.machine.setState(STATES.SPINNING);
}
}
override leave()
{
this.machine.onLeave();
}
}
class SpinningState extends State
{
private machine: BasicStateMachine;
public systems: string[];
constructor(machine: BasicStateMachine)
{
super('SpinningState');
this.machine = machine;
this.systems = ['spin', 'layering'];
}
override enter()
{
this.machine.onEnter();
}
override handleInput(target: BaseMachine)
{
if (!this.systems.every((key) => target.systems[key].state === 'stopped')) return;
this.machine.setState(STATES.IDLE);
}
override leave()
{
this.machine.onLeave();
}
}

View File

@@ -0,0 +1,345 @@
import BaseMachine from '../slotMachines/BaseMachine';
import { State, StateMachine } from '@popiplay/state-machine';
export enum STATES
{
INIT,
IDLE,
SPINNING,
ALL_WINNING_SYMBOLS,
CELEBRATE_SYMBOLS,
ENDING_ROUND,
WINNING_LINES_LOOP,
}
type ClassicReelsStateMachineStates = InitState | IdleState | SpinningState | AllWinningSymbolsState | CelebrateSymbolsState | EndingRoundState | WinningLinesLoopState;
export default class ClassicReelsStateMachine extends StateMachine<BaseMachine, ClassicReelsStateMachineStates>
{
constructor(target: BaseMachine)
{
super(target);
this.provideStates([
new InitState(this),
new IdleState(this),
new SpinningState(this),
new AllWinningSymbolsState(this),
new CelebrateSymbolsState(this),
new EndingRoundState(this),
new WinningLinesLoopState(this),
]);
}
override update(dt: number)
{
super.update(dt);
this.currentState.systems.forEach((systemId) =>
{
this.target.systems[systemId].update(dt);
});
}
onEnter()
{
console.log("Enter: ", this.currentState.name)
this.currentState.systems.forEach((systemId) =>
{
this.target.systems[systemId].onEnter();
});
}
onLeave()
{
this.currentState.systems.forEach((systemId) =>
{
this.target.systems[systemId].onLeave();
});
}
init()
{
this.setState(STATES.INIT);
}
}
class InitState extends State
{
private machine: ClassicReelsStateMachine;
public systems: string[];
constructor(machine: ClassicReelsStateMachine)
{
super('InitState');
this.machine = machine;
this.systems = ['init', 'grid', 'blurSymbols'];
}
override enter()
{
this.machine.onEnter();
}
override handleInput(input: any)
{
if (!input.flags.init) return;
if (input.inputs.wins)
{
this.machine.setState(STATES.ALL_WINNING_SYMBOLS);
}
else if (input.inputs.celebrateSymbolIndexes?.length > 0)
{
this.machine.setState(STATES.CELEBRATE_SYMBOLS);
}
else
{
this.machine.setState(STATES.ENDING_ROUND);
}
}
override leave()
{
this.machine.onLeave();
}
}
class IdleState extends State
{
private machine: ClassicReelsStateMachine;
public systems: string[];
constructor(machine: ClassicReelsStateMachine)
{
super('IdleState');
this.machine = machine;
this.systems = [
'grid',
'idle',
'layering',
'interactive',
'resetWinningSymbols',
'resetTinting',
'tinting',
'winLinesResetIncrement',
'blurSymbols'
];
}
override enter()
{
this.machine.onEnter();
}
override handleInput(target: BaseMachine)
{
if (target.inputs.spin === true)
{
this.machine.setState(STATES.SPINNING);
}
}
override leave()
{
this.machine.onLeave();
}
}
class SpinningState extends State
{
private machine: ClassicReelsStateMachine;
public systems: string[];
constructor(machine: ClassicReelsStateMachine)
{
super('SpinningState');
this.machine = machine;
this.systems = [
'spin',
'layering',
'resetWinningSymbols',
'resetTinting',
'tinting',
'winLinesResetIncrement',
'blurSymbols'
];
}
override enter()
{
this.machine.onEnter();
}
override handleInput(target: BaseMachine)
{
if (!this.systems.every((key) => target.systems[key].state === 'stopped')) return;
if (target.inputs.wins)
{
this.machine.setState(STATES.ALL_WINNING_SYMBOLS);
}
else if (target.inputs.celebrateSymbolIndexes?.length > 0)
{
this.machine.setState(STATES.CELEBRATE_SYMBOLS);
}
else
{
this.machine.setState(STATES.ENDING_ROUND);
}
}
override leave()
{
this.machine.onLeave();
}
}
class AllWinningSymbolsState extends State
{
private machine: ClassicReelsStateMachine;
public systems: string[];
constructor(machine: ClassicReelsStateMachine)
{
super('AllWinningSymbolsState');
this.machine = machine;
this.systems = [
'resetWinningSymbols',
'allWinSymbols',
'allWinLinesSet',
'winningSymbolsAnim',
'tintWinningSymbolsAnim',
'tinting',
'winLinesAnimations',
'displayTotalLinesWin',
'blurSymbols'
];
}
override enter()
{
this.machine.onEnter();
}
override handleInput(target: BaseMachine)
{
if (!this.systems.every((key) => target.systems[key].state === 'stopped')) return;
if (target.inputs.celebrateSymbolIndexes?.length > 0)
{
this.machine.setState(STATES.CELEBRATE_SYMBOLS);
} else
{
this.machine.setState(STATES.ENDING_ROUND);
}
}
override leave()
{
this.machine.onLeave();
}
}
class CelebrateSymbolsState extends State
{
private machine: ClassicReelsStateMachine;
public systems: string[];
constructor(machine: ClassicReelsStateMachine)
{
super('CelebrateSymbolsState');
this.machine = machine;
this.systems = ['celebrateSymbols', 'tinting', 'blurSymbols'];
}
override enter()
{
this.machine.onEnter();
}
override handleInput(target: BaseMachine)
{
if (!this.systems.every((key) => target.systems[key].state === 'stopped')) return;
this.machine.setState(STATES.ENDING_ROUND);
}
override leave()
{
this.machine.onLeave();
}
}
class EndingRoundState extends State
{
private machine: ClassicReelsStateMachine;
public systems: string[];
constructor(machine: ClassicReelsStateMachine)
{
super('EndingRoundState');
this.machine = machine;
this.systems = ['blockExitState', 'blurSymbols'];
}
override enter()
{
this.machine.onEnter();
}
override handleInput(target: BaseMachine)
{
if (!this.systems.every((key) => target.systems[key].state === 'stopped')) return;
if (target.inputs.spin) {
this.machine.setState(STATES.SPINNING);
}
else if (target.inputs.wins)
{
this.machine.setState(STATES.WINNING_LINES_LOOP);
} else
{
this.machine.setState(STATES.IDLE);
}
}
override leave()
{
this.machine.onLeave();
}
}
class WinningLinesLoopState extends State
{
private machine: ClassicReelsStateMachine;
public systems: string[];
constructor(machine: ClassicReelsStateMachine)
{
super('WinningLinesLoopState');
this.machine = machine;
this.systems = [
'resetWinningSymbols',
'winLinesIncrementing',
'setWinLineSymbols',
'setWinLine',
'winningSymbolsAnim',
'tintWinningSymbolsAnim',
'tinting',
'interactive',
'winLinesAnimations',
'displayLineWin',
'blurSymbols'
];
}
override enter()
{
this.machine.onEnter();
}
override handleInput(target: BaseMachine)
{
if (target.inputs.spin === true)
{
this.machine.setState(STATES.SPINNING);
return;
}
if (!this.systems.every((key) => target.systems[key].state === 'stopped')) return;
this.machine.setState(STATES.WINNING_LINES_LOOP);
}
override leave()
{
this.machine.onLeave();
}
}

View File

@@ -0,0 +1,70 @@
import { Assets, Ticker } from 'pixi.js';
import ReelsMachine from 'src/slotMachines/ReelsMachine';
import { STATES } from 'src/states/BasicStateMachine';
import { argTypes, getDefaultArgs } from 'src/stories/utils/argTypes';
import { createScreenInputMap } from 'src/stories/utils/createScreenInputMap';
import MachineContainer from 'src/stories/utils/MachineContainer';
import { centerView } from 'src/stories/utils/resize';
import AnimatedExpandedSymbolsSystem from 'src/systems/AnimatedExpandedSymbolsSystem';
import { SlotMachineConfig } from 'src/types/SlotMachineConfig';
import { Meta, PixiStory, StoryFn } from '@pixi/storybook-renderer';
import MockSystem from "src/systems/MockSystem";
const args = {
key: 'K',
zIndex: 4,
};
const meta: Meta<typeof AnimatedExpandedSymbolsSystem> = {
title: 'Systems/ExpandingSpinSystem',
tags: ['autodocs'],
parameters: {
docs: {
story: {
height: 600
},
description: {
component: 'Expanding symbols mechanics.'
},
}
},
argTypes: argTypes(args),
args: getDefaultArgs(args),
};
export default meta;
export const ReelsMachineStory: StoryFn<typeof args> = (_params, context) => new PixiStory({
context, init: async (view) =>
{
Assets.reset();
await Assets.init({
manifest: 'area69/manifest.json',
});
await Assets.loadBundle('configs');
await Assets.loadBundle('symbols');
const config: SlotMachineConfig = Assets.get('slot_machine_config');
const machine = new ReelsMachine(config);
const container = new MachineContainer(machine);
machine.systems.blockExitState = new MockSystem(machine)
view.addChild(container);
context.machine = machine;
machine.systems.expanding = new AnimatedExpandedSymbolsSystem(machine);
machine.stateMachine.states[STATES.SPINNING].systems = ['layering', 'spin', 'expanding'];
machine.inputs.initMap = createScreenInputMap(config);
machine.inputs.screenMap = machine.inputs.initMap;
machine.systems.expanding.setSettings(_params);
machine.inputs.spin = true;
machine.inputs.blockExitState = true;
setTimeout(() => {
machine.inputs.blockExitState = false;
}, 250);
centerView(view);
},
resize: centerView,
update: () =>
{
context.machine && context.machine.update(Ticker.shared.deltaMS);
}
});

View File

@@ -0,0 +1,97 @@
import { Assets, Ticker } from 'pixi.js';
import { argTypes, getDefaultArgs } from 'src/stories/utils/argTypes';
import { createScreenInputMap } from 'src/stories/utils/createScreenInputMap';
import MachineContainer from 'src/stories/utils/MachineContainer';
import { centerView } from 'src/stories/utils/resize';
import ReelsSpinSystem from 'src/systems/ReelsSpinSystem';
import { SlotMachineConfig } from 'src/types/SlotMachineConfig';
import { Meta, PixiStory, StoryFn } from '@pixi/storybook-renderer';
import ReelsMachine from '../slotMachines/ReelsMachine';
import { STATES } from '../states/ClassicReelsStateMachine';
import AnticipationSystem from '../systems/AnticipationSystem';
import AnticipationAnimationsSystem from "../systems/AnticipationAnimationsSystem";
import AnticipationBack from "../entities/AnticipationBack";
import AnticipationFront from "../entities/AnticipationFront";
import MockSystem from "src/systems/MockSystem";
const args = {
accelerationTime: 1,
backCoef: 1.1,
maxSpeed: 40,
reelStartDelay: 0.2,
reelStopDelay: 0.5,
spinningDuration: 2,
blur: true,
bounce: true
};
const meta: Meta<typeof ReelsSpinSystem> = {
title: 'Systems/AnticipationSystem', // Путь для отображения в Storybook
tags: ['autodocs'], // Включает автоматическую генерацию документации
parameters: {
docs: {
story: {
height: 600
},
description: {
component: 'Anticipation of landing symbols',
},
}
},
argTypes: argTypes(args),
args: getDefaultArgs(args),
};
export default meta;
export const AnticipationSystemStory: StoryFn<typeof args> = (_params, context) => new PixiStory({
context, init: async (view) =>
{
Assets.reset();
await Assets.init({
manifest: 'area69/manifest.json',
});
await Assets.loadBundle('configs');
await Assets.loadBundle('symbols');
const config: SlotMachineConfig = Assets.get('slot_machine_config');
const machine = new ReelsMachine(config);
const container = new MachineContainer(machine);
machine.systems.blockExitState = new MockSystem(machine)
view.addChild(container);
context.machine = machine;
machine.systems.spin.setSettings(_params);
machine.systems.anticipation1 = new AnticipationSystem(machine);
machine.systems.anticipationAnim = new AnticipationAnimationsSystem(machine);
machine.systems.anticipationAnim.setSettings({
entityClass: AnticipationBack,
zIndex: 0
});
machine.systems.anticipationAnimFront = new AnticipationAnimationsSystem(machine);
machine.systems.anticipationAnimFront.setSettings({
entityClass: AnticipationFront,
zIndex: 10
});
machine.inputs.initMap = createScreenInputMap(config);
machine.inputs.screenMap = createScreenInputMap(config)
.map((record, index) => ([0, 1].includes(index) ? { ...record, key: 'A' } : record))
.map((record, index) => ([5].includes(index) ? { ...record, key: 'B' } : record));
// machine.systems.anticipation1.setSettings({ anticipatingSymbolKey: 'A', preventReelStopIndex: 0 });
// machine.systems.anticipation2 = new AnticipationSystem(machine);
// machine.systems.anticipation2.setSettings({ anticipatingSymbolKey: ['B', 'C'], preventReelStopIndex: 1 });
// machine.stateMachine.states[STATES.SPINNING].systems = ['anticipation1', 'anticipation2', 'spin',];
machine.stateMachine.states[STATES.SPINNING].systems = ['anticipation1', "anticipationAnim", "anticipationAnimFront", 'spin'];
machine.inputs.spin = true;
machine.inputs.blockExitState = true;
setTimeout(() => {
machine.inputs.blockExitState = false;
}, 250);
centerView(view);
centerView(view);
},
resize: centerView,
update: () =>
{
context.machine && context.machine.update(Ticker.shared.deltaMS);
}
});

View File

@@ -0,0 +1,84 @@
import { Assets, Ticker } from 'pixi.js';
import { Meta, PixiStory, StoryFn } from '@pixi/storybook-renderer';
import ReelsMachine from 'src/slotMachines/ReelsMachine';
import { argTypes, getDefaultArgs } from 'src/stories/utils/argTypes';
import { createScreenInputMap } from 'src/stories/utils/createScreenInputMap';
import MachineContainer from 'src/stories/utils/MachineContainer';
import { centerView } from 'src/stories/utils/resize';
import MockSystem from 'src/systems/MockSystem';
import BlurSymbolsSystem, { BlurSymbolsSystemSettings } from 'src/systems/BlurSymbolsSystem';
import MaskingSystem from 'src/systems/MaskingSystem';
import { STATES } from 'src/states/ClassicReelsStateMachine';
import { SlotMachineConfig } from 'src/types/SlotMachineConfig';
const args = {
velocityX: 0,
velocityY: -50,
kernelSize: 15,
offset: 7,
};
const meta: Meta<typeof BlurSymbolsSystem> = {
title: 'Systems/BlurSymbolsSystem',
tags: ['autodocs'],
parameters: {
docs: {
story: {
height: 600,
},
description: {
component: 'Adjust the blur filter velocity, kernel size, and offset to preview motion blur strength while the reels spin. The story mirrors the standard machine boot flow and calls `setSettings` so designers can iterate on blur intensity in real time.',
},
},
},
argTypes: argTypes(args),
args: getDefaultArgs(args),
};
export default meta;
export const BlurSymbolsSystemStory: StoryFn<typeof args> = (_params, context) => new PixiStory({
context,
init: async (view) =>
{
Assets.reset();
await Assets.init({
manifest: 'area69/manifest.json',
});
await Assets.loadBundle('configs');
await Assets.loadBundle('symbols');
const config: SlotMachineConfig = Assets.get('slot_machine_config');
const machine = new ReelsMachine(config);
machine.systems.masking = new MaskingSystem(machine);
machine.stateMachine.states[STATES.SPINNING].systems.push('masking');
const container = new MachineContainer(machine, false);
view.addChild(container);
context.machine = machine;
machine.systems.blockExitState = new MockSystem(machine);
machine.inputs.initMap = createScreenInputMap(config);
machine.inputs.screenMap = createScreenInputMap(config);
const blurSettings: Partial<BlurSymbolsSystemSettings> = {
velocity: [_params.velocityX, _params.velocityY],
kernelSize: _params.kernelSize,
offset: _params.offset,
};
(machine.systems.blurSymbols as BlurSymbolsSystem).setSettings(blurSettings);
machine.inputs.spin = true;
machine.inputs.blockExitState = true;
setTimeout(() =>
{
machine.inputs.blockExitState = false;
}, 250);
centerView(view);
},
resize: centerView,
update: () =>
{
context.machine && context.machine.update(Ticker.shared.deltaMS);
}
});

View File

@@ -0,0 +1,64 @@
import { Assets, Ticker } from 'pixi.js';
import CascadeMachine from 'src/slotMachines/CascadeMachine';
import { argTypes, getDefaultArgs } from 'src/stories/utils/argTypes';
import { createScreenInputMap } from 'src/stories/utils/createScreenInputMap';
import MachineContainer from 'src/stories/utils/MachineContainer';
import { centerView } from 'src/stories/utils/resize';
import CascadeSpinSystem from 'src/systems/CascadeSpinSystem';
import { SlotMachineConfig } from 'src/types/SlotMachineConfig';
import { Meta, PixiStory, StoryFn } from '@pixi/storybook-renderer';
import MockSystem from "src/systems/MockSystem";
const args = {
gravity: 4000,
reelDelay: 0.12,
slotDelay: 0.03,
refillDelay: 0.5,
};
const meta: Meta<typeof CascadeSpinSystem> = {
title: 'Systems/CascadeSpinSystem', // Путь для отображения в Storybook
tags: ['autodocs'], // Включает автоматическую генерацию документации
parameters: {
docs: {
story: {
height: 600
},
description: {
component: 'Manages the cascade spinning effect in the slot machine. Handles dropping, refilling, and bouncing symbols with gravity effects.'
},
}
},
argTypes: argTypes(args),
args: getDefaultArgs(args),
};
export default meta;
export const CascadeSpinSystemStory: StoryFn<typeof args> = (_params, context) => new PixiStory({
context, init: async (view) =>
{
Assets.reset();
await Assets.init({
manifest: 'bubblex/manifest.json',
});
await Assets.loadBundle('configs');
await Assets.loadBundle('symbols');
const config: SlotMachineConfig = Assets.get('slot_machine_config');
const machine = new CascadeMachine(config);
const container = new MachineContainer(machine);
machine.systems.blockExitState = new MockSystem(machine)
view.addChild(container);
context.machine = machine;
machine.inputs.initMap = createScreenInputMap(config);
machine.inputs.screenMap = createScreenInputMap(config);
machine.systems.spin.setSettings(_params);
machine.inputs.spin = true;
centerView(view);
},
resize: centerView,
update: () =>
{
context.machine && context.machine.update(Ticker.shared.deltaMS);
}
});

View File

@@ -0,0 +1,70 @@
import { Assets, Ticker } from 'pixi.js';
import { argTypes, getDefaultArgs } from 'src/stories/utils/argTypes';
import { createScreenInputMap } from 'src/stories/utils/createScreenInputMap';
import MachineContainer from 'src/stories/utils/MachineContainer';
import { centerView } from 'src/stories/utils/resize';
import ReelsSpinSystem from 'src/systems/ReelsSpinSystem';
import { SlotMachineConfig } from 'src/types/SlotMachineConfig';
import { Meta, PixiStory, StoryFn } from '@pixi/storybook-renderer';
import ReelsMachine from '../slotMachines/ReelsMachine';
import MockSystem from "src/systems/MockSystem";
const args = {
accelerationTime: 1,
backCoef: 1.1,
maxSpeed: 40,
reelStartDelay: 0.2,
reelStopDelay: 0.5,
spinningDuration: 2,
blur: true,
bounce: true
};
const meta: Meta<typeof ReelsSpinSystem> = {
title: 'Systems/CelebrateSystem', // Путь для отображения в Storybook
tags: ['autodocs'], // Включает автоматическую генерацию документации
parameters: {
docs: {
story: {
height: 600
},
description: {
component: 'Celebrate symbols',
},
}
},
argTypes: argTypes(args),
args: getDefaultArgs(args),
};
export default meta;
export const CelebrateSystemStory: StoryFn<typeof args> = (_params, context) => new PixiStory({
context, init: async (view) =>
{
Assets.reset();
await Assets.init({
manifest: 'area69/manifest.json',
});
await Assets.loadBundle('configs');
await Assets.loadBundle('symbols');
const config: SlotMachineConfig = Assets.get('slot_machine_config');
const machine = new ReelsMachine(config);
const container = new MachineContainer(machine);
machine.systems.blockExitState = new MockSystem(machine)
view.addChild(container);
context.machine = machine;
machine.inputs.initMap = createScreenInputMap(config);
machine.inputs.screenMap = createScreenInputMap(config)
.map((record, index) => ([0, 1].includes(index) ? { ...record, key: 'A' } : record))
.map((record, index) => ([5].includes(index) ? { ...record, key: 'A' } : record));
machine.inputs.celebrateSymbolIndexes = [0, 1, 5];
machine.inputs.spin = true;
centerView(view);
},
resize: centerView,
update: () =>
{
context.machine && context.machine.update(Ticker.shared.deltaMS);
}
});

View File

@@ -0,0 +1,56 @@
import { Assets, Ticker } from 'pixi.js';
import ReelsMachine from 'src/slotMachines/ReelsMachine';
import { createScreenInputMap } from 'src/stories/utils/createScreenInputMap';
import MachineContainer from 'src/stories/utils/MachineContainer';
import { centerView } from 'src/stories/utils/resize';
import GridSystem from 'src/systems/GridSystem';
import { SlotMachineConfig } from 'src/types/SlotMachineConfig';
import { Meta, PixiStory, StoryFn } from '@pixi/storybook-renderer';
import MockSystem from "src/systems/MockSystem";
const args = {
};
const meta: Meta<typeof GridSystem> = {
title: 'Systems/GridSystem', // Путь для отображения в Storybook
tags: ['autodocs'], // Включает автоматическую генерацию документации
parameters: {
docs: {
story: {
height: 600
},
description: {
component: 'Manages the positioning of symbols on a grid for a slot machine'
},
}
},
};
export default meta;
export const GridSystemStory: StoryFn<typeof args> = (_params, context) => new PixiStory({
context, init: async (view) =>
{
Assets.reset();
await Assets.init({
manifest: 'area69/manifest.json',
});
await Assets.loadBundle('configs');
await Assets.loadBundle('symbols');
const config: SlotMachineConfig = Assets.get('slot_machine_config');
const machine = new ReelsMachine(config);
const container = new MachineContainer(machine);
machine.systems.blockExitState = new MockSystem(machine)
view.addChild(container);
context.machine = machine;
machine.inputs.initMap = createScreenInputMap(config);
machine.systems.idle.setSettings(_params);
centerView(view);
},
resize: centerView,
update: () =>
{
context.machine && context.machine.update(Ticker.shared.deltaMS);
}
});

View File

@@ -0,0 +1,225 @@
import { Assets, Ticker } from 'pixi.js';
import HoldAndWinMachine from 'src/slotMachines/HoldAndWinMachine';
import { argTypes, getDefaultArgs } from 'src/stories/utils/argTypes';
import { createScreenInputMap } from 'src/stories/utils/createScreenInputMap';
import MachineContainer from 'src/stories/utils/MachineContainer';
import { centerView } from 'src/stories/utils/resize';
import { SlotMachineConfig } from 'src/types/SlotMachineConfig';
import { Meta, PixiStory, StoryFn } from '@pixi/storybook-renderer';
import IdleSystem from '../systems/IdleSystem';
import StickySymbolsSystem from 'src/systems/StickySymbolsSystem';
import { STATES } from 'src/states/BasicStateMachine';
const args = {
accelerationTime: 1,
backCoef: 1.1,
maxSpeed: 15,
reelStartDelay: 0.2,
reelStopDelay: 0.5,
slotStopDelay: 0,
spinningDuration: 2,
blur: false,
bounce: true
};
/**
* Creates argTypes with fractional range steps for finer Hold & Win tuning.
*/
const createHoldAndWinArgTypes = (initialArgs: typeof args): ReturnType<typeof argTypes> => {
const baseArgTypes = argTypes(initialArgs);
Object.keys(initialArgs).forEach((key) => {
const descriptor = baseArgTypes[key];
const control = descriptor?.control;
if (!control || control.type !== 'range') return;
baseArgTypes[key] = {
...descriptor,
control: {
...control,
step: control.step && control.step < 1 ? control.step : 0.1,
},
};
});
return baseArgTypes;
};
const meta: Meta<typeof IdleSystem> = {
title: 'features/HoldAndWin', // Путь для отображения в Storybook
tags: ['autodocs'], // Включает автоматическую генерацию документации
parameters: {
docs: {
story: {
height: 600
},
description: {
component: 'Manages idle animations for random symbols in the slot machine. When the system is idle, it triggers an idle animation on a randomly chosen symbol.'
},
}
},
argTypes: createHoldAndWinArgTypes(args),
args: getDefaultArgs(args),
};
export default meta;
export const SingleSlotReelsStory: StoryFn<typeof args> = (_params, context) => new PixiStory({
context,
init: async (view) => {
Assets.reset();
await Assets.init({ manifest: 'hold-and-win/manifest.json' });
await Assets.loadBundle('configs');
await Assets.loadBundle('symbols');
const config: SlotMachineConfig = Assets.get('slot_machine_config');
const machine = new HoldAndWinMachine(config);
const container = new MachineContainer(machine);
container.scale.set(0.5);
view.addChild(container);
context.machine = machine;
// Prepare input maps for screen and initialization
machine.inputs.initMap = createScreenInputMap(config);
machine.inputs.screenMap = createScreenInputMap(config);
// Apply story args to spin system and trigger spin
machine.systems.spin.setSettings(_params as any);
machine.inputs.spin = true;
centerView(view);
},
resize: centerView,
update: () => {
const dt = Ticker.shared.deltaMS;
if (context.machine) context.machine.update(dt);
}
});
export const StickSystemStory: StoryFn<typeof args> = (_params, context) => new PixiStory({
context,
init: async (view) => {
Assets.reset();
await Assets.init({ manifest: 'hold-and-win/manifest.json' });
await Assets.loadBundle('configs');
await Assets.loadBundle('symbols');
const config: SlotMachineConfig = Assets.get('slot_machine_config');
const machine = new HoldAndWinMachine(config);
const container = new MachineContainer(machine);
container.scale.set(0.5);
view.addChild(container);
context.machine = machine;
// Attach StickySymbolsSystem to HoldAndWinMachine
machine.systems.stick = new StickySymbolsSystem(machine as any);
// Ensure stick system runs during all states where needed
machine.stateMachine.states[STATES.SPINNING].systems = ['spin', 'layering'];
machine.stateMachine.states.forEach((state) => {
if (!state.systems.includes('stick')) state.systems.push('stick');
});
// Prepare input maps for screen and initialization
machine.inputs.initMap = createScreenInputMap(config);
machine.inputs.screenMap = machine.inputs.initMap;
// Mark which symbols should become sticky; example: stick wilds L/M/N
// This ensures only those cells are hidden behind stickies; others remain visible during spin
machine.maps.stickySymbols = machine.inputs.screenMap.map((s: any) => ['L', 'M', 'N'].includes(s.key));
// Configure systems
if ((machine.systems as any).stick?.setSettings) (machine.systems as any).stick.setSettings({ zIndex: 5 });
machine.systems.spin.setSettings(_params as any);
// Trigger spin
machine.inputs.spin = true;
centerView(view);
},
resize: centerView,
update: () => {
const dt = Ticker.shared.deltaMS;
if (context.machine) context.machine.update(dt);
}
});
export const SingleSlotReelsPreventSpinStory: StoryFn<typeof args> = (_params, context) => new PixiStory({
context,
init: async (view) => {
Assets.reset();
await Assets.init({ manifest: 'hold-and-win/manifest.json' });
await Assets.loadBundle('configs');
await Assets.loadBundle('symbols');
const config: SlotMachineConfig = Assets.get('slot_machine_config');
const machine = new HoldAndWinMachine(config);
const container = new MachineContainer(machine);
container.scale.set(0.5);
view.addChild(container);
context.machine = machine;
// Prepare input maps for screen and initialization
machine.inputs.initMap = createScreenInputMap(config);
machine.maps.preventReelsSpin = machine.inputs.initMap.map((s: any) => s?.key === 'K');
machine.inputs.screenMap = machine.inputs.initMap
// Apply story args to spin system and trigger spin
machine.systems.spin.setSettings(_params as any);
machine.inputs.spin = true;
centerView(view);
},
resize: centerView,
update: () => {
const dt = Ticker.shared.deltaMS;
if (context.machine) context.machine.update(dt);
}
});
export const DoubleHoldAndWinStory: StoryFn<typeof args> = (_params, context) => new PixiStory({
context,
init: async (view) => {
Assets.reset();
await Assets.init({ manifest: 'hold-and-win/manifest.json' });
await Assets.loadBundle('configs');
await Assets.loadBundle('symbols');
const config: SlotMachineConfig = Assets.get('slot_machine_config');
// Create two machines
const machineTop = new HoldAndWinMachine(config);
const containerTop = new MachineContainer(machineTop);
const machineBottom = new HoldAndWinMachine(config);
const containerBottom = new MachineContainer(machineBottom);
// Scale both machines to half size
containerTop.scale.set(0.5);
containerBottom.scale.set(0.5);
// Add to view
view.addChild(containerTop);
view.addChild(containerBottom);
// Keep references for update loop
(context as any).machines = [machineTop, machineBottom];
// Prepare input maps (different for each machine)
const initMapTop = createScreenInputMap(config);
const initMapBottom = [...createScreenInputMap(config)].reverse(); // make it different
machineTop.inputs.initMap = initMapTop;
machineTop.inputs.screenMap = initMapTop;
machineBottom.inputs.initMap = initMapBottom;
machineBottom.inputs.screenMap = initMapBottom;
// Apply story args to both spin systems and trigger spin
machineTop.systems.spin.setSettings(_params as any);
machineBottom.systems.spin.setSettings(_params as any);
machineTop.inputs.spin = true;
machineBottom.inputs.spin = true;
// Layout: one above the other
containerBottom.y = containerTop.height + 50; // spacing between machines
centerView(view);
},
resize: centerView,
update: () => {
const dt = Ticker.shared.deltaMS;
const machines = (context as any).machines as any[] | undefined;
if (machines) machines.forEach((m: any) => m.update(dt));
}
});

View File

@@ -0,0 +1,65 @@
import { Assets, Ticker } from 'pixi.js';
import ReelsMachine from 'src/slotMachines/ReelsMachine';
import { argTypes, getDefaultArgs } from 'src/stories/utils/argTypes';
import { createScreenInputMap } from 'src/stories/utils/createScreenInputMap';
import MachineContainer from 'src/stories/utils/MachineContainer';
import { centerView } from 'src/stories/utils/resize';
import { SlotMachineConfig } from 'src/types/SlotMachineConfig';
import { Meta, PixiStory, StoryFn } from '@pixi/storybook-renderer';
import IdleSystem from '../systems/IdleSystem';
import MockSystem from "src/systems/MockSystem";
const args = {
maxAnimatedSymbols: 5
};
const meta: Meta<typeof IdleSystem> = {
title: 'Systems/IdleSystem', // Путь для отображения в Storybook
tags: ['autodocs'], // Включает автоматическую генерацию документации
parameters: {
docs: {
story: {
height: 600
},
description: {
component: 'Manages idle animations for random symbols in the slot machine. When the system is idle, it triggers an idle animation on a randomly chosen symbol.'
},
}
},
argTypes: argTypes(args),
args: getDefaultArgs(args),
};
export default meta;
export const IdleSystemStory: StoryFn<typeof args> = (_params, context) => new PixiStory({
context, init: async (view) =>
{
Assets.reset();
await Assets.init({
manifest: 'area69/manifest.json',
});
await Assets.loadBundle('configs');
await Assets.loadBundle('symbols');
const config: SlotMachineConfig = Assets.get('slot_machine_config');
const machine = new ReelsMachine(config);
const container = new MachineContainer(machine);
view.addChild(container);
context.machine = machine;
machine.inputs.initMap = createScreenInputMap(config);
machine.systems.idle.setSettings(_params);
machine.systems.blockExitState = new MockSystem(machine)
machine.inputs.blockExitState = true;
setTimeout(() => {
machine.inputs.blockExitState = false;
}, 250);
centerView(view);
},
resize: centerView,
update: () =>
{
context.machine && context.machine.update(Ticker.shared.deltaMS);
context.machine.systems.idle.setSettings(_params);
}
});

View File

@@ -0,0 +1,57 @@
import { Assets, Ticker } from 'pixi.js';
import ReelsMachine from 'src/slotMachines/ReelsMachine';
import { createScreenInputMap } from 'src/stories/utils/createScreenInputMap';
import MachineContainer from 'src/stories/utils/MachineContainer';
import { centerView } from 'src/stories/utils/resize';
import GridSystem from 'src/systems/GridSystem';
import { SlotMachineConfig } from 'src/types/SlotMachineConfig';
import { Meta, PixiStory, StoryFn } from '@pixi/storybook-renderer';
import MockSystem from "src/systems/MockSystem";
const args = {
};
const meta: Meta<typeof GridSystem> = {
title: 'Systems/InitSystem', // Путь для отображения в Storybook
tags: ['autodocs'], // Включает автоматическую генерацию документации
parameters: {
docs: {
story: {
height: 600
},
description: {
component: 'A system responsible for initializing symbols in the slot machine. It only runs once, adding symbols based on `initMap` data to the main scene and marking initialization as complete.'
},
}
},
};
export default meta;
export const GridSystemStory: StoryFn<typeof args> = (_params, context) => new PixiStory({
context, init: async (view) =>
{
Assets.reset();
await Assets.init({
manifest: 'area69/manifest.json',
});
await Assets.loadBundle('configs');
await Assets.loadBundle('symbols');
const config: SlotMachineConfig = Assets.get('slot_machine_config');
const machine = new ReelsMachine(config);
const container = new MachineContainer(machine);
machine.systems.blockExitState = new MockSystem(machine)
view.addChild(container);
context.machine = machine;
machine.inputs.initMap = createScreenInputMap(config);
machine.systems.idle.setSettings(_params);
centerView(view);
},
resize: centerView,
update: () =>
{
context.machine && context.machine.update(Ticker.shared.deltaMS);
context.machine.systems.idle.setSettings(_params);
}
});

View File

@@ -0,0 +1,61 @@
import { Assets, Ticker } from 'pixi.js';
import ReelsMachine from 'src/slotMachines/ReelsMachine';
import { argTypes, getDefaultArgs } from 'src/stories/utils/argTypes';
import { createScreenInputMap } from 'src/stories/utils/createScreenInputMap';
import MachineContainer from 'src/stories/utils/MachineContainer';
import { centerView } from 'src/stories/utils/resize';
import ReelsSpinSystem from 'src/systems/ReelsSpinSystem';
import { SlotMachineConfig } from 'src/types/SlotMachineConfig';
import { Meta, PixiStory, StoryFn } from '@pixi/storybook-renderer';
import MockSystem from "src/systems/MockSystem";
const args = {
};
const meta: Meta<typeof ReelsSpinSystem> = {
title: 'Systems/LayeringSystem', // Путь для отображения в Storybook
tags: ['autodocs'], // Включает автоматическую генерацию документации
parameters: {
docs: {
story: {
height: 600
},
description: {
component: 'Add doc'
},
}
},
argTypes: argTypes(args),
args: getDefaultArgs(args),
};
export default meta;
export const LayeringSystemStory: StoryFn<typeof args> = (_params, context) => new PixiStory({
context, init: async (view) =>
{
Assets.reset();
await Assets.init({
manifest: 'area69/manifest.json',
});
await Assets.loadBundle('configs');
await Assets.loadBundle('symbols');
const config: SlotMachineConfig = Assets.get('slot_machine_config');
const machine = new ReelsMachine(config);
const container = new MachineContainer(machine);
machine.systems.blockExitState = new MockSystem(machine)
view.addChild(container);
context.machine = machine;
machine.inputs.initMap = createScreenInputMap(config);
machine.inputs.screenMap = createScreenInputMap(config);
machine.systems.spin.setSettings(_params);
machine.inputs.spin = false;
centerView(view);
},
resize: centerView,
update: () =>
{
context.machine && context.machine.update(Ticker.shared.deltaMS);
}
});

View File

@@ -0,0 +1,86 @@
import { Assets, Ticker } from 'pixi.js';
import { Meta, PixiStory, StoryFn } from '@pixi/storybook-renderer';
import ReelsMachine from 'src/slotMachines/ReelsMachine';
import { argTypes, getDefaultArgs } from 'src/stories/utils/argTypes';
import { createScreenInputMap } from 'src/stories/utils/createScreenInputMap';
import MachineContainer from 'src/stories/utils/MachineContainer';
import { centerView } from 'src/stories/utils/resize';
import MockSystem from 'src/systems/MockSystem';
import MaskingSystem, { MaskingSystemSettings } from 'src/systems/MaskingSystem';
import { STATES } from 'src/states/ClassicReelsStateMachine';
import { SlotMachineConfig } from 'src/types/SlotMachineConfig';
const args = {
enabled: true,
width: 1920,
height: 1080,
};
const meta: Meta<typeof MaskingSystem> = {
title: 'Systems/MaskingSystem',
tags: ['autodocs'],
parameters: {
docs: {
story: {
height: 600,
},
description: {
component: 'Preview the rectangular reel viewport mask and toggle it with the provided controls. The story mirrors the base reel setup and forwards the knob values into `setSettings` so you can iterate on mask dimensions without leaving Storybook.',
},
},
},
argTypes: argTypes(args),
args: getDefaultArgs(args),
};
export default meta;
export const MaskingSystemStory: StoryFn<typeof args> = (_params, context) => new PixiStory({
context,
init: async (view) =>
{
Assets.reset();
await Assets.init({
manifest: 'area69/manifest.json',
});
await Assets.loadBundle('configs');
await Assets.loadBundle('symbols');
const config: SlotMachineConfig = Assets.get('slot_machine_config');
const machine = new ReelsMachine(config);
machine.systems.masking = new MaskingSystem(machine);
const spinningState = machine.stateMachine.states[STATES.SPINNING];
if (!spinningState.systems.includes('masking'))
{
spinningState.systems.push('masking');
}
const container = new MachineContainer(machine, false);
view.addChild(container);
context.machine = machine;
machine.systems.blockExitState = new MockSystem(machine);
machine.inputs.initMap = createScreenInputMap(config);
machine.inputs.screenMap = createScreenInputMap(config);
const maskSettings: Partial<MaskingSystemSettings> = {
enabled: _params.enabled,
width: _params.width,
height: _params.height,
};
(machine.systems.masking as MaskingSystem).setSettings(maskSettings);
machine.inputs.spin = true;
machine.inputs.blockExitState = true;
setTimeout(() =>
{
machine.inputs.blockExitState = false;
}, 250);
centerView(view);
},
resize: centerView,
update: () =>
{
context.machine && context.machine.update(Ticker.shared.deltaMS);
}
});

View File

@@ -0,0 +1,119 @@
import { Assets, Ticker } from 'pixi.js';
import ReelsMachine from 'src/slotMachines/ReelsMachine';
import { argTypes, getDefaultArgs } from 'src/stories/utils/argTypes';
import { createScreenInputMap } from 'src/stories/utils/createScreenInputMap';
import MachineContainer from 'src/stories/utils/MachineContainer';
import { centerView } from 'src/stories/utils/resize';
import ReelsSpinSystem from 'src/systems/ReelsSpinSystem';
import { SlotMachineConfig } from 'src/types/SlotMachineConfig';
import { Meta, PixiStory, StoryFn } from '@pixi/storybook-renderer';
import MockSystem from 'src/systems/MockSystem';
import MaskingSystem from 'src/systems/MaskingSystem';
import { STATES } from 'src/states/ClassicReelsStateMachine';
const args = {
accelerationTime: 1,
backCoef: 1.1,
maxSpeed: 40,
reelStartDelay: 0.2,
reelStopDelay: 0.5,
spinningDuration: 2,
blur: true,
bounce: true
};
const meta: Meta<typeof ReelsSpinSystem> = {
title: 'Systems/ReelsSpinSystem', // Путь для отображения в Storybook
tags: ['autodocs'], // Включает автоматическую генерацию документации
parameters: {
docs: {
story: {
height: 600
},
description: {
component: 'Controls the spinning behavior of slot machine reels, including acceleration, sustained spinning, and deceleration. This class manages reel state transitions, timing for each stage of the spin, and the display of symbols on each reel.'
},
}
},
argTypes: argTypes(args),
args: getDefaultArgs(args),
};
export default meta;
export const ReelsSpinSystemStory: StoryFn<typeof args> = (_params, context) => new PixiStory({
context, init: async (view) =>
{
Assets.reset();
await Assets.init({
manifest: 'area69/manifest.json',
});
await Assets.loadBundle('configs');
await Assets.loadBundle('symbols');
const config: SlotMachineConfig = Assets.get('slot_machine_config');
const machine = new ReelsMachine(config);
machine.systems.masking = new MaskingSystem(machine);
machine.stateMachine.states[STATES.SPINNING].systems.push('masking');
const container = new MachineContainer(machine, false);
view.addChild(container);
context.machine = machine;
machine.systems.blockExitState = new MockSystem(machine);
machine.inputs.initMap = createScreenInputMap(config);
machine.inputs.screenMap = createScreenInputMap(config);
machine.systems.spin.setSettings(_params);
machine.inputs.spin = true;
machine.inputs.blockExitState = true;
setTimeout(() => {
machine.inputs.blockExitState = false;
}, 250);
centerView(view);
},
resize: centerView,
update: () =>
{
context.machine && context.machine.update(Ticker.shared.deltaMS);
}
});
export const ReelsSpinSystemPreventedReelsStory: StoryFn<typeof args> = (_params, context) => new PixiStory({
context, init: async (view) =>
{
Assets.reset();
await Assets.init({
manifest: 'area69/manifest.json',
});
await Assets.loadBundle('configs');
await Assets.loadBundle('symbols');
const config: SlotMachineConfig = Assets.get('slot_machine_config');
const machine = new ReelsMachine(config);
machine.systems.masking = new MaskingSystem(machine);
machine.stateMachine.states[STATES.SPINNING].systems.push('masking');
const container = new MachineContainer(machine, false);
view.addChild(container);
context.machine = machine;
machine.systems.blockExitState = new MockSystem(machine);
machine.inputs.initMap = createScreenInputMap(config);
machine.inputs.screenMap = createScreenInputMap(config);
machine.systems.spin.setSettings(_params);
// Disable first and last reels from spinning
const reelsCount = config.reels;
machine.maps.preventReelsSpin = Array.from({ length: reelsCount }, (_, i) => i === 0 || i === reelsCount - 1);
machine.inputs.spin = true;
machine.inputs.blockExitState = true;
setTimeout(() => {
machine.inputs.blockExitState = false;
}, 250);
centerView(view);
},
resize: centerView,
update: () =>
{
context.machine && context.machine.update(Ticker.shared.deltaMS);
}
});

View File

@@ -0,0 +1,161 @@
import { Assets, Ticker } from 'pixi.js';
import { Meta, PixiStory, StoryFn } from '@pixi/storybook-renderer';
import HoldAndWinMachine from 'src/slotMachines/HoldAndWinMachine';
import { SlotMachineConfig } from 'src/types/SlotMachineConfig';
import { centerView } from 'src/stories/utils/resize';
import MachineContainer from 'src/stories/utils/MachineContainer';
import { createScreenInputMap } from 'src/stories/utils/createScreenInputMap';
import SlotMultipliersSystem from 'src/systems/SlotMultipliersSystem';
import SlotMultiplierEntity from 'src/entities/SlotMultiplierEntity';
import { STATES } from 'src/states/BasicStateMachine';
import {argTypes, getDefaultArgs} from "src/stories/utils/argTypes";
const args = {
accelerationTime: 1,
backCoef: 1.1,
maxSpeed: 15,
reelStartDelay: 0.2,
reelStopDelay: 0.5,
slotStopDelay: 0,
spinningDuration: 2,
blur: false,
bounce: true
};
/**
* Creates argTypes with fractional range steps for finer Hold & Win tuning.
*/
const createHoldAndWinArgTypes = (initialArgs: typeof args): ReturnType<typeof argTypes> => {
const baseArgTypes = argTypes(initialArgs);
Object.keys(initialArgs).forEach((key) => {
const descriptor = baseArgTypes[key];
const control = descriptor?.control;
if (!control || control.type !== 'range') return;
baseArgTypes[key] = {
...descriptor,
control: {
...control,
step: control.step && control.step < 1 ? control.step : 0.1,
},
};
});
return baseArgTypes;
};
const meta: Meta<typeof SlotMultipliersSystem> = {
title: 'systems/SlotMultipliersSystem',
tags: ['autodocs'],
parameters: {
docs: {
description: {
component: 'Displays Spine-based multiplier overlays for every slot based on the slotMultipliers map.',
},
},
},
argTypes: createHoldAndWinArgTypes(args),
args: getDefaultArgs(args),
};
export default meta;
/**
* Updates the multiplier map with random positive values and zeroed blanks.
*/
function randomiseMultipliers(machine: HoldAndWinMachine): void
{
const total = machine.config.reels * machine.config.slots;
machine.maps.slotMultipliers = Array.from({ length: total }, () =>
(Math.random() > 0.6 ? Math.ceil(Math.random() * 10) : 0));
}
export const HoldAndWinMultipliers: StoryFn = (_params, context) => new PixiStory({
context,
init: async (view) => {
Assets.reset();
await Assets.init({ manifest: 'hold-and-win/manifest.json' });
await Assets.loadBundle('configs');
await Assets.loadBundle('symbols');
await Assets.loadBundle('addons');
const config: SlotMachineConfig = Assets.get('slot_machine_config');
const machine = new HoldAndWinMachine(config);
const container = new MachineContainer(machine);
container.scale.set(1);
view.addChild(container);
context.machine = machine;
context.prevSpinState = machine.systems.spin.state;
machine.inputs.initMap = createScreenInputMap(config);
machine.inputs.screenMap = machine.inputs.initMap;
const slotMultipliersSystem = new SlotMultipliersSystem(machine);
slotMultipliersSystem.setSettings({ entityClass: SlotMultiplierEntity });
machine.systems.slotMultipliers = slotMultipliersSystem;
// Apply story args to spin system and trigger spin
machine.systems.spin.setSettings(_params as any);
const idleState = machine.stateMachine.states[STATES.IDLE];
if (idleState && !idleState.systems.includes('slotMultipliers'))
{
idleState.systems.push('slotMultipliers');
}
const spinningState = machine.stateMachine.states[STATES.SPINNING];
if (spinningState && !spinningState.systems.includes('slotMultipliers'))
{
spinningState.systems.push('slotMultipliers');
}
randomiseMultipliers(machine);
machine.inputs.spin = true;
context.spinDelayMs = 0;
centerView(view);
},
resize: centerView,
update: () => {
const machine: HoldAndWinMachine | undefined = context.machine;
if (!machine) return;
const dt = Ticker.shared.deltaMS;
machine.update(dt);
const spinSystem = machine.systems.spin;
const prevState = context.prevSpinState ?? spinSystem.state;
if (prevState !== spinSystem.state)
{
if (spinSystem.state === 'starting')
{
machine.inputs.spin = false;
}
if (spinSystem.state === 'stopped')
{
context.spinDelayMs = 500;
}
context.prevSpinState = spinSystem.state;
}
if ((context.spinDelayMs ?? 0) > 0)
{
context.spinDelayMs -= dt;
if (context.spinDelayMs <= 0)
{
randomiseMultipliers(machine);
machine.inputs.spin = true;
context.spinDelayMs = 0;
}
}
},
});

View File

@@ -0,0 +1,69 @@
import { Assets, Ticker } from 'pixi.js';
import { Meta, PixiStory, StoryFn } from '@pixi/storybook-renderer';
import StaticReelsMachine from '../slotMachines/StaticReelsMachine';
import { argTypes, getDefaultArgs } from '../stories/utils/argTypes';
import { createScreenInputMap } from '../stories/utils/createScreenInputMap';
import MachineContainer from '../stories/utils/MachineContainer';
import { centerView } from '../stories/utils/resize';
import StaticReelsSpinSystem from '../systems/StaticReelsSpinSystem';
import { SlotMachineConfig } from '../types/SlotMachineConfig';
import MockSystem from "src/systems/MockSystem";
const args = {
reelDelay: 0.1,
slotDelay: 0.03,
refillDelay: 0.15,
};
const meta: Meta<typeof StaticReelsSpinSystem> = {
title: 'Systems/StaticReelsSpinSystem',
tags: ['autodocs'],
parameters: {
docs: {
story: {
height: 600
},
description: {
component: 'Add doc'
},
}
},
argTypes: argTypes(args),
args: getDefaultArgs(args),
};
export default meta;
export const StaticReelsSpinSystemStory: StoryFn<typeof args> = (_params, context) => new PixiStory({
context, init: async (view) =>
{
Assets.reset();
await Assets.init({
manifest: 'bubblex/manifest.json',
});
await Assets.loadBundle('configs');
await Assets.loadBundle('symbols');
const config: SlotMachineConfig = Assets.get('slot_machine_config');
const machine = new StaticReelsMachine(config);
const container = new MachineContainer(machine);
view.addChild(container);
context.machine = machine;
machine.systems.blockExitState = new MockSystem(machine)
machine.inputs.initMap = createScreenInputMap(config);
machine.inputs.screenMap = createScreenInputMap(config);
machine.systems.spin.setSettings(_params);
machine.inputs.spin = true;
machine.inputs.blockExitState = true;
setTimeout(() => {
machine.inputs.blockExitState = false;
}, 250);
centerView(view);
},
resize: centerView,
update: () =>
{
context.machine && context.machine.update(Ticker.shared.deltaMS);
}
});

View File

@@ -0,0 +1,137 @@
import { Assets, Ticker } from 'pixi.js';
import CascadeMachine from 'src/slotMachines/CascadeMachine';
import ReelsMachine from 'src/slotMachines/ReelsMachine';
import StaticReelsMachine from 'src/slotMachines/StaticReelsMachine';
import { STATES } from 'src/states/BasicStateMachine';
import { argTypes, getDefaultArgs } from 'src/stories/utils/argTypes';
import { createScreenInputMap } from 'src/stories/utils/createScreenInputMap';
import MachineContainer from 'src/stories/utils/MachineContainer';
import { centerView } from 'src/stories/utils/resize';
import StickySymbolsSystem from 'src/systems/StickySymbolsSystem';
import { SlotMachineConfig } from 'src/types/SlotMachineConfig';
import { Meta, PixiStory, StoryFn } from '@pixi/storybook-renderer';
import MockSystem from "src/systems/MockSystem";
const args = {
zIndex: 5,
};
const meta: Meta<typeof StickySymbolsSystem> = {
title: 'Systems/StickySpinSystem',
tags: ['autodocs'],
parameters: {
docs: {
story: {
height: 600
},
description: {
component: 'Controls the spinning behavior of slot machine reels, including acceleration, sustained spinning, and deceleration. This class manages reel state transitions, timing for each stage of the spin, and the display of symbols on each reel.'
},
}
},
argTypes: argTypes(args),
args: getDefaultArgs(args),
};
export default meta;
export const ReelsMachineStory: StoryFn<typeof args> = (_params, context) => new PixiStory({
context, init: async (view) =>
{
Assets.reset();
await Assets.init({
manifest: 'area69/manifest.json',
});
await Assets.loadBundle('configs');
await Assets.loadBundle('symbols');
const config: SlotMachineConfig = Assets.get('slot_machine_config');
const machine = new ReelsMachine(config);
const container = new MachineContainer(machine);
view.addChild(container);
context.machine = machine;
machine.systems.stick = new StickySymbolsSystem(machine);
machine.stateMachine.states[STATES.SPINNING].systems = ['spin', 'layering'];
machine.stateMachine.states.forEach((state) => state.systems.push('stick'));
machine.inputs.initMap = createScreenInputMap(config);
machine.inputs.screenMap = machine.inputs.initMap;
machine.maps.stickySymbols = machine.inputs.screenMap.map((s: any) => ['L', 'M', 'N'].includes(s.key));
machine.systems.stick.setSettings(_params);
machine.inputs.spin = true;
machine.inputs.blockExitState = true;
setTimeout(() => {
machine.inputs.blockExitState = false;
}, 250);
centerView(view);
centerView(view);
centerView(view);
},
resize: centerView,
update: () =>
{
context.machine && context.machine.update(Ticker.shared.deltaMS);
}
});
export const CascadeMachineStory: StoryFn<typeof args> = (_params, context) => new PixiStory({
context, init: async (view) =>
{
Assets.reset();
await Assets.init({
manifest: 'bubblex/manifest.json',
});
await Assets.loadBundle('configs');
await Assets.loadBundle('symbols');
const config: SlotMachineConfig = Assets.get('slot_machine_config');
const machine = new CascadeMachine(config);
const container = new MachineContainer(machine);
view.addChild(container);
context.machine = machine;
machine.systems.stick = new StickySymbolsSystem(machine);
machine.stateMachine.states[STATES.SPINNING].systems = ['stick', 'spin'];
machine.inputs.initMap = createScreenInputMap(config);
machine.inputs.screenMap = machine.inputs.initMap;
machine.maps.stickySymbols = machine.inputs.screenMap.map((s: any) => ["RR"].includes(s.key));
machine.systems.stick.setSettings(_params);
machine.inputs.spin = true;
centerView(view);
},
resize: centerView,
update: () =>
{
context.machine && context.machine.update(Ticker.shared.deltaMS);
}
});
export const StaticReelsMachineStory: StoryFn<typeof args> = (_params, context) => new PixiStory({
context, init: async (view) =>
{
Assets.reset();
await Assets.init({
manifest: 'bubblex/manifest.json',
});
await Assets.loadBundle('configs');
await Assets.loadBundle('symbols');
const config: SlotMachineConfig = Assets.get('slot_machine_config');
const machine = new StaticReelsMachine(config);
const container = new MachineContainer(machine);
view.addChild(container);
context.machine = machine;
machine.systems.blockExitState = new MockSystem(machine)
machine.systems.stick = new StickySymbolsSystem(machine);
machine.stateMachine.states[STATES.SPINNING].systems = ['stick', 'spin'];
machine.inputs.initMap = createScreenInputMap(config);
machine.inputs.screenMap = machine.inputs.initMap;
machine.maps.stickySymbols = machine.inputs.screenMap.map((s: any) => ["RR"].includes(s.key));
machine.systems.stick.setSettings(_params);
machine.inputs.spin = true;
centerView(view);
},
resize: centerView,
update: () =>
{
context.machine && context.machine.update(Ticker.shared.deltaMS);
}
});

View File

@@ -0,0 +1,217 @@
import { Assets, Ticker } from 'pixi.js';
import { argTypes, getDefaultArgs } from 'src/stories/utils/argTypes';
import { createScreenInputMap } from 'src/stories/utils/createScreenInputMap';
import MachineContainer from 'src/stories/utils/MachineContainer';
import { centerView } from 'src/stories/utils/resize';
import ReelsSpinSystem from 'src/systems/ReelsSpinSystem';
import { SlotMachineConfig } from 'src/types/SlotMachineConfig';
import { Meta, PixiStory, StoryFn } from '@pixi/storybook-renderer';
import ReelsMachine from '../slotMachines/ReelsMachine';
import MockSystem from "../systems/MockSystem";
const args = {
key: 'A', // skin '10'
accelerationTime: 1,
backCoef: 1.1,
maxSpeed: 40,
reelStartDelay: 0.2,
reelStopDelay: 0.5,
spinningDuration: 2,
blur: true,
bounce: true
};
const meta: Meta<typeof ReelsSpinSystem> = {
title: 'Systems/WinSymbolSystem', // Путь для отображения в Storybook
tags: ['autodocs'], // Включает автоматическую генерацию документации
parameters: {
docs: {
story: {
height: 600
},
description: {
component: 'Animation of win symbols and winlines, all and individual loop'
},
}
},
argTypes: argTypes(args),
args: getDefaultArgs(args),
};
export default meta;
const rawLines = [
[0, 1, 0, 1, 0],
[0, 1, 1, 1, 0]
]
const lines = {
get: (index: number) => {
return rawLines[index]
},
numberOfLines: () => 2,
lines: rawLines
}
export const WinSymbolStory: StoryFn<typeof args> = (_params, context) => new PixiStory({
context, init: async (view) =>
{
Assets.reset();
await Assets.init({
manifest: 'area69/manifest.json',
});
await Assets.loadBundle('configs');
await Assets.loadBundle('symbols');
const config: SlotMachineConfig = Assets.get('slot_machine_config');
const machine = new ReelsMachine({...config, lines: lines.lines});
machine.systems.spin.setSettings(_params);
const container = new MachineContainer(machine);
machine.systems.blockExitState = new MockSystem(machine)
view.addChild(container);
context.machine = machine;
machine.inputs.initMap = createScreenInputMap(config);
machine.inputs.screenMap = createScreenInputMap(config);
machine.inputs.wins = {
totalWin: 200,
totalWinString: '2.00 USD',
wins: [
{
type: 'line',
amount: 100,
amountString: '1.00 USD',
lineSlotsIndexMap: [0, 1, 0],
lineIndex: 0
},
{
type: 'line',
amount: 100,
amountString: '1.00 USD',
lineSlotsIndexMap: [0, 1, 1],
lineIndex: 1
}
]
};
console.log(machine)
console.log(machine.inputs)
machine.inputs.spin = true;
centerView(view);
},
resize: centerView,
update: () =>
{
context.machine && context.machine.update(Ticker.shared.deltaMS);
}
});
// export const WinStickSymbolStory: StoryFn<typeof args> = (_params, context) => new PixiStory({
// context, init: async (view) =>
// {
// Assets.reset();
// await Assets.init({
// manifest: 'area69/manifest.json',
// });
// await Assets.loadBundle('configs');
// await Assets.loadBundle('symbols');
// const config: SlotMachineConfig = Assets.get('slot_machine_config');
// const machine = new ReelsMachine({...config, lines: [
// [0, 1, 0, 1, 0],
// [0, 1, 1, 1, 0]
// ] });
// const container = new MachineContainer(machine);
//
//
// view.addChild(container);
// context.machine = machine;
// machine.inputs.initMap = createScreenInputMap(config);
// machine.inputs.screenMap = createScreenInputMap(config);
// machine.inputs.wins = [
// {
// type: 'line',
// amount: 100,
// amountString: '1.00 USD',
// lineSlotsIndexMap: [0, 1, 0],
// lineIndex: 0
// },
// {
// type: 'line',
// amount: 100,
// amountString: '1.00 USD',
// lineSlotsIndexMap: [0, 1, 1],
// lineIndex: 1
// }
// ]
// machine.stateMachine.states[STATES.SPINNING].systems = ['spin', 'layering', 'stick'];
// machine.systems.stick = new StickySymbolsSystem(machine);
// machine.systems.stick.setSettings(_params);
// machine.systems.spin.setSettings(_params);
// // machine.systems.allWinSymbols.setSettings(winSymbolSettings);
// // machine.systems.winningLinesLoop.setSettings(winSymbolSettings);
//
// machine.inputs.blockExitState = true;
// setTimeout(() => {
// machine.inputs.blockExitState = false;
// }, 250);
//
// centerView(view);
// },
// resize: centerView,
// update: () =>
// {
// context.machine && context.machine.update(Ticker.shared.deltaMS);
// }
// });
//
// export const WinStickExpandingSymbolStory: StoryFn<typeof args> = (_params, context) => new PixiStory({
// context, init: async (view) =>
// {
// Assets.reset();
// await Assets.init({
// manifest: 'area69/manifest.json',
// });
// await Assets.loadBundle('configs');
// await Assets.loadBundle('symbols');
// const config: SlotMachineConfig = Assets.get('slot_machine_config');
// const machine = new ReelsMachine(config);
// const container = new MachineContainer(machine);
// const winSymbolSettings = {
// lines: [[0, 1, 0, 1, 0], [3, 2, 3, 2, 3]],
// hideWinline: false,
// winlineAnimationClass: WinLine,
// basicWinClass: BasicWin,
// };
// const spin = () =>
// {
// machine.inputs.spin = true;
// setTimeout(() => { machine.inputs.spin = false; }, 1000);
// setTimeout(spin, 10000);
// };
//
// view.addChild(container);
// context.machine = machine;
// machine.inputs.initMap = createScreenInputMap(config);
// machine.inputs.screenMap = createScreenInputMap(config);
// machine.inputs.wins = [['line', 100, [0, 1, 0], 0], ['line', 100, [3, 2, 3], 1]];
// machine.inputs.celebrateSymbolIndexes = [0, 1, 5];
// machine.stateMachine.states[STATES.SPINNING].systems = ['spin', 'layering', 'stick', 'expanding'];
// machine.systems.expanding = new ExpandedSymbolsSpinSystem(machine);
// machine.systems.stick = new StickySymbolsSystem(machine);
// machine.systems.expanding.setSettings(_params);
// machine.systems.stick.setSettings(_params);
// machine.systems.spin.setSettings(_params);
// machine.systems.allWinSymbols.setSettings(winSymbolSettings);
// machine.systems.winningLinesLoop.setSettings(winSymbolSettings);
// machine.inputs.blockExitState = true;
// setTimeout(() => {
// machine.inputs.blockExitState = false;
// }, 250);
// centerView(view);
// spin();
// },
// resize: centerView,
// update: () =>
// {
// context.machine && context.machine.update(Ticker.shared.deltaMS);
// }
// });

View File

@@ -0,0 +1,19 @@
anticipation.webp
size:2048,1024
filter:Linear,Linear
scale:0.75
anticip_bg
bounds:777,30,220,836
offsets:319,27,858,879
anticip_circle_whole_source_1.4
bounds:999,463,404,403
offsets:429,445,858,879
anticip_frame
bounds:291,27,262,839
offsets:297,20,858,879
anticip_frame_glow
bounds:2,2,287,864
offsets:285,7,858,879
anticip_glow
bounds:555,29,220,837
offsets:319,27,858,879

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 KiB

View File

@@ -0,0 +1,16 @@
info face="multiplier_value_font" size=90 bold=0 italic=0 charset="" unicode=1 stretchH=100 smooth=1 aa=1 padding=1,1,1,1 spacing=1,1
common lineHeight=104 base=83 scaleW=218 scaleH=218 pages=1 packed=0
page id=0 file="multiplier_value_font.png"
chars count=12
char id=32 x=0 y=0 width=0 height=0 xoffset=0 yoffset=0 xadvance=26 page=0 chnl=15
char id=48 x=167 y=0 width=51 height=71 xoffset=-1 yoffset=-1 xadvance=49 page=0 chnl=15
char id=49 x=0 y=74 width=42 height=72 xoffset=-1 yoffset=-1 xadvance=41 page=0 chnl=15
char id=50 x=0 y=147 width=52 height=71 xoffset=-1 yoffset=-1 xadvance=50 page=0 chnl=15
char id=51 x=53 y=147 width=42 height=71 xoffset=0 yoffset=0 xadvance=41 page=0 chnl=15
char id=52 x=143 y=147 width=54 height=69 xoffset=-1 yoffset=-1 xadvance=52 page=0 chnl=15
char id=53 x=96 y=147 width=46 height=71 xoffset=-1 yoffset=-1 xadvance=44 page=0 chnl=15
char id=54 x=0 y=0 width=56 height=73 xoffset=-1 yoffset=-1 xadvance=54 page=0 chnl=15
char id=55 x=43 y=74 width=50 height=69 xoffset=-1 yoffset=-1 xadvance=48 page=0 chnl=15
char id=56 x=57 y=0 width=52 height=73 xoffset=-1 yoffset=-1 xadvance=50 page=0 chnl=15
char id=57 x=110 y=0 width=56 height=73 xoffset=-1 yoffset=-1 xadvance=54 page=0 chnl=15
char id=88 x=94 y=74 width=55 height=63 xoffset=-1 yoffset=8 xadvance=54 page=0 chnl=15

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

View File

@@ -0,0 +1,16 @@
info face="wild_multiplier_font" size=90 bold=0 italic=0 charset="" unicode=1 stretchH=100 smooth=1 aa=1 padding=1,1,1,1 spacing=1,1
common lineHeight=104 base=83 scaleW=209 scaleH=218 pages=1 packed=0
page id=0 file="wild_multiplier_font.png"
chars count=12
char id=32 x=0 y=0 width=0 height=0 xoffset=0 yoffset=0 xadvance=26 page=0 chnl=15
char id=48 x=0 y=0 width=53 height=73 xoffset=-1 yoffset=-1 xadvance=51 page=0 chnl=15
char id=49 x=166 y=0 width=43 height=72 xoffset=-1 yoffset=-1 xadvance=41 page=0 chnl=15
char id=50 x=150 y=74 width=53 height=71 xoffset=-1 yoffset=-1 xadvance=51 page=0 chnl=15
char id=51 x=58 y=74 width=43 height=72 xoffset=-1 yoffset=-1 xadvance=41 page=0 chnl=15
char id=52 x=0 y=148 width=55 height=70 xoffset=-1 yoffset=-1 xadvance=53 page=0 chnl=15
char id=53 x=102 y=74 width=47 height=72 xoffset=-1 yoffset=-1 xadvance=45 page=0 chnl=15
char id=54 x=54 y=0 width=57 height=73 xoffset=-1 yoffset=-1 xadvance=55 page=0 chnl=15
char id=55 x=56 y=148 width=51 height=70 xoffset=-1 yoffset=-1 xadvance=49 page=0 chnl=15
char id=56 x=112 y=0 width=53 height=73 xoffset=-1 yoffset=-1 xadvance=51 page=0 chnl=15
char id=57 x=0 y=74 width=57 height=73 xoffset=-1 yoffset=-1 xadvance=55 page=0 chnl=15
char id=88 x=108 y=148 width=56 height=64 xoffset=-1 yoffset=8 xadvance=55 page=0 chnl=15

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,111 @@
{
"width": 550,
"height": 420,
"reels": 5,
"slots": 4,
"paddings": {
"left": 16,
"top": 0,
"right": 18,
"bottom": 0
},
"symbolsKeys": ["L", "J", "I", "H", "G", "F", "E", "D", "C", "B", "A", "K", "M", "N"],
"symbols": {
"A": {
"key": "A",
"spine": "lowest_symbols",
"skin": "Ten",
"zIndex": 1
},
"B": {
"key": "B",
"spine": "lowest_symbols",
"skin": "J",
"zIndex": 1
},
"C": {
"key": "C",
"spine": "lowest_symbols",
"skin": "Q",
"zIndex": 1
},
"D": {
"key": "D",
"spine": "low_symbols",
"skin": "K",
"zIndex": 1
},
"E": {
"key": "E",
"spine": "low_symbols",
"skin": "A",
"zIndex": 1
},
"F": {
"key": "F",
"spine": "h5",
"skin": "default",
"zIndex": 1
},
"G": {
"key": "G",
"spine": "h4",
"skin": "default",
"zIndex": 1
},
"H": {
"key": "H",
"spine": "h3",
"skin": "default",
"zIndex": 1
},
"I": {
"key": "I",
"spine": "h2",
"skin": "default",
"zIndex": 1
},
"J": {
"key": "J",
"spine": "h1",
"skin": "default",
"zIndex": 1
},
"K": {
"key": "K",
"spine": "scatter",
"skin": "default",
"zIndex": 2
},
"L": {
"key": "L",
"spine": "wild",
"skin": "low",
"fontStyle": {
"fontSize": 30,
"fontName": "wild_multiplier_font"
},
"zIndex": 3
},
"M": {
"key": "M",
"spine": "wild",
"skin": "med",
"fontStyle": {
"fontSize": 30,
"fontName": "wild_multiplier_font"
},
"zIndex": 3
},
"N": {
"key": "N",
"spine": "wild",
"skin": "high",
"fontStyle": {
"fontSize": 30,
"fontName": "wild_multiplier_font"
},
"zIndex": 3
}
}
}

View File

@@ -0,0 +1,146 @@
{
"bundles": [
{
"name": "configs",
"assets": [
{
"name": "slot_machine_config",
"srcs": "area69/configs/slotMachine.json"
}
]
},
{
"name":"symbols",
"assets":[
{
"name": "wild_multiplier_font",
"srcs": "area69/bitmapFonts/wild_multiplier_font.fnt"
},
{
"name": "symbols_atlas",
"srcs": "area69/symbols/symbols.atlas"
},
{
"name": "wild",
"srcs": "area69/symbols/wild.skel",
"data": {
"spineSkeletonScale": 0.35,
"spineAtlasAlias": "symbols_atlas",
"spineAtlasFile": "area69/symbols/symbols.atlas"
}
},
{
"name": "scatter",
"srcs": "area69/symbols/scatter.skel",
"data": {
"spineSkeletonScale": 0.5,
"spineAtlasAlias": "symbols_atlas",
"spineAtlasFile": "area69/symbols/symbols.atlas"
}
},
{
"name": "lowest_symbols",
"srcs": "area69/symbols/L_10_q_j.skel",
"data": {
"spineSkeletonScale": 0.35,
"spineAtlasAlias": "symbols_atlas",
"spineAtlasFile": "area69/symbols/symbols.atlas"
}
},
{
"name": "low_symbols",
"srcs": "area69/symbols/L_A_K.skel",
"data": {
"spineSkeletonScale": 0.35,
"spineAtlasAlias": "symbols_atlas",
"spineAtlasFile": "area69/symbols/symbols.atlas"
}
},
{
"name": "h5",
"srcs": "area69/symbols/M3.skel",
"data": {
"spineSkeletonScale": 0.45,
"spineAtlasAlias": "symbols_atlas",
"spineAtlasFile": "area69/symbols/symbols.atlas"
}
},
{
"name": "h4",
"srcs": "area69/symbols/M2.skel",
"data": {
"spineSkeletonScale": 0.4,
"spineAtlasAlias": "symbols_atlas",
"spineAtlasFile": "area69/symbols/symbols.atlas"
}
},
{
"name": "h3",
"srcs": "area69/symbols/M1.skel",
"data": {
"spineSkeletonScale": 0.35,
"spineAtlasAlias": "symbols_atlas",
"spineAtlasFile": "area69/symbols/symbols.atlas"
}
},
{
"name": "h2",
"srcs": "area69/symbols/H2.skel",
"data": {
"spineSkeletonScale": 0.5,
"spineAtlasAlias": "symbols_atlas",
"spineAtlasFile": "area69/symbols/symbols.atlas"
}
},
{
"name": "h1",
"srcs": "area69/symbols/H1.skel",
"data": {
"spineSkeletonScale": 0.5,
"spineAtlasAlias": "symbols_atlas",
"spineAtlasFile": "area69/symbols/symbols.atlas"
}
},
{
"name": "expanding",
"srcs": "area69/symbols/expanding_multiplier.skel",
"data": {
"spineSkeletonScale": 0.5,
"spineAtlasAlias": "symbols_atlas",
"spineAtlasFile": "area69/symbols/symbols.atlas"
}
},
{
"name": "win_line",
"srcs": "area69/winline/win_line.skel",
"data": {
"spineAtlasAlias": "main_atlas",
"spineAtlasFile": "area69/winline/assembly.atlas"
}
},
{
"name": "win_frame",
"srcs": "area69/textures/win_frame.png"
},
{
"name": "anticipation_back",
"srcs": "area69/anticipation/anticipation_back.skel",
"data": {
"spineSkeletonScale": 0.4,
"spineAtlasAlias": "anticipation",
"spineAtlasFile": "area69/anticipation/anticipation.atlas"
}
},
{
"name": "anticipation_front",
"srcs": "area69/anticipation/anticipation_front.skel",
"data": {
"spineSkeletonScale": 0.4,
"spineAtlasAlias": "anticipation",
"spineAtlasFile": "area69/anticipation/anticipation.atlas"
}
}
]
}
]
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,526 @@
symbols.webp
size:3236,2056
filter:Linear,Linear
103_S
bounds:223,1060,258,241
rotate:90
10_newspaper1_l
bounds:2296,893,157,220
10_newspaper2_r
bounds:1876,1144,149,211
rotate:90
2(Effect)
bounds:2229,307,214,208
rotate:90
3(Effect)
bounds:2202,523,214,209
A3_S
bounds:2400,1116,295,306
A_newspaper1_l
bounds:1091,1110,188,284
rotate:90
A_newspaper2_r
bounds:3011,1453,187,280
DiscoBall_1_1
bounds:241,107,307,309
DiscoBall_1_2
bounds:550,107,307,309
DiscoBall_1_3
bounds:867,789,307,309
DiscoBall_1_4
bounds:1153,1300,307,309
rotate:90
DiscoBall_1_5
bounds:1176,799,307,309
DiscoBall_1_6
bounds:1464,1300,307,309
rotate:90
DiscoBall_1_sequins
bounds:843,1736,348,318
DiscoBall_2_1
bounds:1775,1424,307,309
DiscoBall_2_2
bounds:2084,1424,307,309
DiscoBall_2_3
bounds:2393,1424,307,309
DiscoBall_2_4
bounds:2702,1424,307,309
DiscoBall_2_5
bounds:1485,799,307,309
DiscoBall_2_6
bounds:2089,1115,307,309
rotate:90
DiscoBall_2_sequins
bounds:1193,1736,348,318
DiscoBall_3_1
bounds:864,364,296,297
rotate:90
DiscoBall_3_2
bounds:859,66,296,297
rotate:90
DiscoBall_3_3
bounds:1179,501,296,297
rotate:90
DiscoBall_3_4
bounds:1478,501,296,297
rotate:90
DiscoBall_3_5
bounds:1163,203,296,297
rotate:90
DiscoBall_3_6
bounds:1462,203,296,297
rotate:90
DiscoBall_3_sequins
bounds:489,1381,349,319
Electricity_m2_00
bounds:1543,1735,328,319
Electricity_m2_01
bounds:1873,1735,328,319
Electricity_m2_03
bounds:2203,1735,328,319
Electricity_m2_04
bounds:2533,1735,328,319
Electricity_m2_06
bounds:2863,1735,328,319
Electricity_m2_07
bounds:489,1060,328,319
Electricity_m2_09
bounds:216,739,328,319
Electricity_m2_10
bounds:216,418,328,319
Electricity_m2_12
bounds:546,730,328,319
rotate:90
H1_BG
bounds:840,1309,300,311
rotate:90
H1_button
bounds:830,93,12,27
rotate:90
H1_cigar
bounds:1724,1110,53,32
H1_cigar _S
bounds:2694,356,51,32
rotate:90
H1_collar_3
bounds:3028,342,71,58
H1_collar_L_2
bounds:2866,284,46,87
rotate:90
H1_collar_R_1
bounds:2775,564,55,117
rotate:90
H1_eyes
bounds:2049,8,109,37
H1_finger_1
bounds:2993,135,73,38
H1_finger_2
bounds:45,7,61,29
H1_finger_3
bounds:3099,422,79,87
H1_hand
bounds:2855,445,54,117
H1_head
bounds:2224,2,152,182
rotate:90
H1_horns
bounds:2694,409,153,67
rotate:90
H1_neck
bounds:2442,189,189,141
H1_shirt
bounds:1750,34,144,100
rotate:90
H1_sleeve_L_2
bounds:3055,2,49,63
H1_sleeve_R_1
bounds:3100,287,50,72
rotate:90
H2_BG
bounds:2697,1118,294,304
H2_brows
bounds:241,2,91,51
H2_collar _2
bounds:1928,2,38,119
rotate:90
H2_collar_top_1
bounds:2894,581,108,120
H2_curler
bounds:3104,232,67,53
H2_ear
bounds:2408,2,138,152
H2_earring_L_1
bounds:2,2,34,41
rotate:90
H2_earring_R_2
bounds:1610,1609,37,41
H2_eyelid_L_2
bounds:1182,2,36,25
H2_eyelid_L_top_1
bounds:1906,6,28,20
rotate:90
H2_eyelid_R_4
bounds:3007,479,21,19
offsets:2,0,23,19
rotate:90
H2_eyelid_R_top_3
bounds:466,1293,25,21
offsets:0,1,25,22
rotate:90
H2_glasses_L_1
bounds:2993,175,78,55
H2_glasses_R_3
bounds:3180,372,54,57
H2_hair_L_1
bounds:2633,215,139,137
rotate:90
H2_hair_L_S_2
bounds:2990,72,61,63
offsets:2,0,66,64
rotate:90
H2_hair_R_2
bounds:2772,215,84,89
rotate:90
H2_hand_L_1
bounds:3007,502,82,117
H2_hand_R_2
bounds:2911,467,94,112
H2_hat_1
bounds:2620,564,153,158
H2_hat_S_2
bounds:2763,429,133,90
rotate:90
H2_head_S
bounds:3028,402,98,69
rotate:90
H2_mouth_3
bounds:3004,2,29,24
H2_mouth_bottom_2
bounds:2479,156,31,23
rotate:90
H2_mouth_top_1
bounds:1220,2,35,25
H2_neck_1
bounds:2675,2,71,112
rotate:90
H2_nose
bounds:2160,5,40,55
rotate:90
H2_protein_L_2
bounds:3091,592,27,23
rotate:90
H2_protein_R_4
bounds:2708,724,24,20
H2_pupil_L_1
bounds:1163,644,14,16
H2_pupil_R_3
bounds:2941,885,13,15
rotate:90
H2_robe
bounds:571,5,257,100
H2_robe_S
bounds:3173,207,76,61
rotate:90
H2_sleeve_L_1
bounds:1473,1609,68,41
H2_sleeve_R_2
bounds:3101,339,81,60
rotate:90
J_newspaper1_l
bounds:2226,734,157,220
rotate:90
J_newspaper2_r
bounds:1663,1144,149,211
rotate:90
K_newspaper1_l
bounds:1377,1110,188,284
rotate:90
K_newspaper2_r
bounds:1468,14,187,280
rotate:90
M1_BG
bounds:1996,47,219,226
rotate:90
M1_backpack
bounds:2796,88,78,125
M1_backpackHandle
bounds:2772,301,92,126
M1_body
bounds:2675,75,138,119
rotate:90
M1_effect_00604
bounds:2993,1191,241,231
M1_effect_00605
bounds:2993,958,241,231
M1_effect_00606
bounds:1794,911,241,231
M1_effect_00607
bounds:2455,883,241,231
M1_effect_00608
bounds:2698,885,241,231
M1_effect_00609
bounds:2993,725,241,231
M1_effect_00610
bounds:1959,511,241,231
M1_effect_00611
bounds:1996,268,241,231
rotate:90
M1_hand_L_1
bounds:3116,655,68,118
rotate:90
M1_hand_L_2
bounds:3180,431,54,78
M1_hand_L_3
bounds:475,2,51,65
rotate:90
M1_hand_L_S
bounds:2894,703,69,97
rotate:90
M1_hand_R_1
bounds:3005,232,53,97
rotate:90
M1_hand_R_2
bounds:2876,122,55,101
offsets:0,0,55,103
M1_hand_R_3
bounds:3174,285,85,60
rotate:90
M1_head
bounds:2548,26,125,161
offsets:0,0,126,161
M1_visor
bounds:2944,359,82,106
offsets:0,2,82,108
M2_detail_1
bounds:3155,107,44,48
M2_detail_2
bounds:2884,774,107,109
M2_detail_3
bounds:2037,905,208,257
rotate:90
M3_can
bounds:819,1100,207,270
offsets:0,0,215,270
rotate:90
M3_handle
bounds:2948,27,50,37
rotate:90
M3_pict
bounds:2775,621,123,117
rotate:90
M3_pict_rays
bounds:2439,332,180,203
Q_newspaper1_l
bounds:2448,724,157,220
rotate:90
Q_newspaper2_r
bounds:2229,156,149,211
rotate:90
X2
bounds:2866,332,111,76
offsets:0,1,111,77
rotate:90
X200
bounds:2621,356,206,71
rotate:90
bg
bounds:2,369,212,838
chick_S
bounds:334,7,139,46
chick_beak
bounds:216,380,36,23
rotate:90
chick_beard
bounds:2911,445,20,30
rotate:90
chick_body
bounds:2789,2,93,71
chick_brows
bounds:3127,1433,44,18
chick_eyelids
bounds:3163,382,38,15
rotate:90
chick_eyes
bounds:2504,158,37,29
chick_hair
bounds:3073,177,66,53
offsets:0,1,66,54
chick_leg_l
bounds:3204,517,28,30
rotate:90
chick_leg_r
bounds:2670,724,36,20
chick_neck
bounds:2933,138,58,85
chick_puppil
bounds:3141,180,28,12
chick_tail
bounds:1425,9,39,73
offsets:0,0,39,74
chick_wing_l
bounds:3106,72,47,61
chick_wing_r
bounds:3106,17,49,53
cow_S
bounds:1158,29,218,53
cow_bell
bounds:3141,194,28,36
cow_body
bounds:2670,746,135,134
offsets:0,0,140,134
rotate:90
cow_collar
bounds:1543,1609,65,41
cow_ear_l
bounds:3204,547,43,30
rotate:90
cow_ear_r
bounds:1852,2,52,32
cow_eye
bounds:164,2,34,18
rotate:90
cow_eyelids
bounds:1143,2,37,25
cow_foot_back_l
bounds:2863,225,57,90
offsets:0,1,57,91
rotate:90
cow_foot_back_r
bounds:2948,2,23,54
rotate:90
cow_foot_front_l
bounds:2955,253,48,104
cow_foot_front_r
bounds:2728,357,35,50
cow_hair
bounds:2037,1115,42,27
cow_hay
bounds:3011,1428,114,23
cow_head
bounds:1852,36,74,139
cow_mouth
bounds:2876,76,62,44
cow_mouth_1
bounds:2633,189,37,24
cow_pupil
bounds:3225,179,26,9
rotate:90
cow_tail
bounds:2940,79,57,48
rotate:90
cow_udder
bounds:3055,67,66,49
rotate:90
effect_shine
bounds:489,1702,352,352
eyelid_l_top2
bounds:1959,488,35,21
eyelid_r_top2
bounds:819,1353,26,19
rotate:90
frame
bounds:2,1209,219,845
glow2
bounds:2418,537,185,200
rotate:90
hand_l_2
bounds:2806,756,76,127
hand_r_2
bounds:1928,42,57,130
offsets:0,1,57,131
m3_l
bounds:1378,2,45,80
m3_r
bounds:3005,287,53,93
rotate:90
pig_S
bounds:2941,900,216,50
rotate:90
pig_body
bounds:1794,799,175,110
pig_brows
bounds:2548,6,45,18
pig_ear_l
bounds:3068,135,49,38
pig_ear_r
bounds:1663,1110,59,32
pig_eyelids
bounds:1649,1615,35,17
rotate:90
pig_eyes
bounds:819,1063,45,35
pig_head
bounds:3004,621,110,102
pig_leg_back_l
bounds:2442,156,31,35
rotate:90
pig_leg_back_r
bounds:3116,592,61,117
rotate:90
pig_leg_front_l
bounds:2987,28,42,66
rotate:90
pig_leg_front_r
bounds:2884,10,62,64
pig_mouth
bounds:542,3,50,26
rotate:90
pig_nose
bounds:3157,60,44,45
pig_puppil
bounds:1750,16,31,16
pig_ribbon
bounds:3091,511,111,79
pig_tail
bounds:108,3,33,54
rotate:90
plate_bg
bounds:546,418,310,316
offsets:0,0,311,318
rotate:90
plate_plate
bounds:2,38,329,237
offsets:0,0,329,243
rotate:90
plate_txt
bounds:1473,1652,198,81
ray
bounds:223,1320,264,734
scroll
bounds:241,55,328,50
ship
bounds:1971,744,253,159
smoke
bounds:1673,1630,95,103
wild_ring_1
bounds:867,662,310,125
wild_ring_10
bounds:1159,1609,312,125
wild_ring_11
bounds:843,1611,314,123
wild_ring_12
bounds:1775,1295,312,127
wild_ring_2
bounds:1777,489,308,97
rotate:90
wild_ring_3
bounds:1864,177,309,76
rotate:90
wild_ring_4
bounds:1942,174,312,52
rotate:90
wild_ring_5
bounds:3193,1741,313,28
rotate:90
wild_ring_6
bounds:830,7,311,57
wild_ring_7
bounds:1876,488,309,81
rotate:90
wild_ring_8
bounds:1761,180,307,101
rotate:90
wild_ring_9
bounds:1158,84,308,117
wild_rope
bounds:3171,157,52,48

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

View File

@@ -0,0 +1,279 @@
assembly.webp
size:2399,2390
filter:Linear,Linear
Abduction_15
bounds:1404,1545,211,191
offsets:1,2,214,195
Abduction_ABDUCTION_txt
bounds:2,543,587,237
Abduction_ALIEN_txt
bounds:716,1773,301,161
Abduction_FREE SPINS_txt
bounds:1399,1290,668,199
Abduction_bg_Llight
bounds:1439,577,247,417
rotate:90
Abduction_bg_Lplate
bounds:281,1645,273,180
Abduction_bg_Mlight
bounds:1304,327,248,473
rotate:90
Abduction_bg_Mplate
bounds:943,1157,335,203
Abduction_bg_Rlight
bounds:677,698,239,445
rotate:90
Abduction_bg_Rplate
bounds:2,1677,277,167
Abduction_chick
bounds:1481,1917,151,205
rotate:90
Abduction_cow
bounds:2,784,238,291
rotate:90
Abduction_pig
bounds:2069,1196,234,230
HANGOVER_txt
bounds:1280,1078,513,210
Hangover_DiscoBall_1_sequins
bounds:2,1459,237,216
Hangover_DiscoBall_2_sequins
bounds:1795,1054,234,236
rotate:90
Hangover_DiscoBall_3_sequins
bounds:2088,1428,239,219
Hangover_X2
bounds:458,2316,85,60
Hangover_X20
bounds:847,2300,124,61
Hangover_X200
bounds:304,2319,152,57
Hangover_ball_1
bounds:2,2,284,342
rotate:90
Hangover_ball_2
bounds:2070,188,277,323
rotate:90
PRESS ANYWHERE TO CONTINUE
bounds:719,2363,471,25
bg_hangar
bounds:1162,855,400,221
offsets:1,2,402,224
bg_paper_angle
bounds:952,2084,250,115
bg_paper_angle_S
bounds:1054,2201,192,88
bg_pict_S
bounds:2103,1649,179,258
rotate:90
bg_sattelite
bounds:774,939,386,216
offsets:1,1,432,218
bubble1
bounds:1192,2363,25,25
bubbles3
bounds:1725,2344,44,44
buildings_dark
bounds:2,1236,939,160
offsets:1,3,940,163
carS_all
bounds:584,2369,133,19
car_all
bounds:2,2143,297,104
car_wheel_front1_all
bounds:2,2323,53,53
car_wheel_front2_all
bounds:2,2323,53,53
glow2
bounds:241,1417,226,243
rotate:90
gold_star
bounds:2285,2137,112,123
hangar_alien_abd
bounds:382,1019,390,215
offsets:2,1,393,217
left_leg_light
bounds:1564,826,226,389
offsets:22,29,256,421
rotate:90
logo
bounds:437,2121,513,107
max_win_MAX_txt_A
bounds:1970,2021,133,180
rotate:90
max_win_MAX_txt_M
bounds:1924,1860,159,180
rotate:90
max_win_MAX_txt_X
bounds:2,1999,142,180
rotate:90
max_win_WIN_txt
bounds:1325,2072,264,115
max_win_plate_light
bounds:1253,1756,180,173
monitor_screen_hangover
bounds:1877,1668,224,190
offsets:4,6,233,199
num_bg_all
bounds:902,86,778,239
particle
bounds:871,1936,146,155
rotate:90
placeholder
bounds:1932,2169,100,98
offsets:0,2,100,100
plate
bounds:1858,467,350,247
plate_S
bounds:1248,2193,166,94
progressbar_blue2
bounds:973,2300,61,149
offsets:1,2,64,152
rotate:90
progressbar_flask1_L
bounds:2093,2339,49,135
rotate:90
progressbar_flask2_L
bounds:2230,2339,49,124
rotate:90
progressbar_flask3_L
bounds:1771,2340,48,135
rotate:90
progressbar_flasks1
bounds:2236,2262,158,75
progressbar_flasks2
bounds:1666,1707,181,209
rotate:90
progressbar_green1
bounds:696,2301,60,149
offsets:2,1,63,152
rotate:90
progressbar_pink3
bounds:545,2308,59,149
offsets:1,1,61,152
rotate:90
progressbar_top
bounds:445,2234,203,72
progressbar_wall1
bounds:1955,716,248,240
progressbar_wall2
bounds:1491,2272,71,218
rotate:90
progressbar_wall3
bounds:1435,1738,177,229
rotate:90
progressbar_wire1
bounds:184,1998,235,142
progressbar_wire1_S
bounds:2152,1991,235,144
progressbar_wire2
bounds:1173,1564,229,190
progressbar_wire2_S
bounds:1019,1765,232,164
progressbar_wire3
bounds:1124,2291,70,93
rotate:90
progressbar_wire4
bounds:1393,2289,68,96
offsets:1,0,69,96
rotate:90
rays2_all
bounds:591,449,387,247
red_star
bounds:2034,2167,100,113
rotate:90
right_leg_light
bounds:295,782,235,380
offsets:5,13,281,401
rotate:90
sandL_all
bounds:57,2322,245,54
sandR_all
bounds:1908,2340,183,48
sattelite_dark
bounds:2,1024,378,210
offsets:0,0,419,210
ship
bounds:980,355,251,322
offsets:1,2,254,326
rotate:90
shipS
bounds:1028,1931,451,139
offsets:19,35,491,176
shipS_alien_abd
bounds:421,1983,448,136
offsets:16,52,478,190
shipSand
bounds:556,1595,615,168
offsets:2,12,618,184
shipSand_alien_abd
bounds:1617,1491,469,175
offsets:2,7,472,186
ship_alien_abd
bounds:1124,608,245,313
offsets:1,2,248,316
rotate:90
ship_all
bounds:1052,1362,345,200
ship_ray_1
bounds:1571,2345,152,43
offsets:9,3,171,49
ship_ray_2
bounds:2033,958,282,236
offsets:0,1,282,242
ship_ray_3
bounds:1219,2359,306,29
offsets:11,3,321,34
sign_danger_dark
bounds:2149,2156,104,134
rotate:90
sign_danger_light
bounds:301,2142,105,134
rotate:90
sign_warning_dark
bounds:1688,1890,234,155
offsets:0,0,234,161
sign_warning_leg_d
bounds:2,1846,150,351
offsets:57,16,235,367
rotate:90
sign_warning_leg_l
bounds:355,1827,154,359
offsets:58,11,240,370
rotate:90
sign_warning_light
bounds:2106,1830,240,159
offsets:0,0,240,164
sing_triangle_dark
bounds:1204,2076,119,115
sing_triangle_light
bounds:1591,2070,121,117
sing_yellow_dark
bounds:1714,2068,119,126
offsets:0,0,120,126
rotate:90
sing_yellow_light
bounds:1842,2047,120,126
rotate:90
spark
bounds:1527,2346,42,42
text1_smw
bounds:486,1398,564,195
text2_smw
bounds:2,288,448,253
text_add
bounds:1711,2269,523,68
text_bw
bounds:1779,212,289,253
text_fs
bounds:1416,2189,514,78
text_fs3
bounds:2,2249,441,68
text_left
bounds:1219,2291,172,66
text_mw
bounds:452,194,448,253
text_yw
bounds:650,2230,402,68
win_line
bounds:2,2378,580,10

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

View File

@@ -0,0 +1,73 @@
{
"width": 317,
"height": 320,
"reels": 6,
"slots": 6,
"symbolsKeys": ["S1", "S2", "S3", "S4", "S5", "S6", "RR"],
"wilds": [],
"scatters": [],
"symbols": {
"S1": {
"key": "S1",
"spine": "symbols",
"skin": "ORANGE"
},
"S2": {
"key": "S2",
"spine": "symbols",
"skin": "YELLOW"
},
"S3": {
"key": "S3",
"spine": "symbols",
"skin": "GREEN"
},
"S4": {
"key": "S4",
"spine": "symbols",
"skin": "BLUE"
},
"S5": {
"key": "S5",
"spine": "symbols",
"skin": "VIOLET"
},
"S6": {
"key": "S6",
"spine": "symbols",
"skin": "ROSE"
},
"SC1": {
"spine": "symbols",
"skin": "WHITE_PAPER"
},
"W0": {
"spine": "symbols",
"skin": "COLOR_WILD"
},
"W1": {
"spine": "symbols",
"skin": "SQUARE_WILD"
},
"RR": {
"key": "RR",
"spine": "symbols",
"skin": "RAINBOW"
},
"ADD": {
"spine": "symbols",
"skin": "X_PLUS"
},
"BL": {
"spine": "symbols",
"skin": "COLOR_WILD"
},
"MUL": {
"spine": "symbols",
"skin": "X_MULTIPLIER"
},
"DE": {
"spine": "symbols"
}
}
}

View File

@@ -0,0 +1,25 @@
{
"bundles": [
{
"name": "configs",
"assets": [
{
"name": "slot_machine_config",
"srcs": "bubblex/configs/slotMachine.json"
}
]
},
{
"name": "symbols",
"assets": [
{
"name": "symbols",
"srcs": "bubblex/symbols/symbols.skel",
"data": {
"spineSkeletonScale": 0.31
}
}
]
}
]
}

View File

@@ -0,0 +1,335 @@
symbols.webp
size:2017,751
filter:Linear,Linear
Black_ball_1
bounds:1292,147,65,66
offsets:3,2,71,70
Black_ball_2
bounds:1119,27,83,84
offsets:4,3,91,90
Black_ball_3
bounds:108,2,103,104
offsets:3,2,109,108
Black_ball_4
bounds:1238,440,129,131
offsets:2,2,135,138
Black_base
bounds:815,573,176,176
offsets:1,1,178,178
Blue_ball_1
bounds:1359,147,65,66
offsets:3,2,71,70
Blue_ball_2
bounds:859,11,85,85
offsets:3,2,91,90
Blue_ball_3
bounds:213,2,103,104
offsets:3,2,109,108
Blue_ball_4
bounds:695,436,132,135
offsets:2,2,135,138
Blue_base
bounds:636,573,177,176
offsets:1,1,179,178
Green_ball_1
bounds:1426,152,65,66
offsets:3,2,71,70
Green_ball_2
bounds:946,11,85,84
offsets:3,3,91,90
rotate:90
Green_ball_3
bounds:439,2,103,104
offsets:3,2,109,108
Green_ball_4
bounds:829,437,133,134
offsets:2,2,136,137
Green_base
bounds:993,573,176,176
offsets:1,1,178,178
Grey_ball_1
bounds:1493,154,65,66
offsets:2,2,71,70
Grey_ball_2
bounds:1838,171,83,84
offsets:3,3,91,90
Grey_ball_3
bounds:544,2,103,104
offsets:2,2,109,108
Grey_ball_4
bounds:1369,440,129,131
offsets:2,2,135,138
Grey_base
bounds:1171,573,176,176
offsets:1,1,178,178
Orange_ball_1
bounds:1560,154,65,66
offsets:3,2,71,70
Orange_ball_2
bounds:1032,27,85,84
offsets:3,3,91,90
Orange_ball_3
bounds:649,2,103,104
offsets:3,2,109,108
Orange_ball_4
bounds:381,237,149,153
offsets:1,2,150,155
Orange_base
bounds:1699,579,169,170
offsets:1,1,171,172
Rose_ball_1
bounds:1292,80,65,66
offsets:3,2,71,70
rotate:90
Rose_ball_2
bounds:1923,181,83,84
offsets:4,3,91,90
Rose_ball_3
bounds:1762,473,103,104
offsets:3,2,109,108
Rose_ball_4
bounds:1103,440,131,133
offsets:2,2,134,136
rotate:90
Rose_base
bounds:431,392,169,169
offsets:1,1,171,172
Violet_ball_1
bounds:1360,79,65,66
offsets:3,2,71,70
Violet_ball_2
bounds:1751,86,83,84
offsets:4,3,91,90
rotate:90
Violet_ball_3
bounds:1762,367,103,104
offsets:3,2,109,108
Violet_ball_4
bounds:1500,440,129,131
offsets:2,2,131,134
Violet_base
bounds:1527,579,170,170
offsets:1,1,172,172
White_ball_1
bounds:1427,84,65,66
offsets:3,2,71,70
White_ball_2
bounds:1837,86,83,84
offsets:4,3,91,90
rotate:90
White_ball_3
bounds:754,2,103,104
offsets:3,2,109,108
White_ball_4
bounds:1631,446,129,131
offsets:1,2,131,133
White_base
bounds:1349,573,176,176
offsets:1,1,178,178
Yellow_ball_1
bounds:1494,86,65,66
offsets:3,2,71,70
Yellow_ball_2
bounds:1923,95,83,84
offsets:4,3,91,90
Yellow_ball_3
bounds:1050,211,103,104
offsets:3,2,109,108
Yellow_ball_4
bounds:1430,322,115,116
Yellow_base
bounds:259,392,169,170
offsets:1,1,171,172
rotate:90
blank_circle
bounds:532,242,148,148
offsets:1,1,150,150
blank_middle
bounds:951,317,118,118
offsets:1,1,120,120
effect/effect_00001
bounds:1813,36,48,48
offsets:51,52,150,150
effect/effect_00002
bounds:1148,139,70,70
offsets:40,41,150,150
effect/effect_00003
bounds:930,98,86,86
offsets:32,33,150,150
effect/effect_00004
bounds:1467,222,98,98
offsets:26,27,150,150
effect/effect_00005
bounds:1365,220,100,100
offsets:25,26,150,150
effect/effect_00006
bounds:1050,113,96,96
offsets:27,28,150,150
effect/effect_00007
bounds:2,2,104,104
offsets:23,24,150,150
effect/effect_00008
bounds:1757,257,108,108
offsets:21,22,150,150
effect/effect_00009
bounds:1547,324,114,114
offsets:18,19,150,150
effect/effect_00010
bounds:1071,320,118,118
offsets:16,17,150,150
effect/effect_00011
bounds:829,315,120,120
offsets:15,16,150,150
effect/effect_00012
bounds:685,189,124,124
offsets:13,14,150,150
effect/effect_00013
bounds:410,109,129,126
offsets:12,13,150,150
effect/effect_00014
bounds:1867,389,132,128
offsets:11,12,150,150
effect/effect_00015
bounds:964,441,137,130
offsets:10,11,150,150
effect/effect_00016
bounds:541,108,142,132
offsets:8,10,150,150
effect/effect_00017
bounds:1870,519,144,132
offsets:6,10,150,150
effect/effect_00018
bounds:1870,653,145,96
offsets:5,27,150,150
effect/effect_00019
bounds:602,415,146,91
offsets:4,30,150,150
rotate:90
rainbow_1
bounds:1427,17,65,65
offsets:1,1,67,67
rainbow_10
bounds:869,127,59,58
offsets:1,1,61,60
rainbow_2
bounds:1220,78,62,62
offsets:1,1,64,64
rainbow_3
bounds:1561,86,65,66
offsets:1,1,67,68
rainbow_4
bounds:1268,18,58,58
offsets:1,1,60,60
rainbow_5
bounds:1628,83,65,66
offsets:1,1,67,68
rotate:90
rainbow_6
bounds:1628,17,64,64
offsets:1,1,66,66
rainbow_7
bounds:1220,142,70,70
offsets:1,1,72,72
rainbow_8
bounds:1696,26,58,58
offsets:1,1,60,60
rainbow_9
bounds:1328,18,58,59
offsets:1,1,60,61
rainbow_S
bounds:2,108,183,189
offsets:1,1,185,191
rainbow_ballS
bounds:1567,224,98,95
offsets:4,2,105,99
rotate:90
symbol_glow_color
bounds:2,494,255,255
symbol_glow_white
bounds:210,221,169,169
symbol_shine/eff_00000
bounds:210,467,20,25
offsets:29,130,203,188
symbol_shine/eff_00001
bounds:1696,86,53,62
offsets:21,106,203,188
symbol_shine/eff_00002
bounds:785,115,70,82
offsets:20,90,203,188
rotate:90
symbol_shine/eff_00003
bounds:1664,236,85,98
offsets:20,76,203,188
symbol_shine/eff_00004
bounds:310,109,98,110
offsets:20,65,203,188
symbol_shine/eff_00005
bounds:187,108,111,121
offsets:20,54,203,188
rotate:90
symbol_shine/eff_00006
bounds:811,187,119,126
offsets:25,44,203,188
symbol_shine/eff_00007
bounds:1867,267,120,128
offsets:37,34,203,188
rotate:90
symbol_shine/eff_00008
bounds:695,315,119,128
offsets:49,25,203,188
rotate:90
symbol_shine/eff_00009
bounds:932,186,116,127
offsets:62,17,203,188
symbol_shine/eff_00010
bounds:318,2,105,119
offsets:75,15,203,188
rotate:90
symbol_shine/eff_00011
bounds:1663,336,92,108
offsets:88,15,203,188
symbol_shine/eff_00012
bounds:685,108,79,98
offsets:101,15,203,188
rotate:90
symbol_shine/eff_00013
bounds:1923,28,65,83
offsets:115,17,203,188
rotate:90
symbol_shine/eff_00014
bounds:1204,27,49,62
offsets:130,22,203,188
rotate:90
symbol_shine_spot
bounds:1756,29,55,55
offsets:0,0,55,56
wildColor_ball_1
bounds:1494,18,65,66
offsets:3,2,71,70
wildColor_ball_2
bounds:1751,171,85,84
offsets:3,3,91,90
wildColor_ball_3
bounds:1155,214,103,104
offsets:3,2,109,108
wildColor_ball_4
bounds:1191,321,118,117
wildSquare_ball_1
bounds:1561,18,65,66
offsets:3,2,71,70
wildSquare_ball_2
bounds:1664,150,85,84
offsets:3,3,91,90
wildSquare_ball_3
bounds:1260,215,103,104
offsets:3,2,109,108
wildSquare_ball_4
bounds:1311,322,117,116
wild_base
bounds:448,563,186,186
offsets:1,1,188,188
wild_base_Square
bounds:259,563,187,186
wild_ray
bounds:2,299,206,193
offsets:4,7,217,206

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 405 KiB

View File

@@ -0,0 +1,19 @@
anticipation.webp
size:2048,1024
filter:Linear,Linear
scale:0.75
anticip_bg
bounds:777,30,220,836
offsets:319,27,858,879
anticip_circle_whole_source_1.4
bounds:999,463,404,403
offsets:429,445,858,879
anticip_frame
bounds:291,27,262,839
offsets:297,20,858,879
anticip_frame_glow
bounds:2,2,287,864
offsets:285,7,858,879
anticip_glow
bounds:555,29,220,837
offsets:319,27,858,879

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 KiB

View File

@@ -0,0 +1,16 @@
info face="multiplier_value_font" size=90 bold=0 italic=0 charset="" unicode=1 stretchH=100 smooth=1 aa=1 padding=1,1,1,1 spacing=1,1
common lineHeight=104 base=83 scaleW=218 scaleH=218 pages=1 packed=0
page id=0 file="multiplier_value_font.png"
chars count=12
char id=32 x=0 y=0 width=0 height=0 xoffset=0 yoffset=0 xadvance=26 page=0 chnl=15
char id=48 x=167 y=0 width=51 height=71 xoffset=-1 yoffset=-1 xadvance=49 page=0 chnl=15
char id=49 x=0 y=74 width=42 height=72 xoffset=-1 yoffset=-1 xadvance=41 page=0 chnl=15
char id=50 x=0 y=147 width=52 height=71 xoffset=-1 yoffset=-1 xadvance=50 page=0 chnl=15
char id=51 x=53 y=147 width=42 height=71 xoffset=0 yoffset=0 xadvance=41 page=0 chnl=15
char id=52 x=143 y=147 width=54 height=69 xoffset=-1 yoffset=-1 xadvance=52 page=0 chnl=15
char id=53 x=96 y=147 width=46 height=71 xoffset=-1 yoffset=-1 xadvance=44 page=0 chnl=15
char id=54 x=0 y=0 width=56 height=73 xoffset=-1 yoffset=-1 xadvance=54 page=0 chnl=15
char id=55 x=43 y=74 width=50 height=69 xoffset=-1 yoffset=-1 xadvance=48 page=0 chnl=15
char id=56 x=57 y=0 width=52 height=73 xoffset=-1 yoffset=-1 xadvance=50 page=0 chnl=15
char id=57 x=110 y=0 width=56 height=73 xoffset=-1 yoffset=-1 xadvance=54 page=0 chnl=15
char id=88 x=94 y=74 width=55 height=63 xoffset=-1 yoffset=8 xadvance=54 page=0 chnl=15

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

View File

@@ -0,0 +1,16 @@
info face="wild_multiplier_font" size=90 bold=0 italic=0 charset="" unicode=1 stretchH=100 smooth=1 aa=1 padding=1,1,1,1 spacing=1,1
common lineHeight=104 base=83 scaleW=209 scaleH=218 pages=1 packed=0
page id=0 file="wild_multiplier_font.png"
chars count=12
char id=32 x=0 y=0 width=0 height=0 xoffset=0 yoffset=0 xadvance=26 page=0 chnl=15
char id=48 x=0 y=0 width=53 height=73 xoffset=-1 yoffset=-1 xadvance=51 page=0 chnl=15
char id=49 x=166 y=0 width=43 height=72 xoffset=-1 yoffset=-1 xadvance=41 page=0 chnl=15
char id=50 x=150 y=74 width=53 height=71 xoffset=-1 yoffset=-1 xadvance=51 page=0 chnl=15
char id=51 x=58 y=74 width=43 height=72 xoffset=-1 yoffset=-1 xadvance=41 page=0 chnl=15
char id=52 x=0 y=148 width=55 height=70 xoffset=-1 yoffset=-1 xadvance=53 page=0 chnl=15
char id=53 x=102 y=74 width=47 height=72 xoffset=-1 yoffset=-1 xadvance=45 page=0 chnl=15
char id=54 x=54 y=0 width=57 height=73 xoffset=-1 yoffset=-1 xadvance=55 page=0 chnl=15
char id=55 x=56 y=148 width=51 height=70 xoffset=-1 yoffset=-1 xadvance=49 page=0 chnl=15
char id=56 x=112 y=0 width=53 height=73 xoffset=-1 yoffset=-1 xadvance=51 page=0 chnl=15
char id=57 x=0 y=74 width=57 height=73 xoffset=-1 yoffset=-1 xadvance=55 page=0 chnl=15
char id=88 x=108 y=148 width=56 height=64 xoffset=-1 yoffset=8 xadvance=55 page=0 chnl=15

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,126 @@
{
"width": 550,
"height": 420,
"reels": 5,
"slots": 4,
"paddings": {
"left": 16,
"top": 0,
"right": 18,
"bottom": 0
},
"symbolsKeys": ["L", "J", "I", "H", "G", "F", "E", "D", "C", "B", "A", "K", "M", "N"],
"symbolsWeights": {
"L": 0,
"J": 0,
"I": 0,
"H": 0,
"G": 0,
"F": 0,
"E": 0,
"D": 0,
"C": 0,
"B": 0,
"K": 0.5,
"M": 0,
"N": 0
},
"symbols": {
"A": {
"key": "A",
"spine": "lowest_symbols",
"skin": "Ten",
"zIndex": 1
},
"B": {
"key": "B",
"spine": "lowest_symbols",
"skin": "J",
"zIndex": 1
},
"C": {
"key": "C",
"spine": "lowest_symbols",
"skin": "Q",
"zIndex": 1
},
"D": {
"key": "D",
"spine": "low_symbols",
"skin": "K",
"zIndex": 1
},
"E": {
"key": "E",
"spine": "low_symbols",
"skin": "A",
"zIndex": 1
},
"F": {
"key": "F",
"spine": "h5",
"skin": "default",
"zIndex": 1
},
"G": {
"key": "G",
"spine": "h4",
"skin": "default",
"zIndex": 1
},
"H": {
"key": "H",
"spine": "h3",
"skin": "default",
"zIndex": 1
},
"I": {
"key": "I",
"spine": "h2",
"skin": "default",
"zIndex": 1
},
"J": {
"key": "J",
"spine": "h1",
"skin": "default",
"zIndex": 1
},
"K": {
"key": "K",
"spine": "scatter",
"skin": "default",
"zIndex": 2
},
"L": {
"key": "L",
"spine": "wild",
"skin": "low",
"fontStyle": {
"fontSize": 30,
"fontName": "wild_multiplier_font"
},
"zIndex": 3
},
"M": {
"key": "M",
"spine": "wild",
"skin": "med",
"fontStyle": {
"fontSize": 30,
"fontName": "wild_multiplier_font"
},
"zIndex": 3
},
"N": {
"key": "N",
"spine": "wild",
"skin": "high",
"fontStyle": {
"fontSize": 30,
"fontName": "wild_multiplier_font"
},
"zIndex": 3
}
}
}

View File

@@ -0,0 +1,158 @@
{
"bundles": [
{
"name": "configs",
"assets": [
{
"name": "slot_machine_config",
"srcs": "hold-and-win/configs/slotMachine.json"
}
]
},
{
"name":"symbols",
"assets":[
{
"name": "wild_multiplier_font",
"srcs": "hold-and-win/bitmapFonts/wild_multiplier_font.fnt"
},
{
"name": "symbols_atlas",
"srcs": "hold-and-win/symbols/symbols.atlas"
},
{
"name": "wild",
"srcs": "hold-and-win/symbols/wild.skel",
"data": {
"spineSkeletonScale": 0.35,
"spineAtlasAlias": "symbols_atlas",
"spineAtlasFile": "hold-and-win/symbols/symbols.atlas"
}
},
{
"name": "scatter",
"srcs": "hold-and-win/symbols/scatter.skel",
"data": {
"spineSkeletonScale": 0.5,
"spineAtlasAlias": "symbols_atlas",
"spineAtlasFile": "hold-and-win/symbols/symbols.atlas"
}
},
{
"name": "lowest_symbols",
"srcs": "hold-and-win/symbols/L_10_q_j.skel",
"data": {
"spineSkeletonScale": 0.35,
"spineAtlasAlias": "symbols_atlas",
"spineAtlasFile": "hold-and-win/symbols/symbols.atlas"
}
},
{
"name": "low_symbols",
"srcs": "hold-and-win/symbols/L_A_K.skel",
"data": {
"spineSkeletonScale": 0.35,
"spineAtlasAlias": "symbols_atlas",
"spineAtlasFile": "hold-and-win/symbols/symbols.atlas"
}
},
{
"name": "h5",
"srcs": "hold-and-win/symbols/M3.skel",
"data": {
"spineSkeletonScale": 0.45,
"spineAtlasAlias": "symbols_atlas",
"spineAtlasFile": "hold-and-win/symbols/symbols.atlas"
}
},
{
"name": "h4",
"srcs": "hold-and-win/symbols/M2.skel",
"data": {
"spineSkeletonScale": 0.4,
"spineAtlasAlias": "symbols_atlas",
"spineAtlasFile": "hold-and-win/symbols/symbols.atlas"
}
},
{
"name": "h3",
"srcs": "hold-and-win/symbols/M1.skel",
"data": {
"spineSkeletonScale": 0.35,
"spineAtlasAlias": "symbols_atlas",
"spineAtlasFile": "hold-and-win/symbols/symbols.atlas"
}
},
{
"name": "h2",
"srcs": "hold-and-win/symbols/H2.skel",
"data": {
"spineSkeletonScale": 0.5,
"spineAtlasAlias": "symbols_atlas",
"spineAtlasFile": "hold-and-win/symbols/symbols.atlas"
}
},
{
"name": "h1",
"srcs": "hold-and-win/symbols/H1.skel",
"data": {
"spineSkeletonScale": 0.5,
"spineAtlasAlias": "symbols_atlas",
"spineAtlasFile": "hold-and-win/symbols/symbols.atlas"
}
},
{
"name": "expanding",
"srcs": "hold-and-win/symbols/expanding_multiplier.skel",
"data": {
"spineSkeletonScale": 0.5,
"spineAtlasAlias": "symbols_atlas",
"spineAtlasFile": "hold-and-win/symbols/symbols.atlas"
}
},
{
"name": "win_line",
"srcs": "hold-and-win/winline/win_line.skel",
"data": {
"spineAtlasAlias": "main_atlas",
"spineAtlasFile": "hold-and-win/winline/assembly.atlas"
}
},
{
"name": "win_frame",
"srcs": "hold-and-win/textures/win_frame.png"
},
{
"name": "anticipation_back",
"srcs": "hold-and-win/anticipation/anticipation_back.skel",
"data": {
"spineSkeletonScale": 0.4,
"spineAtlasAlias": "anticipation",
"spineAtlasFile": "hold-and-win/anticipation/anticipation.atlas"
}
},
{
"name": "anticipation_front",
"srcs": "hold-and-win/anticipation/anticipation_front.skel",
"data": {
"spineSkeletonScale": 0.4,
"spineAtlasAlias": "anticipation",
"spineAtlasFile": "hold-and-win/anticipation/anticipation.atlas"
}
}
]
},
{
"name": "addons",
"assets": [
{
"name": "slot_multiplier",
"srcs": "hold-and-win/slot-multiplier/slot_multiplier.skel",
"data": {
"spineSkeletonScale": 0.4
}
}
]
}
]
}

View File

@@ -0,0 +1,8 @@
slot_multiplier.webp
size:392,280
filter:Linear,Linear
imgs/frame
bounds:2,2,284,276
imgs/placeholder
bounds:288,178,100,100
offsets:1,1,102,102

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More