Compare commits

..

10 Commits
exe ... master

Author SHA1 Message Date
05cbd9daa9 [add] rs 還要調整 2024-08-30 18:17:09 +08:00
f8528713e6 [add] 動態讀json 2024-08-30 15:54:25 +08:00
84bba89e7e [add] 設定env 2024-08-30 15:37:33 +08:00
acd1cb7337 [add] slot系列API Class化 2024-08-30 11:20:52 +08:00
735a0a6bb3 [mod] 先優化為Class 2024-08-29 18:00:41 +08:00
e741029939 [add] 自己計算get 2024-08-29 17:35:40 +08:00
04789455a3 [add] Slot1 FreeGame 2024-08-26 17:22:51 +08:00
a22640bd2b [mod] 拔掉別名 2024-08-26 17:17:41 +08:00
1d0ad3fd99 [add] slot1 2024-08-26 16:44:51 +08:00
cad4bb3b56 [add] 畫面調整 2024-08-25 22:34:21 +08:00
140 changed files with 9308 additions and 13 deletions

2
.env Normal file
View File

@ -0,0 +1,2 @@
PORT=9005
NODE_ENV=dev

2
.env.dev Normal file
View File

@ -0,0 +1,2 @@
PORT=9005
NODE_ENV=dev

2
.env.prod Normal file
View File

@ -0,0 +1,2 @@
PORT=9005
NODE_ENV=prod

367
.eslintrc.json Normal file
View File

@ -0,0 +1,367 @@
{
"env": {
"browser": true,
"es2021": true,
"jest": true
},
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"plugin:@typescript-eslint/recommended"
],
"overrides": [],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": [
"react",
"@typescript-eslint",
"react-hooks"
],
"rules": {
"no-alert": 0, //使alert confirm prompt
"no-bitwise": 0, //使
"no-console": "off", //使console
"no-continue": 0, //使continue
"no-debugger": 2, //使debugger
"no-delete-var": 2, //var使delete
"no-div-regex": 1, //使/=foo/
"no-dupe-args": 2, //
"no-duplicate-case": 2, //switchcase
"no-else-return": "off", //ifreturn,else
"no-empty-label": "off", //使label
"no-eq-null": "off", //null使==!=
"no-extend-native": "off", //native
"no-extra-parens": "off", //
"no-extra-semi": 2, //
"no-floating-decimal": 2, //0 .5 3.
"no-implicit-coercion": "off", //
"no-inline-comments": 0, //
"no-invalid-this": 2, //this
"no-iterator": 2, //使__iterator__
"no-lonely-if": "off", //elseif
"no-mixed-requires": [
0,
false
], //
"no-mixed-spaces-and-tabs": "off", //tab
"no-multiple-empty-lines": [
1,
{
"max": 2
}
], //2
"no-nested-ternary": 0, //使
"no-new": "off", //使new
"no-new-require": 2, //使new require
"no-param-reassign": "off", //
"no-path-concat": 0, //node使__dirname__filename
"no-plusplus": 0, //使++--
"no-process-env": 0, //使process.env
"no-process-exit": 0, //使process.exit()
"no-redeclare": "off", //
"no-restricted-modules": 0, //使
"no-return-assign": "off", //return
"no-self-compare": 2, //
"no-sequences": 0, //使
"no-shadow": "off", //
"no-sync": 0, //nodejs
"no-ternary": 0, //使
"no-this-before-super": 0, //super()使thissuper
"no-throw-literal": 2, // throw "error";
"no-undef": "off", //
"no-undef-init": "off", //undefined
"no-undefined": "off", //使undefined
"no-unexpected-multiline": 2, //
"no-underscore-dangle": "off", //_
"no-unneeded-ternary": 2, // var isYes = answer === 1 ? true : false;
"no-unused-expressions": "off", //
"no-unused-vars": "off", //使
"no-use-before-define": "off", //使
"no-useless-call": "off", //callapply
"no-void": "off", //void
"no-var": 0, //varletconst
"no-warning-comments": "off", //
"no-array-constructor": "error", // 使
"no-caller": "error", // 使arguments.callerarguments.callee
"no-catch-shadow": "error", // catch
"no-class-assign": "error", //
"no-cond-assign": [
"error",
"except-parens"
], // 使
"no-constant-condition": "error", // 使 if(true) if(1)
"no-control-regex": "error", // 使
"no-dupe-keys": "error", // {a: 1, a: 1}
"no-empty": "error", //
"no-empty-character-class": "error", // []
"no-eval": "error", // 使eval
"no-ex-assign": "error", // catch
"no-extra-bind": "error", //
"no-extra-boolean-cast": "off", // bool
"no-fallthrough": "error", // switch穿
"no-func-assign": "error", //
"no-implied-eval": "error", // 使eval
"no-inner-declarations": "off", // 使
"no-invalid-regexp": "error", //
"no-irregular-whitespace": "error", //
"no-label-var": "error", // labelvar
"no-labels": "error", //
"no-lone-blocks": "error", //
"no-loop-func": "error", // 使
"no-multi-spaces": "error", //
"no-multi-str": "error", // \
"no-native-reassign": "error", // native
"no-negated-in-lhs": "error", // in !
"no-new-func": "error", // 使new Function
"no-new-object": "error", // 使new Object()
"no-new-wrappers": "error", // 使newnew String new Boolean new Number
"no-obj-calls": "error", // Math() JSON()
"no-octal": "error", // 使(0)
"no-octal-escape": "error", // 使
"no-proto": "error", // 使__proto__(__proto__)
"no-prototype-builtins": "off",
"no-regex-spaces": "error", // 使 /foo bar/
"no-script-url": "off", // 使javascript:void(0)
"no-shadow-restricted-names": "error", // 使
"no-spaced-func": "error", // ()
"no-sparse-arrays": "error", // [1,,2]
"no-trailing-spaces": [
"error",
{
"skipBlankLines": true
}
], // ( )
"no-unreachable": "error", //
"no-const-assign": "error", // const
"no-with": "error", // with
"comma-dangle": "off", //
"comma-spacing": "error", // 西
"curly": [
"error",
"multi-line"
], // 使 {}
"eqeqeq": "off", // 使
"indent": [
"off",
"tab",
{
"SwitchCase": 1
}
], // tab
"key-spacing": [
"error",
{
"beforeColon": false,
"afterColon": true
}
], //
"keyword-spacing": "off", //
"new-parens": "error", // new const person = new Person();
"quotes": [
"error",
"double",
{
"allowTemplateLiterals": true
}
], // ''
"semi": [
"error",
"always"
], //
"semi-spacing": [
0,
{
"before": false,
"after": true
}
], // 西
"space-before-blocks": [
"error",
"always"
], // {
// "space-before-function-paren": ["error", "never"], //
"space-infix-ops": "error", // a + b
"space-unary-ops": [
"error",
{
"words": true,
"nonwords": false
}
], // / new Foo 1++
// "spaced-comment": ["error", "always", { "markers": ["*!"] }], //
"strict": [
"error",
"global"
], // 使
"use-isnan": "error", // 使NaNisNaN()
"arrow-parens": 0, //
"arrow-spacing": 0, //=>/
"accessor-pairs": 0, //使getter/setter
"block-scoped-var": 0, //使var
"brace-style": "off", //
"callback-return": "off", //
"comma-style": [
"error",
"last"
], //
"complexity": [
0,
11
], //
"computed-property-spacing": [
0,
"never"
], //
"consistent-return": 0, //return
"consistent-this": "off", //this
"constructor-super": 0, //supersuper
"default-case": "off", //switchdefault
"dot-location": 0, //访
"dot-notation": [
0,
{
"allowKeywords": true
}
], //
"eol-last": 0, //
"func-names": 0, //
"func-style": [
0,
"declaration"
], //使/
"generator-star-spacing": 0, //*
"guard-for-in": 0, //for inif
"handle-callback-err": 0, //nodejs
"id-length": 0, //
"init-declarations": 0, //
"lines-around-comment": 0, ///
"max-depth": [
0,
4
], //
"max-len": [
0,
80,
4
], //
"max-nested-callbacks": [
0,
2
], //
"max-params": [
0,
3
], //3
"max-statements": [
0,
10
], //
"new-cap": "off", //使newnew
"newline-after-var": "off", //
"object-shorthand": 0, //
"one-var": "off", //
"operator-assignment": [
0,
"always"
], // += -=
"operator-linebreak": "off", //
"padded-blocks": 0, //
"prefer-spread": 0, //
"prefer-reflect": 0, //Reflect
"quote-props": "off", //
"radix": "off", //parseInt
"id-match": 0, //
"sort-vars": 0, //
"space-after-keywords": [
0,
"always"
], //
"space-before-function-paren": [
0,
"always"
], //
"space-in-parens": [
0,
"never"
], //
"space-return-throw-case": "off", //return throw case
"spaced-comment": 0, //
"valid-jsdoc": 0, //jsdoc
"valid-typeof": "error", //使typeof
"vars-on-top": "error", //var
"wrap-iife": [
"error",
"inside"
], //
"wrap-regex": 0, //
"yoda": [
"error",
"never"
], //
"linebreak-style": [
0,
"windows"
], //
"array-bracket-spacing": [
2,
"never"
], //
"react/react-in-jsx-scope": "off",
"camelcase": "off",
"block-spacing": "error",
"no-duplicate-imports": "error",
"require-yield": "off",
"prefer-const": "off",
"object-curly-spacing": [
"error",
"always"
],
"react/jsx-curly-spacing": [
"error",
"never"
],
"@typescript-eslint/no-namespace": "off",
"@typescript-eslint/no-inferrable-types": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/prefer-namespace-keyword": "off",
"@typescript-eslint/ban-types": "off",
"@typescript-eslint/no-this-alias": "off",
"@typescript-eslint/no-empty-interface": "off",
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/explicit-function-return-type": [
"off",
{
"allowExpressions": true
}
],
"@typescript-eslint/typedef": [
"warn",
{
"arrayDestructuring": false,
"arrowParameter": false,
"objectDestructuring": false,
"memberVariableDeclaration": true,
"parameter": true,
"propertyDeclaration": true,
"variableDeclaration": false,
"variableDeclarationIgnoreFunction": true
}
]
},
"settings": {
"import/resolver": {
"typescript": {}
},
"react": {
"version": "detect"
}
}
}

16
.gitignore vendored
View File

@ -1,13 +1,3 @@
/win-unpacked/LICENSE.electron.txt
/win-unpacked/LICENSES.chromium.html
/win-unpacked/chrome_100_percent.pak
/win-unpacked/chrome_200_percent.pak
/win-unpacked/d3dcompiler_47.dll
/win-unpacked/snapshot_blob.bin
/win-unpacked/vk_swiftshader.dll
/win-unpacked/vk_swiftshader_icd.json
/win-unpacked/vulkan-1.dll
/SDServer Setup 1.0.0.exe
/SDServer Setup 1.0.0.exe.blockmap
/builder-debug.yml
/builder-effective-config.yaml
/dist
/node_modules
/release

23
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,23 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Main Process",
"type": "node",
"request": "launch",
"cwd": "${workspaceFolder}",
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron",
"windows": {
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd"
},
"args": ["."],
"env": {
"NODE_ENV": "dev"
},
"outputCapture": "std",
"sourceMaps": true,
"restart": true
}
]
}

20
.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,20 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "npm: build",
"type": "shell",
"command": "npm",
"args": ["run", "build"],
"problemMatcher": [],
},
{
"label": "npm: start",
"type": "shell",
"command": "npm",
"args": ["start"],
"problemMatcher": [],
}
]
}

10
nodemon.json Normal file
View File

@ -0,0 +1,10 @@
{
"watch": [
"src"
],
"ext": "ts",
"exec": "npm run build",
"ignore": [
"dist"
]
}

5732
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

66
package.json Normal file
View File

@ -0,0 +1,66 @@
{
"name": "sdserver",
"version": "1.0.0",
"main": "dist/electron/main.js",
"scripts": {
"start": "cross-env NODE_ENV=dev electron .",
"build": "cross-env NODE_ENV=prod tsc && npm run copyfiles",
"copyfiles": "copyfiles -u 1 src/electron/index.html dist/ && copyfiles -u 1 src/electron/index.css dist/ && copyfiles -u 1 shared/jsons/* dist/shared",
"buildexe": "npm run build && electron-builder",
"watch": "nodemon"
},
"build": {
"appId": "SDServer",
"productName": "SDServer",
"files": [
"dist/**/*",
"node_modules/**/*",
"package.json",
".env.prod"
],
"directories": {
"output": "release"
},
"extraFiles": [
{
"from": "shared",
"to": "resources/shared"
}
]
},
"dependencies": {
"dayjs": "^1.11.13",
"dotenv": "^16.4.5",
"module-alias": "^2.2.3",
"ws": "^8.18.0"
},
"devDependencies": {
"@types/node": "^22.5.0",
"@types/ws": "^8.5.12",
"copyfiles": "^2.4.1",
"cross-env": "^7.0.3",
"electron": "^32.0.1",
"electron-builder": "^24.7.0",
"electron-packager": "^17.1.2",
"nodemon": "^3.1.4",
"ts-node": "^10.9.2",
"tsrpc-cli": "^2.4.5",
"typescript": "^5.5.4"
},
"_moduleAliases": {
"@": "dist"
},
"bin": "dist/server.js",
"pkg": {
"assets": [
"public/**/*"
],
"outputPath": "executables",
"targets": [
"node18-win-x64"
]
},
"author": "",
"license": "ISC",
"description": ""
}

