add client
This commit is contained in:
Executable
+12
@@ -0,0 +1,12 @@
|
||||
sudo: false
|
||||
language: node_js
|
||||
node_js:
|
||||
- '10'
|
||||
before_install:
|
||||
- npm i npminstall -g
|
||||
install:
|
||||
- npminstall
|
||||
script:
|
||||
- npm run ci
|
||||
after_script:
|
||||
- npminstall codecov && codecov
|
||||
Executable
+58
@@ -0,0 +1,58 @@
|
||||
# node-loan-center
|
||||
|
||||
node服务中心
|
||||
|
||||
## 快速入门
|
||||
详细文档,参见 [egg 文档][eggjs], [midway 文档][midway]。
|
||||
|
||||
### 安装 启动
|
||||
|
||||
```bash
|
||||
$ yarn
|
||||
$ yarn dev
|
||||
$ open http://localhost:7001/
|
||||
```
|
||||
|
||||
### 部署
|
||||
|
||||
```bash
|
||||
$ npm start
|
||||
$ npm stop
|
||||
```
|
||||
|
||||
### 单元测试
|
||||
```
|
||||
yarn test
|
||||
|
||||
```
|
||||
- 具体参见 [midway 文档 - 单元测试](https://eggjs.org/zh-cn/core/unittest)。
|
||||
|
||||
### 目录结构
|
||||
```
|
||||
├─dist
|
||||
├─logs
|
||||
│ ├─ELKLog //上报kibana日志系统log
|
||||
| | ├─info.log
|
||||
| | └─error.log
|
||||
│ └─node-loan-center // 系统日志
|
||||
├─node_modules
|
||||
├─src //开发目录
|
||||
│ ├─app
|
||||
│ │ ├─controller
|
||||
│ │ ├─extend
|
||||
│ │ ├─helper
|
||||
│ │ ├─middleware
|
||||
│ │ └─public // 静态目录
|
||||
│ ├─config // 配置文件目录,包含test,prod,local等config文件
|
||||
│ ├─interface // ts接口目录
|
||||
│ ├─lib // 工具类目录,如基类
|
||||
│ └─service
|
||||
└─test // 单元测试用例目录
|
||||
└─app
|
||||
└─controller
|
||||
|
||||
```
|
||||
|
||||
[midway]: https://midwayjs.org
|
||||
[git-rules]: https://confluence.sui.work/pages/viewpage.action?pageId=51120607
|
||||
[eggjs]: https://eggjs.org/zh-cn/
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
environment:
|
||||
matrix:
|
||||
- nodejs_version: '10'
|
||||
|
||||
install:
|
||||
- ps: Install-Product node $env:nodejs_version
|
||||
- npm i npminstall && node_modules\.bin\npminstall
|
||||
|
||||
test_script:
|
||||
- node --version
|
||||
- npm --version
|
||||
- npm run test
|
||||
|
||||
build: off
|
||||
Executable
Executable
Executable
+12
@@ -0,0 +1,12 @@
|
||||
2020-04-14 22:10:45,972 INFO 36442 [egg:logger] init all loggers with options: {"dir":"/Users/jorky/code/TexasPokerGame/server/logs/game-node-center","encoding":"utf8","env":"local","level":"INFO","consoleLevel":"INFO","disableConsoleAfterReady":false,"outputJSON":false,"buffer":true,"appLogName":"app.log","coreLogName":"core.log","agentLogName":"agent.log","errorLogName":"error.log","coreLogger":{"consoleLevel":"WARN"},"allowDebugAtProd":false,"type":"agent"}
|
||||
2020-04-14 22:10:45,983 INFO 36442 [egg:core] dump config after load, 6ms
|
||||
2020-04-14 22:10:46,034 INFO 36442 [egg-watcher] Start watching: ["/Users/jorky/code/TexasPokerGame/server/src/app","/Users/jorky/code/TexasPokerGame/server/src/lib","/Users/jorky/code/TexasPokerGame/server/src/service","/Users/jorky/code/TexasPokerGame/server/src/config","/Users/jorky/code/TexasPokerGame/server/src/app.ts","/Users/jorky/code/TexasPokerGame/server/src/agent.ts","/Users/jorky/code/TexasPokerGame/server/src/interface.ts"]
|
||||
2020-04-14 22:10:46,034 INFO 36442 [egg-watcher] Start watching: "/Users/jorky/code/TexasPokerGame/server/src/app"
|
||||
2020-04-14 22:10:46,035 INFO 36442 [egg-watcher] Start watching: "/Users/jorky/code/TexasPokerGame/server/src/lib"
|
||||
2020-04-14 22:10:46,035 INFO 36442 [egg-watcher] Start watching: "/Users/jorky/code/TexasPokerGame/server/src/service"
|
||||
2020-04-14 22:10:46,035 INFO 36442 [egg-watcher] Start watching: "/Users/jorky/code/TexasPokerGame/server/src/config"
|
||||
2020-04-14 22:10:46,035 INFO 36442 [egg-watcher] Start watching: "/Users/jorky/code/TexasPokerGame/server/src/app.ts"
|
||||
2020-04-14 22:10:46,036 INFO 36442 [egg-watcher] Start watching: "/Users/jorky/code/TexasPokerGame/server/src/agent.ts"
|
||||
2020-04-14 22:10:46,036 INFO 36442 [egg-watcher] Start watching: "/Users/jorky/code/TexasPokerGame/server/src/interface.ts"
|
||||
2020-04-14 22:10:46,037 INFO 36442 [egg-watcher:agent] watcher start success
|
||||
2020-04-14 22:10:46,045 INFO 36442 [egg:core] dump config after ready, 5ms
|
||||
Executable
Executable
+49
@@ -0,0 +1,49 @@
|
||||
2020-04-14 22:10:47,071 INFO 36443 [egg:logger] init all loggers with options: {"dir":"/Users/jorky/code/TexasPokerGame/server/logs/game-node-center","encoding":"utf8","env":"local","level":"INFO","consoleLevel":"INFO","disableConsoleAfterReady":false,"outputJSON":false,"buffer":true,"appLogName":"app.log","coreLogName":"core.log","agentLogName":"agent.log","errorLogName":"error.log","coreLogger":{"consoleLevel":"WARN"},"allowDebugAtProd":false,"type":"application"}
|
||||
2020-04-14 22:10:47,096 INFO 36443 [egg-multipart] stream mode enable
|
||||
2020-04-14 22:10:47,154 INFO 36443 [egg-redis] server connecting redis://:***@127.0.0.1:6379/0
|
||||
2020-04-14 22:10:47,176 INFO 36443 [egg-mysql] connecting root@127.0.0.1:3306/poker
|
||||
2020-04-14 22:10:47,342 INFO 36443 [egg-static] starting static serve /public/ -> /Users/jorky/code/TexasPokerGame/server/src/app/public
|
||||
2020-04-14 22:10:47,343 INFO 36443 [egg-security] use noopen middleware
|
||||
2020-04-14 22:10:47,344 INFO 36443 [egg-security] use nosniff middleware
|
||||
2020-04-14 22:10:47,345 INFO 36443 [egg-security] use xssProtection middleware
|
||||
2020-04-14 22:10:47,345 INFO 36443 [egg-security] use xframe middleware
|
||||
2020-04-14 22:10:47,346 INFO 36443 [egg-security] use dta middleware
|
||||
2020-04-14 22:10:47,346 INFO 36443 [egg-security] compose 5 middlewares into one security middleware
|
||||
2020-04-14 22:10:47,355 INFO 36443 [egg:core] dump config after load, 6ms
|
||||
2020-04-14 22:10:47,370 INFO 36443 [egg-redis] client connect success
|
||||
2020-04-14 22:10:47,380 ERROR 36443 nodejs.ReplyError: Ready check failed: NOAUTH Authentication required.
|
||||
at parseError (/Users/jorky/code/TexasPokerGame/server/node_modules/redis/node_modules/redis-parser/lib/parser.js:193:12)
|
||||
at parseType (/Users/jorky/code/TexasPokerGame/server/node_modules/redis/node_modules/redis-parser/lib/parser.js:303:14)
|
||||
command: "INFO"
|
||||
code: "NOAUTH"
|
||||
pid: 36443
|
||||
hostname: 192.168.0.100
|
||||
|
||||
2020-04-14 22:10:47,380 ERROR 36443 nodejs.ReplyError: Ready check failed: NOAUTH Authentication required.
|
||||
at parseError (/Users/jorky/code/TexasPokerGame/server/node_modules/redis/node_modules/redis-parser/lib/parser.js:193:12)
|
||||
at parseType (/Users/jorky/code/TexasPokerGame/server/node_modules/redis/node_modules/redis-parser/lib/parser.js:303:14)
|
||||
command: "INFO"
|
||||
code: "NOAUTH"
|
||||
pid: 36443
|
||||
hostname: 192.168.0.100
|
||||
|
||||
2020-04-14 22:10:47,390 ERROR 36443 nodejs.ReplyError: Ready check failed: NOAUTH Authentication required.
|
||||
at parseError (/Users/jorky/code/TexasPokerGame/server/node_modules/redis/node_modules/redis-parser/lib/parser.js:193:12)
|
||||
at parseType (/Users/jorky/code/TexasPokerGame/server/node_modules/redis/node_modules/redis-parser/lib/parser.js:303:14)
|
||||
command: "INFO"
|
||||
code: "NOAUTH"
|
||||
pid: 36443
|
||||
hostname: 192.168.0.100
|
||||
|
||||
2020-04-14 22:10:47,391 ERROR 36443 nodejs.ReplyError: Ready check failed: NOAUTH Authentication required.
|
||||
at parseError (/Users/jorky/code/TexasPokerGame/server/node_modules/redis/node_modules/redis-parser/lib/parser.js:193:12)
|
||||
at parseType (/Users/jorky/code/TexasPokerGame/server/node_modules/redis/node_modules/redis-parser/lib/parser.js:303:14)
|
||||
command: "INFO"
|
||||
code: "NOAUTH"
|
||||
pid: 36443
|
||||
hostname: 192.168.0.100
|
||||
|
||||
2020-04-14 22:10:47,394 INFO 36443 [egg-redis] instance[0] status OK, client ready
|
||||
2020-04-14 22:10:47,400 INFO 36443 [egg-watcher:application] watcher start success
|
||||
2020-04-14 22:10:47,434 INFO 36443 [egg-mysql] instance[0] status OK, rds currentTime: Tue Apr 14 2020 22:10:47 GMT+0800 (China Standard Time)
|
||||
2020-04-14 22:10:47,458 INFO 36443 [egg:core] dump config after ready, 5ms
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
2020-04-14 22:10:47,463 INFO 36442 [Timer] /Users/jorky/code/TexasPokerGame/server/node_modules/egg-multipart/app/schedule/clean_tmpdir.js next time will execute after 22752539ms at 2020-04-15 04:30:00.002
|
||||
2020-04-14 22:10:47,463 INFO 36442 [Timer] /Users/jorky/code/TexasPokerGame/server/node_modules/egg-logrotator/app/schedule/clean_log.js next time will execute after 6552537ms at 2020-04-15 00:00:00.000
|
||||
2020-04-14 22:10:47,463 INFO 36442 [Timer] /Users/jorky/code/TexasPokerGame/server/node_modules/egg-logrotator/app/schedule/rotate_by_file.js next time will execute after 6553537ms at 2020-04-15 00:00:01.000
|
||||
2020-04-14 22:10:47,096 INFO 36443 [egg-schedule]: register schedule /Users/jorky/code/TexasPokerGame/server/node_modules/egg-multipart/app/schedule/clean_tmpdir.js
|
||||
2020-04-14 22:10:47,096 INFO 36443 [egg-schedule]: register schedule /Users/jorky/code/TexasPokerGame/server/node_modules/egg-logrotator/app/schedule/clean_log.js
|
||||
2020-04-14 22:10:47,096 INFO 36443 [egg-schedule]: register schedule /Users/jorky/code/TexasPokerGame/server/node_modules/egg-logrotator/app/schedule/rotate_by_file.js
|
||||
Executable
+32
@@ -0,0 +1,32 @@
|
||||
2020-04-14 22:10:47,379 ERROR 36443 nodejs.ReplyError: Ready check failed: NOAUTH Authentication required.
|
||||
at parseError (/Users/jorky/code/TexasPokerGame/server/node_modules/redis/node_modules/redis-parser/lib/parser.js:193:12)
|
||||
at parseType (/Users/jorky/code/TexasPokerGame/server/node_modules/redis/node_modules/redis-parser/lib/parser.js:303:14)
|
||||
command: "INFO"
|
||||
code: "NOAUTH"
|
||||
pid: 36443
|
||||
hostname: 192.168.0.100
|
||||
|
||||
2020-04-14 22:10:47,380 ERROR 36443 nodejs.ReplyError: Ready check failed: NOAUTH Authentication required.
|
||||
at parseError (/Users/jorky/code/TexasPokerGame/server/node_modules/redis/node_modules/redis-parser/lib/parser.js:193:12)
|
||||
at parseType (/Users/jorky/code/TexasPokerGame/server/node_modules/redis/node_modules/redis-parser/lib/parser.js:303:14)
|
||||
command: "INFO"
|
||||
code: "NOAUTH"
|
||||
pid: 36443
|
||||
hostname: 192.168.0.100
|
||||
|
||||
2020-04-14 22:10:47,381 ERROR 36443 nodejs.ReplyError: Ready check failed: NOAUTH Authentication required.
|
||||
at parseError (/Users/jorky/code/TexasPokerGame/server/node_modules/redis/node_modules/redis-parser/lib/parser.js:193:12)
|
||||
at parseType (/Users/jorky/code/TexasPokerGame/server/node_modules/redis/node_modules/redis-parser/lib/parser.js:303:14)
|
||||
command: "INFO"
|
||||
code: "NOAUTH"
|
||||
pid: 36443
|
||||
hostname: 192.168.0.100
|
||||
|
||||
2020-04-14 22:10:47,390 ERROR 36443 nodejs.ReplyError: Ready check failed: NOAUTH Authentication required.
|
||||
at parseError (/Users/jorky/code/TexasPokerGame/server/node_modules/redis/node_modules/redis-parser/lib/parser.js:193:12)
|
||||
at parseType (/Users/jorky/code/TexasPokerGame/server/node_modules/redis/node_modules/redis-parser/lib/parser.js:303:14)
|
||||
command: "INFO"
|
||||
code: "NOAUTH"
|
||||
pid: 36443
|
||||
hostname: 192.168.0.100
|
||||
|
||||
Executable
+59
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"name": "game-node-center",
|
||||
"version": "1.0.0",
|
||||
"description": "node服务中心",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@types/socket.io": "^2.1.4",
|
||||
"chai": "^4.2.0",
|
||||
"egg-cors": "^2.2.3",
|
||||
"egg-jwt": "^3.1.7",
|
||||
"egg-mysql": "^3.0.0",
|
||||
"egg-redis": "^2.4.0",
|
||||
"egg-scripts": "^2.10.0",
|
||||
"egg-socket.io": "^4.1.6",
|
||||
"midway": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mocha": "^5.2.7",
|
||||
"@types/node": "^10.5.5",
|
||||
"cross-env": "^6.0.0",
|
||||
"egg-ci": "^1.8.0",
|
||||
"midway-bin": "1",
|
||||
"midway-mock": "1",
|
||||
"ts-node": "^8.3.0",
|
||||
"tslib": "^1.8.1",
|
||||
"tslint": "^5.11.0",
|
||||
"tslint-config-egg": "^1.0.0",
|
||||
"typescript": "^3.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.16.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "egg-scripts start --daemon --title=midway-server-node-loan-center --framework=midway --ts",
|
||||
"stop": "egg-scripts stop --title=midway-server-node-loan-center",
|
||||
"start_build": "npm run build && cross-env NODE_ENV=development midway-bin dev",
|
||||
"clean": "midway-bin clean",
|
||||
"dev": "cross-env NODE_ENV=local midway-bin dev --ts",
|
||||
"debug": "cross-env NODE_ENV=local midway-bin debug --ts",
|
||||
"test": "npm run lint && midway-bin test --ts",
|
||||
"cov": "midway-bin cov --ts",
|
||||
"lint": "tslint --fix -p tsconfig.json -t stylish",
|
||||
"ci": "npm run cov",
|
||||
"build": "midway-bin build -c"
|
||||
},
|
||||
"ci": {
|
||||
"version": "10"
|
||||
},
|
||||
"midway-bin-clean": [
|
||||
".vscode/.tsbuildinfo",
|
||||
"dist"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": ""
|
||||
},
|
||||
"author": "Cai",
|
||||
"license": "MIT"
|
||||
}
|
||||
Executable
+31
@@ -0,0 +1,31 @@
|
||||
import { Application } from 'egg';
|
||||
import * as path from 'path';
|
||||
import ElkTransport from './app/helper/logTransport';
|
||||
|
||||
export default (app: Application) => {
|
||||
app.beforeStart(async () => {
|
||||
});
|
||||
app.use(async (ctx, next) => {
|
||||
try {
|
||||
await next();
|
||||
} catch (err) {
|
||||
if (err.name === 'UnauthorizedError') {
|
||||
ctx.status = 401;
|
||||
ctx.body = {
|
||||
code: '999999',
|
||||
data: {},
|
||||
message: 'invalid token...',
|
||||
};
|
||||
ctx.logger.error('invalid token...', err);
|
||||
}
|
||||
}
|
||||
});
|
||||
app.logger.set('remoteInfo', new ElkTransport({
|
||||
level: 'INFO',
|
||||
file: path.join(app.baseDir, '../logs/ELKLog/info.log'),
|
||||
}));
|
||||
app.logger.set('remoteError', new ElkTransport({
|
||||
level: 'ERROR',
|
||||
file: path.join(app.baseDir, '../logs/ELKLog/error.log'),
|
||||
}));
|
||||
};
|
||||
Executable
+40
@@ -0,0 +1,40 @@
|
||||
import BaseController from '../../lib/baseController';
|
||||
import { controller, inject, post, provide } from 'midway';
|
||||
import { IAccountService } from '../../interface/IAccountService';
|
||||
import { IAccountInfo } from '../../interface/IAccountInfo';
|
||||
|
||||
@provide()
|
||||
@controller('/node/user/')
|
||||
export class Account extends BaseController {
|
||||
|
||||
@inject('AccountService')
|
||||
service: IAccountService;
|
||||
|
||||
@post('/login')
|
||||
async login() {
|
||||
try {
|
||||
const { body } = this.getRequestBody();
|
||||
const { userAccount, password } = body;
|
||||
const accountInfo: IAccountInfo = { userAccount, password };
|
||||
const result = await this.service.login(accountInfo);
|
||||
this.success(result);
|
||||
} catch (e) {
|
||||
this.ctx.logger.error('login-----:', e);
|
||||
this.fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
@post('/register')
|
||||
async register() {
|
||||
try {
|
||||
const { body } = this.getRequestBody();
|
||||
const { userAccount, password, nickName } = body;
|
||||
const accountInfo: IAccountInfo = { userAccount, password, nickName };
|
||||
const result = await this.service.register(accountInfo);
|
||||
this.success(result);
|
||||
} catch (e) {
|
||||
this.ctx.logger.error('login-----:', e);
|
||||
this.fail(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+39
@@ -0,0 +1,39 @@
|
||||
import { Context, inject, controller, post, provide } from 'midway';
|
||||
import BaseController from '../../lib/baseController';
|
||||
import { IRoomService } from '../../interface/IRoom';
|
||||
|
||||
@provide()
|
||||
@controller('/node/game/room')
|
||||
export class RoomController extends BaseController {
|
||||
|
||||
@inject()
|
||||
ctx: Context;
|
||||
|
||||
@inject('RoomService')
|
||||
roomService: IRoomService;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@post('/')
|
||||
async index() {
|
||||
try {
|
||||
const result = await this.roomService.add();
|
||||
this.success(result);
|
||||
} catch (e) {
|
||||
this.fail('create room error');
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
@post('/find')
|
||||
async find() {
|
||||
try {
|
||||
const { body } = this.getRequestBody();
|
||||
const result = await this.roomService.findByRoomNumber(body.roomNumber);
|
||||
this.success({ hasRoom: result });
|
||||
} catch (e) {
|
||||
this.fail('create room error');
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+32
@@ -0,0 +1,32 @@
|
||||
import { Context, inject, controller, post, provide, plugin } from 'midway';
|
||||
import BaseController from '../../lib/baseController';
|
||||
import { IUserService } from '../../interface/IUserService';
|
||||
|
||||
@provide()
|
||||
@controller('/node/user')
|
||||
export class UserController extends BaseController {
|
||||
|
||||
@inject()
|
||||
ctx: Context;
|
||||
|
||||
@plugin()
|
||||
jwt: any;
|
||||
|
||||
@inject('UserService')
|
||||
user: IUserService;
|
||||
/**
|
||||
* 处理ocr数据转发
|
||||
*/
|
||||
@post('/')
|
||||
async index() {
|
||||
try {
|
||||
const token: string = this.ctx.get('Authorization') || '';
|
||||
const userInfo = await this.jwt.verify(token);
|
||||
const user = this.user.findByAccount(userInfo.userAccount);
|
||||
this.success(user);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
this.fail('server error');
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+129
@@ -0,0 +1,129 @@
|
||||
export interface IPlayer {
|
||||
counter: number;
|
||||
position?: number;
|
||||
userId: string;
|
||||
}
|
||||
|
||||
export enum ECommand {
|
||||
SMALL_BLIND = 'small_blind',
|
||||
BIG_BLIND = 'big_blind',
|
||||
STRADDLE = 'straddle',
|
||||
CALL = 'call',
|
||||
ALL_IN = 'allin',
|
||||
RAISE = 'raise',
|
||||
CHECK = 'check',
|
||||
FOLD = 'fold',
|
||||
}
|
||||
|
||||
export enum EPlayerType {
|
||||
DEFAULT = 'default',
|
||||
DEALER = 'dealer',
|
||||
BIG_BLIND = 'big_blind',
|
||||
SMALL_BLIND = 'small_blind',
|
||||
}
|
||||
|
||||
export class Player {
|
||||
handCard: string[] = [];
|
||||
position: number = 0;
|
||||
counter: number = 0;
|
||||
userId: string = '';
|
||||
actionSize: number = 0;
|
||||
type: string = EPlayerType.DEFAULT;
|
||||
evPot: number = Infinity;
|
||||
pokeStyle: string = '';
|
||||
|
||||
// commandRecord: Array<string> = [];
|
||||
constructor(config: IPlayer = { counter: 0, position: 0, userId: '' }) {
|
||||
this.counter = config.counter;
|
||||
this.position = config.position || 0;
|
||||
this.userId = config.userId;
|
||||
if (this.position === 0) {
|
||||
this.type = EPlayerType.DEALER;
|
||||
}
|
||||
if (this.position === 1) {
|
||||
this.type = EPlayerType.SMALL_BLIND;
|
||||
}
|
||||
if (this.position === 2) {
|
||||
this.type = EPlayerType.BIG_BLIND;
|
||||
}
|
||||
}
|
||||
|
||||
setHandCard(card: string) {
|
||||
this.handCard.push(card);
|
||||
}
|
||||
|
||||
getHandCard() {
|
||||
return this.handCard;
|
||||
}
|
||||
|
||||
/**
|
||||
* player action
|
||||
* @param {string} commandString
|
||||
* @param {number} prevSize
|
||||
* @example action('command:raise:10')
|
||||
*/
|
||||
action(commandString: string, prevSize: number = 0) {
|
||||
const commandArr = commandString.split(':');
|
||||
const command = commandArr[0];
|
||||
const raiseSize = Number(commandArr[1]);
|
||||
let size = 0;
|
||||
if (command !== ECommand.ALL_IN
|
||||
&& (prevSize > (this.counter + this.actionSize) || raiseSize > this.counter)) {
|
||||
throw 'error action, overflow action size';
|
||||
}
|
||||
|
||||
// BLIND
|
||||
if (command === ECommand.SMALL_BLIND || command === ECommand.BIG_BLIND) {
|
||||
size = raiseSize;
|
||||
}
|
||||
|
||||
// todo STRADDLE
|
||||
if (command === ECommand.STRADDLE) {
|
||||
// position 0 is dealer
|
||||
if (this.position === 3) {
|
||||
size = raiseSize;
|
||||
} else {
|
||||
throw 'error action STRADDLE';
|
||||
}
|
||||
}
|
||||
|
||||
// player raise,get the raise size
|
||||
if (command === ECommand.RAISE) {
|
||||
// raise must double to prevSize
|
||||
if ((raiseSize + this.actionSize) >= prevSize * 2) {
|
||||
size = raiseSize + this.actionSize;
|
||||
} else {
|
||||
throw 'error action: raise size too small';
|
||||
}
|
||||
}
|
||||
|
||||
if (command === ECommand.ALL_IN) {
|
||||
size = this.counter;
|
||||
}
|
||||
|
||||
if (command === ECommand.CALL) {
|
||||
size = prevSize - this.actionSize;
|
||||
}
|
||||
|
||||
if (command === ECommand.CHECK) {
|
||||
size = -1;
|
||||
}
|
||||
|
||||
if (command === ECommand.FOLD) {
|
||||
size = 0;
|
||||
}
|
||||
if (size > 0) {
|
||||
this.counter -= size;
|
||||
}
|
||||
this.actionSize += size;
|
||||
return size;
|
||||
}
|
||||
|
||||
clearActionSize() {
|
||||
this.actionSize = 0;
|
||||
}
|
||||
|
||||
income(size: number) {
|
||||
this.counter += size;
|
||||
}
|
||||
}
|
||||
Executable
+54
@@ -0,0 +1,54 @@
|
||||
export interface IPoker {
|
||||
init(): void;
|
||||
|
||||
getCard(): string;
|
||||
|
||||
getRandom(number: number): number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Created by jorky on 2020/2/23.
|
||||
*/
|
||||
export class Poker implements IPoker {
|
||||
private pokers: string [] = [];
|
||||
|
||||
constructor() {
|
||||
this.init();
|
||||
}
|
||||
|
||||
init(): void {
|
||||
const size = [
|
||||
'a',
|
||||
'b',
|
||||
'c',
|
||||
'd',
|
||||
'e',
|
||||
'f',
|
||||
'g',
|
||||
'h',
|
||||
'i',
|
||||
'j',
|
||||
'k',
|
||||
'l',
|
||||
'm' ];
|
||||
const color = [ 1, 2, 3, 4 ];
|
||||
for (const i of size) {
|
||||
for (const j of color) {
|
||||
this.pokers.push(`${i}${j}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getCard(): string {
|
||||
if (this.pokers.length === 0) return 'done';
|
||||
const currCardIndex = this.getRandom(this.pokers.length);
|
||||
const currCard = this.pokers[currCardIndex];
|
||||
this.pokers.splice(currCardIndex, 1);
|
||||
return currCard;
|
||||
}
|
||||
|
||||
getRandom(number: number): number {
|
||||
const maxNumber = Math.ceil(number);
|
||||
return Math.floor(Math.random() * maxNumber);
|
||||
}
|
||||
}
|
||||
Executable
+407
@@ -0,0 +1,407 @@
|
||||
/**
|
||||
* Created by jorky on 2020/2/23.
|
||||
*/
|
||||
import { Poker } from './Poker';
|
||||
import { ECommand, EPlayerType, IPlayer, Player } from './Player';
|
||||
import { PokerStyle } from './PokerStyle';
|
||||
import { ILinkNode, Link } from '../../utils/Link';
|
||||
|
||||
interface IPokerGame {
|
||||
users: IPlayer[];
|
||||
smallBlind: number;
|
||||
}
|
||||
|
||||
export enum EGameStatus {
|
||||
GAME_READY,
|
||||
GAME_START,
|
||||
GAME_FLOP,
|
||||
GAME_TURN,
|
||||
GAME_RIVER,
|
||||
GAME_ACTION,
|
||||
GAME_SHOWDOWN,
|
||||
GAME_OVER,
|
||||
}
|
||||
|
||||
export class PokerGame {
|
||||
commonCard: string[] = [];
|
||||
fireCards: string[] = [];
|
||||
playerLink: Link<Player>;
|
||||
allPlayer: Player[] = [];
|
||||
poker = new Poker();
|
||||
pot: number;
|
||||
status: EGameStatus;
|
||||
currPlayer: ILinkNode<Player>;
|
||||
smallBlind: number;
|
||||
playerSize: number;
|
||||
prevSize: number;
|
||||
prevPot: number;
|
||||
allInPlayers: Player[] = [];
|
||||
currActionAllinPlayer: Player[] = [];
|
||||
hasStraddle = false;
|
||||
winner: Player[][] = [];
|
||||
|
||||
constructor(config: IPokerGame) {
|
||||
this.smallBlind = config.smallBlind;
|
||||
this.init(config.users);
|
||||
}
|
||||
|
||||
init(users: IPlayer[]) {
|
||||
if (this.playerSize < 2) {
|
||||
throw 'player Inadequate';
|
||||
}
|
||||
|
||||
this.status = EGameStatus.GAME_READY;
|
||||
// init playerLink
|
||||
this.playerLink = this.setPlayer(users);
|
||||
this.playerSize = users.length;
|
||||
// set SB, BB,Straddle
|
||||
this.getBlind();
|
||||
// utg
|
||||
this.currPlayer = this.playerLink.getNode(3);
|
||||
}
|
||||
|
||||
getBlind() {
|
||||
// sb blind
|
||||
const SBPlayerNode = this.playerLink.getNode(1);
|
||||
const SBPlayer = SBPlayerNode.node;
|
||||
if (SBPlayerNode.next) {
|
||||
// big blind
|
||||
const BBPlayerNode: ILinkNode<Player> = SBPlayerNode.next;
|
||||
const BBPlayer = BBPlayerNode.node;
|
||||
SBPlayer.action(`small_blind:${this.smallBlind}`);
|
||||
BBPlayer.action(`big_blind:${this.smallBlind * 2}`);
|
||||
this.prevSize = this.smallBlind * 2;
|
||||
// add counter to pot
|
||||
// this.pots.push(this.smallBlind * 3);
|
||||
this.pot = this.smallBlind * 3;
|
||||
// todo straddle
|
||||
} else {
|
||||
throw 'player Inadequate';
|
||||
}
|
||||
}
|
||||
|
||||
play() {
|
||||
this.status = EGameStatus.GAME_START;
|
||||
this.sendCard();
|
||||
}
|
||||
|
||||
action(commandString: string) {
|
||||
if (this.status === EGameStatus.GAME_ACTION && this.currPlayer.next) {
|
||||
const commandArr = commandString.split(':');
|
||||
const command = commandArr[0];
|
||||
let size = Number(commandArr[1]);
|
||||
if (command === ECommand.ALL_IN) {
|
||||
size = this.currPlayer.node.counter > this.prevSize ?
|
||||
this.currPlayer.node.counter : this.prevSize;
|
||||
this.allIn();
|
||||
this.pot += this.currPlayer.node.counter;
|
||||
// other pot
|
||||
// only one player,or none player,game is over
|
||||
}
|
||||
if (command === ECommand.CALL) {
|
||||
size = this.prevSize;
|
||||
this.pot += size - this.currPlayer.node.actionSize;
|
||||
}
|
||||
if (command === ECommand.FOLD) {
|
||||
size = this.prevSize;
|
||||
this.removePlayer(this.currPlayer.node);
|
||||
// only one player,or none player,game is over
|
||||
}
|
||||
if (command === ECommand.CHECK) {
|
||||
console.log(this.currPlayer.node.type === EPlayerType.BIG_BLIND
|
||||
&& this.prevSize === this.smallBlind * 2, 'big blind', this.currPlayer);
|
||||
// prev player must be check
|
||||
if (!(this.prevSize === 0 ||
|
||||
(this.currPlayer.node.type === EPlayerType.BIG_BLIND
|
||||
&& this.prevSize === this.smallBlind * 2))) {
|
||||
throw 'incorrect action: check';
|
||||
}
|
||||
}
|
||||
if (command === ECommand.RAISE) {
|
||||
this.pot += size;
|
||||
// 3 bet
|
||||
size += this.currPlayer.node.actionSize;
|
||||
|
||||
if ((size + this.currPlayer.node.actionSize) < this.prevSize * 2) {
|
||||
throw 'incorrect action: raise';
|
||||
}
|
||||
}
|
||||
this.currPlayer.node.action(commandString, this.prevSize);
|
||||
this.prevSize = size;
|
||||
const nextPlayer = this.currPlayer.next.node;
|
||||
// all check actionSize === -1
|
||||
// all player allin
|
||||
// only 2 player, curr player fold, next player already action
|
||||
// only one player ,one player fold,other player allin
|
||||
// pre flop big blind check and other player call
|
||||
// console.log('this.currPlayer----------', this.currPlayer, nextPlayer, command);
|
||||
if (this.playerSize === 0
|
||||
|| (command === ECommand.FOLD && this.currPlayer.next.node.actionSize !== 0)
|
||||
|| (nextPlayer.actionSize === this.currPlayer.node.actionSize && command !== ECommand.FOLD)
|
||||
|| (this.commonCard.length === 0
|
||||
&& this.currPlayer.node.type === EPlayerType.BIG_BLIND
|
||||
&& command === ECommand.CHECK)) {
|
||||
// console.log('ccc------', this.currPlayer, nextPlayer, command, this.playerSize);
|
||||
this.actionComplete();
|
||||
return;
|
||||
}
|
||||
this.currPlayer = this.currPlayer.next;
|
||||
} else {
|
||||
throw 'incorrect action flow';
|
||||
}
|
||||
}
|
||||
allIn() {
|
||||
this.currActionAllinPlayer.push(this.currPlayer.node);
|
||||
this.removePlayer(this.currPlayer.node);
|
||||
}
|
||||
|
||||
private actionComplete() {
|
||||
// pre flop
|
||||
if (this.commonCard.length === 0
|
||||
&& this.currPlayer.node.actionSize === this.smallBlind * 2
|
||||
&& this.currPlayer.node.type !== EPlayerType.BIG_BLIND) {
|
||||
if (this.currPlayer.next) {
|
||||
this.currPlayer = this.currPlayer.next;
|
||||
} else {
|
||||
throw 'currPlayer.next is null';
|
||||
}
|
||||
return;
|
||||
}
|
||||
// action has allin, sum the allin player ev_pot
|
||||
if (this.currActionAllinPlayer.length !== 0) {
|
||||
let currAllinPlayerPot = 0;
|
||||
this.currActionAllinPlayer.forEach(allinPlayer => {
|
||||
this.allPlayer.forEach(p => {
|
||||
if (p.actionSize < allinPlayer.actionSize) {
|
||||
currAllinPlayerPot += p.actionSize;
|
||||
} else {
|
||||
currAllinPlayerPot += allinPlayer.actionSize;
|
||||
}
|
||||
});
|
||||
allinPlayer.evPot = this.prevPot + currAllinPlayerPot;
|
||||
currAllinPlayerPot = 0;
|
||||
});
|
||||
this.allInPlayers = [ ...this.allInPlayers, ...this.currActionAllinPlayer ];
|
||||
}
|
||||
// action complete clear player actionSize = 0
|
||||
this.clearPlayerAction();
|
||||
this.currActionAllinPlayer = [];
|
||||
this.prevSize = 0;
|
||||
this.prevPot = this.pot;
|
||||
// new action ring first action is sb
|
||||
this.currPlayer = this.playerLink.getNode(1);
|
||||
this.setSate();
|
||||
console.log(this.playerSize, 'playerS-------', this.status);
|
||||
if (this.status === EGameStatus.GAME_SHOWDOWN || this.playerSize <= 1) {
|
||||
this.gameOver();
|
||||
}
|
||||
}
|
||||
setSate() {
|
||||
if (this.status === EGameStatus.GAME_ACTION) {
|
||||
if (this.commonCard.length === 0) {
|
||||
this.status = EGameStatus.GAME_FLOP;
|
||||
}
|
||||
if (this.commonCard.length === 3) {
|
||||
this.status = EGameStatus.GAME_TURN;
|
||||
}
|
||||
if (this.commonCard.length === 4) {
|
||||
this.status = EGameStatus.GAME_RIVER;
|
||||
}
|
||||
if (this.commonCard.length === 5) {
|
||||
this.status = EGameStatus.GAME_SHOWDOWN;
|
||||
}
|
||||
} else {
|
||||
this.status = EGameStatus.GAME_ACTION;
|
||||
}
|
||||
}
|
||||
|
||||
sendCard() {
|
||||
if (this.status === EGameStatus.GAME_START) {
|
||||
this.setHandCard();
|
||||
this.setSate();
|
||||
return;
|
||||
}
|
||||
if (this.status === EGameStatus.GAME_FLOP) {
|
||||
this.fireCards.push(this.poker.getCard());
|
||||
this.flop();
|
||||
this.setSate();
|
||||
return;
|
||||
}
|
||||
if (this.status === EGameStatus.GAME_TURN || this.status === EGameStatus.GAME_RIVER) {
|
||||
this.fireCards.push(this.poker.getCard());
|
||||
this.commonCard.push(this.poker.getCard());
|
||||
this.setSate();
|
||||
return;
|
||||
}
|
||||
throw 'error flow sendCard';
|
||||
}
|
||||
|
||||
flop() {
|
||||
for (let i = 0; i < 3; i++) {
|
||||
this.commonCard.push(this.poker.getCard());
|
||||
}
|
||||
}
|
||||
|
||||
compareCard(player: Player, otherPlayer: Player) {
|
||||
const firstWeight = player.pokeStyle;
|
||||
const lastWeight = otherPlayer.pokeStyle;
|
||||
let flag = -1;
|
||||
if (firstWeight > lastWeight) {
|
||||
flag = 1;
|
||||
}
|
||||
if (firstWeight === lastWeight) {
|
||||
flag = 0;
|
||||
}
|
||||
if (firstWeight < lastWeight) {
|
||||
flag = -1;
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
setPlayer(users: IPlayer[]) {
|
||||
users.forEach((u, position) => {
|
||||
const player = new Player({
|
||||
...u,
|
||||
position,
|
||||
});
|
||||
this.allPlayer.push(player);
|
||||
});
|
||||
return new Link<Player>(this.allPlayer);
|
||||
}
|
||||
|
||||
getPlayers(type= 'all', withoutPlayers?: Player[]) {
|
||||
let players = [];
|
||||
let nextPlayer: ILinkNode<Player> = this.playerLink.link;
|
||||
let i = 0;
|
||||
while (i < this.playerSize) {
|
||||
players.push(nextPlayer.node);
|
||||
if (nextPlayer.next) {
|
||||
nextPlayer = nextPlayer.next;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
players = type === 'all' ? [ ...players, ...this.allInPlayers ] : players;
|
||||
return withoutPlayers ? players.filter(p => {
|
||||
const isNotPlayer = withoutPlayers.filter(withoutPlayer => withoutPlayer.userId === p.userId
|
||||
|| withoutPlayer.evPot >= p.evPot);
|
||||
return isNotPlayer.length === 0;
|
||||
}) : players;
|
||||
}
|
||||
|
||||
private clearPlayerAction() {
|
||||
this.allPlayer.forEach(player => {
|
||||
player.clearActionSize();
|
||||
});
|
||||
}
|
||||
|
||||
removePlayer(currPlayer: Player) {
|
||||
let playerLink = this.playerLink.link;
|
||||
let player: Player;
|
||||
while (playerLink.next) {
|
||||
player = playerLink.next.node;
|
||||
if (currPlayer.userId === player.userId) {
|
||||
playerLink.next = playerLink.next.next;
|
||||
this.playerSize--;
|
||||
return;
|
||||
}
|
||||
playerLink = playerLink.next;
|
||||
}
|
||||
}
|
||||
|
||||
getWinner() {
|
||||
if (this.allInPlayers.length === 0 && this.playerSize === 1) {
|
||||
this.winner.push([ this.currPlayer.node ]);
|
||||
return;
|
||||
}
|
||||
while (this.status !== EGameStatus.GAME_SHOWDOWN) {
|
||||
this.sendCard();
|
||||
this.setSate();
|
||||
}
|
||||
|
||||
this.getPlayerPokeStyle();
|
||||
|
||||
const getOtherWinner = (withoutPlayers: Player[]) => {
|
||||
// all player allin, winner can't get all pot
|
||||
const allPlayer = this.getPlayers('all', withoutPlayers);
|
||||
const maxLastPlayer = this.getMaxPlayers(allPlayer);
|
||||
this.winner.push(maxLastPlayer);
|
||||
if (this.getLeftoverPot() > 0) {
|
||||
getOtherWinner([ ...withoutPlayers, ...maxLastPlayer ]);
|
||||
}
|
||||
};
|
||||
getOtherWinner([]);
|
||||
// // compare allin player and last player
|
||||
// const allPlayers: Player [] = this.getPlayers()
|
||||
// const maxPlayers = this.getMaxPlayers(allPlayers);
|
||||
// this.winner.push(maxPlayers);
|
||||
// // max player can't winner all pot
|
||||
// const hasSecondWinner = maxPlayers[0].evPot < this.pot;
|
||||
// // all of winner is all in, must get max curr player
|
||||
// if (hasSecondWinner) {
|
||||
// // all player allin, winner can't get all pot
|
||||
// getOtherWinner(maxPlayers);
|
||||
// }
|
||||
}
|
||||
|
||||
getPlayerPokeStyle() {
|
||||
this.allPlayer.map(p => {
|
||||
p.pokeStyle = new PokerStyle([ ...p.getHandCard(), ...this.commonCard ]).getPokerWeight();
|
||||
return p;
|
||||
});
|
||||
}
|
||||
|
||||
getLeftoverPot() {
|
||||
if (this.winner.length === 0) {
|
||||
return this.pot;
|
||||
}
|
||||
return this.pot - this.winner[this.winner.length - 1][0].evPot;
|
||||
}
|
||||
|
||||
getMaxPlayers(lastPlayers: Player[]) {
|
||||
const _maxPlayers: Player[] = [];
|
||||
const maxPlayer = lastPlayers.reduce((acc, cur) => {
|
||||
return this.compareCard(acc, cur) === 1 ? acc : cur;
|
||||
});
|
||||
// has many winner ,equal max player
|
||||
lastPlayers.forEach(p => {
|
||||
if (this.compareCard(p, maxPlayer) === 0) {
|
||||
_maxPlayers.push(p);
|
||||
}
|
||||
});
|
||||
return _maxPlayers;
|
||||
}
|
||||
|
||||
setHandCard() {
|
||||
// send card start by small blind
|
||||
let playerLink = this.playerLink.link;
|
||||
let player: Player;
|
||||
for (let i = 0; i < 2; i++) {
|
||||
let j = 0;
|
||||
while (j < this.playerSize) {
|
||||
player = playerLink.node;
|
||||
player.handCard.push(this.poker.getCard());
|
||||
if (playerLink.next) {
|
||||
playerLink = playerLink.next;
|
||||
}
|
||||
j++;
|
||||
}
|
||||
}
|
||||
}
|
||||
counting() {
|
||||
let prevEvPot = 0;
|
||||
this.winner.forEach((winnerList, key) => {
|
||||
if (key !== 0) {
|
||||
prevEvPot = this.winner[key - 1][0].evPot;
|
||||
}
|
||||
winnerList.forEach(winner => {
|
||||
winner.income((winner.evPot - prevEvPot) / winnerList.length);
|
||||
});
|
||||
});
|
||||
}
|
||||
gameOver() {
|
||||
// only one player,other fold
|
||||
this.getWinner();
|
||||
// todo counting
|
||||
this.counting();
|
||||
}
|
||||
}
|
||||
Executable
+197
@@ -0,0 +1,197 @@
|
||||
const POKER_STR = 'abcdefghijklm';
|
||||
|
||||
function sort(cards: string []): string[] {
|
||||
let temp = '';
|
||||
// 排序
|
||||
for (let i = 0; i < cards.length; i++) {
|
||||
for (let j = i + 1; j < cards.length; j++) {
|
||||
if (cards[i] > cards[j]) {
|
||||
temp = cards[i];
|
||||
cards[i] = cards[j];
|
||||
cards[j] = temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
return cards;
|
||||
}
|
||||
|
||||
interface IPokerStyle {
|
||||
init(): void;
|
||||
isStraight(str?: string []): string;
|
||||
}
|
||||
|
||||
export class PokerStyle implements IPokerStyle {
|
||||
cards: string[] = [];
|
||||
flushObj = {
|
||||
1: [] as string[],
|
||||
2: [] as string[],
|
||||
3: [] as string[],
|
||||
4: [] as string[],
|
||||
};
|
||||
straightArr: string[] = [];
|
||||
pokerStyle: string[] = [ '0', '0', '0', '0', '0', '0', '0', '0', '0', '0' ];
|
||||
numObj: Map<string, number> = new Map(
|
||||
POKER_STR.split('').map(m => [ m, 0 ]));
|
||||
|
||||
constructor(cards: string[]) {
|
||||
this.cards = sort(cards);
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
let i = 0;
|
||||
const isTwo = [];
|
||||
const isThree = [];
|
||||
let isFour = '0';
|
||||
let isFullHouse = '0';
|
||||
let isStraightFlush = '0';
|
||||
let isFlush = [];
|
||||
let isRoyalFlush = '0';
|
||||
let isThreeKind = '';
|
||||
let isTowPair = '';
|
||||
let isPair = '';
|
||||
const highCard = [];
|
||||
|
||||
while (i < this.cards.length) {
|
||||
const color = this.cards[i][1];
|
||||
const num = this.cards[i][0];
|
||||
this.straightArr.push(this.cards[i][0]);
|
||||
this.flushObj[color].push(num);
|
||||
let value = this.numObj.get(num) || 0;
|
||||
value++;
|
||||
this.numObj.set(num, value);
|
||||
i++;
|
||||
}
|
||||
|
||||
// find flush
|
||||
for (const f in this.flushObj) {
|
||||
if (this.flushObj[f].length >= 5) {
|
||||
// flush is order,so flush[length - 1] is max flush card
|
||||
isFlush = this.flushObj[f];
|
||||
}
|
||||
}
|
||||
|
||||
// find two,three,four
|
||||
for (const [ key, value ] of this.numObj) {
|
||||
// high card
|
||||
if (value === 1) {
|
||||
highCard.unshift(key);
|
||||
}
|
||||
// pairs max count 3, source is small to large
|
||||
if (value >= 2) {
|
||||
isTwo.unshift(key);
|
||||
}
|
||||
// three of kind max count 2
|
||||
if (value === 3) {
|
||||
isThree.unshift(key);
|
||||
}
|
||||
// four of kind only one
|
||||
if (value === 4) {
|
||||
isFour = key;
|
||||
}
|
||||
}
|
||||
// straight flush
|
||||
if (isFlush.length !== 0 && this.isStraight(isFlush) !== '0') {
|
||||
if (this.isStraight(isFlush) === 'i') {
|
||||
isRoyalFlush = '1';
|
||||
this.pokerStyle[0] = isRoyalFlush;
|
||||
return;
|
||||
}
|
||||
isStraightFlush = this.isStraight(isFlush);
|
||||
this.pokerStyle[1] = isStraightFlush;
|
||||
return;
|
||||
}
|
||||
|
||||
// four of kind
|
||||
if (isFour !== '0') {
|
||||
isFour += highCard[0];
|
||||
this.pokerStyle[2] = isFour;
|
||||
return;
|
||||
}
|
||||
|
||||
// full house
|
||||
if (isThree.length > 0 && isTwo.length > isThree.length || isThree.length === 2) {
|
||||
const maxTwoCard = isThree.length === 2 ? isThree[1] : isThree[0] === isTwo[0] ? isTwo[1] : isTwo[0];
|
||||
const maxThree = isThree[0];
|
||||
isFullHouse = maxThree + maxTwoCard;
|
||||
this.pokerStyle[3] = isFullHouse;
|
||||
return;
|
||||
}
|
||||
|
||||
// flush
|
||||
if (isFlush.length !== 0) {
|
||||
this.pokerStyle[4] = isFlush;
|
||||
return;
|
||||
}
|
||||
|
||||
// straight
|
||||
if (this.isStraight() !== '0') {
|
||||
this.pokerStyle[5] = `${this.isStraight()}`;
|
||||
return;
|
||||
}
|
||||
|
||||
// three of kind
|
||||
if (isThree.length > 0) {
|
||||
isThreeKind = isThree.join('');
|
||||
isThreeKind += highCard[0] + highCard[1];
|
||||
this.pokerStyle[6] = isThreeKind;
|
||||
return;
|
||||
}
|
||||
// tow pair
|
||||
if (isTwo.length >= 2) {
|
||||
isTowPair = isTwo.join('');
|
||||
// third tow pair card big then high card
|
||||
const highCardForTowPair = isTwo[3] && isTwo[3] > highCard[0] ? isTwo[3] : highCard[0];
|
||||
isTowPair += highCardForTowPair;
|
||||
this.pokerStyle[7] = isTowPair;
|
||||
return;
|
||||
}
|
||||
// pair
|
||||
if (isTwo.length === 1) {
|
||||
isPair = isTwo.join('');
|
||||
isPair += highCard[0] + highCard[1] + highCard[2];
|
||||
this.pokerStyle[8] = isPair;
|
||||
return;
|
||||
}
|
||||
// High card
|
||||
highCard.length = 5;
|
||||
this.pokerStyle[9] = highCard.join('');
|
||||
}
|
||||
|
||||
isStraight(str?: string []): string {
|
||||
const straightStr = str && str.join('') || [ ...new Set(this.straightArr) ].join('');
|
||||
let first = -1;
|
||||
let second = -1;
|
||||
let three = -1;
|
||||
function indexOf(str: string): number {
|
||||
return POKER_STR.indexOf(str);
|
||||
}
|
||||
if (straightStr.length === 5 && indexOf(straightStr) > -1) {
|
||||
return POKER_STR.charAt(indexOf(straightStr));
|
||||
}
|
||||
if (straightStr.length === 6) {
|
||||
first = indexOf(straightStr.slice(0, 5));
|
||||
second = indexOf(straightStr.slice(1, 6));
|
||||
if (Math.max(first, second) > -1) {
|
||||
return POKER_STR.charAt(Math.max(first, second));
|
||||
}
|
||||
}
|
||||
if (straightStr.length === 7) {
|
||||
first = indexOf(straightStr.slice(0, 5));
|
||||
second = indexOf(straightStr.slice(1, 6));
|
||||
three = indexOf(straightStr.slice(2, 7));
|
||||
if (Math.max(first, second, three) > -1) {
|
||||
return POKER_STR.charAt(Math.max(first, second, three));
|
||||
}
|
||||
}
|
||||
// special straight "A2345",'m' -> A
|
||||
if (straightStr.indexOf('m') > -1 && straightStr.indexOf('abcd') > -1) {
|
||||
return POKER_STR.charAt(12);
|
||||
}
|
||||
return '0';
|
||||
}
|
||||
|
||||
getPokerWeight() {
|
||||
return this.pokerStyle.join('');
|
||||
}
|
||||
}
|
||||
Executable
+34
@@ -0,0 +1,34 @@
|
||||
import { Context } from 'egg';
|
||||
|
||||
const LOG_COLLECTION = Symbol('Context#logCollection');
|
||||
export default {
|
||||
context: this,
|
||||
/**
|
||||
* 获取收集的日志信息
|
||||
* @returns {any}
|
||||
*/
|
||||
getLogs(this: Context) {
|
||||
const result: any = {};
|
||||
for (const [ k, val ] of this.logCollection) {
|
||||
result[k] = val;
|
||||
}
|
||||
return result;
|
||||
},
|
||||
/**
|
||||
* 收集日志信息
|
||||
* @param {string} key
|
||||
* @param {string} value
|
||||
*/
|
||||
setLogCollection(this: Context, key: string, value: string) {
|
||||
this.logCollection.set(key, value);
|
||||
},
|
||||
get logCollection(this: Context) {
|
||||
if (!this.context[LOG_COLLECTION]) {
|
||||
this.context[LOG_COLLECTION] = new Map();
|
||||
}
|
||||
return this.context[LOG_COLLECTION];
|
||||
},
|
||||
set logCollection(this: Context, value) {
|
||||
this.context[LOG_COLLECTION] = value;
|
||||
},
|
||||
};
|
||||
Executable
+15
@@ -0,0 +1,15 @@
|
||||
module.exports = {
|
||||
parseMsg(action: any, payload = {}, metadata = {}) {
|
||||
const meta = Object.assign({}, {
|
||||
timestamp: Date.now(),
|
||||
}, metadata);
|
||||
|
||||
return {
|
||||
meta,
|
||||
data: {
|
||||
action,
|
||||
payload,
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
Executable
+95
@@ -0,0 +1,95 @@
|
||||
import { LoggerLevel, Context } from 'egg';
|
||||
import { FileBufferTransport } from 'egg-logger';
|
||||
import * as iconv from 'iconv-lite';
|
||||
import * as moment from 'moment';
|
||||
import * as os from 'os';
|
||||
import { OSLogField, BusinessLogField } from '../../interface/Ilog';
|
||||
|
||||
const { uid, username } = os.userInfo();
|
||||
|
||||
/**
|
||||
* 日志格式化
|
||||
*/
|
||||
class LogFormat extends Map {
|
||||
logField: BusinessLogField;
|
||||
osField: OSLogField;
|
||||
constructor() {
|
||||
super();
|
||||
this.osField = {
|
||||
pid: process.pid,
|
||||
nodeVersion: process.version,
|
||||
launchTime: moment().format('YYYY-MM-DD HH:mm:ss'),
|
||||
osUser: username,
|
||||
osUid: uid,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Log日志进行utf-8编码
|
||||
*/
|
||||
protected toBuffer() {
|
||||
const fields = { ...this.osField, ...this.logField };
|
||||
const str = JSON.stringify(fields) + '\n';
|
||||
return iconv.encode(str, 'utf8');
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化Log上下文
|
||||
* @param logInfo
|
||||
* @param level 日志级别
|
||||
*/
|
||||
public formatFetchInfoMsg(logInfo: any[], level: LoggerLevel) {
|
||||
const collect = logInfo[0];
|
||||
this.logField = {
|
||||
fetchConsumeTime: 0,
|
||||
level: '',
|
||||
message: '',
|
||||
requestTime: '',
|
||||
stack: '',
|
||||
status: '',
|
||||
timestamp: '',
|
||||
total: 0,
|
||||
requestBody: {},
|
||||
method: '',
|
||||
url: '',
|
||||
};
|
||||
if (typeof collect === 'string') {
|
||||
this.logField.message = collect;
|
||||
this.logField.requestTime = moment(Date.now()).format('YYYY-MM-DD HH:mm:ss');
|
||||
this.logField.level = level;
|
||||
this.logField.stack = level === 'ERROR' ? logInfo[1] : '';
|
||||
} else {
|
||||
this.logField.timestamp = collect.startTime;
|
||||
this.logField.requestTime = moment(collect.startTime).format('YYYY-MM-DD HH:mm:ss');
|
||||
this.logField.status = collect.status;
|
||||
this.logField.message = collect.message;
|
||||
this.logField.stack = collect.stack;
|
||||
this.logField.level = collect.level || level;
|
||||
this.logField.url = collect.url;
|
||||
this.logField.total = collect.end - collect.start;
|
||||
if (collect.fetchStart && collect.fetchEnd) {
|
||||
this.logField.requestBody = collect.requestBody;
|
||||
this.logField.method = collect.method;
|
||||
this.logField.fetchConsumeTime = collect.fetchEnd - collect.fetchStart;
|
||||
}
|
||||
}
|
||||
return this.toBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
export default class ElkTransport extends FileBufferTransport {
|
||||
|
||||
log(this: Context, level: LoggerLevel, args: any) {
|
||||
const logFormat = new LogFormat();
|
||||
let buf;
|
||||
if (!this._stream) {
|
||||
const err = new Error(`${this.options.file} log stream had been closed`);
|
||||
console.error(err.stack);
|
||||
return;
|
||||
}
|
||||
buf = logFormat.formatFetchInfoMsg(args, level);
|
||||
if (buf.length) {
|
||||
this._write(buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+15
@@ -0,0 +1,15 @@
|
||||
module.exports = {
|
||||
parseMsg(action: any, payload = {}, metadata = {}) {
|
||||
const meta = Object.assign({}, {
|
||||
timestamp: Date.now(),
|
||||
}, metadata);
|
||||
|
||||
return {
|
||||
meta,
|
||||
data: {
|
||||
action,
|
||||
payload,
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
Executable
+29
@@ -0,0 +1,29 @@
|
||||
'use strict';
|
||||
|
||||
import { Controller } from 'egg';
|
||||
|
||||
class GameController extends Controller {
|
||||
async play() {
|
||||
const { ctx } = this;
|
||||
const socket = ctx.socket as any;
|
||||
const app = ctx.app as any;
|
||||
const nsp = app.io.of('/socket');
|
||||
const { room } = socket.handshake.query;
|
||||
console.log(room);
|
||||
try {
|
||||
nsp.adapter.clients([ room ], (err: any, clients: any) => {
|
||||
// 广播信息
|
||||
nsp.to(room).emit('game', {
|
||||
clients,
|
||||
action: 'broadcast',
|
||||
target: 'participator',
|
||||
message: '',
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
app.logger.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = GameController;
|
||||
Executable
+48
@@ -0,0 +1,48 @@
|
||||
'use strict';
|
||||
|
||||
import { Controller } from 'egg';
|
||||
|
||||
class NspController extends Controller {
|
||||
async exchange() {
|
||||
const { ctx } = this;
|
||||
const socket = ctx.socket as any;
|
||||
const app = ctx.app as any;
|
||||
const nsp = app.io.of('/socket');
|
||||
const message = ctx.args[0] || {};
|
||||
const client = socket.id;
|
||||
try {
|
||||
const { target, payload } = message;
|
||||
if (!target) return;
|
||||
const msg = ctx.helper.parseMsg('exchange', payload, { client, target });
|
||||
nsp.emit(target, msg);
|
||||
} catch (error) {
|
||||
app.logger.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async broadcast() {
|
||||
const { ctx } = this;
|
||||
const socket = ctx.socket as any;
|
||||
const app = ctx.app as any;
|
||||
const nsp = app.io.of('/socket');
|
||||
const message = ctx.args[0] || {};
|
||||
const { room } = socket.handshake.query;
|
||||
const rooms = [ room ];
|
||||
try {
|
||||
const { payload } = message;
|
||||
nsp.adapter.clients(rooms, (err: any, clients: any) => {
|
||||
// 广播信息
|
||||
nsp.to(room).emit('online', {
|
||||
clients,
|
||||
action: 'broadcast',
|
||||
target: 'participator',
|
||||
message: payload,
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
app.logger.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = NspController;
|
||||
Executable
+55
@@ -0,0 +1,55 @@
|
||||
import { Context } from 'midway';
|
||||
import { ITickMsg } from '../../../interface/ITickMsg';
|
||||
|
||||
export default function auth(): any {
|
||||
return async (ctx: Context, next: () => Promise<any>) => {
|
||||
const socket = ctx.socket as any;
|
||||
const id = socket.id;
|
||||
const app = ctx.app as any;
|
||||
const nsp = app.io.of('/socket');
|
||||
const roomService = await app.applicationContext.getAsync('RoomService');
|
||||
const query = socket.handshake.query;
|
||||
// 用户信息
|
||||
const { room, userName } = query;
|
||||
|
||||
// 检查房间是否存在,不存在则踢出用户
|
||||
// 备注:此处 app.redis 与插件无关,可用其他存储代替
|
||||
const hasRoom = await roomService.findByRoomNumber(room);
|
||||
function tick(id: number, msg: ITickMsg, nsp: any, socket: any) {
|
||||
// 踢出用户前发送消息
|
||||
socket.emit(id, ctx.helper.parseMsg('deny', msg));
|
||||
// 调用 adapter 方法踢出用户,客户端触发 disconnect 事件
|
||||
nsp.adapter.remoteDisconnect(id, true, (err: any) => {
|
||||
ctx.logger.error('room service tick', err);
|
||||
});
|
||||
}
|
||||
|
||||
function join(roomNumber: string, userName: string, nsp: any, socket: any) {
|
||||
socket.join(roomNumber);
|
||||
updatePlayer(roomNumber, `User(${userName}) joined.`, nsp);
|
||||
}
|
||||
|
||||
function updatePlayer(roomNumber: string, message: string, nsp: any) {
|
||||
// 在线列表
|
||||
nsp.adapter.clients([ roomNumber ], (err: any, clients: any) => {
|
||||
// 更新在线用户列表
|
||||
nsp.to(roomNumber).emit('online', {
|
||||
clients,
|
||||
action: 'join',
|
||||
target: 'participator',
|
||||
message,
|
||||
});
|
||||
});
|
||||
}
|
||||
if (!hasRoom) {
|
||||
tick(id, {
|
||||
type: 'deleted',
|
||||
message: 'deleted, room has been deleted.',
|
||||
}, nsp, socket);
|
||||
return;
|
||||
}
|
||||
join(room, userName, nsp, socket);
|
||||
await next();
|
||||
updatePlayer(room, userName, nsp);
|
||||
};
|
||||
}
|
||||
Executable
+26
@@ -0,0 +1,26 @@
|
||||
import { Context, EggAppConfig } from 'egg';
|
||||
|
||||
/**
|
||||
* elk 日志拦截中间件
|
||||
* @description 接口请求拦截,进行日志上报,根据config的环境配置,设置日志收集,prod默认开启
|
||||
* @param {EggAppConfig["elkLogger"]} options config 配置项 elkLogger
|
||||
* @returns {any}
|
||||
*/
|
||||
export default function elkLogger(options: EggAppConfig['elkLogger']): any {
|
||||
return async (ctx: Context, next: () => Promise<any>) => {
|
||||
const { match, enable } = options;
|
||||
console.log('jwt', ctx.jwt);
|
||||
// 是否符合配置规则
|
||||
if (match(ctx) && enable) {
|
||||
ctx.setLogCollection('fetchStart', Date.now());
|
||||
ctx.setLogCollection('url', ctx.url);
|
||||
ctx.setLogCollection('requestBody', ctx.request.body);
|
||||
ctx.setLogCollection('message', `${ctx.request.method} ${ctx.url} info`);
|
||||
ctx.setLogCollection('level', 'INFO');
|
||||
await next();
|
||||
ctx.setLogCollection('status', ctx.res.statusCode);
|
||||
ctx.setLogCollection('fetchEnd', Date.now());
|
||||
ctx.logger.info(ctx.getLogs());
|
||||
}
|
||||
};
|
||||
}
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
import { Context } from 'egg';
|
||||
|
||||
/**
|
||||
* 404状态处理
|
||||
* @returns {any}
|
||||
*/
|
||||
export default function notFound(): any {
|
||||
return async (ctx: Context, next: () => Promise<any>) => {
|
||||
await next();
|
||||
if (ctx.status === 404 && !ctx.body) {
|
||||
if (ctx.acceptJSON) {
|
||||
ctx.body = {
|
||||
code: '404',
|
||||
data: {},
|
||||
msg: '页面坐火箭去了',
|
||||
};
|
||||
} else {
|
||||
ctx.body = '<h1>Page Not Found</h1>';
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
Executable
+1
@@ -0,0 +1 @@
|
||||
## public static file directory!
|
||||
Executable
+6
@@ -0,0 +1,6 @@
|
||||
import { Application } from 'midway';
|
||||
|
||||
export default function (app: Application) {
|
||||
app.io.of('/socket').route('exchange', app.io.controller.nsp.exchange);
|
||||
app.io.of('/socket').route('broadcast', app.io.controller.nsp.broadcast);
|
||||
}
|
||||
Executable
+122
@@ -0,0 +1,122 @@
|
||||
import { EggAppConfig, EggAppInfo, PowerPartial, Context } from 'midway';
|
||||
|
||||
export type DefaultConfig = PowerPartial<EggAppConfig>;
|
||||
|
||||
export default (appInfo: EggAppInfo) => {
|
||||
const config = {} as DefaultConfig;
|
||||
|
||||
// use for cookie sign key, should change to your own and keep security
|
||||
config.keys = appInfo.name + '_{{keys}}';
|
||||
|
||||
// elk日志中间件,404处理中间件
|
||||
config.middleware = [ 'elkLogger', 'notFound' ];
|
||||
|
||||
// 合成配置
|
||||
const bizConfig = {
|
||||
sourceUrl: '',
|
||||
elkLogger: {
|
||||
// 请求url匹配规则
|
||||
match(ctx: Context) {
|
||||
const reg = /.*/;
|
||||
return reg.test(ctx.url);
|
||||
},
|
||||
// 是否启用
|
||||
enable: true,
|
||||
},
|
||||
};
|
||||
// 安全处理
|
||||
config.security = {
|
||||
csrf: {
|
||||
enable: false,
|
||||
},
|
||||
methodnoallow: {
|
||||
enable: false,
|
||||
},
|
||||
};
|
||||
// CORS 跨域处理
|
||||
config.cors = {
|
||||
allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH,OPTIONS',
|
||||
credentials: true,
|
||||
origin(ctx: Context) {
|
||||
const origin: string = ctx.get('origin');
|
||||
// 允许*域名访问
|
||||
if (origin.indexOf('172.22.88.118') > -1) {
|
||||
console.log('come in');
|
||||
return origin;
|
||||
} else {
|
||||
return '*';
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// 日志配置
|
||||
config.logger = {
|
||||
outputJSON: false,
|
||||
appLogName: 'app.log',
|
||||
coreLogName: 'core.log',
|
||||
agentLogName: 'agent.log',
|
||||
errorLogName: 'error.log',
|
||||
};
|
||||
|
||||
// 业务接口domain
|
||||
config.apiDomain = {
|
||||
loanDomain: '*',
|
||||
};
|
||||
|
||||
// jsonwebtoken 插件配置
|
||||
config.jwt = {
|
||||
secret: '123456',
|
||||
enable: true,
|
||||
match(ctx: Context) {
|
||||
const reg = /login|register/;
|
||||
return !reg.test(ctx.originalUrl);
|
||||
},
|
||||
};
|
||||
|
||||
// socket io setting
|
||||
config.io = {
|
||||
namespace: {
|
||||
'/socket': {
|
||||
connectionMiddleware: [ 'auth' ],
|
||||
packetMiddleware: [],
|
||||
},
|
||||
},
|
||||
redis: {
|
||||
host: '127.0.0.1',
|
||||
port: 6379,
|
||||
},
|
||||
};
|
||||
|
||||
config.redis = {
|
||||
client: {
|
||||
port: 6379,
|
||||
host: '127.0.0.1',
|
||||
password: '123456',
|
||||
db: 0,
|
||||
},
|
||||
};
|
||||
config.mysql = {
|
||||
// 单数据库信息配置
|
||||
client: {
|
||||
// host
|
||||
host: '127.0.0.1',
|
||||
// 端口号
|
||||
port: '3306',
|
||||
// 用户名
|
||||
user: 'root',
|
||||
// 密码
|
||||
password: '123456',
|
||||
// 数据库名
|
||||
database: 'poker',
|
||||
},
|
||||
// 是否加载到 app 上,默认开启
|
||||
app: true,
|
||||
// 是否加载到 agent 上,默认关闭
|
||||
agent: false,
|
||||
};
|
||||
|
||||
return {
|
||||
...bizConfig,
|
||||
...config,
|
||||
};
|
||||
};
|
||||
Executable
+12
@@ -0,0 +1,12 @@
|
||||
export const development = {
|
||||
watchDirs: [
|
||||
'app',
|
||||
'lib',
|
||||
'service',
|
||||
'config',
|
||||
'app.ts',
|
||||
'agent.ts',
|
||||
'interface.ts',
|
||||
],
|
||||
overrideDefault: true,
|
||||
};
|
||||
Executable
+25
@@ -0,0 +1,25 @@
|
||||
import { EggAppConfig, PowerPartial } from 'egg';
|
||||
import { Context } from 'midway';
|
||||
|
||||
export default () => {
|
||||
const config: PowerPartial<EggAppConfig> = {};
|
||||
// 业务接口domain
|
||||
config.apiDomain = {
|
||||
loan: '',
|
||||
};
|
||||
// CORS 跨域处理
|
||||
config.cors = {
|
||||
allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH,OPTIONS',
|
||||
credentials: true,
|
||||
origin(ctx: Context) {
|
||||
const origin: string = ctx.get('origin');
|
||||
// 允许*域名访问
|
||||
if (origin.indexOf('*') > -1) {
|
||||
return origin;
|
||||
} else {
|
||||
return '*';
|
||||
}
|
||||
},
|
||||
};
|
||||
return config;
|
||||
};
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
import { EggAppConfig, PowerPartial } from 'egg';
|
||||
|
||||
export default () => {
|
||||
const config: PowerPartial<EggAppConfig> = {};
|
||||
// 业务接口domain
|
||||
config.apiDomain = {
|
||||
loan: '',
|
||||
};
|
||||
return config;
|
||||
};
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
import { EggPlugin } from 'egg';
|
||||
|
||||
const plugin: EggPlugin = {
|
||||
static: true,
|
||||
cors: {
|
||||
enable: true,
|
||||
package: 'egg-cors',
|
||||
},
|
||||
redis: {
|
||||
enable: true,
|
||||
package: 'egg-redis',
|
||||
},
|
||||
io: {
|
||||
enable: true,
|
||||
package: 'egg-socket.io',
|
||||
},
|
||||
jwt: {
|
||||
enable: true,
|
||||
package: 'egg-jwt',
|
||||
},
|
||||
mysql: {
|
||||
enable: true,
|
||||
package: 'egg-mysql',
|
||||
},
|
||||
};
|
||||
|
||||
export default plugin;
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
export interface IAccountInfo {
|
||||
userAccount: string;
|
||||
password: string;
|
||||
nickName?: string;
|
||||
}
|
||||
Executable
+8
@@ -0,0 +1,8 @@
|
||||
import { IAccountInfo } from './IAccountInfo';
|
||||
import { ILoginResult } from './ILoginResult';
|
||||
|
||||
export interface IAccountService {
|
||||
login(accountInfo: IAccountInfo): Promise<ILoginResult>;
|
||||
authUser(userInfo: IAccountInfo): Promise<boolean>;
|
||||
register(accountInfo: IAccountInfo): Promise<string>;
|
||||
}
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
export interface ICommandRecord {
|
||||
roomId: string;
|
||||
gameId: string;
|
||||
userId: string;
|
||||
type: string;
|
||||
command: string;
|
||||
}
|
||||
Executable
+15
@@ -0,0 +1,15 @@
|
||||
import { HttpMethod } from 'urllib';
|
||||
|
||||
/**
|
||||
* @description User-Service abstractions
|
||||
*/
|
||||
export interface IFetchOptions {
|
||||
method?: string;
|
||||
url: string;
|
||||
body: object;
|
||||
head?: object;
|
||||
timeout?: number;
|
||||
headers?: object;
|
||||
type?: HttpMethod;
|
||||
ssjToken: string;
|
||||
}
|
||||
Executable
+6
@@ -0,0 +1,6 @@
|
||||
export interface IGame {
|
||||
roomId: string;
|
||||
pot: string;
|
||||
status: number;
|
||||
commonCard: string;
|
||||
}
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
export interface IGameRecord {
|
||||
roomId: string;
|
||||
userId: string;
|
||||
commonCard: string;
|
||||
handCards: string;
|
||||
status: string;
|
||||
}
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
export interface ILoginResult {
|
||||
token: string;
|
||||
}
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
|
||||
export interface IRequestBody {
|
||||
head: any;
|
||||
body: any;
|
||||
}
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
export enum ResultCode {
|
||||
SUCCESS = '000000',
|
||||
FAILD = '100000',
|
||||
UNAUTH = '999999',
|
||||
}
|
||||
|
||||
export interface IResult {
|
||||
code: ResultCode;
|
||||
data: any;
|
||||
message: string;
|
||||
}
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
export interface IRoom {
|
||||
roomNumber: string;
|
||||
}
|
||||
|
||||
export interface IRoomService {
|
||||
findById(uid: string): Promise<IRoom>;
|
||||
findByRoomNumber(roomNumber: number): Promise<boolean>;
|
||||
add(): Promise<any>;
|
||||
// join(roomNumber: string, userName: string): void;
|
||||
// leave(roomNumber: string, message: string): void;
|
||||
}
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
export interface ITickMsg {
|
||||
type: string;
|
||||
message: string;
|
||||
}
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
export interface IUser {
|
||||
nickName: string;
|
||||
account: string;
|
||||
password?: string;
|
||||
}
|
||||
Executable
+8
@@ -0,0 +1,8 @@
|
||||
import { IAccountInfo } from './IAccountInfo';
|
||||
import { IUser } from './IUser';
|
||||
|
||||
export interface IUserService {
|
||||
findById(uid: string): Promise<IUser>;
|
||||
findByAccount(account: string): Promise<IUser>;
|
||||
addUser(accountInfo: IAccountInfo): Promise<any>;
|
||||
}
|
||||
Executable
+75
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* 系统日志属性
|
||||
*/
|
||||
export interface OSLogField {
|
||||
/**
|
||||
* 进程识别号
|
||||
*/
|
||||
pid: number;
|
||||
/**
|
||||
* node 版本号
|
||||
*/
|
||||
nodeVersion: string;
|
||||
/**
|
||||
* 启动时间
|
||||
*/
|
||||
launchTime: string;
|
||||
/**
|
||||
* 系统用户名
|
||||
*/
|
||||
osUser: string;
|
||||
/**
|
||||
* 系统用户id
|
||||
*/
|
||||
osUid: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 业务日志属性
|
||||
*/
|
||||
export interface BusinessLogField {
|
||||
/**
|
||||
* 日志时间戳
|
||||
*/
|
||||
timestamp: string;
|
||||
/**
|
||||
* 请求时间戳
|
||||
*/
|
||||
requestTime: string;
|
||||
/**
|
||||
* 请求总数
|
||||
*/
|
||||
total: number;
|
||||
/**
|
||||
* 请求url
|
||||
*/
|
||||
url: string;
|
||||
/**
|
||||
* 请求状态码
|
||||
*/
|
||||
status: string;
|
||||
/**
|
||||
* 请求消耗时间
|
||||
*/
|
||||
fetchConsumeTime: number;
|
||||
/**
|
||||
* 请求描述
|
||||
*/
|
||||
message: string;
|
||||
/**
|
||||
* 日志级别
|
||||
*/
|
||||
level: string;
|
||||
/**
|
||||
* 请求错误栈
|
||||
*/
|
||||
stack: string;
|
||||
/**
|
||||
* 请求信息
|
||||
*/
|
||||
requestBody?: any;
|
||||
/**
|
||||
* 请求类型
|
||||
*/
|
||||
method: string;
|
||||
}
|
||||
Executable
+45
@@ -0,0 +1,45 @@
|
||||
import { inject, Context } from 'midway';
|
||||
import { IRequestBody } from '../interface/IRequestBody';
|
||||
import { IResult, ResultCode } from '../interface/IResult';
|
||||
|
||||
export default class BaseController {
|
||||
|
||||
@inject()
|
||||
protected ctx: Context;
|
||||
|
||||
/**
|
||||
* 获取请求内容
|
||||
* @returns {IRequestBody}
|
||||
*/
|
||||
public getRequestBody(): IRequestBody {
|
||||
let params: IRequestBody;
|
||||
params = this.ctx.request.body.params && JSON.parse(this.ctx.request.body.params) || {};
|
||||
console.log(this.ctx.request.body, 'params');
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* 失败回调封装
|
||||
* @param {Object} data
|
||||
*/
|
||||
public success(data: any) {
|
||||
const result: IResult = {
|
||||
code: ResultCode.SUCCESS,
|
||||
data,
|
||||
message: 'successful',
|
||||
};
|
||||
this.ctx.body = result;
|
||||
}
|
||||
/**
|
||||
* 错误回调封装
|
||||
* @param {string} message
|
||||
*/
|
||||
public fail(message: string) {
|
||||
const result: IResult = {
|
||||
code: ResultCode.FAILD,
|
||||
data: {},
|
||||
message,
|
||||
};
|
||||
this.ctx.body = result;
|
||||
}
|
||||
}
|
||||
Executable
+45
@@ -0,0 +1,45 @@
|
||||
import { Context } from 'egg';
|
||||
import { IFetchOptions } from '../interface/IFetchOptions';
|
||||
import { inject, config } from 'midway';
|
||||
|
||||
export default class BaseService {
|
||||
@inject()
|
||||
protected ctx: Context;
|
||||
|
||||
@config('apiDomain')
|
||||
protected apiDomainConfig: any;
|
||||
|
||||
/**
|
||||
* 处理请求
|
||||
* @param {IFetchOptions} option
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
public async fetch(option: IFetchOptions) {
|
||||
try {
|
||||
const data = {
|
||||
head: { ...option.head },
|
||||
body: { ...option.body },
|
||||
};
|
||||
// 用户登录标识
|
||||
const headers = {
|
||||
Authorization: `Bearer ${option.ssjToken}`,
|
||||
};
|
||||
const ajaxUrl = this.apiDomainConfig.loanDomain + option.url;
|
||||
// 发起服务请求
|
||||
const result = await this.ctx.curl(ajaxUrl, {
|
||||
data,
|
||||
type: option.type || 'POST',
|
||||
headers,
|
||||
dataType: 'json',
|
||||
});
|
||||
// 接口请求失败日志上报
|
||||
if (result.status !== 200) {
|
||||
this.ctx.logger.error(this.ctx.getLogs());
|
||||
}
|
||||
return result.data;
|
||||
} catch (e) {
|
||||
this.ctx.logger.error(this.ctx.getLogs());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Executable
+80
@@ -0,0 +1,80 @@
|
||||
import BaseService from '../lib/baseService';
|
||||
import { Context, inject, provide, plugin, config } from 'midway';
|
||||
import { IAccountInfo } from '../interface/IAccountInfo';
|
||||
import { IAccountService } from '../interface/IAccountService';
|
||||
import { ILoginResult } from '../interface/ILoginResult';
|
||||
import { IUserService } from '../interface/IUserService';
|
||||
import { IUser } from '../interface/IUser';
|
||||
|
||||
@provide('AccountService')
|
||||
export class AccountService extends BaseService implements IAccountService {
|
||||
|
||||
@inject()
|
||||
ctx: Context;
|
||||
|
||||
@plugin()
|
||||
jwt: any;
|
||||
|
||||
@inject('UserService')
|
||||
user: IUserService;
|
||||
|
||||
@config('jwt')
|
||||
protected jwtConfig: any;
|
||||
|
||||
public login(accountInfo: IAccountInfo): Promise<ILoginResult> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
let token = '';
|
||||
// 校验用户信息
|
||||
const isAuth = await this.authUser(accountInfo);
|
||||
if (isAuth) {
|
||||
token = await this.getToken(accountInfo.userAccount);
|
||||
}
|
||||
const result: ILoginResult = { token };
|
||||
resolve(result);
|
||||
} catch (e) {
|
||||
this.ctx.logger.error('login service error:', e);
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async register(accountInfo: IAccountInfo): Promise<string> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const hasUser = await this.checkHasUser(accountInfo.userAccount);
|
||||
if (!hasUser) {
|
||||
const result = await this.user.addUser(accountInfo);
|
||||
if (result.affectedRow === 1) {
|
||||
resolve('user create successful');
|
||||
}
|
||||
} else {
|
||||
reject('User already exists');
|
||||
}
|
||||
} catch (e) {
|
||||
this.ctx.logger.error('register service error:', e);
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async authUser(accountInfo: IAccountInfo) {
|
||||
const user: IUser = await this.checkHasUser(accountInfo.userAccount);
|
||||
const valid = user.password === accountInfo.password;
|
||||
if (!valid) {
|
||||
throw 'incorrect user account or password.';
|
||||
}
|
||||
return valid;
|
||||
}
|
||||
|
||||
private async checkHasUser(userAccount: string): Promise<IUser> {
|
||||
return await this.user.findByAccount(userAccount);
|
||||
}
|
||||
|
||||
private getToken(userAccount: string) {
|
||||
const token = this.jwt.sign({ userAccount },
|
||||
this.jwtConfig.secret, { expiresIn: 60 * 60 });
|
||||
this.ctx.logger.info(`AccountService getToken token--${token}`);
|
||||
return token;
|
||||
}
|
||||
}
|
||||
Executable
+37
@@ -0,0 +1,37 @@
|
||||
import BaseService from '../lib/baseService';
|
||||
import { Context, inject, provide, plugin } from 'midway';
|
||||
import { IRoom } from '../interface/IRoom';
|
||||
|
||||
@provide('RoomService')
|
||||
export class RoomService extends BaseService {
|
||||
|
||||
@inject()
|
||||
ctx: Context;
|
||||
|
||||
@plugin()
|
||||
mysql: any;
|
||||
|
||||
@plugin()
|
||||
redis: any;
|
||||
|
||||
async findById(uid: string): Promise<IRoom> {
|
||||
return await this.mysql.get('room', { id: uid });
|
||||
}
|
||||
|
||||
async findByRoomNumber(number: string): Promise<boolean> {
|
||||
const roomNumber = await this.redis.get(`room:${number}`);
|
||||
console.log(roomNumber, 'redis', number);
|
||||
return !!roomNumber;
|
||||
}
|
||||
|
||||
async add(expires: number = 3600) {
|
||||
const number = Math.floor(Math.random() * (1000000 - 100000)) + 100000;
|
||||
const result = await this.mysql.insert('room', { room_number: number });
|
||||
const roomRedis = await this.redis.set(`room:${number}`, `${number}`, 'ex', expires);
|
||||
if (result.affectedRows === 1 && roomRedis === 'OK') {
|
||||
return { roomNumber: number };
|
||||
} else {
|
||||
throw 'room add error';
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+31
@@ -0,0 +1,31 @@
|
||||
import { Context, inject, provide, plugin } from 'midway';
|
||||
import { IUser } from '../interface/IUser';
|
||||
import { IUserService } from '../interface/IUserService';
|
||||
import { IAccountInfo } from '../interface/IAccountInfo';
|
||||
|
||||
@provide('UserService')
|
||||
export class UserService implements IUserService {
|
||||
|
||||
@inject()
|
||||
ctx: Context;
|
||||
|
||||
@plugin()
|
||||
mysql: any;
|
||||
|
||||
async findById(uid: string): Promise<IUser> {
|
||||
return await this.mysql.get('user', { id: uid });
|
||||
}
|
||||
|
||||
async findByAccount(account: string) {
|
||||
return await this.mysql.get('user', { account });
|
||||
}
|
||||
|
||||
async addUser(accountInfo: IAccountInfo): Promise<any> {
|
||||
return await this.mysql.insert('user', {
|
||||
account: accountInfo.userAccount,
|
||||
password: accountInfo.password,
|
||||
nick_name: accountInfo.nickName,
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
Executable
+78
@@ -0,0 +1,78 @@
|
||||
export interface ILinkNode<T> {
|
||||
node: T;
|
||||
next: ILinkNode<T> | null;
|
||||
}
|
||||
|
||||
// interface ILink<T> {
|
||||
// getNode(position: number): T;
|
||||
// setNode(position: number): void;
|
||||
// removeNode(position: number): void;
|
||||
|
||||
export class Link<T> {
|
||||
link: ILinkNode<T> = {
|
||||
node: {} as T,
|
||||
next: null,
|
||||
};
|
||||
|
||||
constructor(nodes: T[], isCircular: boolean = true) {
|
||||
let prevNode: ILinkNode<T> = {
|
||||
node: {} as T,
|
||||
next: null,
|
||||
};
|
||||
nodes.forEach((node, key) => {
|
||||
const currNode: ILinkNode<T> = {
|
||||
node,
|
||||
next: null,
|
||||
};
|
||||
// head
|
||||
if (key === 0) {
|
||||
this.link = currNode;
|
||||
} else {
|
||||
// circular, last node next is first
|
||||
if (key === nodes.length - 1 && isCircular) {
|
||||
currNode.next = this.link;
|
||||
}
|
||||
prevNode.next = currNode;
|
||||
}
|
||||
prevNode = currNode;
|
||||
});
|
||||
}
|
||||
|
||||
getNode(position: number) {
|
||||
let linkNode = this.link;
|
||||
let i = 0;
|
||||
while (linkNode.next) {
|
||||
if (i === position) {
|
||||
return linkNode;
|
||||
}
|
||||
linkNode = linkNode.next;
|
||||
i++;
|
||||
}
|
||||
return linkNode;
|
||||
}
|
||||
|
||||
setNode(node: T, position: number) {
|
||||
let linkNode = this.link;
|
||||
let i = 0;
|
||||
const currNode: ILinkNode<T> = {
|
||||
node,
|
||||
next: null,
|
||||
};
|
||||
while (linkNode.next) {
|
||||
if (i === position) {
|
||||
currNode.next = linkNode.next;
|
||||
linkNode.next = currNode;
|
||||
return;
|
||||
}
|
||||
linkNode = linkNode.next;
|
||||
i++;
|
||||
}
|
||||
currNode.next = linkNode.next;
|
||||
linkNode.next = currNode;
|
||||
}
|
||||
|
||||
removeNode(position: number) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Executable
+119
@@ -0,0 +1,119 @@
|
||||
import { PokerGame, EGameStatus } from '../../../src/app/core/PokerGame';
|
||||
// @ts-ignore
|
||||
import { expect } from 'chai';
|
||||
import { IPlayer } from '../../../src/app/core/Player';
|
||||
|
||||
describe('test/app/core/pokerGame.test.ts', () => {
|
||||
const users: IPlayer[] = [
|
||||
{
|
||||
userId: '1',
|
||||
counter: 200,
|
||||
},
|
||||
{
|
||||
userId: '2',
|
||||
counter: 200,
|
||||
},
|
||||
{
|
||||
userId: '3',
|
||||
counter: 50,
|
||||
},
|
||||
{
|
||||
userId: '4',
|
||||
counter: 400,
|
||||
},
|
||||
{
|
||||
userId: '5',
|
||||
counter: 1200,
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* game ready
|
||||
*/
|
||||
it('game init', async () => {
|
||||
const game = new PokerGame({
|
||||
smallBlind: 1,
|
||||
users,
|
||||
});
|
||||
game.play();
|
||||
expect(game.status).to.equal(EGameStatus.GAME_ACTION);
|
||||
expect(game.currPlayer.node.actionSize).to.equal(0);
|
||||
expect(game.pot).to.equal(3);
|
||||
expect(game.pot).to.equal(3);
|
||||
expect(game.playerLink.getNode(1).node.actionSize).to.equal(1);
|
||||
});
|
||||
|
||||
/**
|
||||
* game playing
|
||||
*/
|
||||
it('game play', async () => {
|
||||
const game = new PokerGame({
|
||||
smallBlind: 1,
|
||||
users,
|
||||
});
|
||||
game.play();
|
||||
game.action('call');
|
||||
game.action('call');
|
||||
game.action('call');
|
||||
game.action('call');
|
||||
game.action('check');
|
||||
game.sendCard();
|
||||
game.action('raise:10');
|
||||
game.action('raise:20');
|
||||
game.action('call');
|
||||
game.action('call');
|
||||
game.action('raise:40');
|
||||
game.action('call');
|
||||
game.action('call');
|
||||
game.action('call');
|
||||
game.action('call');
|
||||
game.sendCard();
|
||||
game.action('allin');
|
||||
game.action('allin');
|
||||
game.action('allin');
|
||||
game.action('fold');
|
||||
game.action('allin');
|
||||
console.log('cc');
|
||||
// game.action('raise:10');
|
||||
console.log(game.commonCard);
|
||||
console.log(game.pot);
|
||||
console.log(game.getPlayers());
|
||||
console.log(game.winner);
|
||||
console.log(game.winner[0][0].handCard, game.commonCard);
|
||||
});
|
||||
// flop
|
||||
// turn
|
||||
// river
|
||||
// chip in
|
||||
// has Allin need separate pot
|
||||
// many allin
|
||||
|
||||
/**
|
||||
* game over
|
||||
*/
|
||||
it('game over', async () => {
|
||||
// only one player
|
||||
// last player, other player fold
|
||||
|
||||
// multiple player
|
||||
// last player, has all in player
|
||||
// all player all in
|
||||
// one player all in
|
||||
// many player all in
|
||||
|
||||
// winner
|
||||
// one winner
|
||||
// multiple winner
|
||||
// bisecting pot
|
||||
// allin player winner and small pot, multiple second winner bisecting pot
|
||||
// allin player winner and small pot, one second winner
|
||||
// all player allin, winner can't win all pot,
|
||||
});
|
||||
/**
|
||||
* count
|
||||
*/
|
||||
it('count', async () => {
|
||||
|
||||
});
|
||||
// has other pot
|
||||
});
|
||||
Executable
+45
@@ -0,0 +1,45 @@
|
||||
/* tslint:disable */
|
||||
import {PokerStyle} from '../../../src/app/core/PokerStyle';
|
||||
const assert = require('assert');
|
||||
|
||||
describe('test/app/core/pokerStyle.test.ts', () => {
|
||||
it('Royal Flush', async () => {
|
||||
let pokerStyle: PokerStyle = new PokerStyle(['i1','j1', 'k1', 'l1', 'm1', 'a2', 'a4'])
|
||||
// console.log(pokerStyle)
|
||||
assert.strictEqual(pokerStyle.getPokerWeight() , '1000000000')
|
||||
});
|
||||
it('straight flush', async () => {
|
||||
let pokerStyle: PokerStyle = new PokerStyle(['a1','b1', 'c1', 'd1', 'e1', 'a2', 'a4'])
|
||||
// console.log(pokerStyle)
|
||||
assert.strictEqual(pokerStyle.getPokerWeight() , '0a00000000')
|
||||
});
|
||||
it('four of kind', async () => {
|
||||
let pokerStyle: PokerStyle = new PokerStyle(['a1','b2', 'a3', 'b4', 'd1', 'a2', 'a4'])
|
||||
assert.strictEqual(pokerStyle.getPokerWeight() , '00ad0000000')
|
||||
});
|
||||
//
|
||||
it('full house', async () => {
|
||||
let pokerStyle: PokerStyle = new PokerStyle(['a1','b2', 'c3', 'b4', 'd1', 'a2', 'b3'])
|
||||
assert.strictEqual(pokerStyle.getPokerWeight(), '000ba000000');
|
||||
});
|
||||
it('two full house', async () => {
|
||||
let pokerStyle: PokerStyle = new PokerStyle(['a1','b2', 'a3', 'b4', 'd1', 'a2', 'b3'])
|
||||
assert.strictEqual(pokerStyle.getPokerWeight() , '000ba000000')
|
||||
});
|
||||
it('straight', async () => {
|
||||
let pokerStyle: PokerStyle = new PokerStyle(['a1','c2', 'e3', 'b4', 'd1', 'g2', 'm3'])
|
||||
assert.strictEqual(pokerStyle.getPokerWeight() , '00000a0000')
|
||||
});
|
||||
it('tow pairs to tow full house', async () => {
|
||||
let pokerStyle: PokerStyle = new PokerStyle(['a1','b2', 'a3', 'b4', 'd1', 'd2', 'b3'])
|
||||
assert.strictEqual(pokerStyle.getPokerWeight() , '000bd000000')
|
||||
});
|
||||
it('tow pairs', async () => {
|
||||
let pokerStyle: PokerStyle = new PokerStyle(['a1','c2', 'd3', 'b4', 'f1', 'a2', 'c3'])
|
||||
assert.strictEqual(pokerStyle.getPokerWeight() , '0000000caf00')
|
||||
});
|
||||
it('high card', async () => {
|
||||
let pokerStyle: PokerStyle = new PokerStyle(['a1','i2', 'e3', 'b4', 'd1', 'g2', 'm3'])
|
||||
assert.strictEqual(pokerStyle.getPokerWeight() , '000000000miged')
|
||||
});
|
||||
});
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
import { Link } from '../../src/utils/Link';
|
||||
import { Player } from '../../src/app/core/Player';
|
||||
|
||||
describe('test/utils/link.test.ts', () => {
|
||||
it('link', async () => {
|
||||
const person1 = new Player({ counter: 1, position: 1, userId: '1' });
|
||||
const person2 = new Player({ counter: 2, position: 2, userId: '2' });
|
||||
const person3 = new Player({ counter: 2, position: 3, userId: '3' });
|
||||
const person4 = new Player({ counter: 2, position: 4, userId: '4' });
|
||||
// const person5 = new Player({ counter: 2, position: 5, userId: '5' });
|
||||
const link = new Link<Player>([ person1, person2, person3, person4 ], false);
|
||||
// console.log(link.getNode(0), 'link--------')
|
||||
console.log(link);
|
||||
});
|
||||
|
||||
});
|
||||
Executable
+31
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"compileOnSave": true,
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"inlineSourceMap": true,
|
||||
"module": "commonjs",
|
||||
"newLine": "lf",
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUnusedLocals": true,
|
||||
"outDir": "dist",
|
||||
"pretty": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"strictPropertyInitialization": false,
|
||||
"stripInternal": true,
|
||||
"strictNullChecks": true,
|
||||
"suppressImplicitAnyIndexErrors": true,
|
||||
"target": "ES2018"
|
||||
},
|
||||
"exclude": [
|
||||
"app/public",
|
||||
"app/views",
|
||||
"dist",
|
||||
"node_modules*",
|
||||
"test",
|
||||
"**/*.d.ts",
|
||||
"**/*.spec.ts"
|
||||
]
|
||||
}
|
||||
Executable
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": ["tslint-config-egg"],
|
||||
"rules": {
|
||||
"linebreak-style": [ true, "CRLF" ]
|
||||
}
|
||||
}
|
||||
Executable
+7976
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user