Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
05cbd9daa9 | |||
f8528713e6 | |||
84bba89e7e | |||
acd1cb7337 | |||
735a0a6bb3 | |||
e741029939 | |||
04789455a3 | |||
a22640bd2b | |||
1d0ad3fd99 | |||
cad4bb3b56 |
367
.eslintrc.json
Normal file
367
.eslintrc.json
Normal 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, //switch中的case标签不能重复
|
||||
"no-else-return": "off", //如果if语句里面有return,后面不能跟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", //禁止else语句内只有if语句
|
||||
"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()之前不能使用this或super
|
||||
"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", //禁止不必要的call和apply
|
||||
"no-void": "off", //禁用void操作符
|
||||
"no-var": 0, //禁用var,用let和const代替
|
||||
"no-warning-comments": "off", //不能有警告备注
|
||||
"no-array-constructor": "error", // 禁止使用数组构造器
|
||||
"no-caller": "error", // 禁止使用arguments.caller或arguments.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", // label名不能与var声明的变量名相同
|
||||
"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", // 禁止使用new创建包装实例,new 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", // 禁止比较时使用NaN,只能用isNaN()
|
||||
"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, //非派生类不能调用super,派生类必须调用super
|
||||
"default-case": "off", //switch语句最后必须有default
|
||||
"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 in循环要用if语句过滤
|
||||
"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", //函数名首行大写必须使用new方式调用,首行小写必须用不带new方式调用
|
||||
"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
16
.gitignore
vendored
@ -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
23
.vscode/launch.json
vendored
Normal 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
20
.vscode/tasks.json
vendored
Normal 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
10
nodemon.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"watch": [
|
||||
"src"
|
||||
],
|
||||
"ext": "ts",
|
||||
"exec": "npm run build",
|
||||
"ignore": [
|
||||
"dist"
|
||||
]
|
||||
}
|
5732
package-lock.json
generated
Normal file
5732
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
66
package.json
Normal file
66
package.json
Normal 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": ""
|
||||
}
|
29
shared/jsons/slot70 copy.json
Normal file
29
shared/jsons/slot70 copy.json
Normal 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
8
shared/jsons/slot70.json
Normal 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}
|
||||
]
|
||||
}
|
16
src/CatanEngine/CSharp/String.ts
Normal file
16
src/CatanEngine/CSharp/String.ts
Normal 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;
|
||||
});
|
||||
}
|
125
src/CatanEngine/CSharp/System/Action.ts
Normal file
125
src/CatanEngine/CSharp/System/Action.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
166
src/CatanEngine/CSharp/System/ActionWithType.ts
Normal file
166
src/CatanEngine/CSharp/System/ActionWithType.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
166
src/CatanEngine/CSharp/System/ActionWithType2.ts
Normal file
166
src/CatanEngine/CSharp/System/ActionWithType2.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
106
src/CatanEngine/CSharp/System/Text/Encoding.ts
Normal file
106
src/CatanEngine/CSharp/System/Text/Encoding.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
43
src/CatanEngine/CoroutineV2/CancellationTokenSource.ts
Normal file
43
src/CatanEngine/CoroutineV2/CancellationTokenSource.ts
Normal 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;
|
||||
}
|
||||
}
|
17
src/CatanEngine/CoroutineV2/Core/ActionEnumerator.ts
Normal file
17
src/CatanEngine/CoroutineV2/Core/ActionEnumerator.ts
Normal 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 };
|
||||
}
|
||||
}
|
131
src/CatanEngine/CoroutineV2/Core/BaseEnumerator.ts
Normal file
131
src/CatanEngine/CoroutineV2/Core/BaseEnumerator.ts
Normal 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);
|
||||
}
|
||||
}
|
103
src/CatanEngine/CoroutineV2/Core/CoroutineExecutor.ts
Normal file
103
src/CatanEngine/CoroutineV2/Core/CoroutineExecutor.ts
Normal 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;
|
||||
}
|
||||
}
|
168
src/CatanEngine/CoroutineV2/Core/EnumeratorExecutor.ts
Normal file
168
src/CatanEngine/CoroutineV2/Core/EnumeratorExecutor.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
46
src/CatanEngine/CoroutineV2/Core/ParallelEnumerator.ts
Normal file
46
src/CatanEngine/CoroutineV2/Core/ParallelEnumerator.ts
Normal 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 };
|
||||
}
|
||||
}
|
18
src/CatanEngine/CoroutineV2/Core/SingleEnumerator.ts
Normal file
18
src/CatanEngine/CoroutineV2/Core/SingleEnumerator.ts
Normal 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);
|
||||
}
|
||||
}
|
21
src/CatanEngine/CoroutineV2/Core/WaitTimeEnumerator.ts
Normal file
21
src/CatanEngine/CoroutineV2/Core/WaitTimeEnumerator.ts
Normal 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 };
|
||||
}
|
||||
}
|
||||
}
|
75
src/CatanEngine/CoroutineV2/CoroutineV2.ts
Normal file
75
src/CatanEngine/CoroutineV2/CoroutineV2.ts
Normal 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);
|
||||
}
|
||||
}
|
16
src/CatanEngine/CoroutineV2/IEnumeratorV2.ts
Normal file
16
src/CatanEngine/CoroutineV2/IEnumeratorV2.ts
Normal 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;
|
||||
}
|
140
src/Utils/CCExtensions/ArrayExtension.ts
Normal file
140
src/Utils/CCExtensions/ArrayExtension.ts
Normal 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 多維陣列也OK
|
||||
*/
|
||||
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;
|
||||
},
|
||||
});
|
187
src/Utils/CCExtensions/NumberExtension.ts
Normal file
187
src/Utils/CCExtensions/NumberExtension.ts
Normal 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位小數,如:2,會在2後面補上00.即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;
|
||||
}
|
||||
})
|
180
src/Utils/Number/NumberEx.ts
Normal file
180
src/Utils/Number/NumberEx.ts
Normal 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);
|
||||
}
|
||||
|
||||
}
|
92
src/Utils/Number/RandomEx.ts
Normal file
92
src/Utils/Number/RandomEx.ts
Normal 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];
|
||||
}
|
||||
}
|
||||
}
|
27
src/Utils/Singleton/BaseSingleton.ts
Normal file
27
src/Utils/Singleton/BaseSingleton.ts
Normal 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;
|
||||
}
|
28
src/Utils/Singleton/BaseSingletonV2.ts
Normal file
28
src/Utils/Singleton/BaseSingletonV2.ts
Normal 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
16
src/Utils/catan.ts
Normal 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
15
src/Utils/tools.ts
Normal 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
18
src/api/account/login.ts
Normal 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;
|
||||
}
|
49
src/api/slot/SlotFgSpinBase.ts
Normal file
49
src/api/slot/SlotFgSpinBase.ts
Normal 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;
|
||||
}
|
||||
}
|
27
src/api/slot/SlotInBase.ts
Normal file
27
src/api/slot/SlotInBase.ts
Normal 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;
|
||||
}
|
||||
}
|
8
src/api/slot/SlotSetting.ts
Normal file
8
src/api/slot/SlotSetting.ts
Normal 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;
|
||||
}
|
206
src/api/slot/SlotSpinBase.ts
Normal file
206
src/api/slot/SlotSpinBase.ts
Normal 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
8
src/api/slot/ae.ts
Normal 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
23
src/api/slot/in.ts
Normal 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
35
src/api/slot/req.ts
Normal 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
4
src/api/slot1/fgspin.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import SlotFgSpinBase from "../slot/SlotFgSpinBase";
|
||||
|
||||
|
||||
export default class SlotFgSpin1 extends SlotFgSpinBase { }
|
7
src/api/slot1/in.ts
Normal file
7
src/api/slot1/in.ts
Normal 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
6
src/api/slot1/setting.ts
Normal 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
3
src/api/slot1/spin.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import SlotSpinBase from "../slot/SlotSpinBase";
|
||||
|
||||
export default class SlotSpin1 extends SlotSpinBase { }
|
4
src/api/slot70/fgspin.ts
Normal file
4
src/api/slot70/fgspin.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import SlotFgSpinBase from "../slot/SlotFgSpinBase";
|
||||
|
||||
|
||||
export default class SlotFgSpin70 extends SlotFgSpinBase { }
|
6
src/api/slot70/in.ts
Normal file
6
src/api/slot70/in.ts
Normal 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];
|
||||
}
|
7
src/api/slot70/setting.ts
Normal file
7
src/api/slot70/setting.ts
Normal 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
3
src/api/slot70/spin.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import SlotSpinBase from "../slot/SlotSpinBase";
|
||||
|
||||
export default class SlotSpin70 extends SlotSpinBase { }
|
38
src/electron/index.css
Normal file
38
src/electron/index.css
Normal 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
30
src/electron/index.html
Normal 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
156
src/electron/main.ts
Normal 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
15
src/electron/preload.ts
Normal 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
79
src/electron/renderer.ts
Normal 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
12
src/global.d.ts
vendored
Normal 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
42
src/pkg/server.ts
Normal 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');
|
@ -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();
|
||||
}
|
@ -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": {}
|
||||
}
|
@ -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();
|
||||
// }
|
@ -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": {}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
export interface INetResponse<TResponse> {
|
||||
readonly Method: string;
|
||||
readonly Status: number;
|
||||
readonly Data: TResponse;
|
||||
readonly IsValid: boolean;
|
||||
}
|
@ -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": {}
|
||||
}
|
4
src/script/Engine/CatanEngine/NetManagerV2/NetConfig.ts
Normal file
4
src/script/Engine/CatanEngine/NetManagerV2/NetConfig.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export default class NetConfig {
|
||||
/**是否顯示RPC接送JSON的LOG */
|
||||
public static ShowServerLog: boolean = true;
|
||||
}
|
152
src/script/Engine/CatanEngine/NetManagerV2/NetConnector.ts
Normal file
152
src/script/Engine/CatanEngine/NetManagerV2/NetConnector.ts
Normal 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
|
||||
};
|
53
src/script/Engine/CatanEngine/NetManagerV2/NetManager.ts
Normal file
53
src/script/Engine/CatanEngine/NetManagerV2/NetManager.ts
Normal 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");
|
||||
// }
|
||||
|
||||
// }
|
21
src/script/Engine/CatanEngine/NetManagerV2/NetRequest.ts
Normal file
21
src/script/Engine/CatanEngine/NetManagerV2/NetRequest.ts
Normal 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
0
src/shared/.gitkeep
Normal file
6
src/shared/protocols/AccountRequest.ts
Normal file
6
src/shared/protocols/AccountRequest.ts
Normal 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
|
6
src/shared/protocols/Slot1Request.ts
Normal file
6
src/shared/protocols/Slot1Request.ts
Normal file
@ -0,0 +1,6 @@
|
||||
// #region Request
|
||||
|
||||
export type RpcSlot1SpinRequest = { pay: number }
|
||||
export type RpcSlot1SpinResponse = { slot: number[], pay: number[][], money: number }
|
||||
|
||||
// #endregion
|
9
src/shared/protocols/SlotRequest.ts
Normal file
9
src/shared/protocols/SlotRequest.ts
Normal 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
|
4
src/shared/protocols/define/enum.ts
Normal file
4
src/shared/protocols/define/enum.ts
Normal file
@ -0,0 +1,4 @@
|
||||
// export enum EGameState {
|
||||
// /** 準備 */
|
||||
// Ready,
|
||||
// }
|
12
src/shared/protocols/define/interface.ts
Normal file
12
src/shared/protocols/define/interface.ts
Normal 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
23
tsconfig.json
Normal 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
Loading…
Reference in New Issue
Block a user