View File

@ -0,0 +1,29 @@
{
"slotData": [
{"slot":[10,9,4,5,7,11,5,6,8,6,6,5,9,6,9],"pay":[[1,-120]],"money":10017339},
{"slot":[11,1,5,3,11,4,13,6,8,8,5,5,9,4,5],"way":[[[10,11,2],60]],"pay":[[1,-120]],"get":[[1,60]],"money":10017519},
{"slot":[4,2,9,10,1,5,10,7,6,8,5,13,5,7,9],"way":[[[5,10,1,12],120]],"pay":[[1,-120]],"get":[[1,120]],"money":10017339},
{"slot":[7,7,7,10,11,6,7,7,11,10,6,9,6,5,5],"way":[[[0,1,6,2,7],120]],"pay":[[1,-120]],"get":[[1,120]],"money":10017459},
{"slot":[11,12,5,7,10,7,7,7,8,5,7,2,8,2,5],"way":[[[5,10,6,11,7,3,13],480]],"pay":[[1,-120]],"get":[[1,480]],"money":10017579},
{"slot":[4,8,5,10,10,5,5,5,5,5,5,6,11,5,5],"way":[[[5,10,6,2,7,8,13,9,14],4800]],"pay":[[1,-120]],"get":[[1,4800]],"money":10024359},
{"slot":[12,4,4,2,2,13,4,2,12,8,4,10,6,10,5],"way":[[[10,1,6,2,7,3,4],2400]],"pay":[[1,-120]],"get":[[1,2400]],"money":10019799},
{"slot":[6,1,7,10,8,12,10,1,9,5,1,8,6,6,11],"scatter":[[[10,1,7],240]],"pay":[[1,-120]],"rs":0},
{"slot":[6,1,7,10,8,12,10,1,9,5,1,8,6,1,11],"scatter":[[[10,1,7,13],600]],"pay":[[1,-120]],"rs":0},
{"slot":[1,2,5,11,1,11,11,1,2,12,6,4,6,9,13],"way":[[[10,1,12,8],60]],"scatter":[[[0,1,7,8,4],2400]],"pay":[[1,-120]],"rs":0}
],
"slotFreeData": [
{"slot":[12,8,13,9,8,7,3,7,10,3,8,9,13,4,13,9,9,12,11,9,1,4,1,12,10],"t":[[[5,6,21,7,13,9],3000,3]]},
{"slot":[11,9,12,10,12,11,1,12,12,10,5,9,13,6,12,12,10,13,7,11,6,10,1,13,12]},
{"slot":[10,1,5,13,13,1,13,3,5,5,11,13,13,13,8,8,7,12,8,13,11,13,11,2,8]},
{"slot":[12,1,13,12,9,1,11,4,1,4,8,9,13,13,9,3,10,11,12,12,8,6,11,6,11],"t":[[[15,21,7,23,9],120,7]]},
{"slot":[5,10,8,1,11,12,6,9,13,13,6,12,9,12,9,12,8,5,6,13,7,4,10,8,10],"t":[[[0,10,20,6,21,17,18],360,7]],"get":[[1,3720]],"money":10027449},
{"slot":[5,5,13,3,12,10,10,12,11,10,12,13,13,8,11,10,8,6,9,8,11,9,9,4,11,6,3,3,9,13],"t":[[[0,25,1,26,17,27,3,23],2400,4]]},
{"slot":[12,5,13,9,3,9,9,12,9,11,7,8,13,8,10,12,10,6,12,11,9,13,9,13,10,10,3,3,12,10],"way":[[[5,20,6,22,3,8],120]],"t":[[[10,1,26,17,27],120,7]]},
{"slot":[8,13,9,9,11,11,6,10,8,12,12,7,13,8,13,5,11,3,11,10,10,4,8,11,4,12,11,8,12,8],"t":[[[15,6,11,21,17],180,5]],"get":[[1,3420]],"money":10030749},
{"slot":[10,4,13,7,13,9,13,13,11,11,8,10,12,13,10,6,3,4,4,5,9,7,3,11,12,10,9,8,12,13,11,6,9,11,12,7,5,13,3,13],"way":[[[5,20,26,32],30]],"t":[[[15,35,1,16,21,31,36,17,22,3,18,38,19],9000,6]],"get":[[1,11490]],"money":10042119}
]
}

8
shared/jsons/slot70.json Normal file
View File

@ -0,0 +1,8 @@
{
"slotData": [
{"slot":[1,2,5,11,1,11,11,1,2,12,6,4,6,9,13],"way":[[[10,1,12,8],60]],"scatter":[[[0,1,7,8,4],2400]],"pay":[[1,-120]],"rs":0}
],
"slotFreeData": [
{"slot":[5,10,8,1,11,12,6,9,13,13,6,12,9,12,9,12,8,5,6,13,7,4,10,8,10],"t":[[[0,10,20,6,21,17,18],360,7]],"get":[[1,100]],"money":10027449}
]
}

View File

@ -0,0 +1,16 @@
interface StringConstructor {
IsNullOrEmpty: (value: string) => boolean;
Format: (format: string, ...args: any[]) => string;
}
String.IsNullOrEmpty = function (value: string): boolean {
return value === undefined || value === null || value.trim() === '';
};
String.Format = function (format: string, ...args: any[]): string {
return format.replace(/{(\d+)}/g, (match, index) => {
let value = args[index];
if (value === null || value === undefined) return '';
return '' + value;
});
}

View File

@ -0,0 +1,125 @@
/**
* 回呼函數: fnname (arg: TArg): void
*/
interface ActionCallback<TArg> {
(arg: TArg): void;
}
interface Struct<TArg> {
callback: ActionCallback<TArg>;
target: any;
once?: boolean;
}
export class Action<TArg> {
private _queue: Struct<TArg>[] = [];
/**
*
* @param callback 回呼函數: fnname (arg: TArg): void
* @param bindTarget this綁定的對象
*/
AddCallback(callback: ActionCallback<TArg>, bindTarget?: any) {
let q = <Struct<TArg>> {
callback: callback,
target: bindTarget
};
this._queue.push(q);
}
/**
* ()
* @param callback 回呼函數: fnname (arg: TArg): void
* @param bindTarget this綁定的對象
*/
AddCallbackOnce(callback: ActionCallback<TArg>, bindTarget?: any) {
let q = <Struct<TArg>> {
callback: callback,
target: bindTarget,
once: true
};
this._queue.push(q);
}
/**
*
* @param callback
*/
RemoveByCallback(callback: ActionCallback<TArg>) {
let index = this._queue.length;
if (index > 0) {
while (index--) {
let q = this._queue[index];
if (!q.callback || q.callback === callback) {
q.callback = undefined;
this._queue.splice(index, 1);
}
}
}
}
/**
*
* @param bindTarget this綁定的對象
*/
RemoveByBindTarget(bindTarget: any) {
let index = this._queue.length;
if (index > 0) {
while (index--) {
let q = this._queue[index];
if (!q.callback || q.target === bindTarget) {
q.callback = undefined;
this._queue.splice(index, 1);
}
}
}
}
/**
*
*/
RemoveAllCallbacks() {
this._queue.forEach(q => q.callback = undefined);
this._queue.length = 0;
}
/**
*
* @param arg
*/
DispatchCallback(arg: TArg) {
let index = this._queue.length;
if (index > 0) {
let cleanRemoved = false;
this._queue.slice().forEach(q => {
if (!q.callback) {
cleanRemoved = true;
return;
}
if (q.target) {
q.callback.call(q.target, arg);
} else {
q.callback(arg);
}
if (q.once) {
q.callback = undefined;
cleanRemoved = true;
}
});
if (cleanRemoved) {
index = this._queue.length;
if (index > 0) {
while (index--) {
let q = this._queue[index];
if (!q.callback) {
this._queue.splice(index, 1);
}
}
}
}
}
}
}

View File

@ -0,0 +1,166 @@
/**
* 回呼函數: fnname (arg: TArg): void
*/
interface ActionCallback<TArg> {
(arg: TArg): void;
}
interface Struct<TType, TArg> {
callback: ActionCallback<TArg>;
target: any;
type: TType;
once?: boolean;
}
export class ActionWithType<TType, TArg> {
private _queue: Struct<TType, TArg>[] = [];
/**
*
* @param callback 回呼函數: fnname (arg: TArg): void
* @param bindTarget this綁定的對象
*/
AddCallback(type: TType, callback: ActionCallback<TArg>, bindTarget?: any) {
let q = <Struct<TType, TArg>> {
callback: callback,
target: bindTarget,
type: type
};
this._queue.push(q);
}
/**
* ()
* @param callback 回呼函數: fnname (arg: TArg): void
* @param bindTarget this綁定的對象
*/
AddCallbackOnce(type: TType, callback: ActionCallback<TArg>, bindTarget?: any) {
let q = <Struct<TType, TArg>> {
callback: callback,
target: bindTarget,
type: type,
once: true
};
this._queue.push(q);
}
/**
*
* @param callback
*/
RemoveByCallback(callback: ActionCallback<TArg>) {
let index = this._queue.length;
if (index > 0) {
while (index--) {
let q = this._queue[index];
if (!q.callback || q.callback === callback) {
q.callback = undefined;
this._queue.splice(index, 1);
}
}
}
}
/**
*
* @param bindTarget this綁定的對象
*/
RemoveByBindTarget(bindTarget: any) {
let index = this._queue.length;
if (index > 0) {
while (index--) {
let q = this._queue[index];
if (!q.callback || q.target === bindTarget) {
q.callback = undefined;
this._queue.splice(index, 1);
}
}
}
}
/**
*
* @param type
*/
RemoveByType(type: TType) {
let index = this._queue.length;
if (index > 0) {
while (index--) {
let q = this._queue[index];
if (!q.callback || q.type === type) {
q.callback = undefined;
this._queue.splice(index, 1);
}
}
}
}
/**
*
* @param type
* @param callback
*/
RemoveCallback(type:TType, callback: ActionCallback<TArg>) {
let index = this._queue.length;
if (index > 0) {
while (index--) {
let q = this._queue[index];
if (!q.callback || (q.type === type && q.callback === callback)) {
q.callback = undefined;
this._queue.splice(index, 1);
}
}
}
}
/**
*
*/
RemoveAllCallbacks() {
this._queue.forEach(q => q.callback = undefined);
this._queue.length = 0;
}
/**
*
* @param type
* @param arg
*/
DispatchCallback(type: TType, arg: TArg) {
let index = this._queue.length;
if (index > 0) {
let cleanRemoved = false;
this._queue.slice().forEach(q => {
if (!q.callback)
{
cleanRemoved = true;
return;
}
if (q.type !== type) return;
if (q.target) {
q.callback.call(q.target, arg);
} else {
q.callback(arg);
}
if (q.once) {
q.callback = undefined;
cleanRemoved = true;
}
});
if (cleanRemoved) {
index = this._queue.length;
if (index > 0) {
while (index--) {
let q = this._queue[index];
if (!q.callback) {
this._queue.splice(index, 1);
}
}
}
}
}
}
}

View File

@ -0,0 +1,166 @@
/**
* 回呼函數: fnname (type: TType, arg: TArg): void
*/
interface ActionCallback<TType, TArg> {
(type: TType, arg: TArg): void;
}
interface Struct<TType, TArg> {
callback: ActionCallback<TType, TArg>;
target: any;
type: TType;
once?: boolean;
}
export class ActionWithType2<TType, TArg> {
private _queue: Struct<TType, TArg>[] = [];
/**
*
* @param callback 回呼函數: fnname (type: TType, arg: TArg): void
* @param bindTarget this綁定的對象
*/
AddCallback(type: TType, callback: ActionCallback<TType, TArg>, bindTarget?: any) {
let q = <Struct<TType, TArg>> {
callback: callback,
target: bindTarget,
type: type
};
this._queue.push(q);
}
/**
* ()
* @param callback 回呼函數: fnname (type: TType, arg: TArg): void
* @param bindTarget this綁定的對象
*/
AddCallbackOnce(type: TType, callback: ActionCallback<TType, TArg>, bindTarget?: any) {
let q = <Struct<TType, TArg>> {
callback: callback,
target: bindTarget,
type: type,
once: true
};
this._queue.push(q);
}
/**
*
* @param callback
*/
RemoveByCallback(callback: ActionCallback<TType, TArg>) {
let index = this._queue.length;
if (index > 0) {
while (index--) {
let q = this._queue[index];
if (!q.callback || q.callback === callback) {
q.callback = undefined;
this._queue.splice(index, 1);
}
}
}
}
/**
*
* @param bindTarget this綁定的對象
*/
RemoveByBindTarget(bindTarget: any) {
let index = this._queue.length;
if (index > 0) {
while (index--) {
let q = this._queue[index];
if (!q.callback || q.target === bindTarget) {
q.callback = undefined;
this._queue.splice(index, 1);
}
}
}
}
/**
*
* @param type
*/
RemoveByType(type: TType) {
let index = this._queue.length;
if (index > 0) {
while (index--) {
let q = this._queue[index];
if (!q.callback || q.type === type) {
q.callback = undefined;
this._queue.splice(index, 1);
}
}
}
}
/**
*
* @param type
* @param callback
*/
RemoveCallback(type:TType, callback: ActionCallback<TType, TArg>) {
let index = this._queue.length;
if (index > 0) {
while (index--) {
let q = this._queue[index];
if (!q.callback || (q.type === type && q.callback === callback)) {
q.callback = undefined;
this._queue.splice(index, 1);
}
}
}
}
/**
*
*/
RemoveAllCallbacks() {
this._queue.forEach(q => q.callback = undefined);
this._queue.length = 0;
}
/**
*
* @param type
* @param arg
*/
DispatchCallback(type: TType, arg: TArg) {
let index = this._queue.length;
if (index > 0) {
let cleanRemoved = false;
this._queue.slice().forEach(q => {
if (!q.callback)
{
cleanRemoved = true;
return;
}
if (q.type !== type) return;
if (q.target) {
q.callback.call(q.target, type, arg);
} else {
q.callback(type, arg);
}
if (q.once) {
q.callback = undefined;
cleanRemoved = true;
}
});
if (cleanRemoved) {
index = this._queue.length;
if (index > 0) {
while (index--) {
let q = this._queue[index];
if (!q.callback) {
this._queue.splice(index, 1);
}
}
}
}
}
}
}

View File

@ -0,0 +1,106 @@
export module Encoding.UTF8 {
export function GetBytes(str: string) {
let len = str.length, resPos = -1;
let resArr = new Uint8Array(len * 3);
for (let point = 0, nextcode = 0, i = 0; i !== len;) {
point = str.charCodeAt(i), i += 1;
if (point >= 0xD800 && point <= 0xDBFF) {
if (i === len) {
resArr[resPos += 1] = 0xef;
resArr[resPos += 1] = 0xbf;
resArr[resPos += 1] = 0xbd;
break;
}
nextcode = str.charCodeAt(i);
if (nextcode >= 0xDC00 && nextcode <= 0xDFFF) {
point = (point - 0xD800) * 0x400 + nextcode - 0xDC00 + 0x10000;
i += 1;
if (point > 0xffff) {
resArr[resPos += 1] = (0x1e << 3) | (point >>> 18);
resArr[resPos += 1] = (0x2 << 6) | ((point >>> 12) & 0x3f);
resArr[resPos += 1] = (0x2 << 6) | ((point >>> 6) & 0x3f);
resArr[resPos += 1] = (0x2 << 6) | (point & 0x3f);
continue;
}
} else {
resArr[resPos += 1] = 0xef;
resArr[resPos += 1] = 0xbf;
resArr[resPos += 1] = 0xbd;
continue;
}
}
if (point <= 0x007f) {
resArr[resPos += 1] = (0x0 << 7) | point;
} else if (point <= 0x07ff) {
resArr[resPos += 1] = (0x6 << 5) | (point >>> 6);
resArr[resPos += 1] = (0x2 << 6) | (point & 0x3f);
} else {
resArr[resPos += 1] = (0xe << 4) | (point >>> 12);
resArr[resPos += 1] = (0x2 << 6) | ((point >>> 6) & 0x3f);
resArr[resPos += 1] = (0x2 << 6) | (point & 0x3f);
}
}
return resArr.subarray(0, resPos + 1);
}
export function GetString(array: Uint8Array) {
var charCache = new Array(128);
var codePt, byte1;
var result = [];
var buffLen = array.length;
var charFromCodePt = String.fromCodePoint || String.fromCharCode;
for (var i = 0; i < buffLen;) {
byte1 = array[i++];
if (byte1 <= 0x7F) {
codePt = byte1;
} else if (byte1 <= 0xDF) {
codePt = ((byte1 & 0x1F) << 6) | (array[i++] & 0x3F);
} else if (byte1 <= 0xEF) {
codePt = ((byte1 & 0x0F) << 12) | ((array[i++] & 0x3F) << 6) | (array[i++] & 0x3F);
} else if (String.fromCodePoint) {
codePt = ((byte1 & 0x07) << 18) | ((array[i++] & 0x3F) << 12) | ((array[i++] & 0x3F) << 6) | (array[i++] & 0x3F);
} else {
codePt = 63; // Cannot convert four byte code points, so use "?" instead
i += 3;
}
result.push(charCache[codePt] || (charCache[codePt] = charFromCodePt(codePt)));
}
return result.join('');
}
/**
*
* @param {string} msg
*/
export function IsNotChineseOrEnglish(str: string): boolean {
var regExp: RegExp = /^[\u3105-\u312c\u4e00-\u9fff\uff10-\uff19\uFF21-\uFF3AA-Za-z0-9_]+$/;
if (str.match(regExp)) {
return true;
} else {
return false;
}
}
export function b64EncodeUnicode(str) {
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function (match, p1) {
//@ts-ignore
return String.fromCharCode('0x' + p1);
}));
}
export function b64DecodeUnicode(str) {
return decodeURIComponent(atob(str).split('').map(function (c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
}
export function isBase64(str) {
if (str === '' || str.trim() === '') { return false; }
try {
return btoa(atob(str)) == str;
} catch (err) {
return false;
}
}
}

View File

@ -0,0 +1,43 @@
const CANCEL = Symbol();
export interface CancellationToken {
readonly IsCancellationRequested: boolean;
ThrowIfCancellationRequested(): void;
}
export class CancellationTokenSource {
readonly Token: CancellationToken;
constructor() {
this.Token = new CancellationTokenImpl();
}
Cancel() {
this.Token[CANCEL]();
}
}
export class TaskCancelledException extends Error {
constructor() {
super("Task Cancelled");
Reflect.setPrototypeOf(this, TaskCancelledException.prototype);
}
}
class CancellationTokenImpl implements CancellationToken {
IsCancellationRequested: boolean;
constructor() {
this.IsCancellationRequested = false;
}
ThrowIfCancellationRequested() {
if (this.IsCancellationRequested) {
throw new TaskCancelledException();
}
}
[CANCEL]() {
this.IsCancellationRequested = true;
}
}

View File

@ -0,0 +1,17 @@
import { BaseEnumerator } from "./BaseEnumerator";
export class ActionEnumerator extends BaseEnumerator {
private _action: Function;
constructor(action: Function) {
super();
this._action = action;
}
next(value?: any): IteratorResult<any> {
if (this._action) {
this._action();
}
return { done: true, value: undefined };
}
}

View File

@ -0,0 +1,131 @@
import { IEnumeratorV2, IEnumeratorV2Started } from "../IEnumeratorV2";
import { CoroutineExecutor } from "./CoroutineExecutor";
let EnumeratorExecutorClass: typeof import("./EnumeratorExecutor").EnumeratorExecutor = null;
let SingleEnumeratorClass: typeof import("./SingleEnumerator").SingleEnumerator = null;
let ParallelEnumeratorClass: typeof import("./ParallelEnumerator").ParallelEnumerator = null;
let WaitTimeEnumeratorClass: typeof import("./WaitTimeEnumerator").WaitTimeEnumerator = null;
let ActionEnumeratorClass: typeof import("./ActionEnumerator").ActionEnumerator = null;
export abstract class BaseEnumerator implements IEnumeratorV2 {
public nextEnumerator: BaseEnumerator;
abstract next(value?: any): IteratorResult<any>;
public static isInit: boolean = false;
public static async Init(): Promise<any> {
await Promise.all([
(async () => {
EnumeratorExecutorClass = (await import("./EnumeratorExecutor")).EnumeratorExecutor;
})(),
(async () => {
SingleEnumeratorClass = (await import("./SingleEnumerator")).SingleEnumerator;
})(),
(async () => {
ParallelEnumeratorClass = (await import("./ParallelEnumerator")).ParallelEnumerator;
})(),
(async () => {
WaitTimeEnumeratorClass = (await import("./WaitTimeEnumerator")).WaitTimeEnumerator;
})(),
(async () => {
ActionEnumeratorClass = (await import("./ActionEnumerator")).ActionEnumerator;
})(),
]);
BaseEnumerator.isInit = true;
}
Start(target?: any): IEnumeratorV2Started {
let executor = LazyLoad.EnumeratorExecutor(this, target);
CoroutineExecutor.instance.StartCoroutine(executor);
return executor;
}
Then(iterator: Iterator<any>): IEnumeratorV2 {
if (!iterator) return this;
if (iterator instanceof BaseEnumerator) {
BaseEnumerator.getLastEnumerator(this).nextEnumerator = iterator;
return this;
} else {
let enumerator = LazyLoad.SingleEnumerator(iterator);
BaseEnumerator.getLastEnumerator(this).nextEnumerator = enumerator;
return this;
}
}
ThenSerial(...iterators: Iterator<any>[]): IEnumeratorV2 {
let last = BaseEnumerator.getLastEnumerator(this);
for (let iterator of iterators) {
if (iterator instanceof BaseEnumerator) {
last.nextEnumerator = iterator;
} else {
let enumerator = LazyLoad.SingleEnumerator(iterator);
last.nextEnumerator = enumerator;
}
last = last.nextEnumerator;
}
return this;
}
ThenParallel(...iterators: Iterator<any>[]): IEnumeratorV2 {
return this.Then(LazyLoad.ParallelEnumerator(...iterators));
}
ThenAction(action: Function, delaySeconds?: number): IEnumeratorV2 {
if (delaySeconds > 0) {
return this.ThenSerial(LazyLoad.WaitTimeEnumerator(delaySeconds), LazyLoad.ActionEnumerator(action));
} else {
return this.Then(LazyLoad.ActionEnumerator(action));
}
}
ThenWaitTime(seconds: number): IEnumeratorV2 {
return this.Then(LazyLoad.WaitTimeEnumerator(seconds));
}
static getLastEnumerator(enumerator: BaseEnumerator): BaseEnumerator {
let next = enumerator;
while (next.nextEnumerator) {
next = next.nextEnumerator;
}
return next;
}
}
module LazyLoad {
export function EnumeratorExecutor(enumerator: BaseEnumerator, target: any) {
if (BaseEnumerator.isInit) {
return new EnumeratorExecutorClass(enumerator, target);
}
return new (require("./EnumeratorExecutor") as typeof import("./EnumeratorExecutor")).EnumeratorExecutor(enumerator, target);
}
export function SingleEnumerator(iterator: Iterator<any>) {
if (BaseEnumerator.isInit) {
return new SingleEnumeratorClass(iterator);
}
return new (require("./SingleEnumerator") as typeof import("./SingleEnumerator")).SingleEnumerator(iterator);
}
export function ParallelEnumerator(...iterators: Iterator<any>[]) {
if (BaseEnumerator.isInit) {
return new ParallelEnumeratorClass(iterators);
}
return new (require("./ParallelEnumerator") as typeof import("./ParallelEnumerator")).ParallelEnumerator(iterators);
}
export function WaitTimeEnumerator(seconds: number) {
if (BaseEnumerator.isInit) {
return new WaitTimeEnumeratorClass(seconds);
}
return new (require("./WaitTimeEnumerator") as typeof import("./WaitTimeEnumerator")).WaitTimeEnumerator(seconds);
}
export function ActionEnumerator(action: Function) {
if (BaseEnumerator.isInit) {
return new ActionEnumeratorClass(action);
}
return new (require("./ActionEnumerator") as typeof import("./ActionEnumerator")).ActionEnumerator(action);
}
}

View File

@ -0,0 +1,103 @@
import { EnumeratorExecutor } from "./EnumeratorExecutor";
export class CoroutineExecutor {
private static _instance: CoroutineExecutor;
static get instance() {
return CoroutineExecutor._instance = CoroutineExecutor._instance || new CoroutineExecutor();
}
private _executors: EnumeratorExecutor[] = [];
private _nextExecutors: EnumeratorExecutor[] = [];
private _isRunning: boolean = false;
private _cleanRemoved: boolean = false;
private _scheduler: NodeJS.Timeout;
private _time: number = 0;
constructor() {
this._time = new Date().getTime();
// console.debug("[CoroutineV2] Coroutines Start");
this._scheduler = setInterval(this.update.bind(this), 1 / 60);
}
StartCoroutine(executor: EnumeratorExecutor) {
executor.next(0);
//TODO: 這邊要考量next後馬上接BaseEnumerator/Iterator的情形
if (!this._isRunning) {
this._executors.push(executor);
if (!this._scheduler) {
// console.debug("[CoroutineV2] Coroutines Start");
this._time = new Date().getTime();
this._scheduler = setInterval(this.update.bind(this), 1 / 60);
} else {
// console.debug(`[CoroutineV2] Coroutines add now: ${this._executors.length}`);
}
} else {
this._nextExecutors.push(executor);
}
}
StopCoroutineBy(target: any) {
if (!target) return;
for (let r of this._executors) {
if (target === r.target) {
r.Stop();
}
}
for (let r of this._nextExecutors) {
if (target === r.target) {
r.Stop();
}
}
}
update(delta: number) {
const time: number = new Date().getTime();
delta = (time - this._time) / 1000;
this._time = time;
if (this._nextExecutors.length) {
this._executors.push(...this._nextExecutors);
// console.debug(`[CoroutineV2] Coroutines addNext now: ${this._executors.length}, next: ${this._nextExecutors.length}`);
this._nextExecutors.length = 0;
}
if (this._cleanRemoved) {
// 移除[doneFlag=true]的協程
let index = this._executors.length;
while (index--) {
let r = this._executors[index];
if (r.doneFlag) {
this._executors.splice(index, 1);
// console.debug(`[CoroutineV2] Coroutines sub now: ${this._executors.length}`);
}
}
this._cleanRemoved = false;
}
if (this._executors.length == 0) {
console.debug("[CoroutineV2] All Coroutines Done");
clearInterval(<NodeJS.Timeout>this._scheduler);
this._scheduler = null;
return;
}
this._isRunning = true;
// 執行協程
for (let r of this._executors) {
if (r.doneFlag || r.pauseFlag || r.childFlag) {
if (r.doneFlag) {
this._cleanRemoved = true;
}
continue;
}
r.next(delta);
}
this._isRunning = false;
}
}

View File

@ -0,0 +1,168 @@
import { IEnumeratorV2Started } from "../IEnumeratorV2";
import { BaseEnumerator } from "./BaseEnumerator";
import { SingleEnumerator } from "./SingleEnumerator";
export class EnumeratorExecutor implements IEnumeratorV2Started {
public Current: any;
public target: any;
public pauseFlag: boolean;
public doneFlag: boolean;
public childFlag: boolean;
public asyncFlag: boolean;
public error: any;
private _executor: EnumeratorExecutor;
private _enumerator: BaseEnumerator;
constructor(enumerator: BaseEnumerator, target: any) {
this.target = target;
this._enumerator = enumerator;
}
next(delta?: any): IteratorResult<any> {
if (this._executor && this._executor.doneFlag) {
this._executor = null;
}
if (this.doneFlag || (!this._enumerator && !this._executor)) {
this.doneFlag = true;
return { done: true, value: undefined };
}
if (this.asyncFlag || this.pauseFlag) return { done: false, value: undefined };
let result: IteratorResult<any>;
if (this._executor) {
result = this._executor.next(delta);
this.Current = this._executor.Current;
if (this._executor.doneFlag) {
this._executor = null;
} else {
result.done = false;
return result;
}
}
if (!this._enumerator) {
this.doneFlag = true;
return { done: true, value: undefined };
}
try {
result = this._enumerator.next(delta);
let value = result.value;
let done = result.done;
if (value) {
// Iterator
if (typeof value[Symbol.iterator] === 'function') {
value = new SingleEnumerator(<Iterator<any>>value);
}
if (value instanceof BaseEnumerator) {
if (!done) {
BaseEnumerator.getLastEnumerator(value).nextEnumerator = this._enumerator;
}
this._enumerator = value;
result = this._enumerator.next(delta);
value = result.value;
done = result.done;
if (value) {
// Iterator again
if (typeof value[Symbol.iterator] === 'function') {
value = new SingleEnumerator(<Iterator<any>>value);
}
if (value instanceof BaseEnumerator) {
if (!done) {
BaseEnumerator.getLastEnumerator(value).nextEnumerator = this._enumerator;
}
this._enumerator = value;
result.done = false;
done = false;
}
}
}
if (value instanceof EnumeratorExecutor) {
if (done) {
this._enumerator = this._enumerator.nextEnumerator;
}
value.childFlag = true;
result.done = false;
done = false;
this._executor = value;
} else if (Promise.resolve(value) === value) {
this.asyncFlag = true;
result.done = false;
done = false;
(<Promise<any>>value)
.then(v => {
this.asyncFlag = false;
this.Current = v;
if (done) {
this._enumerator = this._enumerator.nextEnumerator;
}
})
.catch(e => {
this.asyncFlag = false;
this.doneFlag = true;
this._enumerator = null;
this.error = e;
if (e instanceof Error) {
console.error(e.stack);
} else {
console.error(`Error: ${JSON.stringify(e)}`);
}
});
}
this.Current = value;
}
if (done) {
this._enumerator = this._enumerator.nextEnumerator;
if (this._enumerator) {
result.done = false;
}
}
}
catch (e) {
this.doneFlag = true;
this.error = e;
if (e instanceof Error) {
console.error(e.stack);
} else {
console.error(`Error: ${JSON.stringify(e)}`);
}
result = { done: true, value: e };
}
return result;
}
Stop(): void {
this.doneFlag = true;
if (this._executor) {
this._executor.Stop();
}
}
Pause(): void {
this.pauseFlag = true;
if (this._executor) {
this._executor.Pause();
}
}
Resume(): void {
this.pauseFlag = false;
if (this._executor) {
this._executor.Resume();
}
}
}

View File

@ -0,0 +1,46 @@
import { BaseEnumerator } from "./BaseEnumerator";
import { EnumeratorExecutor } from "./EnumeratorExecutor";
import { SingleEnumerator } from "./SingleEnumerator";
export class ParallelEnumerator extends BaseEnumerator {
private _executors: EnumeratorExecutor[] = [];
constructor(iterators: Iterator<any>[]) {
super();
if (iterators && iterators.length) {
for (let iterator of iterators) {
if (iterator instanceof BaseEnumerator) {
this._executors.push(new EnumeratorExecutor(iterator, null));
} else {
this._executors.push(new EnumeratorExecutor(new SingleEnumerator(iterator), null));
}
}
}
}
next(value?: any): IteratorResult<any> {
if (this._executors.length) {
// 先移除[doneFlag=true]協程
let index = this._executors.length;
while (index--) {
let r = this._executors[index];
if (r.doneFlag) {
this._executors.splice(index, 1);
}
}
if (this._executors.length == 0) {
return { done: true, value: undefined };
}
// 執行協程
for (let r of this._executors) {
r.next(value);
}
return { done: false, value: undefined };
}
return { done: true, value: undefined };
}
}

View File

@ -0,0 +1,18 @@
import { BaseEnumerator } from "./BaseEnumerator";
export class SingleEnumerator extends BaseEnumerator {
private _iterator: Iterator<any>;
constructor(iterator: Iterator<any>) {
super();
this._iterator = iterator;
}
next(value?: any): IteratorResult<any> {
if (!this._iterator) {
return { done: true, value: undefined };
}
return this._iterator.next(value);
}
}

View File

@ -0,0 +1,21 @@
import { BaseEnumerator } from "./BaseEnumerator";
export class WaitTimeEnumerator extends BaseEnumerator {
private _seconds: number;
constructor(seconds: number) {
super();
this._seconds = seconds;
}
next(value?: any): IteratorResult<any> {
let delta = value as number;
this._seconds -= delta;
if (this._seconds <= 0) {
return { done: true, value: 0 };
} else {
return { done: false, value: this._seconds };
}
}
}

View File

@ -0,0 +1,75 @@
import { IEnumeratorV2, IEnumeratorV2Started } from "./IEnumeratorV2";
import { BaseEnumerator } from "./Core/BaseEnumerator";
import { SingleEnumerator } from "./Core/SingleEnumerator";
import { ParallelEnumerator } from "./Core/ParallelEnumerator";
import { WaitTimeEnumerator } from "./Core/WaitTimeEnumerator";
import { ActionEnumerator } from "./Core/ActionEnumerator";
import { CoroutineExecutor } from "./Core/CoroutineExecutor";
export module CoroutineV2 {
/**
*
*/
export function StartCoroutine(iterator: Iterator<any>, target?: any): IEnumeratorV2Started {
return Single(iterator).Start(target);
}
/**
* IEnumeratorV2.Start(target),
* @param target
*/
export function StopCoroutinesBy(target: any) {
CoroutineExecutor.instance.StopCoroutineBy(target);
}
/**
*
*/
export function Single(iterator: Iterator<any>): IEnumeratorV2 {
if (iterator instanceof BaseEnumerator) {
return iterator;
} else {
return new SingleEnumerator(iterator);
}
}
/**
*
*/
export function Parallel(...iterators: Iterator<any>[]): IEnumeratorV2 {
return new ParallelEnumerator(iterators);
}
/**
*
*/
export function Serial(...iterators: Iterator<any>[]): IEnumeratorV2 {
let [iterator, ...others] = iterators;
if (iterator instanceof BaseEnumerator) {
return iterator.ThenSerial(...others);
} else {
return new SingleEnumerator(iterator).ThenSerial(...others);
}
}
/**
*
* @param action
* @param delaySeconds
*/
export function Action(action: Function, delaySeconds?: number): IEnumeratorV2 {
if (delaySeconds > 0) {
return new WaitTimeEnumerator(delaySeconds).Then(new ActionEnumerator(action));
} else {
return new ActionEnumerator(action);
}
}
/**
*
* @param seconds
*/
export function WaitTime(seconds: number): IEnumeratorV2 {
return new WaitTimeEnumerator(seconds);
}
}

View File

@ -0,0 +1,16 @@
export interface IEnumeratorV2 extends Iterator<any> {
Start(target?: any): IEnumeratorV2Started;
Then(iterator: Iterator<any>): IEnumeratorV2;
ThenSerial(...iterators: Iterator<any>[]): IEnumeratorV2;
ThenParallel(...iterators: Iterator<any>[]): IEnumeratorV2;
ThenAction(action: Function, delaySeconds?: number): IEnumeratorV2;
ThenWaitTime(seconds: number): IEnumeratorV2;
}
export interface IEnumeratorV2Started {
readonly Current: any;
Pause(): void;
Resume(): void;
Stop(): void;
}

View File

@ -0,0 +1,140 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
declare interface Array<T> {
/**
*
* @param index
*/
ExRemoveAt(index: number): T
/**
* (. )
* @example
*
* let bar: number[] = [1, 2, 3];
* let bar2: number[] = bar;
* bar.Clear();
* console.log(bar, bar2);
*
* // {
* // "bar": [],
* // "bar2": []
* // }
*/
Clear(): void
/**
*
* PS. pass by value
*/
Copy(): T[]
/**
* asc&key陣列長度請一樣
* PS. boolean false是先true在false
* @link JavaScript Object http://www.eion.com.tw/Blogger/?Pid=1170#:~:text=JavaScript%20Object%20排序
* @param asc ()
* @param key key()()
*/
ObjectSort(asc?: boolean[], key?: string[]): T[]
/**
* Array<cc.Component.EventHandler>forHoldButton使用
* Add a none persistent listener to the UnityEvent.
* @param call Callback function.
*/
// eslint-disable-next-line @typescript-eslint/ban-types
AddListener(call: Function): void
}
Array.prototype.ExRemoveAt ||
Object.defineProperty(Array.prototype, "ExRemoveAt", {
enumerable: false,
value: function (index: number): any {
const item: any = this.splice(index, 1);
return item[0];
},
});
Array.prototype.Clear ||
Object.defineProperty(Array.prototype, "Clear", {
enumerable: false,
value: function (): void {
this.length = 0;
// let foo: number[] = [1, 2, 3];
// let bar: number[] = [1, 2, 3];
// let foo2: number[] = foo;
// let bar2: number[] = bar;
// foo = [];
// bar.length = 0;
// console.log(foo, bar, foo2, bar2);
// {
// "foo": [],
// "bar": [],
// "foo2": [
// 1,
// 2,
// 3
// ],
// "bar2": []
// }
},
});
Array.prototype.Copy ||
Object.defineProperty(Array.prototype, "Copy", {
enumerable: false,
value: function (): any[] {
return Array.from(this);
},
});
Array.prototype.ObjectSort ||
Object.defineProperty(Array.prototype, "ObjectSort", {
enumerable: false,
/**
* @param asc ()
* @param key key()()
*/
value: function (asc: boolean[] = [true], key?: string[]): any[] {
if (this.length === 0) {
return this;
} else if (!key || key.length === 0) {
console.error("ObjectSort key error");
return this;
} else if (asc.length !== key.length) {
console.error(
`ObjectSort key asc error asc.length: ${asc.length}, key.length: ${key.length}`,
);
return this;
}
for (let i: number = 0; i < key.length; i++) {
const keyname: string = key[i];
if (this[0][keyname] === undefined) {
console.error(`ObjectSort has not key[${i}]: ${keyname}`);
return this;
}
}
const count: number = key ? key.length : 1;
let arr: any[];
for (let i: number = count - 1; i >= 0; i--) {
arr = this.sort(function (a: any, b: any): 1 | -1 {
let mya: any = a;
let myb: any = b;
if (key) {
mya = a[key[i]];
myb = b[key[i]];
}
// 加個等於數字相同不要再去排序到
if (asc[i]) {
return mya >= myb ? 1 : -1;
} else {
return mya <= myb ? 1 : -1;
}
});
}
return arr;
},
});

View File

@ -0,0 +1,187 @@
declare interface Number {
/**
* (), 2
* 41,038,560.00
* @param precision
* @param isPadZero
* */
ExFormatNumberWithComma(precision?: number, isPadZero?: boolean): string;
/**
* 4(9,999-999B-T)
* */
ExTransferToBMK(precision?: number, offset?: number): string;
/**
* , 0
* @param size
*/
Pad(size: number): string;
/**
* X位 (server計算規則)
* @param precision
*/
ExToNumRoundDecimal(precision: number): number;
/**
* X位
* @param precision
*/
ExToNumFloorDecimal(precision: number): number;
/**
* X位小數2200.2.00
* @param precision
* @param isPadZero
*/
ExToStringFloorDecimal(precision: number, isPadZero?: boolean): string;
/**
* )
*/
ExToInt(): number;
/**
* ()
*/
Float2Fixed(): number;
/**
* ()
*/
DigitLength(): number;
target: number;
}
Number.prototype.ExFormatNumberWithComma || Object.defineProperty(Number.prototype, 'ExFormatNumberWithComma', {
enumerable: false,
value: function (precision: number, isPadZero: boolean) {
// let arr = String(this).split('.');
let arr = this.ExToStringFloorDecimal(precision, isPadZero).split('.');
let num = arr[0], result = '';
while (num.length > 3) {
result = ',' + num.slice(-3) + result;
num = num.slice(0, num.length - 3);
}
if (num.length > 0) result = num + result;
return arr[1] ? result + '.' + arr[1] : result;
}
})
Number.prototype.ExTransferToBMK || Object.defineProperty(Number.prototype, 'ExTransferToBMK', {
enumerable: false,
value: function (precision: number, offset: number) {
/**千 */
let MONEY_1K: number = 1000;
/**萬 */
// let MONEY_10K: number = 10000;
/**十萬 */
// let MONEY_100K: number = 100000;
/**百萬 */
let MONEY_1M: number = 1000000;
/**千萬 */
// let MONEY_10M: number = 10000000;
/**億 */
// let MONEY_100M: number = 100000000;
/**十億 */
let MONEY_1B: number = 1000000000;
/**百億 */
// let MONEY_10B: number = 10000000000;
/**千億 */
// let MONEY_100B: number = 100000000000;
/**兆 */
// let MONEY_1T: number = 1000000000000;
offset = Math.pow(10, offset);
// if (this >= MONEY_1T * offset) {
// //(3)1,000T
// //1T~
// return (~~(this / MONEY_1T)).ExFormatNumberWithComma(0) + "T";
// }
if (this >= MONEY_1B * offset) {
//1,000B~900,000B
//1B~900B
return (this / MONEY_1B).ExFormatNumberWithComma(3, false) + "B";
}
else if (this >= MONEY_1M * offset) {
//1,000M~900,000M
//1M~900M
return (this / MONEY_1M).ExFormatNumberWithComma(3, false) + "M";
}
else if (this >= MONEY_1K * offset) {
//1,000K~900,000K
//1K~90K
return (this / MONEY_1K).ExFormatNumberWithComma(3, false) + "K";
}
else {
//0~9,000,000
//0~9,000
return this.ExFormatNumberWithComma(precision);
}
}
})
Number.prototype.Pad || Object.defineProperty(Number.prototype, 'Pad', {
enumerable: false,
value: function (size: number) {
let s = this + "";
while (s.length < size) s = "0" + s;
return s;
}
})
Number.prototype.ExToNumRoundDecimal || Object.defineProperty(Number.prototype, 'ExToNumRoundDecimal', {
enumerable: false,
value: function (precision: number) {
return Math.round(Math.round(this * Math.pow(10, (precision || 0) + 1)) / 10) / Math.pow(10, (precision || 0));
}
})
Number.prototype.ExToInt || Object.defineProperty(Number.prototype, 'ExToInt', {
enumerable: false,
value: function () {
return ~~this;
}
})
Number.prototype.ExToNumFloorDecimal || Object.defineProperty(Number.prototype, 'ExToNumFloorDecimal', {
enumerable: false,
value: function (precision: number) {
let str = this.toPrecision(12);
let dotPos = str.indexOf('.');
return dotPos == -1 ? this : +`${str.substr(0, dotPos + 1 + precision)}`;
}
})
Number.prototype.ExToStringFloorDecimal || Object.defineProperty(Number.prototype, 'ExToStringFloorDecimal', {
enumerable: false,
value: function (precision: number, isPadZero: boolean) {
// 取小數點第X位
let f = this.ExToNumFloorDecimal(precision);
let s = f.toString();
// 補0
if (isPadZero) {
let rs = s.indexOf('.');
if (rs < 0) {
rs = s.length;
s += '.';
}
while (s.length <= rs + precision) {
s += '0';
}
}
return s;
}
})
Number.prototype.Float2Fixed || Object.defineProperty(Number.prototype, 'Float2Fixed', {
enumerable: false,
value: function () {
if (this.toString().indexOf('e') === -1) {
return Number(this.toString().replace('.', ''));
}
const dLen = this.DigitLength();
return dLen > 0 ? +parseFloat((this * Math.pow(10, dLen)).toPrecision(12)) : this;
}
})
Number.prototype.DigitLength || Object.defineProperty(Number.prototype, 'DigitLength', {
enumerable: false,
value: function () {
const eSplit = this.toString().split(/[eE]/);
const len = (eSplit[0].split('.')[1] || '').length - (+(eSplit[1] || 0));
return len > 0 ? len : 0;
}
})

View File

@ -0,0 +1,180 @@
import { CoroutineV2 } from "../../CatanEngine/CoroutineV2/CoroutineV2";
import { RandomEx } from "./RandomEx";
export module NumberEx {
/**
*
* @param startNum
* @param endNum
* @param callbackfn
* @param toInt (FALSE)
*/
export function* ChangeScore(startNum: number, endNum: number, callbackfn: (num: number) => void, sec: number, toInt: boolean = true) {
let fps = 30;
let waitTime = 0.03;
let changeRate = sec * fps;
changeRate = changeRate - 1 <= 0 ? changeRate : changeRate - 1;
changeRate = 1 / changeRate;
let diff = endNum - startNum;
let isIncrease = endNum >= startNum;
let tempScore = startNum;
let counter = 0;
while (true) {
if (endNum != tempScore) {
tempScore += diff * changeRate;
// 遞增
if (isIncrease && tempScore > endNum) {
tempScore = endNum;
}
// 遞減
if (!isIncrease && tempScore < endNum) {
tempScore = endNum;
}
if (toInt) {
callbackfn(tempScore.ExToInt());
} else {
callbackfn(tempScore);
}
counter++;
yield CoroutineV2.WaitTime(waitTime);
}
else {
callbackfn(endNum);
break;
}
}
}
/**
* (時間內循環EX:1~4=>1.2.3.4.1.2.3.4.1.2.3.4...)
* @param minNum
* @param maxNum
* @param callbackfn callbackfn
* @param sec
*/
export function* BeatScore(minNum: number, maxNum: number, endNum: number, callbackfn: (num: number) => void, sec: number): IterableIterator<any> {
let fps: number = 13;
let waitTime: number = 0.07;
let changeRate: number = sec * fps; // -1為了讓changeRate數字能混亂點
changeRate = changeRate - 1 <= 0 ? changeRate : changeRate - 1;
changeRate = 1 / changeRate;
let diff: number = maxNum - minNum;
let isIncrease: boolean = maxNum >= minNum;
let tempScore: number = minNum;
let counter: number = 0;
let randomRate: number = 0;
let lastNum: number = minNum;
let nowNum: number = minNum;
while (true) {
if (maxNum !== tempScore) {
if (counter % 2 === 0) {
if (isIncrease) {
randomRate = RandomEx.GetFloat(0, diff * changeRate).ExToNumFloorDecimal(2);
} else {
randomRate = RandomEx.GetFloat(0, -diff * changeRate).ExToNumFloorDecimal(2);
}
} else {
randomRate = -randomRate;
}
tempScore += diff * changeRate + randomRate;
// 遞增
if (isIncrease && tempScore > maxNum) {
tempScore = maxNum;
}
// 遞減
if (!isIncrease && tempScore < maxNum) {
tempScore = maxNum;
}
while (nowNum === lastNum) {
nowNum = RandomEx.GetInt(minNum, maxNum + 1);
}
lastNum = nowNum;
callbackfn(nowNum);
// yield null;
counter++;
yield CoroutineV2.WaitTime(waitTime);
} else {
callbackfn(endNum);
break;
}
}
}
/**是否进行边界检查 */
let _boundaryCheckingState = false;
/**
*
* @param {*number} num
*/
function checkBoundary(num: number) {
if (_boundaryCheckingState) {
if (num > Number.MAX_SAFE_INTEGER || num < Number.MIN_SAFE_INTEGER) {
console.warn(`${num} is beyond boundary when transfer to integer, the results may not be accurate`);
}
}
}
/**
*
*/
export function times(num1: number, num2: number, ...others: number[]): number {
if (others.length > 0) {
return times(times(num1, num2), others[0], ...others.slice(1));
}
const num1Changed = num1.Float2Fixed();
const num2Changed = num2.Float2Fixed();
const baseNum = num1.DigitLength() + num2.DigitLength();
const leftValue = num1Changed * num2Changed;
checkBoundary(leftValue);
return leftValue / Math.pow(10, baseNum);
}
/**
*
*/
export function plus(num1: number, num2: number, ...others: number[]): number {
if (others.length > 0) {
return plus(plus(num1, num2), others[0], ...others.slice(1));
}
const baseNum = Math.pow(10, Math.max(num1.DigitLength(), num2.DigitLength()));
return (times(num1, baseNum) + times(num2, baseNum)) / baseNum;
}
/**
*
*/
export function minus(num1: number, num2: number, ...others: number[]): number {
if (others.length > 0) {
return minus(minus(num1, num2), others[0], ...others.slice(1));
}
const baseNum = Math.pow(10, Math.max(num1.DigitLength(), num2.DigitLength()));
return (times(num1, baseNum) - times(num2, baseNum)) / baseNum;
}
/**
*
*/
export function divide(num1: number, num2: number, ...others: number[]): number {
if (others.length > 0) {
return divide(divide(num1, num2), others[0], ...others.slice(1));
}
const num1Changed = num1.Float2Fixed();
const num2Changed = num2.Float2Fixed();
checkBoundary(num1Changed);
checkBoundary(num2Changed);
return times((num1Changed / num2Changed), Math.pow(10, num2.DigitLength() - num1.DigitLength()));
}
/**
*
*/
export function round(num: number, ratio: number): number {
const base = Math.pow(10, ratio);
return divide(Math.round(times(num, base)), base);
}
}

View File

@ -0,0 +1,92 @@
export module RandomEx {
/**
*
*/
export function GetBool() {
return GetInt() >= 0;
}
/**
* (min ~ max - 1)
* @param min
* @param max
*/
export function GetInt(min: number = Number.MIN_VALUE, max: number = Number.MAX_VALUE): number {
return Math.floor(Math.random() * (max - min)) + min;
}
/**
*
* @param min
* @param max
*/
export function GetFloat(min: number = Number.MIN_VALUE, max: number = Number.MAX_VALUE): number {
return Math.random() * (max - min) + min;
}
/**
*
* @param num
* @param items
*/
export function GetMultiNoRepeat(num: number, items: any[]): any[] {
let result: any[] = [];
for (let i: number = 0; i < num; i++) {
let ran: number = Math.floor(Math.random() * items.length);
let item = items.splice(ran, 1)[0];
if (result.indexOf(item) == -1) {
result.push(item);
}
}
return result;
}
/**
*
* @param prize
* @param weights
* @param count
*/
export function GetMultiNoRepeatByWeight(prize: any[], weights: number[] = null, count: number = 1): any[] {
if (weights === null) {
weights = [];
for (let i: number = 0; i < prize.length; i++) {
weights.push(1);
}
}
let target: any[] = [];
for (let i: number = 0; i < count; i++) {
let results: number[] = RandomEx.GetPrizeByWeight(prize, weights);
prize.splice(results[0], 1);
weights.splice(results[0], 1);
target.push(results[1]);
}
return target;
}
/**
*
* @param prize
* @param weights
*/
export function GetPrizeByWeight(prize: any[], weights: number[]): any[] {
if (prize.length !== weights.length) {
console.error(`GetWeight error -> prize.length:${prize.length} !== weights.length:${weights.length}`);
return null;
}
let totalWeight: number = 0;
for (let i: number = 0; i < weights.length; i++) {
totalWeight += weights[i];
}
let random: number = RandomEx.GetInt(0, totalWeight) + 1;
let nowWeight: number = weights[0];
for (let i: number = 0; i < weights.length; i++) {
if (nowWeight >= random) {
return [i, prize[i]];
}
nowWeight += weights[i + 1];
}
}
}

View File

@ -0,0 +1,27 @@
/**
* (new在使用)
* @example
* export default class Test extends BaseSingleton<Test>() { ...... }
* new Test();
* Test.Instance.Init();
*/
// tslint:disable-next-line:typedef
export default function BaseSingleton<T>() {
class BaseSingleton {
public constructor() {
if ((<any>this)._instance == null) {
BaseSingleton._instance = <any>this;
}
}
public static _instance: BaseSingleton = null;
public static get Instance(): T {
return (<any>this)._instance;
}
/** 銷毀 */
public Disp(): void {
(<any>this)._instance = null;
}
}
return BaseSingleton;
}

View File

@ -0,0 +1,28 @@
/**
*
* @example
* export default class Test extends BaseSigleton<Test>() { ...... }
* Test.Instance.Init();
*/
// tslint:disable-next-line:typedef
export default function BaseSingletonV2<T>() {
class BaseSingleton {
protected constructor() { }
public static _instance: BaseSingleton = null;
public static get Instance(): T {
if ((<any>this)._instance == null) {
(<any>this)._instance = new (<any>this)();
}
return (<any>this)._instance;
}
/** 初始化 */
public Init(): void { /** */ }
/** 銷毀 */
public Disp(): void {
(<any>this)._instance = null;
}
}
return BaseSingleton;
}

16
src/Utils/catan.ts Normal file
View File

@ -0,0 +1,16 @@
// src/Utils/catan.ts
namespace cc {
export function log(msg: string | any, ...subst: any[]): void {
console.log(msg, ...subst);
}
export function warn(msg: string | any, ...subst: any[]): void {
console.warn(msg, ...subst);
}
export function error(msg: string | any, ...subst: any[]): void {
console.error(msg, ...subst);
}
}
if (!window["cc"]) {
window["cc"] = cc;
}

15
src/Utils/tools.ts Normal file
View File

@ -0,0 +1,15 @@
import path from 'path';
export const slotJsons: Map<number, any> = new Map();
export function getExternalJsonPath(filename: string): string {
if (process.env.NODE_ENV === 'dev') {
// 开发模式下使用相对路径
return path.join(__dirname, '../../shared/jsons', filename);
} else {
// 生产模式下使用外部路径
const filepath = path.join(__dirname, `../../../shared/jsons/${filename}`);
// console.log('filepath', filepath);
return filepath;
}
}

18
src/api/account/login.ts Normal file
View File

@ -0,0 +1,18 @@
import { INetRequest } from "../../script/Engine/CatanEngine/NetManagerV2/Core/INetRequest";
import { INetResponse } from "../../script/Engine/CatanEngine/NetManagerV2/Core/INetResponse";
import { RpcAccountLoginRequest, RpcAccountLoginResponse } from "../../shared/protocols/AccountRequest";
import { ClientData } from "../../shared/protocols/define/interface";
export default function* (clientData: ClientData, req: INetRequest<RpcAccountLoginRequest>): IterableIterator<any> {
const data: RpcAccountLoginRequest = req.Data
clientData.name = `test_${clientData.id}`;
clientData.money = 1000000000;
const response: INetResponse<RpcAccountLoginResponse> = {
Status: 0,
Method: req.Method,
Data: { pr: 2, cu: "TWC", id: clientData.id, name: clientData.name, m: clientData.money },
IsValid: true
};
return response;
}

View File

@ -0,0 +1,49 @@
import { INetRequest } from "../../script/Engine/CatanEngine/NetManagerV2/Core/INetRequest";
import { INetResponse } from "../../script/Engine/CatanEngine/NetManagerV2/Core/INetResponse";
import { ClientData } from "../../shared/protocols/define/interface";
import { RpcSlot1SpinRequest, RpcSlot1SpinResponse } from "../../shared/protocols/Slot1Request";
import SlotSpinBase from "./SlotSpinBase";
export default abstract class SlotFgSpinBase extends SlotSpinBase {
public *fgspin(clientData: ClientData, req: INetRequest<RpcSlot1SpinRequest>): IterableIterator<any> {
const data: RpcSlot1SpinRequest = req.Data
yield* this.init(clientData);
// if (clientData.reqFree) {
// const { count, freeData } = clientData.free;
// const slotData: any = freeData[count];
// const response: INetResponse<RpcSlot1SpinResponse> = {
// Status: 0,
// Method: req.Method,
// Data: slotData,
// IsValid: true
// };
// clientData.free.count++;
// return response;
// }
const { nowFree, freeData } = clientData.free;
const { count, totalGet } = clientData.reqFree;
const slotData: any = freeData[count];
if (clientData.reqFree && nowFree === count - 1) {
if (totalGet) {
slotData.get = [[1, totalGet]];
}
if (slotData.get) {
clientData.money += slotData.get[0][1];
}
slotData["money"] = clientData.money;
}
const response: INetResponse<RpcSlot1SpinResponse> = {
Status: 0,
Method: req.Method,
Data: slotData,
IsValid: true
};
clientData.free.nowFree++;
return response;
}
}

View File

@ -0,0 +1,27 @@
import { INetRequest } from "../../script/Engine/CatanEngine/NetManagerV2/Core/INetRequest";
import { INetResponse } from "../../script/Engine/CatanEngine/NetManagerV2/Core/INetResponse";
import { ClientData } from "../../shared/protocols/define/interface";
import { RpcSlotInRequest, RpcSlotInResponse } from "../../shared/protocols/SlotRequest";
export default abstract class SlotInBase {
protected ver: string = "";
protected abstract db: number;
protected abstract br: number[];
protected jp: { [key: string]: number; };
public *in(clientData: ClientData, req: INetRequest<RpcSlotInRequest>): IterableIterator<any> {
const data: RpcSlotInRequest = req.Data;
const { ver, db, br, jp } = this;
const respData = { ver, db, br };
if (jp) respData["jp"] = jp;
const response: INetResponse<RpcSlotInResponse> = {
Status: 0,
Method: req.Method,
Data: respData,
IsValid: true
};
return response;
}
}

View File

@ -0,0 +1,8 @@
export default abstract class SlotSetting {
public IsHaveFreeSpin: boolean = false;
public IsHaveReSpin: boolean = false;
public IsHaveRetriggerFreeSpin: boolean = false;
public IsHaveReq: boolean = false;
public FreeMax: number = 50;
}

View File

@ -0,0 +1,206 @@
import * as fs from 'fs';
import { INetRequest } from "../../script/Engine/CatanEngine/NetManagerV2/Core/INetRequest";
import { INetResponse } from "../../script/Engine/CatanEngine/NetManagerV2/Core/INetResponse";
import { ClientData } from "../../shared/protocols/define/interface";
import { RpcSlot1SpinRequest, RpcSlot1SpinResponse } from "../../shared/protocols/Slot1Request";
import { RandomEx } from "../../Utils/Number/RandomEx";
import { getExternalJsonPath, slotJsons } from "../../Utils/tools";
import SlotSetting from './SlotSetting';
export default abstract class SlotSpinBase {
protected setting: SlotSetting;
protected ID: number;
protected get IsHaveFreeSpin(): boolean { return this.setting.IsHaveFreeSpin };
protected get IsHaveReSpin(): boolean { return this.setting.IsHaveReSpin };
protected get IsHaveRetriggerFreeSpin(): boolean { return this.setting.IsHaveRetriggerFreeSpin };
protected get IsHaveReq(): boolean { return this.setting.IsHaveReq };
protected get FreeMax(): number { return this.setting.FreeMax };
protected JsonData: any;
protected get Temps(): JSON[] { return this.JsonData.slotData };
protected get FreeTemps(): JSON[] { return this.JsonData.slotFreeData };
public *spin(clientData: ClientData, req: INetRequest<RpcSlot1SpinRequest>): IterableIterator<any> {
const data: RpcSlot1SpinRequest = req.Data
yield* this.init(clientData);
clientData.free = null;
clientData.reqFree = null;
const slotData: any = this.Temps[RandomEx.GetInt(0, this.Temps.length)];
delete slotData.get;
delete slotData.money;
let totalGet: number = 0;
if (slotData.line) {
totalGet += this.getLineMoney(slotData.line);
}
if (slotData.way) {
totalGet += this.getWayMoney(slotData.way);
}
if (slotData.scatter) {
totalGet += this.getScatterMoney(slotData.scatter);
if (this.IsHaveReq && this.isHasReqFree(slotData.scatter)) {
clientData.reqFree = { count: 0, totalGet };
}
}
if (this.IsHaveFreeSpin && slotData.free) {
const count = slotData.free[1];
const { freeData, totalFreeGet } = this.getFree(count);
totalGet += totalFreeGet;
clientData.free = { nowFree: 0, freeData };
}
if (totalGet) {
slotData.get = [[1, totalGet]];
}
clientData.money -= data.pay;
if (clientData.reqFree) {
slotData["rs"] = 0;
} else {
if (slotData.get) {
clientData.money += slotData.get[0][1];
}
slotData["money"] = clientData.money;
}
slotData["pay"] = [[1, -data.pay]];
const response: INetResponse<RpcSlot1SpinResponse> = {
Status: 0,
Method: req.Method,
Data: slotData,
IsValid: true
};
return response;
}
public getFree(count: number) {
let freeData = [];
let totalFreeGet = 0;
for (let i: number = 0; i < count; i++) {
const slotData: any = this.FreeTemps[RandomEx.GetInt(0, this.FreeTemps.length)];
delete slotData.get;
delete slotData.money;
freeData.push(slotData);
if (slotData.line) {
totalFreeGet += this.getLineMoney(slotData.line);
}
if (slotData.scatter) {
totalFreeGet += this.getScatterMoney(slotData.scatter);
if (this.IsHaveRetriggerFreeSpin && slotData.free && freeData.length < this.FreeMax) {
const count = slotData.free[1] + freeData.length > this.FreeMax
? slotData.free[1] + freeData.length - this.FreeMax
: slotData.free[1];
const { freeData: reFreeData, totalFreeGet: reTotalFreeGet } = this.getFree(count);
freeData = freeData.concat(reFreeData);
totalFreeGet += reTotalFreeGet;
}
}
}
return { freeData, totalFreeGet }
}
private getLineMoney(lines: any[][]): number {
let totalGet: number = 0;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
totalGet += line[2]
}
return totalGet;
}
private getWayMoney(ways: any[][]): number {
let totalGet: number = 0;
for (let i = 0; i < ways.length; i++) {
const way = ways[i];
totalGet += way[1]
}
return totalGet;
}
private getScatterMoney(scatters: any[][]): number {
let totalGet: number = 0;
let hasReqFree: boolean = false;
for (let i = 0; i < scatters.length; i++) {
const scatter = scatters[i];
totalGet += scatter[1]
if (this.IsHaveReq && scatter[0].length >= 3) {
hasReqFree = true;
}
}
return totalGet;
}
private isHasReqFree(scatters: any[][]): boolean {
for (let i = 0; i < scatters.length; i++) {
const scatter = scatters[i];
if (this.IsHaveReq && scatter[0].length >= 3) {
return true;
}
}
return false;
}
protected async GetTemps(): Promise<any> {
const filePath = getExternalJsonPath(`slot${this.ID}.json`);
// console.log('filePath', filePath);
const data = await fs.promises.readFile(filePath, 'utf-8');
const jsonData = JSON.parse(data);
return jsonData;
}
public *init(clientData: ClientData): IterableIterator<any> {
const self = this;
this.ID = clientData.slotId;
let AsyncFunction = async function () {
let module = await import(`../slot${self.ID}/setting`);
self.setting = new module.default();
};
AsyncFunction();
while (!this.setting) {
yield;
}
if (!slotJsons.has(this.ID)) {
let slotData: any;
let AsyncFunction = async function () {
slotData = await self.GetTemps();
};
AsyncFunction();
while (!slotData) {
yield;
}
slotJsons.set(this.ID, slotData);
}
this.JsonData = slotJsons.get(this.ID);
}
}
// // 原始的编码字符串
// // // {"slot":[4,4,1,12,14,9,10,1,6,4,8,9,1,14,10],"scatter":[[[2,7,12],100]],"pay":[[1,-100]],"get":[[1,100]],"money":9990624}
// // const encodedStr = 'H4sIAAAAAAAAA1WOMQ+CMBCF/0vnG9paRdkcSRwMjoahQjE1hZIWooTw371TSLS54d3ru7tvYpylE4vO97mJg+uztvbkNNq2WcVSAax2/rnIe/BDR5qj7UNDUipgtq3M62Rjz9KrUrBNgMMGSxbw2b1+gQIBQoJQcADBsdmhtaeGPMGLGZhutRujjbkxLq50Zz0uaJh4dJexuXm3wlY2mJIiX16kOdI5CQkeo5Wx/B/oKMp/oshZB2PQnPG9ARdrnfUWAQAA';
// // {"slot":[5,6,14,6,5,11,12,3,14,11,6,2,11,2,12],"line":[[[5,11,12,13,9],161,1500]],"pay":[[1,-100]],"get":[[1,1500]],"money":9991524}
// const encodedStr = 'H4sIAAAAAAAAA11Pu27DMAz8F84cQjlSEG9FpwAdinQMMqi2XKiQJcOykRqB/72kH0OrQTreEXenJxygfEIOabi6PIbhEpskTGt9vNRQEkIT0mODX30aO8EHplPfClRHBB9r9/Pm8wDlrdBIZ6QjFoSk77iYb5pGI4pB3mFVYSEjQ4NKHr7UfUaw0YYp+3x1LuS94Lud9nZkaGGn9jMFbsbVqjRGjtAIDx/q13VSCMFHt7Q3JMNWY02nAs9csLMT63rm4O/uY/Hcg2rfu0qS1y/zP1/EQDrm6u9q93/pxNZN7xyzM59fbBqv0msBAAA=';
// // 解码 Base64
// const decodedBytes = Buffer.from(encodedStr, 'base64');
// // 解压缩 zlib 数据
// gunzip(decodedBytes, (err, decompressedBytes) => {
// if (err) {
// console.error('解压缩失败:', err);
// return;
// }
// // 解析 JSON 数据
// try {
// const jsonData = JSON.parse(decompressedBytes.toString());
// console.log(JSON.stringify(jsonData, null, 4));
// } catch (parseErr) {
// console.error('JSON 解析失败:', parseErr);
// }
// });

8
src/api/slot/ae.ts Normal file
View File

@ -0,0 +1,8 @@
import { INetRequest } from "../../script/Engine/CatanEngine/NetManagerV2/Core/INetRequest";
import { ClientData } from "../../shared/protocols/define/interface";
export default function* (clientData: ClientData, req: INetRequest<any>): IterableIterator<any> {
const data = req.Data
return null;
}

23
src/api/slot/in.ts Normal file
View File

@ -0,0 +1,23 @@
import { INetRequest } from "../../script/Engine/CatanEngine/NetManagerV2/Core/INetRequest";
import { INetResponse } from "../../script/Engine/CatanEngine/NetManagerV2/Core/INetResponse";
import { ClientData } from "../../shared/protocols/define/interface";
import { RpcSlotInRequest, RpcSlotInResponse } from "../../shared/protocols/SlotRequest";
import SlotIn1 from "../slot1/in";
export default function* (clientData: ClientData, req: INetRequest<RpcSlotInRequest>): IterableIterator<any> {
const data: RpcSlotInRequest = req.Data;
const { id } = data;
clientData.slotId = id;
let moduleClass: SlotIn1;
let AsyncFunction = async function () {
const module = await import(`../slot${id}/in`);
moduleClass = new module.default();
};
AsyncFunction();
while (!moduleClass) {
yield;
}
const response: INetResponse<RpcSlotInResponse> = yield* moduleClass.in(clientData, req);
return response;
}

35
src/api/slot/req.ts Normal file
View File

@ -0,0 +1,35 @@
import { INetRequest } from "../../script/Engine/CatanEngine/NetManagerV2/Core/INetRequest";
import { INetResponse } from "../../script/Engine/CatanEngine/NetManagerV2/Core/INetResponse";
import { ClientData } from "../../shared/protocols/define/interface";
import { RpcSlotReqRequest, RpcSlotReqResponse } from "../../shared/protocols/SlotRequest";
import SlotFgSpinBase from "./SlotFgSpinBase";
export default function* (clientData: ClientData, req: INetRequest<RpcSlotReqRequest>): IterableIterator<any> {
const count = req.Data
const { slotId } = clientData;
let totalGet = clientData.reqFree.totalGet;
let fgspin: SlotFgSpinBase;
let AsyncFunction = async function () {
let module = await import(`../slot${slotId}/fgspin`);
fgspin = new module.default();
};
AsyncFunction();
while (!fgspin) {
yield;
}
yield* fgspin.init(clientData);
const { freeData, totalFreeGet } = fgspin.getFree(count);
totalGet += totalFreeGet;
clientData.free = { nowFree: 0, freeData };
clientData.reqFree = { count: count, totalGet };
const response: INetResponse<RpcSlotReqResponse> = {
Status: 0,
Method: req.Method,
Data: req.Data,
IsValid: true
};
return response;
}

4
src/api/slot1/fgspin.ts Normal file
View File

@ -0,0 +1,4 @@
import SlotFgSpinBase from "../slot/SlotFgSpinBase";
export default class SlotFgSpin1 extends SlotFgSpinBase { }

7
src/api/slot1/in.ts Normal file
View File

@ -0,0 +1,7 @@
import SlotInBase from "../slot/SlotInBase";
export default class SlotIn1 extends SlotInBase {
protected db: number = 4;
protected br: number[] = [4, 10, 20, 40, 80, 100, 120, 160, 200, 400, 600, 800, 1000, 1200, 1600, 2000, 4000, 10000, 20000, 30000];
protected jp: { [key: string]: number; } = { "1": 1500000, "3": 3000000, "5": 30000000 };
}

6
src/api/slot1/setting.ts Normal file
View File

@ -0,0 +1,6 @@
import SlotSetting from "../slot/SlotSetting";
export default class SlotSetting1 extends SlotSetting {
public IsHaveFreeSpin: boolean = true;
public IsHaveRetriggerFreeSpin: boolean = true;
}

3
src/api/slot1/spin.ts Normal file
View File

@ -0,0 +1,3 @@
import SlotSpinBase from "../slot/SlotSpinBase";
export default class SlotSpin1 extends SlotSpinBase { }

4
src/api/slot70/fgspin.ts Normal file
View File

@ -0,0 +1,4 @@
import SlotFgSpinBase from "../slot/SlotFgSpinBase";
export default class SlotFgSpin70 extends SlotFgSpinBase { }

6
src/api/slot70/in.ts Normal file
View File

@ -0,0 +1,6 @@
import SlotInBase from "../slot/SlotInBase";
export default class SlotIn70 extends SlotInBase {
protected db: number = 5;
protected br: number[] = [4, 12, 20, 40, 80, 120, 160, 200, 240, 400, 600, 800, 1000, 1200, 1600, 2000, 4000, 10000, 20000, 30000];
}

View File

@ -0,0 +1,7 @@
import SlotSetting from "../slot/SlotSetting";
export default class SlotSetting70 extends SlotSetting {
public IsHaveFreeSpin: boolean = true;
public IsHaveRetriggerFreeSpin: boolean = true;
public IsHaveReq: boolean = true;
}

3
src/api/slot70/spin.ts Normal file
View File

@ -0,0 +1,3 @@
import SlotSpinBase from "../slot/SlotSpinBase";
export default class SlotSpin70 extends SlotSpinBase { }

38
src/electron/index.css Normal file
View File

@ -0,0 +1,38 @@
/* index.css */
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
display: flex;
height: 100vh;
}
#log-container {
width: 70%;
background-color: black;
color: green;
padding: 10px;
overflow-y: scroll; /* 允许垂直滚动 */
white-space: pre-wrap;
font-family: 'Courier New', Courier, monospace;
box-sizing: border-box; /* 包括 padding 在内的计算方式 */
}
#control-panel {
width: 30%;
padding: 20px;
box-sizing: border-box;
background-color: #f0f0f0;
display: flex;
flex-direction: column;
align-items: flex-start;
}
button {
margin-top: 10px;
}
input {
margin-bottom: 10px;
}

30
src/electron/index.html Normal file
View File

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<title>SD Server Control</title>
<link rel="stylesheet" href="index.css">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<div id="log-container">
<div id="log"></div>
</div>
<div id="control-panel">
<h2>Control Panel</h2>
<label for="port">Port:</label>
<input type="number" id="port" value="8080">
<button id="startBtn">啟動</button>
<button id="stopBtn">關閉</button>
<button id="jsonReload">重載Json</button>
<button id="devToolsBtn">DevTools</button>
<p id="status">Status: Waiting for actions...</p>
</div>
<script src="renderer.js"></script>
</body>
</html>

156
src/electron/main.ts Normal file
View File

@ -0,0 +1,156 @@
// src/electron/main.ts
// 需要先載入
import "module-alias/register";
// // 必載入
// import "../Utils/catan.ts";
import dotenv from 'dotenv';
import { app, BrowserWindow, ipcMain } from 'electron';
import fs from 'fs';
import * as path from 'path';
import { WebSocketServer } from 'ws';
import { BaseEnumerator } from "../CatanEngine/CoroutineV2/Core/BaseEnumerator";
import { NetConnector } from "../script/Engine/CatanEngine/NetManagerV2/NetConnector";
import { slotJsons } from "../Utils/tools";
onload();
function onload() {
BaseEnumerator.Init();
setLog();
// Load environment variables from .env file
const filePath = ".env.dev";
// 检查文件是否存在
if (fs.existsSync(filePath)) {
dotenv.config({ path: `.env.dev` });
} else {
dotenv.config({ path: path.resolve(__dirname, `../../.env.prod`) });
}
}
let server: WebSocketServer | null = null;
let port: number = process.env.PORT ? parseInt(process.env.PORT, 10) : 8080; // 默认端口为 8080
function createWindow() {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false,
},
});
win.loadFile(path.join(__dirname, 'index.html'));
// win.webContents.openDevTools();
return win;
}
let mainWindow: BrowserWindow | null = null;
app.whenReady().then(() => {
mainWindow = createWindow();
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
// 启动 WebSocket 服务器
ipcMain.on('start-websocket', (event: Electron.IpcMainEvent, portNumber: number) => {
if (server) {
closeServer(event);
}
port = portNumber || 8080;
server = new WebSocketServer({ port });
server.on('connection', NetConnector.OnWebSocketConnection);
event.reply('websocket-status', `WebSocket server started on port ${port}`);
});
// 关闭 WebSocket 服务器
ipcMain.on('stop-websocket', (event: Electron.IpcMainEvent) => {
closeServer(event);
});
// 打开开发者工具
ipcMain.on('json-reload', () => {
NetConnector.clients.forEach(client => {
slotJsons.clear();
});
console.log(`重載成功`);
});
// 打开开发者工具
ipcMain.on('open-devtools', () => {
mainWindow.webContents.openDevTools();
});
function closeServer(event: Electron.IpcMainEvent) {
if (server) {
server.clients.forEach(client => {
if (client.readyState === 1) {
client.close();
}
});
server.close((err) => {
if (err) {
event.reply('websocket-status', 'Failed to stop WebSocket server');
}
});
event.reply('websocket-status', 'WebSocket server stopped');
} else {
event.reply('websocket-status', 'No WebSocket server is running');
}
}
// Create a function to send log messages to the renderer
function sendLogToRenderer(window: BrowserWindow, message: string, color: string) {
window.webContents.send('log-message', [message, color]);
}
function setLog() {
// 重寫 console.log 方法以便將訊息發送到渲染進程
const originalConsoleLog = console.log;
console.log = (...args: any[]) => {
const message = args.join(' ');
originalConsoleLog(message); // 保留原始行為
if (BrowserWindow.getAllWindows().length > 0) {
sendLogToRenderer(BrowserWindow.getAllWindows()[0], message, "green"); // 發送訊息到渲染進程
}
};
// 重写 console.warn
const originalConsoleWarn = console.warn;
console.warn = (...args: any[]) => {
const message = args.join(' ');
originalConsoleWarn(message);
if (BrowserWindow.getAllWindows().length > 0) {
sendLogToRenderer(BrowserWindow.getAllWindows()[0], message, "yellow"); // 發送訊息到渲染進程
}
};
// 重写 console.error
const originalConsoleError = console.error;
console.error = (...args: any[]) => {
const message = args.join(' ');
originalConsoleError(message);
if (BrowserWindow.getAllWindows().length > 0) {
sendLogToRenderer(BrowserWindow.getAllWindows()[0], message, "red"); // 發送訊息到渲染進程
}
};
}

15
src/electron/preload.ts Normal file
View File

@ -0,0 +1,15 @@
// src/electron/preload.ts
import { contextBridge, ipcRenderer } from 'electron';
// import "../Utils/catan"; // 导入定义全局 cc 对象的文件
contextBridge.exposeInMainWorld('electron', {
startWebSocket: (port: number) => ipcRenderer.send('start-websocket', port),
stopWebSocket: () => ipcRenderer.send('stop-websocket'),
jsonReload: () => ipcRenderer.send('json-reload'),
openDevTools: () => ipcRenderer.send('open-devtools'),
onWebSocketStatus: (callback: (message: string) => void) => ipcRenderer.on('websocket-status', (event, message) => callback(message)),
onLogMessage: (callback: (message: string, color: string) => void) => ipcRenderer.on('log-message', (event, [message, color]) => callback(message, color)),
env: {
PORT: process.env.PORT || '8080' // 提供環境變數
}
});

79
src/electron/renderer.ts Normal file
View File

@ -0,0 +1,79 @@
// src/electron/renderer.ts
const maxLogs = 200; // 设置最大日志数量
const logs: string[] = [];
// 格式化时间戳的函数
function formatDate(date: Date): string {
// const year = date.getFullYear();
// const month = String(date.getMonth() + 1).padStart(2, '0'); // 月份从0开始
// const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
// return `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`;
return `${hours}:${minutes}:${seconds}`;
}
function updateLogDisplay() {
const logContainer = document.getElementById('log') as HTMLElement;
if (logContainer) {
logContainer.innerHTML = logs.join(''); // 使用 innerHTML 来渲染带有样式的日志
logContainer.scrollTop = 0; // 保持滚动位置在顶部
}
}
function addLog(message: string, color: string = 'green') {
const timestamp = formatDate(new Date()); // 使用当前时间生成时间戳
const logMessage = `<div style="color:${color};">[${timestamp}] ${message}</div>`; // 在日志消息前添加时间戳,并设置颜色
logs.unshift(logMessage); // 在数组开头插入新消息
// 如果日志超过200条删除最旧的一条数组末尾的元素
if (logs.length > maxLogs) {
logs.pop();
}
updateLogDisplay();
}
// 设置默认端口值
const portInputElement = document.getElementById('port') as HTMLInputElement;
if (portInputElement) {
const defaultPort = window.electron.env.PORT; // 从预加载脚本中获取默认端口
portInputElement.value = defaultPort;
}
document.getElementById('startBtn')?.addEventListener('click', () => {
const portElement = document.getElementById('port') as HTMLInputElement;
const port = parseInt(portElement.value, 10);
window.electron.startWebSocket(port);
});
document.getElementById('stopBtn')?.addEventListener('click', () => {
window.electron.stopWebSocket();
});
document.getElementById('jsonReload')?.addEventListener('click', () => {
window.electron.jsonReload();
});
document.getElementById('devToolsBtn')?.addEventListener('click', () => {
window.electron.openDevTools();
});
window.electron.onWebSocketStatus((message: string) => {
console.log(`WebSocket status: ${message}`);
const statusElement = document.getElementById('status');
if (statusElement) {
statusElement.innerText = `Status: ${message}`;
}
addLog(message); // 將 WebSocket 狀態消息添加到日誌顯示區
});
// 监听主进程发送的日志消息
window.electron.onLogMessage((message: string, color: string) => {
addLog(message, color); // 將日誌消息添加到顯示區
});

12
src/global.d.ts vendored Normal file
View File

@ -0,0 +1,12 @@
// src/global.d.ts
interface Window {
electron: {
startWebSocket: (port: number) => void;
stopWebSocket: () => void;
jsonReload: () => void;
openDevTools: () => void;
onWebSocketStatus: (callback: (message: string) => void) => void;
onLogMessage: (callback: (message: string, color: string) => void) => void; // 添加這行以處理日誌消息
env: { PORT: string; };
};
}

42
src/pkg/server.ts Normal file
View File

@ -0,0 +1,42 @@
// src/pkg/server.ts
// 需要先載入
import "module-alias/register";
// // 必載入
// import "../Utils/catan.ts";
import { WebSocketServer } from 'ws';
import { NetConnector } from "../script/Engine/CatanEngine/NetManagerV2/NetConnector";
/*
http://119.77.165.60/MyWeb/SD2/slot1/index.html?token=test&slotid=1&v=1724652287&host=127.0.0.1&language=zh-tw&logo=2&pl=1
http://entry.lybobet.com/casino_sd_2/resource-internal/_Debug/slot1/index.html?token=test&slotid=1&v=1724652287&host=127.0.0.1&language=zh-tw&logo=2&pl=1
ws://192.168.5.36:9005
ws://127.0.0.1:9005
*/
const port: number = process.env.PORT ? parseInt(process.env.PORT, 10) : 8080; // 默认端口为 8080
const server = new WebSocketServer({ port });
// server.on('connection', (socket: WebSocket) => {
// console.log('客户端已连接');
// // 发送消息到客户端
// socket.send('欢迎连接到WebSocket服务器');
// // 接收来自客户端的消息
// socket.on('message', (message: string) => {
// console.log(`收到客户端消息: ${message}`);
// // 回应消息
// socket.send(`服务器收到你的消息: ${message}`);
// });
// // 处理客户端断开连接
// socket.on('close', () => {
// console.log('客户端已断开连接');
// });
// });
server.on('connection', NetConnector.OnWebSocketConnection);
console.log('WebSocket服务器运行在 ws://localhost:8080');

View File

@ -0,0 +1,13 @@
import { Action } from "../../../../../CatanEngine/CSharp/System/Action";
import { INetResponse } from "./INetResponse";
export interface INetConnector {
readonly OnDataReceived: Action<INetResponse<any>>;
readonly OnDisconnected: Action<void>;
readonly IsConnected: boolean;
// SendAsync<TRequest, TResponse>(req: INetRequest<TRequest, TResponse>): Iterator<any>;
// Send<TRequest, TResponse>(req: INetRequest<TRequest, TResponse>);
// Logout();
}

View File

@ -0,0 +1,10 @@
{
"ver": "1.1.0",
"uuid": "f97991b5-0da6-4220-ab29-13c8f8f7e405",
"importer": "typescript",
"isPlugin": false,
"loadPluginInWeb": true,
"loadPluginInNative": true,
"loadPluginInEditor": false,
"subMetas": {}
}

View File

@ -0,0 +1,17 @@
export interface INetRequest<TResponse> {
readonly Method: string;
readonly Data: TResponse;
}
// import { INetResponse } from "./INetResponse";
// export interface INetRequest<TRequest, TResponse> {
// readonly Method: string;
// readonly MethodBack: string;
// Data: TRequest;
// Result: INetResponse<TResponse>;
// SendAsync(): Iterator<any>;
// Send();
// }

View File

@ -0,0 +1,10 @@
{
"ver": "1.1.0",
"uuid": "339fcf27-bdb9-4b8f-ae18-dd54c9500145",
"importer": "typescript",
"isPlugin": false,
"loadPluginInWeb": true,
"loadPluginInNative": true,
"loadPluginInEditor": false,
"subMetas": {}
}

View File

@ -0,0 +1,6 @@
export interface INetResponse<TResponse> {
readonly Method: string;
readonly Status: number;
readonly Data: TResponse;
readonly IsValid: boolean;
}

View File

@ -0,0 +1,10 @@
{
"ver": "1.1.0",
"uuid": "c4cb0cd4-b98c-4f8e-b1e6-ac3b51281b28",
"importer": "typescript",
"isPlugin": false,
"loadPluginInWeb": true,
"loadPluginInNative": true,
"loadPluginInEditor": false,
"subMetas": {}
}

View File

@ -0,0 +1,4 @@
export default class NetConfig {
/**是否顯示RPC接送JSON的LOG */
public static ShowServerLog: boolean = true;
}

View File

@ -0,0 +1,152 @@
import { IncomingMessage } from "http";
import { WebSocket } from "ws";
import { CoroutineV2 } from "../../../../CatanEngine/CoroutineV2/CoroutineV2";
import { Action } from "../../../../CatanEngine/CSharp/System/Action";
import { Encoding } from "../../../../CatanEngine/CSharp/System/Text/Encoding";
import { ClientData } from "../../../../shared/protocols/define/interface";
import { INetRequest } from "./Core/INetRequest";
import { INetResponse } from "./Core/INetResponse";
let id = 1;
export class NetConnector {
readonly OnDataReceived: Action<INetResponse<any>> = new Action<INetResponse<any>>();
readonly OnDisconnected: Action<void> = new Action<void>();
readonly OnLoadUIMask: Action<boolean> = new Action<boolean>();
public static readonly clients = new Map<WebSocket, ClientData>();
public static OnWebSocketConnection(socket: WebSocket, request: IncomingMessage) {
const ip = request.socket.remoteAddress.replace("::ffff:", "") || 'Unknown IP';
console.log(`Client connected from IP: ${ip}`);
NetConnector.clients.set(socket, { socket, id: id, name: "", money: 0 });
id++;
socket.on('message', (message: Buffer) => NetConnector.OnWebSocketMessage(socket, message));
socket.on('close', NetConnector.OnWebSocketClose);
}
public static async OnWebSocketMessage(socket: WebSocket, message: Buffer) {
// 将 Buffer 转换为 ArrayBuffer
const buffer = message.buffer.slice(message.byteOffset, message.byteOffset + message.byteLength);
let startIndex = 0, byteLength = buffer.byteLength;
while (startIndex + 4 < byteLength) {
const view: DataView = new DataView(buffer, 0, 3);
const strlen: number = view.getUint16(0, true) + (view.getUint8(2) << 16);
const decoder: TextDecoder = new TextDecoder("utf-8");
const str: string = decoder.decode(new Uint8Array(buffer, startIndex + 4, strlen));
startIndex += strlen + 4;
// try {
let json = JSON.parse(str);
let method = <string>json[0];
let data = json[1];
let req = <INetRequest<any>>{
Method: method,
Data: data,
};
if (data) {
console.log(`[RPC] 收到client呼叫: ${req.Method}(${JSON.stringify(req.Data)})`);
} else {
console.log(`[RPC] 收到client呼叫: ${req.Method}()`);
}
// 动态导入处理函数
try {
// 动态导入文件
let module = await import(`../../../../api/${req.Method.replace(".", "/")}`);
const isClass = typeof module.default === 'function' && module.default.prototype && Object.getOwnPropertyNames(module.default.prototype).includes('constructor');
// 调用导入模块中的处理函数
if (module.default) {
if (isClass) {
const moduleClass = new module.default();
let AsyncFunction: () => IterableIterator<any> = function* (): IterableIterator<any> {
const clientData: ClientData = NetConnector.clients.get(socket);
const method: string = req.Method.split(".")[1];
try {
const response: INetResponse<any> = yield* moduleClass[method](clientData, req);
if (response) {
NetConnector.Send(socket, response);
}
} catch (error) {
console.error(`Error handling request ${req.Method}: ${error.message}`);
NetConnector.sendError(socket, req);
}
module = null;
};
CoroutineV2.Single(AsyncFunction()).Start();
} else {
let AsyncFunction: () => IterableIterator<any> = function* (): IterableIterator<any> {
const clientData: ClientData = NetConnector.clients.get(socket);
const response: INetResponse<any> = yield* module.default(clientData, req);
if (response) {
NetConnector.Send(socket, response);
}
module = null;
};
CoroutineV2.Single(AsyncFunction()).Start();
}
} else {
throw new Error(`Module for ${req.Method} does not export a default function.`);
}
} catch (error) {
console.error(`Error handling request ${req.Method}: ${error.message}`);
NetConnector.sendError(socket, req);
}
}
}
private static sendError(socket: WebSocket, req: INetRequest<any>) {
const response: INetResponse<any> = {
Status: -1,
Method: req.Method,
Data: null,
IsValid: false
};
NetConnector.Send(socket, response);
}
public static OnWebSocketClose() {
console.log('Client disconnected');
}
private static Send(socket: WebSocket, resp: INetResponse<any>) {
let json: any = [resp.Method, [resp.Status]];
//@ts-ignore
if (resp.Data != null && resp.Data != undefined && resp.Data != NaN) {
json[1].push(resp.Data);
}
//@ts-ignore
if (resp.Data != null && resp.Data != undefined && resp.Data != NaN) {
console.log(`[RPC] 回傳client呼叫:(${resp.Status}): ${resp.Method}(${JSON.stringify(resp.Data)})`);
} else {
console.log(`[RPC] 回傳client呼叫:(${resp.Status}): ${resp.Method}()`);
}
let str = JSON.stringify(json);
if (str.length > 65535) {
throw new Error("要傳的資料太大囉");
}
let strary = Encoding.UTF8.GetBytes(str);
let buffer = new Uint8Array(4 + strary.byteLength);
let u16ary = new Uint16Array(buffer.buffer, 0, 3);
u16ary[0] = strary.byteLength;
buffer[3] = 0x01;
buffer.set(strary, 4);
socket.send(buffer);
}
}
const ErrorResponse: INetResponse<any> = {
Status: -1,
Method: "",
Data: {},
IsValid: false
};

View File

@ -0,0 +1,53 @@
// import { INetRequest } from "./Core/INetRequest";
// import { NetConnector } from "./NetConnector";
// export class NetManager {
// static get IsConnected() { return this._connector && this._connector.IsConnected; }
// static get HasInit() { return this._connector != null; }
// private static _connector: NetConnector;
// static Initialize(connector: NetConnector) {
// this._connector = connector;
// }
// static ConnectAsync() {
// this.CheckConnector();
// return this._connector.ConnectAsync();
// }
// /**
// * 斷線
// */
// static Disconnect() {
// this.CheckConnector();
// this._connector.Disconnect();
// }
// /**
// * 傳送資料給Server, 不等待回應
// * @param req
// */
// static Send(req: INetRequest<any, any>) {
// this.CheckConnector();
// if (NativeClass.IsLineProject) {
// NativeClass.Instance.GetSend(req);
// } else {
// this._connector.Send(req);
// }
// }
// /**
// * 傳送資料給Server, 並等待回應
// * @param req
// */
// static SendAsync(req: INetRequest<any, any>, mask: boolean) {
// this.CheckConnector();
// return this._connector.SendAsync(req, mask);
// }
// private static CheckConnector() {
// if (!this._connector) throw new Error("請先呼叫CasinoNetManager.Initialize()初始化connector");
// }
// }

View File

@ -0,0 +1,21 @@
// import { INetRequest } from "./Core/INetRequest";
// import { NetManager } from "./NetManager";
// export abstract class NetRequest<TResquest, TResponse> implements INetRequest<TResquest, TResponse> {
// abstract get Method(): string;
// get MethodBack(): string {
// return this.Method;
// }
// Data: TResquest;
// Result: import("./Core/INetResponse").INetResponse<TResponse>;
// SendAsync(mask: boolean = false): Iterator<any> {
// return NetManager.SendAsync(this, mask);
// }
// Send() {
// NetManager.Send(this);
// }
// }

0
src/shared/.gitkeep Normal file
View File

View File

@ -0,0 +1,6 @@
// #region Request
export type RpcAccountLoginRequest = { token: string }
export type RpcAccountLoginResponse = { "pr": number, "cu": string, "id": number, "name": string, "m": number }
// #endregion

View File

@ -0,0 +1,6 @@
// #region Request
export type RpcSlot1SpinRequest = { pay: number }
export type RpcSlot1SpinResponse = { slot: number[], pay: number[][], money: number }
// #endregion

View File

@ -0,0 +1,9 @@
// #region Request
export type RpcSlotInRequest = { id: number }
export type RpcSlotInResponse = { ver: string, db: number, br: number[], jp?: { [key: string]: number } }
export type RpcSlotReqRequest = number
export type RpcSlotReqResponse = number
// #endregion

View File

@ -0,0 +1,4 @@
// export enum EGameState {
// /** 準備 */
// Ready,
// }

View File

@ -0,0 +1,12 @@
import { WebSocket } from "ws";
// ClientData
export interface ClientData {
socket: WebSocket;
id: number;
name: string;
money: number;
slotId?: number;
free?: { nowFree: number, freeData: any[] };
reqFree?: { count: number, totalGet: number };
}

23
tsconfig.json Normal file
View File

@ -0,0 +1,23 @@
{
"compilerOptions": {
"target": "es2015", // ES2015
"module": "commonjs", // 使 CommonJS
"moduleResolution": "node",
"sourceMap": true,
"strict": false, //
"esModuleInterop": true, // ES
"skipLibCheck": true, //
"forceConsistentCasingInFileNames": true,
// "baseUrl": ".",
// "paths": {
// "@/*": ["src/*"]
// },
"outDir": "./dist",
"rootDir": "./src",
},
"include": [
"src/**/*.ts"
],
"exclude": ["node_modules", "dist", "release"]
}

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.

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.

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.

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